diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 881d5297..161cf41b 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,7 +1,7 @@ contact_links: - name: Help - url: https://github.com/replit/kaboom/discussions/new?category=q-a + url: https://github.com/marklovers/kaplay/discussions/new?category=q-a about: How to use certain Kaboom feature, or how to achieve something with Kaboom - name: Suggestion - url: https://github.com/replit/kaboom/discussions/new?category=ideas + url: https://github.com/replit/kaplay/discussions/new?category=ideas about: Features you'd like Kaboom to have diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a40bb3cf..552a40bc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,3 +19,65 @@ Most kaboom docs are written in `src/types.ts` as [jsDoc](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html) above each kaboom component entry. **Help on improving the documentation is appreciated! Thank you for contributing!** + +## Conventional Commits Guide + +You must follow the following rules when writing commit messages: + +A commit starts with a type, a scope, and a subject: + +``` +(): +``` + +- The **type** is mandatory. [Should be one of the following](#commit-types). +- We don't use the **scope** right now, you must omit it. This may change in the future. +- The subject must be a short description of the change. +Use the imperative, present tense: "change" not "changed" nor "changes". + +### Commit types + +`feat`: a new feature or part of a feature +``` +feat: add hello() component +``` +`fix`: a bug fix +``` +fix: fix platformer example +``` +`docs`: changes to documentation (jsDoc, md files, etc) +``` +docs: update add() component jsDoc example +``` +`style`: changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) +``` +style: format all files +``` +`refactor`: a code change that neither fixes a bug nor adds a feature +``` +refactor: move assets to src/assets +``` +`test`: adding missing tests or correcting existing tests +``` +test: added tests for add() component +``` +`build`: changes that affect the build system or external dependencies (esbuild, typescript) +``` +build: update esbuild to 0.12.0 +``` +`ci`: changes to our CI configuration files and scripts (Github Actions) +``` +ci: add examples test workflow +``` +`revert`: reverts a previous commit +``` +revert: feat: add hello() component +``` +`chore`: updating tasks, general maintenance, etc (anything that doesn't fit in the above types) +``` +chore: update README.md +``` +`example`: adding a new example +``` +example: add firework example +``` diff --git a/LICENSE b/LICENSE index 98ea411d..3086b9ec 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2021 Replit +Copyright (c) 2024 KatTeam and kaplay contributers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/doc/comp.md b/doc/comp.md deleted file mode 100644 index 5e7d9258..00000000 --- a/doc/comp.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -title: Custom Components -description: Learn about create your own components for your game objects. ---- - -# Custom Components - -Kaboom uses a flexible component system that helps you compose game logic. - -Let's take a look at how the default component `lifespan()` is implemented. - -```js -function lifespan(time) { - let timer = 0; - return { - id: "lifespan", - update() { - timer -= dt(); - if (timer <= 0) { - destroy(this); - } - }, - }; -} -``` - -Components are just plain functions that returns an object. The return object will contain all the exposed states, methods, and event hooks of the component. In this case, the `lifespan()` component returns an `id`, which is a string which serves as an unique identification of the comp. There's also an `update()`, which is an event hook that'll run every frame. All `this` inside the component functions refer to the game obj it's attached to. - -All special fields: - -```js -function mycomp() { - // use closed local variable for internal data - let data = 123; - return { - id: "mycomp", - // if this comp requires other comps to work - require: ["area", "pos"], - // runs when the obj is added to scene - add() { - debug.log("Hi! This should only be fire once."); - }, - // runs every frame - update() { - // we're using a method from "pos" comp here, so we declare require "pos" above - this.move(200, 0); - }, - // runs every frame, after update - draw() { - drawLine(this.pos, mousePos()); - }, - // runs when obj is destroyed - destroy() { - debug.log("Oh bye"); - }, - // what to display in inspect mode - inspect() { - return "some state that deserves to be shown in inspect mode"; - }, - }; -} -``` - -Most kaboom built-in components are built using public interfaces, feel free to check them out. Also check out the "drag", "platformer", "doublejump" demos with their own custom components. - -Check out the [component demo](https://kaboomjs.com/play?example=component). diff --git a/doc/dev.md b/doc/dev.md deleted file mode 100644 index 6268cf5c..00000000 --- a/doc/dev.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: Contributing -description: Learn how to contribute to Kaboom. ---- - -# Developing Kaboom - -To start developing Kaboom, first clone the github repo - -```sh -$ git clone https://github.com/replit/kaboom -``` - -Run the `install` command to install the dev packages - -```sh -$ npm run install -``` - -Then run the `dev` command to start the dev server - -```sh -$ npm run dev -``` - -This will start a server that serves the kaboom website, and build the source files on file change. - -Also remember to run `check` to check for typescript errors and linting. - -```sh -$ npm run check -``` - -## Folder structure - -- `blog/` blog posts -- `dist/` built distribution files -- `doc/` various documentations -- `examples/` examples -- `pkgs/` other related packages such `kaboom-create` -- `scripts/` development scripts -- `sprites/` some examples sprites -- `src/` kaboom library source code diff --git a/doc/intro.md b/doc/intro.md deleted file mode 100644 index 1a39c37c..00000000 --- a/doc/intro.md +++ /dev/null @@ -1,455 +0,0 @@ ---- -title: Getting started -description: Learn the basics of Kaboom and make a simple game. ---- - -# Intro to Kaboom - -Welcome! Kaboom is a JavaScript library that helps you make games fast and fun :D - -This is an intro tutorial that will cover the basic concepts and make a very simple [Chrome Dino](https://en.wikipedia.org/wiki/Dinosaur_Game) - ish game. For setting up Kaboom development, see the [setup guide](/doc/setup). - -![game](intro/game.png) - -(scroll to bottom to see / copy the full game code) - -Let's start by initializing the context with the `kaboom()` function. - -```js -kaboom(); -``` - -This should give you a blank canvas with a nice checkerboard pattern like this - -![empty](intro/empty.png) - -Then let's add some stuff to screen, like an image. Copy this piece of code to your editor and see what happens when you run the game. - -```js -// load a sprite "bean" from an image -loadSprite("bean", "sprites/bean.png"); - -// add something to screen -add([ - sprite("bean"), - pos(80, 40), -]); -``` - -Introducing Frog the "Bean"! A happy frog that enjoys life. You'll see Bean a lot around here. - -![isbean](intro/bean.png) - -Before explaining what this code does, let's try adding some more stuff to it and see what happens: - -```js -// add something to screen -add([ - sprite("bean"), - pos(80, 40), - scale(3), - rotate(30), - color(0, 0, 255), -]); -``` - -Feel free to tweak some parameters and see how it affects what happens on screen. - -In Kaboom, each game object is composed from multiple components. Each component will give the game obj certain functionality. - -> A game object is basically any character in the game, like the player character, a bullet, a rock, a cloud - -For example, some component might decide what's the shape, some components might decide if it should subject to gravity, some components might decide what color it is, some component might decide how long it can live. - -![comps](intro/comps.png) - -If you're having trouble understanding, consider this Human Bean: - -![humanbean](intro/humanbean.png) - -Human are also composed from a list of components, each component provides different functionalities, which is exactly what component means in Kaboom. `add()` is the function you use to assemble all the components into a game object in kaboom: - -![assemble](intro/assemble.png) - -It's actually kinda like playing with lego pieces! Let's keep this in mind and start making the actual player character in our game: - -```js -// putting together our player character -const bean = add([ - sprite("bean"), - pos(80, 40), - area(), - body(), -]); - -// .jump() when "space" key is pressed -onKeyPress("space", () => { - bean.jump(); -}); -``` - -Let's see what components we're using: - -- `sprite()` makes it render as a sprite, with the `"bean"` sprite we just loaded with `loadSprite()` -- `pos()` gives it a position on screen, at X: 80 Y: 40 -- `area()` gives it a collider area, so we can check for collisions with other characters later on -- `body()` gives it a physical body, making it fall due to gravity and ability to jump, - -We're also testing out our player character with a little interaction here. `onKeyPress()` registers an event that runs every time user presses a certain key. In this case, we're calling the `.jump()` method (which is provided by the `body()` component) when `"space"` key is pressed. Go ahead and slap that space key! - -With the `body()` component, our Bean is going to keep falling into oblivion if we don't hit "space" key enough. Let's add a static platform for Bean to land on. - -```js -// add platform -add([ - rect(width(), 48), - pos(0, height() - 48), - outline(4), - area(), - body({ isStatic: true }), - color(127, 200, 255), -]); -``` - -Woah! That looks like a lot, but it's actually really simple, let's look at each component - -- `rect()` renders a rectangle. It accepts 2 arguments, the width and height, which we give it the game width (returned by `width()`) and height of 48 pixels -- `pos()` position. We give it a x: 0 and y: `height() - 48` so it sits right on the bottom of the screen -- `outline()` renders an outline of `4` pixels -- `area()` adds a collider to it -- `body({ isStatic: true })` the object won't move, and all non static objects won't move past it -- `color()` makes it render with an RGB color, we give it a R: 127 G: 200 B: 255 which is a blue-ish color - -Now, before run, we should define the gravity of our world: - -```js -setGravity(1600); -``` - -Pretty straightforward! Refresh the game and you should see our Bean is now safely landed on a static blue platform. - -![land](intro/land.png) - -Let's also make sure our Bean can only jump when isGrounded. - -```js -onKeyPress("space", () => { - if (bean.isGrounded()) { - bean.jump(); - } -}); -``` - -`isGrounded()` is another function provided by `body()` component which checks if currently landed on a platform. Now our game is slightly more physically correct. - -Bean loves challanges. Let's start adding in obstacles to jump over! Time to build a game object from components again. - -```js -// add tree -add([ - rect(48, 64), - area(), - outline(4), - pos(width(), height() - 48), - anchor("botleft"), - color(255, 180, 255), - move(LEFT, 240), -]); -``` - -A lot of these we have already seen you should know what they do, but some new ones here: - -- `anchor()` defines the origin point of positioning. By default `pos()` defines the top left point of the shape, here we change it to the bottom left point because we want it to be just above the platform, so we give it Y position of `height() - 48` -- `move()` makes it move towards a direction infinitely. In this case we move towards the `LEFT` by `480` pixels per second - -![tree](intro/tree.png) - -Challenger appears! Try jumping over it. - -Oh but it's.. not really fun! Or rather, there's no feedback to whether we managed to jump over the ramp. Let's add some feedback. - -To do this we'll need to check for collision between the two. - -First we'll need to give the tree a tag. Any game object can have any number of tags, they're kinda like components but much more light weight. We often use tags to quickly describe behaviors for a group of objects. - -```js -// add tree -add([ - rect(48, 64), - area(), - outline(4), - pos(width(), height() - 48), - anchor("botleft"), - color(255, 180, 255), - move(LEFT, 240), - "tree", // add a tag here -]); -``` - -To add a tag we simply put a string in the component array. Then we can check for collision between Bean and any object with tag "tree". - -```js -bean.onCollide("tree", () => { - addKaboom(bean.pos); - shake(); -}); -``` - -`.onCollide()` is a function provided by the `area()` component. It registers an event that runs every time the object collides with another object with a certain tag, passed by the first argument. In this case, it means every time Bean collides with another game obj with tag `"tree"`, run the callback. - -Inside the callback we're doing 2 things. `addKaboom()` spawns an explosion animation which is basically kaboom's logo, it accepts 1 argument the position to spawn, which we pass in the player's current position with `.pos` (which is provided by the `pos()` component). - -The second thing is `shake()`, which just shakes the screen, - -![hit](intro/hit.gif) - -Here's a trick. Try pressing `F1` in the game. See all the blue outlines? This is inspect mode and it'll show all the bounding boxes of every game obj with `area()` component. Also try hovering over each game object to inspect its states like position and tags. - -![inspect](intro/inspect.png) - -Now it's time to add more trees. How can we keep them spawning constantly? - -Let's try the `loop()` function, which performs an action every x seconds. - -```js -loop(1, () => { - // add tree - add([ - rect(48, 64), - area(), - outline(4), - pos(width(), height() - 48), - anchor("botleft"), - color(255, 180, 255), - move(LEFT, 240), - "tree", // add a tag here - ]); -}); -``` - -Let's just put the tree adding code inside a `loop()`. The first argument is the time in seconds here, so it'll spawn a tree every 1 seconds. - -Sick! Lots of trees coming to you now. Now we already have most of the game mechanics done. Some improvements we can make: - -1. It might be better if trees all have different random heights. We can use `rand()` to assign different value to the tree's rect height: - -```js -rect(48, rand(24, 64)), -``` - -2. It'll be more fun if the trees spawn at different intervals. We cannot do that from `loop()`, but we can compose that with recursive `wait()`s, which waits for x seconds to execute some code. - -```js -function spawnTree() { - add([ - // the tree components - ]); - wait(rand(0.5, 1.5), () => { - spawnTree(); - }); -} - -spawnTree(); -``` - -See? We're calling `spawnTree()` recursively / endlessly, with a random interval between 0.5 - 1.5 seconds each time. - -Before adding a score counter, let's actually complete the game loop first, by sending player to a gameover scene when they hit a tree. We can achieve this with kaboom's `scene()` system - -```js -scene("game", () => { - add([ - sprite("bean"), - ]); -}); - -scene("lose", () => { - add([ - text("Game Over"), - ]); -}); - -go("game"); -``` - -Consider this example above, we're declaring 2 scenes here, "game" and "lose". The function we pass to `scene()` is the function to run when this scene is activated (by `go()`). In this case, we want to add a "bean" sprite on "game" scene, and want to add a "Game Over" text on the "lose" scene. - -Let's first move everything game code we have into a scene. - -```js -// don't move these init / loader functions -kaboom(); -loadSprite("bean", "sprites/bean.png"); - -scene("game", () => { - // add bean - // add platform - // spawn trees -}); - -go("game"); -``` - -Try this, this shouldn't change any of your game's content. - -Then we can add a "lose" scene independent to your core game content here. - -```js -scene("lose", () => { - add([ - text("Game Over"), - pos(center()), - anchor("center"), - ]); -}); -``` - -So in the "lose" scene, we'll add a piece of text in the center says "Game Over" (`text()` is a component that renders text). Go ahead and go to this scene when player collides with a tree: - -```js -player.onCollide("tree", () => { - addKaboom(bean.pos); - shake(); - go("lose"); // go to "lose" scene here -}); -``` - -Ok! Now we've arrived at the final part of our game: score counter. - -```js -let score = 0; -const scoreLabel = add([ - text(score), - pos(24, 24), -]); -``` - -Here we've declared a number variable to store the score, and added a game obj with `text()` component to display the text. - -Let's keep it simple and just use time as score. - -```js -// increment score every frame -onUpdate(() => { - score++; - scoreLabel.text = score; -}); -``` - -We can use the `onUpdate()` function, which takes a function, and runs it every frame. In this case we're going to increment the score, and update the score label's text every frame. - -(todo) - -Full game code here: - -```js -const FLOOR_HEIGHT = 48; -const JUMP_FORCE = 800; -const SPEED = 480; - -// initialize context -kaboom(); - -// load assets -loadSprite("bean", "sprites/bean.png"); - -scene("game", () => { - // define gravity - setGravity(1600); - - // add a game object to screen - const player = add([ - // list of components - sprite("bean"), - pos(80, 40), - area(), - body(), - ]); - - // floor - add([ - rect(width(), FLOOR_HEIGHT), - outline(4), - pos(0, height()), - anchor("botleft"), - area(), - body({ isStatic: true }), - color(127, 200, 255), - ]); - - function jump() { - if (player.isGrounded()) { - player.jump(JUMP_FORCE); - } - } - - // jump when user press space - onKeyPress("space", jump); - onClick(jump); - - function spawnTree() { - // add tree obj - add([ - rect(48, rand(32, 96)), - area(), - outline(4), - pos(width(), height() - FLOOR_HEIGHT), - anchor("botleft"), - color(255, 180, 255), - move(LEFT, SPEED), - "tree", - ]); - - // wait a random amount of time to spawn next tree - wait(rand(0.5, 1.5), spawnTree); - } - - // start spawning trees - spawnTree(); - - // lose if player collides with any game obj with tag "tree" - player.onCollide("tree", () => { - // go to "lose" scene and pass the score - go("lose", score); - burp(); - addKaboom(player.pos); - }); - - // keep track of score - let score = 0; - - const scoreLabel = add([ - text(score), - pos(24, 24), - ]); - - // increment score every frame - onUpdate(() => { - score++; - scoreLabel.text = score; - }); -}); - -scene("lose", (score) => { - add([ - sprite("bean"), - pos(width() / 2, height() / 2 - 80), - scale(2), - anchor("center"), - ]); - - // display score - add([ - text(score), - pos(width() / 2, height() / 2 + 80), - scale(2), - anchor("center"), - ]); - - // go back to game with space is pressed - onKeyPress("space", () => go("game")); - onClick(() => go("game")); -}); - -go("game"); -``` diff --git a/doc/intro/assemble.png b/doc/intro/assemble.png deleted file mode 100644 index d1b458e3..00000000 Binary files a/doc/intro/assemble.png and /dev/null differ diff --git a/doc/intro/bean.png b/doc/intro/bean.png deleted file mode 100644 index 0dd6b945..00000000 Binary files a/doc/intro/bean.png and /dev/null differ diff --git a/doc/intro/comps.png b/doc/intro/comps.png deleted file mode 100644 index c57f24ca..00000000 Binary files a/doc/intro/comps.png and /dev/null differ diff --git a/doc/intro/empty.png b/doc/intro/empty.png deleted file mode 100644 index ad36cce8..00000000 Binary files a/doc/intro/empty.png and /dev/null differ diff --git a/doc/intro/game.png b/doc/intro/game.png deleted file mode 100644 index 39e55894..00000000 Binary files a/doc/intro/game.png and /dev/null differ diff --git a/doc/intro/hit.gif b/doc/intro/hit.gif deleted file mode 100644 index 42957007..00000000 Binary files a/doc/intro/hit.gif and /dev/null differ diff --git a/doc/intro/humanbean.png b/doc/intro/humanbean.png deleted file mode 100644 index 7fb4c075..00000000 Binary files a/doc/intro/humanbean.png and /dev/null differ diff --git a/doc/intro/inspect.png b/doc/intro/inspect.png deleted file mode 100644 index e2585032..00000000 Binary files a/doc/intro/inspect.png and /dev/null differ diff --git a/doc/intro/kaboom.png b/doc/intro/kaboom.png deleted file mode 100644 index 0662fd6a..00000000 Binary files a/doc/intro/kaboom.png and /dev/null differ diff --git a/doc/intro/land.png b/doc/intro/land.png deleted file mode 100644 index 65f35a06..00000000 Binary files a/doc/intro/land.png and /dev/null differ diff --git a/doc/intro/tree.png b/doc/intro/tree.png deleted file mode 100644 index feb349b4..00000000 Binary files a/doc/intro/tree.png and /dev/null differ diff --git a/doc/migration-3000.md b/doc/migration-3000.md deleted file mode 100644 index 49af36f3..00000000 --- a/doc/migration-3000.md +++ /dev/null @@ -1,427 +0,0 @@ ---- -title: Migrating from v2000 to v3000 -description: Migrate your codebase from Kaboom v2000 to v3000. ---- - -# Migrating from v2000 to v3000 - -- `obj._id` is renamed to `obj.id` - -```js -const obj = add([ - pos(300, 200), - sprite("bean"), - area(), -]); - -// before -console.log(obj._id); - -// v3000 -console.log(obj.id); -``` - -- `origin()` is renamed to `anchor()` - -```js -// before -add([ - sprite("bean"), - origin("center"), -]); - -// v3000 -add([ - sprite("bean"), - anchor("center"), -]); -``` - -- `obj.onHover()` in `area()` comp is renamed to `obj.onHoverUpdate()`, `obj.onHover()` now only runs once when obj is hovered - -```js -const obj = add([ - pos(300, 200), - sprite("bean"), - area(), -]); - -// before -obj.onHover(() => { - console.log("this will print every frame when obj is hovered"); -}, () => { - console.log("this will print every frame when obj is not hovered"); -}); - -// v3000 -obj.onHover(() => { - console.log("this will run once when obj is hovered"); -}); - -obj.onHoverUpdate(() => { - console.log("this will run every frame when obj is hovered"); -}); - -obj.onHoverEnd(() => { - console.log("this will run once when obj stopped being hovered"); -}); -``` - -- `obj.pushOut()` is renamed to `obj.resolveCollision()` - -```js -const player = add([ - sprite("bean"), - pos(300, 200), - area(), -]); - -// before -player.pushOut(rock); - -// v3000 -player.resolveCollision(rocker); -``` - -- `solid()` comp becomes an option in `body({ isStatic: true })` - -```js -// before -add([ - sprite("bean"), - area(), - body(), - solid(), -]); - -// v3000 -add([ - sprite("bean"), - area(), - body({ isStatic: true }), -]); -``` - -- gravity now needs to be manually enabled, `gravity()` is renamed to `setGravity()` and `getGravity()` - -```js -// before, gravity will be enabled by body() component -add([ - pos(100, 100), - sprite("bean"), - area(), - body(), -]); - -// v3000, use gravity() to manually enable gravity -setGravity(1600); - -add([ - pos(100, 100), - sprite("bean"), - area(), - body(), -]); -``` - -- `body.weight` is renamed to `body.gravityScale` - -```js -// before -add([ - body({ weight: 2 }), -]); - -// before -add([ - body({ gravityScale: 2 }), -]); -``` - -- `body.doubleJump()` is removed in favor of new `doubleJump()` component - -```js -const obj = add([ - pos(100, 100), - sprite("bean"), - area(), - body(), -]); - -obj.doubleJump(); - -// after -const obj = add([ - pos(100, 100), - sprite("bean"), - area(), - body(), - doubleJump(), -]); - -obj.doubleJump(); -``` - -- `body.onFall()` is renamed to `body.onFallOff()`, `body.onFall()` now runs when body is in the air and starts to fall - -```js -gravity(1600); - -const obj = add([ - pos(100, 100), - sprite("bean"), - area(), - body(), -]); - -// before -obj.onFall(() => { - console.log("this will print when object falls off a platform"); -}); - -// v3000 -obj.onFallOff(() => { - console.log("this will print when object falls off a platform"); -}); - -obj.onFall(() => { - console.log("this will print when object is in the air and starts falling"); -}); -``` - -- removed `outview()` in favor of `offscreen()`, which is less accurate but much faster - -```js -// before -add([ - sprite("flower"), - outview({ hide: true }), -]); - -// v3000 -add([ - sprite("flower"), - // will hide itself when its position is 64 pixels offscreen - offscreen({ hide: true, distance: 64 }), -]); -``` - -- removed `cleanup()` in favor of `offscreen({ destroy: true })` - -```js -// before -add([ - pos(player.pos), - sprite("bullet"), - cleanup(), -]); - -// v3000 -add([ - pos(player.pos), - sprite("bullet"), - offscreen({ destroy: true }), -]); -``` - -- `sprite.flipX` and `sprite.flipY` becomes properties instead of functions - -```js -const bean = add([ - sprite("bean"), -]); - -// before -bean.flipX(true); - -// v3000 -bean.flipX = true; -``` - -- `sprite.onAnimStart()` and `sprite.onAnimEnd()` now triggers on any animation - -```js -const bean = add([ - sprite("bean"), -]); - -// before -bean.onAnimStart("walk", () => { - // do something -}); - -// before -bean.onAnimStart((anim) => { - if (anim === "walk") { - // do something - } -}); -``` - -- `obj.scale` now is always a `Vec2` - -```js -scale(2); // scale is vec2(2, 2) -obj.scale; // vec2(2, 2) -``` - -- `loadFont()` now only loads `.ttf`, `.otf`, `.woff` etc fonts that browser support, use `loadBitmapFont()` to load bitmap fonts - -```js -// before -loadFont("unscii", "/examples/fonts/unscii_8x8.png", 8, 8); - -// v3000 -loadBitmapFont("unscii", "/examples/fonts/unscii_8x8.png", 8, 8); -loadFont("apl386", "/examples/fonts/apl386.ttf"); -``` - -- removed builtin fonts `apl386`, `apl386o`, `sink` and `sinko`, using browser built-in `monospace` font as default font now - -```js -// v3000, manually load these fonts if you need them -loadFont("apl386", "/examples/fonts/apl386.ttf"); -loadBitmapFont("sink", "/examples/fonts/sink_6x8.png"); - -// use outline option for "apl386o" -loadFont("apl386", "/examples/fonts/apl386.ttf", { - outline: 3, -}); -``` - -- changed vertex format from `vec3` to `vec2` (only applied in shaders) - -```js -// before -loadShader( - "test", - null, - ` -vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { - return def_frag(); -} -`, -); - -// v3000 -loadShader( - "test", - null, - ` -vec4 frag(vec2 pos, vec2 uv, vec4 color, sampler2D tex) { - return def_frag(); -} -`, -); -``` - -- `anchor` (previously `origin`) no longer controls text alignment (only controls the anchor of the whole text area), use `text({ align: "left" })` option for text alignment - -```js -// before -add([ - pos(center()), - origin("center"), - text("oh hi"), -]); - -// v3000 -add([ - pos(center()), - anchor("center"), - text("oh hi", { align: "center" }), -]); -``` - -- changed text styling syntax to bbcode - -```js -const textOpts = { - styles: { - "green": { - color: rgb(128, 128, 255), - }, - "wavy": (idx, ch) => ({ - color: hsl2rgb((time() * 0.2 + idx * 0.1) % 1, 0.7, 0.8), - pos: vec2(0, wave(-4, 4, time() * 6 + idx * 0.5)), - }), - }, -}; - -// before -add([ - text("[oh hi].green here's some [styled].wavy text", textOpts), -]); - -// v3000 -add([ - text("[green]oh hi[/green] here's some [wavy]styled[/wavy] text", textOpts), -]); -``` - -- changed all event handlers to return an `EventController` object, instead of a function to cancel - -```js -// before -const cancel = onUpdate(() => {/* ... */}); -cancel(); - -// v3000 -const ev = onUpdate(() => {/* ... */}); -ev.paused = true; -ev.cancel(); -``` - -- changed the interface for `addLevel()` - -```js -// before -addLevel([ - "@ ^ $$", - "=======", -], { - width: 32, - height: 32, - "=": () => [ - sprite("grass"), - area(), - body({ isStatic: true }), - ], - "$": () => [ - sprite("coin"), - area(), - "coin", - ], - any: (symbol) => { - if (symbol === "@") { - return [/* ... */]; - } - }, -}); - -// v3000 -addLevel([ - "@ ^ $$", - "=======", -], { - tileWidth: 32, - tileHeight: 32, - tiles: { - "=": () => [ - sprite("grass"), - area(), - body({ isStatic: true }), - ], - "$": () => [ - sprite("coin"), - area(), - "coin", - ], - }, - wildcardTile: (symbol) => { - if (symbol === "@") { - return [/* ... */]; - } - }, -}); -``` diff --git a/doc/publishing.md b/doc/publishing.md deleted file mode 100644 index 325268e4..00000000 --- a/doc/publishing.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: Publishing -description: Learn how to publish your Kaboom game in platforms like Itch.io or Newgrounds.com. ---- - -# Publishing a Kaboom game - -When we finish our game, the most important time it's here... -Publish it! - -## Preparing the game - -We should prepare our game to pubish in the different platforms. - -- use relative paths like `sprites/bean.png` or `./sprites/bean.png` -- create a zip with your game, with all assets, code files and a `index.html` as the entry point -- use a custom `canvas` is a good idea too - -```js -kaboom({ - canvas: document.queryElement("#myCanvas"), -}); -``` - -## Itch.io - -First, go to your account, then **Upload a new project** - -![image](publishing/itchio-1.png) - -Then, in **Kind of project**, you should select **HTML**, so itch.io will let you upload a web game - -![image](publishing/itchio-2.png) - -After that, the only thing left to do is to upload your zip with your game files, or if you have a single `ìndex.html` file -(very tasteful) upload it! - -[**Itch.io Documentation about HTML5 games**](https://itch.io/docs/creators/html5) - -## Newgrounds.com - -First, go to the up arrow and select Game - -![image](publishing/newgrounds-1.png) - -Now, as Itch.io, only upload a zip with your fabolous `index.html` game file. - -### Tag your game - -You can tag your game in the different platforms with the tag `kaboomjs`. Probbably we will see it and even promote in our Twitter :D diff --git a/doc/publishing/itchio-1.png b/doc/publishing/itchio-1.png deleted file mode 100644 index e74b9cca..00000000 Binary files a/doc/publishing/itchio-1.png and /dev/null differ diff --git a/doc/publishing/itchio-2.png b/doc/publishing/itchio-2.png deleted file mode 100644 index e4c2eacb..00000000 Binary files a/doc/publishing/itchio-2.png and /dev/null differ diff --git a/doc/publishing/newgrounds-1.png b/doc/publishing/newgrounds-1.png deleted file mode 100644 index 6ec58a6c..00000000 Binary files a/doc/publishing/newgrounds-1.png and /dev/null differ diff --git a/doc/sections/Assets.md b/doc/sections/Assets.md deleted file mode 100644 index f571493e..00000000 --- a/doc/sections/Assets.md +++ /dev/null @@ -1 +0,0 @@ -Every function with the `load` prefix is an async function that loads something into the asset manager, and should return a promise that resolves upon load complete. diff --git a/doc/sections/Components.md b/doc/sections/Components.md deleted file mode 100644 index 674dc502..00000000 --- a/doc/sections/Components.md +++ /dev/null @@ -1,28 +0,0 @@ -Kaboom uses a flexible component system which values composition over inheritence. Each game object is composed from a list of components, each component gives the game object certain capabilities. - -Use `add()` to assemble the components together into a Game Object and add them to the world. - -```js -const player = add([ - sprite("froggy"), - pos(100, 200), - area(), - body(), -]); - -// .jump() is provided by body() component -player.jump(); - -// .moveTo() is provided by pos() component -player.moveTo(120, 80); - -// .onCollide() is provided by the area() component -player.onCollide("enemy", (enemy) => { - destroy(enemy); - addExplosion(); -}); -``` - -To see what methods and properties a component offers, click on the type that the component function returns, e.g. `PosComp`, which will open a panel showing all the properties and methods it'd give the game object. - -To learn more about how components work or how to make your own component, check out the [component](/play?demo=component) demo. diff --git a/doc/sections/Debug.md b/doc/sections/Debug.md deleted file mode 100644 index 1c052eaa..00000000 --- a/doc/sections/Debug.md +++ /dev/null @@ -1,12 +0,0 @@ -By default kaboom starts in debug mode, which enables key bindings that calls out various debug utilities: - -- `f1` to toggle inspect mode -- `f2` to clear debug console -- `f7` to slow down -- `f8` to pause / resume -- `f9` to speed up -- `f10` to skip frame - -Some of these can be also controlled with stuff under the `debug` object. - -If you want to turn debug mode off when releasing you game, set `debug` option to false in `kaboom()` diff --git a/doc/sections/Draw.md b/doc/sections/Draw.md deleted file mode 100644 index 32b5571b..00000000 --- a/doc/sections/Draw.md +++ /dev/null @@ -1,22 +0,0 @@ -Kaboom exposes all of the drawing interfaces it uses in the render components like `sprite()`, and you can use these drawing functions to build your own richer render components. - -Also note that you have to put `drawXXX()` functions inside an `onDraw()` event or the `draw()` hook in component definitions which runs every frame (after the `update` events), or it'll be immediately cleared next frame and won't persist. - -```js -onDraw(() => { - drawSprite({ - sprite: "froggy", - pos: vec2(120, 160), - angle: 90, - }); - - drawLine({ - p1: vec2(0), - p2: mousePos(), - width: 4, - color: rgb(0, 0, 255), - }); -}); -``` - -There's also the option to use Kaboom purely as a rendering library. Check out the [draw](/play?demo=draw) demo. diff --git a/doc/sections/Events.md b/doc/sections/Events.md deleted file mode 100644 index b01a7c95..00000000 --- a/doc/sections/Events.md +++ /dev/null @@ -1,22 +0,0 @@ -Kaboom uses events extensively for a flat and declarative code style. - -For example, it's most common for a game to have something run every frame which can be achieved by adding an `onUpdate()` event - -```js -// Make something always move to the right -onUpdate(() => { - banana.move(320, 0); -}); -``` - -Events are also used for input handlers. - -```js -onKeyPress("space", () => { - player.jump(); -}); -``` - -Every function with the `on` prefix is an event register function that takes a callback function as the last argument, and should return a function that cancels the event listener. - -Note that you should never nest one event handler function inside another or it might cause severe performance punishment. diff --git a/doc/sections/Game Obj.md b/doc/sections/Game Obj.md deleted file mode 100644 index 55ae0eda..00000000 --- a/doc/sections/Game Obj.md +++ /dev/null @@ -1,3 +0,0 @@ -Game Object is the basic unit of entity in a kaboom world. Everything is a game object, the player, a butterfly, a tree, or even a piece of text. - -This section contains functions to add, remove, and access game objects. To actually make them do stuff, check out the [Components](#Components) section. diff --git a/doc/setup.md b/doc/setup.md deleted file mode 100644 index cf0b6cd4..00000000 --- a/doc/setup.md +++ /dev/null @@ -1,149 +0,0 @@ ---- -title: Installation -description: Learn how to install Kaboom. ---- - -# Installation - -The most easy way to get started with Kaboom is to use the [CLI tool](https://www.npmjs.com/package/create-kaboom), which will generate a Kaboom project for you: - -```sh -$ npm init kaboom -- mygame -$ cd mygame -$ npm run dev -``` - -This will create your game in the `mygame` directory, and start a development server for you to preview your game. If you edit `src/main.js` and refresh the page, you will see your changes. - -To see all options, run: - -```sh -$ npm init kaboom -- --help -``` - -## Using a CDN - -Getting the package from a CDN provider is the quickest and easiest way to start using Kaboom. - -```html - -``` - -You can paste this directly in a `.html` file and open with the browser. This will give you the standard fullscreen Kaboom canvas. Feel free to put more HTML in there. - -The Kaboom package is deployed to npm, so it's availbale on various CDN providers, like `unpkg`, `skypack`, `jsdelivr`, etc. - -You can also just include it with a ` - - -``` - -## Setup your own Node.js environment - -With Node.js and npm it's easier have some other packages and use version control, also it's easier to get typescript autocomplete support, but it requires a bit more setup. (This is the way of `create-kaboom`) - -```sh -$ npm install kaboom -``` - -You'll need to use a bundler to use Kaboom with NPM. There's a lot of options like: - -- `esbuild`, -- `webpack`, -- `parcel`, -- `vitejs`, - -This is a short example of how to use Kaboom with ["esbuild"](https://esbuild.github.io/). - -Once you have `esbuild` installed, and you have this in a `.js` or `.ts` file: - -```js -import kaboom from "kaboom"; - -kaboom(); - -add([ - text("hello"), - pos(120, 80), -]); -``` - -just run - -```sh -$ esbuild game.js --bundle > build.js -``` - -and it'll find the Kaboom package and include it in the built `build.js`, include `build.js` in your HTML and you're good to go. Feel free to automate this process. - -## Loading Assets - -You might have encountered errors when trying to `loadSprite()` from local file system, that is because browser won't allow loading local files with JavaScript. To get around that you'll need to use a static file that serves the files through HTTP. There're a lot of programs that helps you to do that. - -- `$ python3 -m http.server` if you have [python3](https://www.python.org) installed -- `$ python -m SimpleHTTPServer` if you have [python2](https://www.python.org) installed -- `$ serve` if you have [serve](https://github.com/vercel/serve) installed -- `$ caddy file-server` if you have [caddy](https://caddyserver.com/) installed -- `$ static-here` if you have [static-here](https://github.com/amasad/static-here) installed - -Let's say you have a folder structure like this: - -```sh -. -├── sprites -│   ├── froggy.png -│   └── cloud.png -├── sounds -│   └── horse.mp3 -└── index.html -``` - -and you have the static file server running on port `8000`, just go to `http://localhost:8000/index.html`, and you should be able to load stuff from relative paths like - -```js -loadSprite("froggy", "sprites/froggy.png"); -loadSound("horse", "sounds/horse.mp3"); -``` - -To learn more check out this [MDN doc](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/set_up_a_local_testing_server). - -## Using Replit - -Replit has templates that gets rid of manual setup. Fork from either of these 2 templates: - -### [Official Template](https://replit.com/@replit/Kaboom) - -This is a complete package including - -- In-editor tutorial -- NPM / typescript support -- Autocomplete -- Assets Library -- Replit DB & Auth integration - -![official template](setup/replit.png) - -### [Lighter Template](https://replit.com/@replit/Kaboom-light) - -A lighter version that only contains the barebone HTML and JS file. - -Cool! Now you should be all ready to start using Kaboom. Try this [intro tutorial](/doc/intro) if you don't know how to start. diff --git a/doc/setup/replit.png b/doc/setup/replit.png deleted file mode 100644 index 81c3dc90..00000000 Binary files a/doc/setup/replit.png and /dev/null differ diff --git a/doc/shaders.md b/doc/shaders.md deleted file mode 100644 index 3a6561a0..00000000 --- a/doc/shaders.md +++ /dev/null @@ -1,114 +0,0 @@ ---- -title: Shaders -description: Learn how to write and use custom shaders in Kaboom. ---- - -# Writing a shader - -## Vertex shader - -A vertex shader can manipulate data on vertex level. Usually a vertex shader can change any of the vertex's properties, but for kaboom only the position can be altered. In most instances the mesh will be a four point quad, thus the shader is called 4 times, once for each point in the quad. -A default vertex shader would look like this. The function is passed the position, uv coordinate and color of the vertex and needs to return the updated position. - -``` -vec4 vert(vec2 pos, vec2 uv, vec4 color) { - return def_vert(); -} -``` - -The default vertex shader returns the primitive's vertex unchanged. -If a modified vertex is to be returned, it should be a 4 dimensional vector with x, y the position of the vertex, z=0 and w=1. Since there is no z-buffer, and the view is orthogonal, z has no effect. The w coordinate is 1 because we are returning a point, not a vector. Vectors can't be moved, therefore their w would be 0 and would not be influenced by the translation part of the matrix. - -## Fragment shader - -Once the positions of all vertices is determined, the primitive is rasterized. For each pixel drawn, the fragment shader is called. This shader can no longer change the position, but it can affect the color. -A default fragment shader would look like this. - -``` -vec4 frag(vec2 pos, vec2 uv, vec4 color, sampler2D tex) { - return def_frag(); -} -``` - -This default shader mixes the base color and texture colors together. -If an altered color is to be returned, it should be a vec4 containing the r, g, b and a channels as floating point numbers between 0 and 1. -For example, the following shader only uses the texture channel's alpha, while using the base color. - -``` -vec4 frag(vec2 pos, vec2 uv, vec4 color, sampler2D tex) { - return vec4(color.rgb, texture2D(tex, uv).a); -} -``` - -The following shader takes the texture color, grayscales it and then recolors it with the base color. - -``` -vec4 frag(vec2 pos, vec2 uv, vec4 color, sampler2D tex) { - vec4 tcolor = texture2D(tex, uv); - float gray = dot(tcolor.rgb, vec3(0.299, 0.587, 0.114)); - return vec4(color.rgb * gray, tcolor.a); -} -``` - -# Loading a shader - -There are two ways to load a shader: - -- loadShader takes two strings with the vertex and fragment shader code. -- loadShaderURL takes file URLs for the vertex and fragment shaders. - -# Passing data - -Without parameters, a shader would be static, or would have to be redefined each frame if some dynamism was expected. Therefore a shader can have parameters which can change every time the scene is rendered. These parameters are called uniforms. Every function passing a shader also has a parameter to pass uniforms to the shader. For example, the following sprite effect defines a function which returns an object with a uniform called u_time. This function is called each frame, and the parameters are sent to the shader before rendering. - -```ts -loadShader( - "invert", - null, - ` - uniform float u_time; - - vec4 frag(vec2 pos, vec2 uv, vec4 color, sampler2D tex) { - vec4 c = def_frag(); - float t = (sin(u_time * 4.0) + 1.0) / 2.0; - return mix(c, vec4(1.0 - c.r, 1.0 - c.g, 1.0 - c.b, c.a), t); - } -`, -); - -add([ - sprite("bean"), - pos(80, 40), - scale(8), - // Use the shader with shader() component and pass uniforms - shader("invert", () => ({ - "u_time": time(), - })), -]); -``` - -Instead of a function, an object can be passed. This can be used in case the uniforms are not frame dependent. Note though that to replace uniforms set using an object, the function needs to be called once more (in case of usePostEffect) or the component readded (in case of the shader component). -When using the direct draw API, like drawSprite or drawUVQuad, the shader and uniforms are passed through the render properties. - -```ts -drawSprite({ - sprite: "bean", - pos: vec2(100, 200), - shader: "invert", - uniforms: { - "u_time": time(), - }, -}); -``` - -# Multipass shaders - -Some shaders, like gaussian blur, need multiple passes in order to work. This can be done by making a framebuffer (makeCanvas), drawing inside this framebuffer (by using the drawon component or Canvas.draw), and using the famebuffer's texture (frameBuffer.tex) to draw a quad (uvquad component or drawUVQuad). - -# Learning more about shaders - -GLSL has a variety of functions which makes it easier to express your ideas in code. So be sure to look these up. -Here are some resources to get started on writing GLSL shaders. - -- [https://thebookofshaders.com/] -- [https://www.khronos.org/files/webgl/webgl-reference-card-1_0.pdf] diff --git a/doc/tips.md b/doc/tips.md deleted file mode 100644 index 5dd7183f..00000000 --- a/doc/tips.md +++ /dev/null @@ -1,144 +0,0 @@ ---- -title: Optimization -description: Tips on optimizing performance / maintainability for kaboom games. ---- - -# Optimization Tips - -Here's some tips on optimizing performance / maintainability for kaboom games - -## Cleanup One-off Objects - -Sometimes there are some objects that gets created, leaves screen, and never seen again, like a bullet. These objects will keep being rendered / updated and be detrimental to performance if they get created a lot, it's better to remove them when they leave screen. - -`offscreen()` is a component that helps you define behavior when objects go off-screen. - -```js -k.add([ - k.sprite("bullet"), - k.pos(player.pos), - // the bullet move left forever - k.move(LEFT, 600), - // destroy the bullet when it's far out of view - k.offscreen({ destroy: true }), -]); -``` - -## Hide Off-Screen Objects - -Sometimes you might be drawing a lot of objects that's not on screen (e.g. if you have a big map and your camera only sees a small area), this is very unnessecary, use `offscreen()` component to define object's behavior when they're not on screen. - -```js -// planting flowers all over the map -for (let i = 0; i < 1000; i++) { - k.add([ - k.sprite("flower"), - k.pos(k.rand(-5000, 5000), k.rand(-5000, 5000)), - // don't draw or update the flower when they're out of view - k.offscreen({ hide: true, pause: true }), - ]); -} -``` - -## Use `await` - -Kaboom use a lot of `Promise` and `Promise`-like in time / event related stuff, use `await` on those to make code look nicer - -```js -async function example() { - await k.wait(3); - await k.tween(0, 100, 1, (x) => mark.pos.x = x); -} -``` - -## Avoid Global Namespace - -By default Kaboom uses a lot of common names like `pos`, `sprite` that occupies global namespace, it's often better to use `global: false` to not export kaboom functions to `window` - -```js -kaboom({ - global: false, -}); - -const pos = k.vec2(120, 200); -``` - -## Use Game Object local timers - -When programming timer / tween behavior for a specific game object, it's better to attach `timer()` component to the game object and use that instead of global timer functions. This way the timer is tied to the life cycle of the game object, when then game object pauses or gets destroyed, the timer will not run. - -```js -// prefer -const player = k.add([ - k.sprite("bean"), - k.pos(100, 200), - k.timer(), - k.state("idle"), -]); - -// these timers will only run when player game object is not paused / destroyed -player.wait(2, () => { - // ... -}); - -await player.tween( - player.pos, - k.mousePos(), - 0.5, - (p) => player.pos = p, - k.easings.easeOutQuad, -); - -// this will pause all the timer events -player.paused = true; - -// this will stop all the timer events -player.destory(); - -player.onStateEnter("attack", async () => { - // ... state code - // if we use global k.wait() here it'll create infinitely running state transitions even when player game object doesn't exist anymore - await player.wait(2); - player.enterState("idle"); -}); - -player.onStateEnter("idle", async () => { - // ... state code - // if we use global k.wait() here it'll create infinitely running state transitions even when player game object doesn't exist anymore - await player.wait(1); - player.enterState("attack"); -}); -``` - -## Use Game Object local input handlers - -Similar to above, it's often better to use local input handlers as opposed to global ones. - -```js -const gameScene = k.add([]); - -const player = gameScene.add([ - k.sprite("bean"), - k.pos(100, 200), - k.area(), - k.body(), -]); - -// these -gameScene.onKeyPress("space", () => { - player.jump(); -}); - -// this will pause all the input events -gameScene.paused = true; - -// this will stop all the input events -gameScene.destory(); -``` - -## Compress Assets - -Loading assets takes time, compress them when you can. - -- Compress `.ttf` or `.otf` to `.woff2` (with [google/woff2](https://github.com/google/woff2)) -- Compress `.wav` files to `.ogg` or `.mp3`