// 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)]) }),
])