kaboom.js (beta) is a JavaScript library that helps you make games fast and fun!

also check out the kaboom environment on replit.com!

Usage

quick start

<script src="https://kaboomjs.com/lib/0.1.0/kaboom.js"></script>
<script type="module">

// make kaboom functions global
kaboom.global();

// init kaboom context
init();

// define a scene
scene("main", () => {

	// add a text at position (100, 100)
	add([
		text("ohhimark", 32),
		pos(100, 100),
	]);

});

// start the game
start("main");

</script>

Import

All functions are under a global object 'kaboom', but you can also choose to import all functions into global namespace.

kaboom.global()

import all kaboom functions into global namespace

// 1) import everything to global
kaboom.global();
init();

// 2) keep them under kaboom namespace to prevent any collision
const k = kaboom;
k.init();

Lifecycle

Application Lifecycle Methods

init([conf])

initialize context

// quickly create a 640x480 canvas and get going
init();

// options
init({
	width: 480, // width of canvas
	height: 480, // height of canvas
	canvas: document.getElementById("game"), // use custom canvas
	scale: 2, // pixel size (for pixelated games you might want small canvas + scale)
	clearColor: rgb(0, 0, 1), // background color (default black)
	fullscreen: true, // if fullscreen
	crisp: true, // if pixel crisp (for sharp pixelated games)
});

start(scene, [...args])

start the game loop with specified scene

scene("game", () => {/* .. */});
scene("menu", () => {/* .. */});
scene("lose", () => {/* .. */});
start("game");

Scene

Scenes are the different stages of a game, like different levels, menu screen, and start screen etc. Everything belongs to a scene.

scene(name)

describe a scene

scene("level1", () => {
	// all objs are bound to a scene
	add(/* ... */)
	// all events are bound to a scene
	keyPress(/* ... */)
});

scene("level2", () => {
	add(/* ... */)
});

scene("gameover", () => {
	add(/* ... */)
});

start("level1");

go(name, [...args])

switch to a scene

// go to "paused" scene when pressed "p"
scene("main", () => {
	let score = 0;
	keyPress("p", () => {
		go("gameover", score);
	})
});

scene("gameover", (score) => {
	// display score passed by scene "main"
	add([
		text(score),
	]);
});

layers(names, [default])

define the draw layers of the scene

// draw background on the bottom, ui on top, layer "obj" is default
layers([
	"bg",
	"obj",
	"ui",
], "obj");

// this will be added to the "obj" layer since it's defined as default above
const player = add([
	sprite("froggy"),
]);

// this will be added to the "ui" layer cuz it's specified by the layer() component
const score = add([
	text("0"),
	layer("ui"),
]);

gravity(value)

set the gravity value (defaults to 980)

// (pixel per sec.)
gravity(1600);

camPos(pos)

set the camera position

// camera position follow player
player.action(() => {
	camPos(player.pos);
});

camScale(scale)

set the camera scale

if (win) {
	camPos(player.pos);
	// get a close up shot of the player
	camScale(3);
}

camRot(angle)

set the camera angle

camRot(0.1);

camIgnore(layers)

make camera don't affect certain layers

// make camera not affect objects on layer "ui" and "bg"
camIgnore(["bg", "ui"]);

Asset Loading

Load assets into asset manager. These should be at application top.

loadSprite(name, src, [conf])

load a sprite

loadSprite("froggy", "froggy.png");
loadSprite("froggy", "https://replit.com/public/images/mark.png");

// slice a spritesheet and add anims manually
loadSprite("froggy", "froggy.png", {
	sliceX: 4,
	sliceY: 1,
	anims: {
		run: [0, 2],
		jump: [3],
	},
});

// load with aseprite sprite sheet
loadSprite("froggy", "froggy.png", {
	aseSpriteSheet: "froggy.json", // use spritesheet exported from aseprite
});

loadSound(name, src, [conf])

load a sound

loadSound("shoot", "shoot.ogg");

loadFont(name, src, charWidth, charHeight, [chars])

load a font

// default character mappings: (ASCII 32 - 126)
// const ASCII_CHARS = " !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~";

// load a bitmap font called "04b03", with bitmap "04b03.png", each character on bitmap has a size of (6, 8), and contains default ASCII_CHARS
loadFont("04b03", "04b03.png", 6, 8);

// load a font with custom characters
loadFont("CP437", "CP437.png", 6, 8, "☺☻♥♦♣♠");

Objects

Game Object is the basic unit of Kaboom, each game object uses components to compose their data and behavior.

add(comps)

add a game object to scene

// a game object consists of a list of components
const player = add([
	// a 'sprite' component gives it the render ability
	sprite("froggy"),
	// a 'pos' component gives it a position
	pos(100, 100),
	// a 'body' component makes it fall and gives it jump()
	body(),
	// raw strings are tags
	"player",
	"killable",
	// custom fields are assigned directly to the returned obj ref
	{
		dir: vec2(-1, 0),
		dead: false,
		speed: 240,
	},
]);

player.action(() => {
	player.move(player.dir.scale(player.speed));
});

player.hidden = false; // if this obj renders
player.paused = true // if this obj updates

// runs every frame as long as player is not destroy() ed
player.action(() => {
	player.move(100, 0);
});

// provided by 'sprite()'
player.play("jump"); // play a spritesheet animation
console.log(player.frame); // get current frame

// provided by 'pos()'
player.move(100, 20);
console.log(player.pos);

// provided by 'body()'
player.jump(320); // make player jump

destroy(obj)

remove a game object from scene

collides("bullet", "killable", (b, k) => {
	// remove both the bullet and the thing bullet hit with tag "killable" from scene
	destroy(b);
	destroy(k);
	score++;
});

obj.action(cb)

update the object, the callback is run every frame

player.action(() => {
	player.move(SPEED, 0);
});

obj.use(comp)

add a component to a game object

// rarely needed since you usually specify all comps in the 'add()' step
obj.use(scale(2, 2));

obj.exists()

check if obj exists in scene

// sometimes you might keep a reference of an object that's already 'destroy()'ed, use exists() to check if they were
if (obj.exists()) {
	child.pos = obj.pos.clone();
}

obj.is(tag)

if obj has certain tag(s)

if (obj.is("killable")) {
	destroy(obj);
}

obj.on(event, cb)

listen to an event

// when obj is 'destroy()'ed
obj.on("destroy", () => {
	add([
		sprite("explosion"),
	]);
});

// runs every frame when obj exists
obj.on("update", () => {
	// ...
});

// custom event from comp 'body()'
obj.on("grounded", () => {
	// ...
});

obj.trigger(event)

trigger an event (triggers 'on')

obj.on("grounded", () => {
	obj.jump();
});

// mainly for custom components defining custom events
obj.trigger("grounded");

get(tag)

get a list of obj reference with a certain tag

const enemies = get("enemy");

every(tag, cb)

run a callback on every obj with a certain tag

// equivalent to destroyAll("enemy")
every("enemy", (obj) => {
	destroy(obj);
});

destroyAll(tag)

destroy every obj with a certain tag

Components

Built-in components. Each component gives the game object certain data / behaviors.

pos(x, y)

object's position in the world

const obj = add([
	pos(0, 50),
]);

// get the current position in vec2
console.log(obj.pos);

// move an object by a speed (dt will be multiplied)
obj.move(100, 100);

scale(x, y)

scale

const obj = add([
	scale(2),
]);

// get the current scale in vec2
console.log(obj.scale);

rotate(angle)

scale

const obj = add([
	rotate(2),
]);

obj.action(() => {
	obj.angle += dt();
});

color(r, g, b, [a])

color

const obj = add([
	sprite("froggy"),
	// give it a blue tint
	color(0, 0, 1),
]);

obj.color = rgb(1, 0, 0); // make it red instead

sprite(id, [conf])

draw sprite

// note: this automatically gives the obj an 'area()' component
const obj = add([
	// sprite is loaded by loadSprite("froggy", src)
	sprite("froggy"),
]);

const obj = add([
	sprite("froggy", {
		animSpeed: 0.3, // time per frame (defaults to 0.1)
		frame: 2, // start frame (defaults to 0)
	}),
]);

// get current frame
console.log(obj.frame);

// play animation
obj.play("jump");

// stop the anim
obj.stop();

obj.onAnimEnd("jump", () => {
	obj.play("fall");
});

text(txt, size, [conf])

draw text

// note: this automatically gives the obj an 'area()' component
const obj = add([
	// content, size
	text("oh hi", 64),
]);

const obj = add([
	text("oh hi", 64, {
		width: 120, // wrap when exceeds this width (defaults to 0 no wrap)
		font: "proggy", // font to use (defaults to "unscii")
	}),
]);

// update the content
obj.text = "oh hi mark";

rect(w, h)

draw rectangle

// note: this automatically gives the obj an 'area()' component
const obj = add([
	// width, height
	rect(50, 75),
	pos(25, 25),
	color(0, 1, 1),
]);

// update size
obj.width = 75;
obj.height = 75;

area(p1, p2)

a rectangular area for collision checking

// 'area()' is given automatically by 'sprite()' and 'rect()', but you can override it
const obj = add([
	sprite("froggy"),
	// override to a smaller region
	area(vec2(6), vec2(24)),
]);

// callback when collides with a certain tag
obj.collides("collectable", (c) => {
	destroy(c);
	score++;
});

// similar to collides(), but doesn't pass if 2 objects are just touching each other (checks for distance < 0 instead of distance <= 0)
obj.overlaps("collectable", (c) => {
	destroy(c);
	score++;
});

// checks if the obj is collided with another
if (obj.isCollided(obj2)) {
	// ...
}

if (obj.isOverlapped(obj2)) {
	// ...
}

// register an onClick callback
obj.clicks(() => {
	// ...
});

// if the obj is clicked last frame
if (obj.isClicked()) {
	// ...
}

// register an onHover callback
obj.hovers(() => {
	// ...
});

// if the obj is currently hovered
if (obj.isHovered()) {
	// ...
}

// check if a point is inside the obj area
obj.hasPt();

// resolve all collisions with objects with 'solid'
// for now this checks against all solid objs in the scene (this is costly now)
obj.resolve();

body([conf])

component for falling / jumping

const player = add([
	pos(0, 0),
	// now player will fall in this gravity world
	body(),
]);

const player = add([
	pos(0, 0),
	body({
		// force of .jump()
		jumpForce: 640,
		// maximum fall velocity
		maxVel: 2400,
	}),
]);

// body() gives obj jump() and grounded() methods
keyPress("up", () => {
	if (player.grounded()) {
		player.jump(JUMP_FORCE);
	}
});

// and a "grounded" event
player.on("grounded", () => {
	console.log("horray!");
});

solid()

mark the obj so other objects can't move past it if they have an area and resolve()

const obj = add([
	sprite("wall"),
	solid(),
]);

// need to call resolve() (provided by 'area') to make sure they cannot move past solid objs
player.action(() => {
	player.resolve();
});

origin(orig)

the origin to draw the object (default center)

const obj = add([
	sprite("froggy"),

	// defaults to "topleft"
	origin("topleft"),
	// other options
	origin("top"),
	origin("topright"),
	origin("left"),
	origin("center"),
	origin("right"),
	origin("botleft"),
	origin("bot"),
	origin("botright"),
	origin(vec2(0, 0.25)), // custom
]);

layer(name)

specify the layer to draw on

layers([
	"bg",
	"game",
	"ui",
], "game");

add([
	sprite("sky"),
	layer("bg"),
]);

// we specified "game" to be default layer above, so a manual layer() comp is not needed
const player = add([
	sprite("froggy"),
]);

const score = add([
	text("0"),
	layer("ui"),
]);

Events

kaboom uses tags to group objects and describe their behaviors, functions below all accepts the tag as first arguments, following a callback

action(tag, cb)

calls every frame for a certain tag

// every frame move objs with tag "bullet" up with speed of 100
action("bullet", (b) => {
	b.move(vec2(0, 100));
});

action("flashy", (f) => {
	f.color = rand(rgb(0, 0, 0), rgb(1, 1, 1));
});

collides(tag, cb)

calls when objects collides with others

collides("enemy", "bullet", (e, b) => {
	destroy(b);
	e.life--;
	if (e.life <= 0) {
		destroy(e);
	}
});

overlaps(tag, cb)

calls when objects collides with others

// similar to collides(), but doesn't pass if 2 objects are just touching each other (checks for distance < 0 instead of distance <= 0)
overlaps("enemy", "bullet", (e, b) => {
	destroy(b);
	e.life--;
	if (e.life <= 0) {
		destroy(e);
	}
});

on(event, tag, cb)

add lifecycle events to a tag group

// called when objs with tag "enemy" is added to scene
on("add", "enemy", (e) => {
	console.log("run!!");
});

// per frame (action() is actually an alias to this)
on("update", "bullet", (b) => {
	b.move(100, 0);
});

// per frame but drawing phase if you want custom drawing
on("draw", "bullet", (e) => {
	drawSprite(...);
});

// when objs gets destroy() ed
on("destroy", "bullet", (e) => {
	play("explosion");
});

Input

input events

keyDown(key, cb)

runs every frame when specified key is being pressed

keyPress(key, cb)

runs once when specified key is just pressed

keyRelease(key, cb)

runs once when specified key is just released

mouseDown(cb)

runs every frame when left mouse is being pressed

mouseClick(cb)

runs once when left mouse is just clicked

mouseRelease(cb)

runs once when left mouse is just released

Query

information about current window and input states

width()

canvas width

height()

canvas height

time()

current game time

dt()

delta time since last frame

mousePos()

current mouse position

Timer

timed events

wait(time, cb)

runs the callback after time seconds

wait(3, () => {
	destroy(froggy);
});

// or
await wait(3);
destroy(froggy);

loop(time, cb)

runs the callback every time seconds

loop(0.5, () => {
	console.log("just like setInterval");
});

Audio

yeah

play(id, [conf])

plays a sound

on("destroy", "enemy", (e) => {
	play("explode", {
		volume: 2.0,
		speed: 0.8,
		detune: 1200,
	});
});

const music = play("mysong");

music.pause();
music.resume();
music.stop();

volume(volume)

set the master volume

Math

math types & utils

vec2(x, y)

creates a vector 2

vec2() // => { x: 0, y: 0 }
vec2(1) // => { x: 1, y: 1 }
vec2(10, 5) // => { x: 10, y: 5 }

const p = vec2(5, 10);

p.x // 5
p.y // 10
p.clone(); // => vec2(5, 10)
p.add(vec2(10, 10)); // => vec2(15, 20)
p.sub(vec2(5, 5)); // => vec2(0, 5)
p.scale(2); // => vec2(10, 20)
p.dist(vec2(15, 10)); // => 10
p.len(); // => 11.58
p.unit(); // => vec2(0.43, 0.86)
p.dot(vec2(2, 1)); // => vec2(10, 10)
p.angle(); // => 1.1

rgba(r, g, b, a)

creates a color from red, green, blue and alpha values (note: values are 0 - 1 not 0 - 255)

const c = rgba(0, 0, 1, 1); // blue

p.r // 0
p.g // 0
p.b // 1
p.a // 1

c.clone(); // => rgba(0, 0, 1, 1)

rgb(r, g, b)

shorthand for rgba() with a = 1

rand(a, b)

generate random value

rand() // 0.0 - 1.0
rand(1, 4) // 1.0 - 4.0
rand(vec2(0), vec2(100)) // => vec2(29, 73)
rand(rgb(0, 0, 0.5), rgb(1, 1, 1)) // => rgba(0.3, 0.6, 0.9, 1)

randSeed(seed)

set seed for rand generator

randSeed(Date.now());

makeRng(seed)

create a seedable random number generator

const rng = makeRng(Date.now());

rng.gen(); // works the same as rand()

choose(arr)

get random element from array

chance(p)

rand(0, 1) <= p

lerp(a, b, t)

linear interpolation

map(a, b, x, y, t)

map number to another range

Draw

Raw immediate drawing functions (you prob won't need these)

render(cb)

use a generic draw loop for custom drawing

scene("draw", () => {
	render(() => {
		drawSprite(...);
		drawRect(...);
		drawLine(...);
	});
});

drawSprite(name, [conf])

draw a sprite

drawSprite("car", {
	pos: vec2(100),
	scale: 3,
	rot: time(),
	frame: 0,
});

drawRect(pos, w, h, [conf])

draw a rectangle

drawRect(vec2(100), 20, 50);

drawLine(p1, p2, [conf])

draw a rectangle

drawLine(vec2(0), mousePos(), {
	width: 2,
	color: rgba(0, 0, 1, 1),
	z: 0.5,
});

drawText(text, [conf])

draw a rectangle

drawText("hi", {
	size: 64,
	pos: mousePos(),
	origin: "topleft",
});

Level

helpers on building tiled maps

addLevel(map, ref)

takes a level drawing and turn them into game objects according to the ref map

const characters = {
	"a": {
		sprite: "ch1",
		msg: "ohhi how are you",
	},
};

const map = addLevel([
	"                 a ",
	"                ===",
	"   ?       *       ",
	"         ====  ^^  ",
	"===================",
], {
	width: 11,
	height: 11,
	pos: vec2(0, 0),
	// every "=" on the map above will be turned to a game object with following comps
	"=": [
		sprite("ground"),
		solid(),
		"block",
	],
	"*": [
		sprite("coin"),
		solid(),
		"block",
	],
	// use a callback for dynamic evauations per block
	"?": () => {
		return [
			sprite("prize"),
			color(0, 1, rand(0, 1)),
			"block",
		];
	},
	"^": [
		sprite("spike"),
		solid(),
		"spike",
		"block",
	],
	// any catches anything that's not defined by the mappings above, good for more dynamic stuff like this
	any(ch) {
		if (characters[ch]) {
			return [
				sprite(char.sprite),
				solid(),
				"character",
				{
					msg: characters[ch],
				},
			];
		}
	},
});

// query size
map.width();
map.height();

// get screen pos through map index
map.getPos(x, y);

// destroy all
map.destroy();

// there's no spatial hashing yet, if too many blocks causing lag, consider hard disabling collision resolution from blocks far away by turning off 'solid'
action("block", (b) => {
	b.solid = player.pos.dist(b.pos) <= 20;
});

Debug

debug utilities

fps()

current frames per second

objCount()

current number of objects in scene

pause()

pause the game

unpause()

unpause the game

kaboom.debug()

debug flags

// scale the time
kaboom.debug.timeScale = 0.5;

// show the bounding box of objects with area()
kaboom.debug.showArea = true;

// hover to inspect objects (needs showArea checked)
kaboom.debug.hoverInfo = true;