
// start the game kaboom() // define gravity setGravity(2400) // load a default sprite loadBean() // add character to screen, from a list of components const player = add([ sprite("bean"), // renders as a sprite pos(120, 80), // position in world area(), // has a collider body(), // responds to physics and gravity ]) // jump when player presses "space" key onKeyPress("space", () => { // .jump() is provided by the body() component player.jump() }) Play with it yourself or check out the examples in the Playground!
// Start kaboom with default options (will create a fullscreen canvas under <body>) kaboom() // Init with some options (check out #KaboomOpt for full options list) kaboom({ width: 320, height: 240, font: "sans-serif", canvas: document.querySelector("#mycanvas"), background: [ 0, 0, 255, ], }) // All kaboom functions are imported to global after calling kaboom() add() onUpdate() onKeyPress() vec2() // If you want to prevent kaboom from importing all functions to global and use a context handle for all kaboom functions const k = kaboom({ global: false }) k.add(...) k.onUpdate(...) k.onKeyPress(...) k.vec2(...) const player = add([ // List of components, each offers a set of functionalities sprite("mark"), pos(100, 200), area(), body(), health(8), // Plain strings are tags, a quicker way to let us define behaviors for a group "player", "friendly", // Components are just plain objects, you can pass an object literal as a component. { dir: LEFT, dead: false, speed: 240, }, ]) // .jump is provided by body() player.jump() // .moveTo is provided by pos() player.moveTo(300, 200) // .onUpdate() is on every game object, it registers an event that runs every frame player.onUpdate(() => { // .move() is provided by pos() player.move(player.dir.scale(player.speed)) }) // .onCollide is provided by area() player.onCollide("tree", () => { destroy(player) }) const label = make([ text("oh hi"), ]) add([ rect(label.width, label.height), color(0, 0, 255), children(label), ]) // Common way to use this is to have one sprite overlap another sprite, and use readd() to have the bottom sprite on top of the other. // Create two sprites. const greenBean = add([ sprite("bean"), pos(200,140), color(255, 255, 255), * area(), ]) // This bean will overlap the green bean. const purpleBean = add([ sprite("bean"), pos(230,140), color(255, 0, 255), area(), ]) // Example 1: simply call readd() on the target you want on top. readd(greenBean) // Example 2: using onClick() or other functions with readd(). // If you comment out the first example, and use this readd() with a function like onClick(), you can keep switching which sprite is above the other ( click on edge of face ). purpleBean.onClick(() => { readd(greenBean) }) greenBean.onClick(() => { readd(purpleBean) }) // get a list of all game objs with tag "bomb" const allBombs = get("bomb") // To get all objects use "*" const allObjs = get("*") // Recursively get all children and descendents const allObjs = get("*", { recursive: true }) // every time bean collides with anything with tag "fruit", remove it bean.onCollide("fruit", (fruit) => { destroy(fruit) }) // destroy all objects with tag "bomb" when you click one onClick("bomb", () => { destroyAll("bomb") }) // This game object will draw a "bean" sprite at (100, 200) add([ pos(100, 200), sprite("bean"), ]) // scale uniformly with one value add([ sprite("bean"), scale(3), ]) // scale with x & y values. In this case, scales more horizontally. add([ sprite("bean"), scale(3, 1), ]) // scale with vec2(x,y). bean.scale = vec2(2,4) // blue frog add([ sprite("bean"), color(0, 0, 255) ]) // minimal setup add([ sprite("bean"), ]) // with options const bean = add([ sprite("bean", { // start with animation "idle" anim: "idle", }), ]) // play / stop an anim bean.play("jump") bean.stop() // manually setting a frame bean.frame = 3 // a simple score counter const score = add([ text("Score: 0"), pos(24, 24), { value: 0 }, ]) player.onCollide("coin", () => { score.value += 1 score.text = "Score:" + score.value }) // with options add([ pos(24, 24), text("ohhi", { size: 48, // 48 pixels tall width: 320, // it'll wrap to next line when width exceeds this value font: "sans-serif", // specify any font you loaded or browser built-in }), ]) // Make a square the hard way add([ pos(80, 120), polygon([vec2(0,0), vec2(50,0), vec2(50,50), vec2(0,50)]), outline(4), area(), ]) // i don't know, could be an obstacle or something add([ pos(80, 120), rect(20, 40), outline(4), area(), ]) add([ pos(80, 120), circle(16), ]) add([ uvquad(width(), height()), shader("spiral"), ]) // Automatically generate area information from the shape of render const player = add([ sprite("bean"), area(), ]) // Die if player collides with another game obj with tag "tree" player.onCollide("tree", () => { destroy(player) go("lose") }) // Check for collision manually every frame instead of registering an event player.onUpdate(() => { if (player.isColliding(bomb)) { score += 1 } }) add([ sprite("flower"), // Scale to 0.6 of the generated area area({ scale: 0.6 }), // If we want the area scale to be calculated from the center anchor("center"), ]) add([ sprite("bean"), // Define area with custom shape area({ shape: new Polygon([vec2(0), vec2(100), vec2(-100, 100)]) }), ]) // set anchor to "center" so it'll rotate from center add([ rect(40, 10), rotate(45), anchor("center"), ]) // bean jumpy const bean = add([ sprite("bean"), // body() requires "pos" and "area" component pos(), area(), body(), ]) // when bean is grounded, press space to jump // check out #BodyComp for more methods onKeyPress("space", () => { if (bean.isGrounded()) { bean.jump() } }) // run something when bean falls and hits a ground bean.onGround(() => { debug.log("oh no!") }) // enemy throwing feces at player const projectile = add([ sprite("feces"), pos(enemy.pos), area(), move(player.pos.angle(enemy.pos), 1200), offscreen({ destroy: true }), ]) add([ pos(player.pos), sprite("bullet"), offscreen({ destroy: true }), "projectile", ]) const obj = add([ timer(), ]) obj.wait(2, () => { ... }) obj.loop(0.5, () => { ... }) obj.tween(obj.pos, mousePos(), 0.5, (p) => obj.pos = p, easings.easeOutElastic) // this will be be fixed on top left and not affected by camera const score = add([ text(0), pos(12, 12), fixed(), ]) player.onCollide("bomb", () => { // spawn an explosion and switch scene, but don't destroy the explosion game obj on scene switch add([ sprite("explosion", { anim: "burst", }), stay(), lifespan(1), ]) go("lose", score) }) const player = add([ health(3), ]) player.onCollide("bad", (bad) => { player.hurt(1) bad.hurt(1) }) player.onCollide("apple", () => { player.heal(1) }) player.on("hurt", () => { play("ouch") }) // triggers when hp reaches 0 player.on("death", () => { destroy(player) go("lose") }) // spawn an explosion, destroy after 1 seconds, start fading away after 0.5 second add([ sprite("explosion", { anim: "burst", }), lifespan(1, { fade: 0.5 }), ]) const enemy = add([ pos(80, 100), sprite("robot"), state("idle", ["idle", "attack", "move"]), ]) // this callback will run once when enters "attack" state enemy.onStateEnter("attack", () => { // enter "idle" state when the attack animation ends enemy.play("attackAnim", { // any additional arguments will be passed into the onStateEnter() callback onEnd: () => enemy.enterState("idle", rand(1, 3)), }) checkHit(enemy, player) }) // this will run once when enters "idle" state enemy.onStateEnter("idle", (time) => { enemy.play("idleAnim") wait(time, () => enemy.enterState("move")) }) // this will run every frame when current state is "move" enemy.onStateUpdate("move", () => { enemy.follow(player) if (enemy.pos.dist(player.pos) < 16) { enemy.enterState("attack") } }) const enemy = add([ pos(80, 100), sprite("robot"), state("idle", ["idle", "attack", "move"], { "idle": "attack", "attack": "move", "move": [ "idle", "attack" ], }), ]) // this callback will only run once when enter "attack" state from "idle" enemy.onStateTransition("idle", "attack", () => { checkHit(enemy, player) }) // a custom event defined by body() comp // every time an obj with tag "bomb" hits the floor, destroy it and addKaboom() on("ground", "bomb", (bomb) => { destroy(bomb) addKaboom(bomb.pos) }) // a custom event can be defined manually // by passing a name and a callback function on("talk", (message, posX, posY) => { add([ text(message), pos(posX, posY - 100) ]) }) onKeyPress("space", () => { // the trigger method on game objs can be used to trigger a custom event npc.trigger("talk", "Hello World!", npc.pos.x, npc.pos.y) }) // move every "tree" 120 pixels per second to the left, destroy it when it leaves screen // there'll be nothing to run if there's no "tree" obj in the scene onUpdate("tree", (tree) => { tree.move(-120, 0) if (tree.pos.x < 0) { destroy(tree) } }) // This will run every frame onUpdate(() => { debug.log("ohhi") }) onDraw(() => { drawLine({ p1: vec2(0), p2: mousePos(), color: rgb(0, 0, 255), }) }) const bean = add([ sprite("bean"), ]) // certain assets related data are only available when the game finishes loading onLoad(() => { debug.log(bean.width) }) onCollide("sun", "earth", () => { addExplosion() }) onCollideUpdate("sun", "earth", () => { runWorldEndTimer() }) onCollideEnd("bean", "earth", () => { worldEnd() }) // click on any "chest" to open onClick("chest", (chest) => chest.open()) // click on anywhere to go to "game" scene onClick(() => go("game")) // move left by SPEED pixels per frame every frame when left arrow key is being held down onKeyDown("left", () => { bean.move(-SPEED, 0) }) // .jump() once when "space" is just being pressed onKeyPress("space", () => { bean.jump() }) // Call restart() when player presses any key onKeyPress(() => { restart() }) // delete last character when "backspace" is being pressed and held onKeyPressRepeat("backspace", () => { input.text = input.text.substring(0, input.text.length - 1) }) // type into input onCharInput((ch) => { input.text += ch }) loadRoot("https://myassets.com/") loadSprite("bean", "sprites/bean.png") // will resolve to "https://myassets.com/sprites/bean.png" // due to browser policies you'll need a static file server to load local files loadSprite("bean", "bean.png") loadSprite("apple", "https://kaboomjs.com/sprites/apple.png") // slice a spritesheet and add anims manually loadSprite("bean", "bean.png", { sliceX: 4, sliceY: 1, anims: { run: { from: 0, to: 3, }, jump: { from: 3, to: 3, }, }, }) // See #SpriteAtlasData type for format spec loadSpriteAtlas("sprites/dungeon.png", { "hero": { x: 128, y: 68, width: 144, height: 28, sliceX: 9, anims: { idle: { from: 0, to: 3 }, run: { from: 4, to: 7 }, hit: 8, }, }, }) const player = add([ sprite("hero"), ]) player.play("run") // Load from json file, see #SpriteAtlasData type for format spec loadSpriteAtlas("sprites/dungeon.png", "sprites/dungeon.json") const player = add([ sprite("hero"), ]) player.play("run") loadAseprite("car", "sprites/car.png", "sprites/car.json") loadBean() // use it right away add([ sprite("bean"), ]) loadSound("shoot", "/sounds/horse.ogg") loadSound("shoot", "/sounds/squeeze.mp3") loadSound("shoot", "/sounds/shoot.wav") loadMusic("shoot", "/music/bossfight.mp3") // load a font from a .ttf file loadFont("frogblock", "fonts/frogblock.ttf") // load a bitmap font called "04b03", with bitmap "fonts/04b03.png" // each character on bitmap has a size of (6, 8), and contains default ASCII_CHARS loadBitmapFont("04b03", "fonts/04b03.png", 6, 8) // load a font with custom characters loadBitmapFont("myfont", "myfont.png", 6, 8, { chars: "☺☻♥♦♣♠" }) // default shaders and custom shader format loadShader("outline", `vec4 vert(vec2 pos, vec2 uv, vec4 color) { // predefined functions to get the default value by kaboom return def_vert(); }`, `vec4 frag(vec2 pos, vec2 uv, vec4 color, sampler2D tex) { // turn everything blue-ish return def_frag() * vec4(0, 0, 1, 1); }`, false) // load only a fragment shader from URL loadShader("outline", null, "/shaders/outline.glsl", true) load(new Promise((resolve, reject) => { // anything you want to do that stalls the game in loading state resolve("ok") })) // add bean to the center of the screen add([ sprite("bean"), pos(center()), // ... ]) // rotate bean 100 deg per second bean.onUpdate(() => { bean.angle += 100 * dt() }) // equivalent to the calling bean.move() in an onKeyDown("left") onUpdate(() => { if (isKeyDown("left")) { bean.move(-SPEED, 0) } }) // shake intensively when bean collides with a "bomb" bean.onCollide("bomb", () => { shake(120) }) // camera follows player player.onUpdate(() => { camPos(player.pos) }) button.onHover((c) => { setCursor("pointer") }) // toggle fullscreen mode on "f" onKeyPress("f", (c) => { setFullscreen(!isFullscreen()) }) // 3 seconds until explosion! Runnn! wait(3, () => { explode() }) // wait() returns a PromiseLike that can be used with await await wait(1) // spawn a butterfly at random position every 1 second loop(1, () => { add([ sprite("butterfly"), pos(rand(vec2(width(), height()))), area(), "friend", ]) }) // play a one off sound play("wooosh") // play a looping soundtrack (check out AudioPlayOpt for more options) const music = play("OverworldlyFoe", { volume: 0.8, loop: true }) // using the handle to control (check out AudioPlay for more controls / info) music.paused = true music.speed = 1.2 // makes everything quieter volume(0.5) // a random number between 0 - 8 rand(8) // a random point on screen rand(vec2(width(), height())) // a random color rand(rgb(255, 255, 255)) rand(50, 100) rand(vec2(20), vec2(100)) // spawn something on the right side of the screen but with random y value within screen height add([ pos(width(), rand(0, height())), ]) randi(10) // returns 0 to 9 randi(0, 3) // returns 0, 1, or 2 randi() // returns either 0 or 1 randSeed(Date.now()) // { x: 0, y: 0 } vec2() // { x: 10, y: 10 } vec2(10) // { x: 100, y: 80 } vec2(100, 80) // move to 150 degrees direction with by length 10 player.pos = pos.add(Vec2.fromAngle(150).scale(10)) // update the color of the sky to light blue sky.color = rgb(0, 128, 255) sky.color = rgb("#ef6360") // animate rainbow color onUpdate("rainbow", (obj) => { obj.color = hsl2rgb(wave(0, 1, time()), 0.6, 0.6) }) // decide the best fruit randomly const bestFruit = choose(["apple", "banana", "pear", "watermelon"]) // every frame all objs with tag "unlucky" have 50% chance die onUpdate("unlucky", (o) => { if (chance(0.5)) { destroy(o) } }) // tween bean to mouse position tween(bean.pos, mousePos(), 1, (p) => bean.pos = p, easings.easeOutBounce) // tween() returns a then-able that can be used with await await tween(bean.opacity, 1, 0.5, (val) => bean.opacity = val, easings.easeOutQuad) // bounce color between 2 values as time goes on onUpdate("colorful", (c) => { c.color.r = wave(0, 255, time()) c.color.g = wave(0, 255, time() + 1) c.color.b = wave(0, 255, time() + 2) }) Color.fromHex(0xfcef8d) Color.fromHex("#5ba675") Color.fromHex("d46eb3") addLevel([ " $", " $", " $$ = $", " % ==== = $", " = ", " ^^ = > = &", "===========================", ], { // define the size of tile block tileWidth: 32, tileHeight: 32, // define what each symbol means, by a function returning a component list (what will be passed to add()) tiles: { "=": () => [ sprite("floor"), area(), solid(), ], "$": () => [ sprite("coin"), area(), pos(0, -9), ], "^": () => [ sprite("spike"), area(), "danger", ], } }) drawSprite({ sprite: "bean", pos: vec2(100, 200), frame: 3, }) drawText({ text: "oh hi", size: 48, font: "sans-serif", width: 120, pos: vec2(100, 200), color: rgb(0, 0, 255), }) drawRect({ width: 120, height: 240, pos: vec2(20, 20), color: YELLOW, outline: { color: BLACK, width: 4 }, }) drawLine({ p1: vec2(0), p2: mousePos(), width: 4, color: rgb(0, 0, 255), }) drawLines({ pts: [ vec2(0), vec2(0, height()), mousePos() ], width: 4, pos: vec2(100, 200), color: rgb(0, 0, 255), }) drawCurve(t => evaluateBezier(a, b, c, d, t) { width: 2, color: rgb(0, 0, 255), }) drawBezier({ pt1: vec2(100, 100), pt2: vec2(200, 100), pt3: vec2(200, 200), pt4: vec2(100, 200), width: 2, color: GREEN }) drawTriangle({ p1: vec2(0), p2: vec2(0, height()), p3: mousePos(), pos: vec2(100, 200), color: rgb(0, 0, 255), }) drawCircle({ pos: vec2(100, 200), radius: 120, color: rgb(255, 255, 0), }) drawEllipse({ pos: vec2(100, 200), radiusX: 120, radiusY: 120, color: rgb(255, 255, 0), }) drawPolygon({ pts: [ vec2(-12), vec2(0, 16), vec2(12, 4), vec2(0, -2), vec2(-8), ], pos: vec2(100, 200), color: rgb(0, 0, 255), }) // text background const txt = formatText({ text: "oh hi", }) drawRect({ width: txt.width, height: txt.height, }) drawFormattedText(txt) pushTransform() // these transforms will affect every render until popTransform() pushTranslate(120, 200) pushRotate(time() * 120) pushScale(6) drawSprite("bean") drawCircle(vec2(0), 120) // restore the transformation stack to when last pushed popTransform() pushTranslate(100, 100) // this will be drawn at (120, 120) drawText({ text: "oh hi", pos: vec2(20, 20), }) loadShader("invert", null, ` vec4 frag(vec2 pos, vec2 uv, vec4 color, sampler2D tex) { vec4 c = def_frag(); return vec4(1.0 - c.r, 1.0 - c.g, 1.0 - c.b, c.a); } `) usePostEffect("invert") // text background const txt = formatText({ text: "oh hi", }) drawRect({ width: txt.width, height: txt.height, }) drawFormattedText(txt) // pause the whole game debug.paused = true // enter inspect mode debug.inspect = true // play a random note in the octave play("noteC", { detune: randi(0, 12) * 100, }) // tune down a semitone music.detune = -100 // tune up an octave music.detune = 1200 Color.fromHex(0xfcef8d) Color.fromHex("#5ba675") Color.fromHex("d46eb3") add([ sprite("butterfly"), pos(100, 200), // a triangle shape! area({ shape: new Polygon([vec2(0), vec2(100), vec2(-100, 100)]) }), ])