kaboomjs
Start
Debug

Kaboom v3000 Beta Release

tga, 01/15/2023

We're releasing Kaboom v3000 beta! This major version update includes a lot of new features and improvements, and also breaking changes (see migration guide below).

To try the beta version, install kaboom@next

$ npm install kaboom@next
<script src="https://unpkg.com/kaboom@next/dist/kaboom.js"></script>

or use this Replit template

Note that this is a beta release, there are bugs and possiblly breaking changes before official release. Please report bugs to github issues, and general questions / discussions to github discussions.

Scene Graph

Objects can now have children with obj.add()! Children will inherit the transform (position, scale and rotation) of the parent.

const bean = add([
    sprite("bean"),
    pos(160, 120),
])

const sword = bean.add([
    sprite("sword"),
    // transforms will be relative to parent bean object
    pos(20, 20),
    rotate(20),
])

const hat = bean.add([
    sprite("hat"),
    // transforms will be relative to parent bean object
    pos(0, -10),
])

// children will be moved alongside the parent
bean.moveBy(100, 200)

// children will be destroyed alongside the parent
bean.destroy()

Check out the rotating pineapples in this live example (in case you didn't notice, it also demonstrates rotating areas!)

More Font Formats

loadFont() now loads .ttf, .otf, .woff, .woff2 fonts (any font that browser font-face supports). The function to load bitmap font is renamed to loadBitmapFont().

// ComicSans all the way!
kaboom({
    font: "ComicSans",
})

loadFont("ComicSans", "assets/fonts/ComicSans.ttf")

// Load a font with options
loadFont("apl386", "assets/fonts/apl386.ttf", { outline: 4, filter: "linear" })

// To load bitmap font is renamed to loadBitmapFont()
loadBitmapFont("4x4", "/examples/fonts/4x4.png", 4, 4)

Tweening

const t = tween(
    // start value
    obj.pos,
    // destination value
    mousePos(),
    // duration (in seconds)
    0.5,
    // how value should be updated
    (p) => obj.pos = p,
    // interpolation function (defaults to easings.linear)
    easings.easeOutElastic,
)

// can cancel the tween any time
t.cancel()

t.onEnd(() => {
    // register event that runs when tween finishes
})

// tween() returns a then-able, which can be used with await
await tween(...)
await wait(1)
await tween(...)
await tween(...)
await wait(1)

Go to tween example to play around with it! Press left / right arrow key to change the interpolation function, mouse click anywhere to set destination.

Rotated Areas

Previously Kaboom only supports unrotated AABB boxes for collision detection, now Kaboom supports collision detection and resolution between any arbitrary convex polygons (e.g. rotated rectangles).

// rotate() will also affect the area() collider box!
add([
    sprite("bean"),
    pos(200, 100),
    rotate(30),
    area(),
])

// use a custom polygon area shape (a triangle in this case)
add([
    sprite("bean"),
    pos(200, 100),
    area({ shape: new Polygon([vec2(0), vec2(100), vec2(-100, 100)]) }),
])

Check out the new collision example. Press Q / E to rotate bean, and arrow keys to move around and see how bean now reacts with others!

Improved Performance

Graphics performance improved ~3x - 50x, collision detection performance improved ~2x - 4x. Read more about this in this blog post.

Custom Loading Screen

Now you can draw your custom loading screen with onLoading() (note that this function runs every frame, you can only use the drawXXX() functions here)

// the callback is run every frame when assets are initially loading
onLoading(() => {

    // Black background
    drawRect({
        width: width(),
        height: height(),
        color: rgb(0, 0, 0),
    })

    // A pie representing current load progress
    drawCircle({
        pos: center(),
        radius: 32,
        end: map(progress, 0, 1, 0, 360),
    })

    drawText({
        text: "loading" + ".".repeat(wave(1, 4, time() * 12)),
        font: "monospace",
        size: 24,
        anchor: "center",
        pos: center().add(0, 70),
    })

})

Another option is to turn off loading screen completely, default or custom:

kaboom({
    // by doing this, unloaded assets will simply be not drawn or played until they're loaded
    loadingScreen: false,
})

Try it out in the loader example! It also demonstrated some other loading techniques.

Post Effect

Kaboom now provides an easy way to add full screen visual effect with shader:

loadShader("pixelate", null, `
uniform float u_size;
uniform vec2 u_resolution;

vec4 frag(vec2 pos, vec2 uv, vec4 color, sampler2D tex) {
    if (u_size <= 0.0) return def_frag();
    vec2 nsize = vec2(u_size / u_resolution.x, u_size / u_resolution.y);
    float x = floor(uv.x / nsize.x + 0.5);
    float y = floor(uv.y / nsize.y + 0.5);
    vec4 c = texture2D(tex, vec2(x, y) * nsize);
    return c * color;
}
`)

// use the post effect "pixelate", and send the shader uniform
usePostEffect("pixelate", {
    "u_resolution": vec2(width(), height()),
    "u_size": 4,
})

Try it out in the postEffect example. Use UP/DOWN arrow to cycle through the example effects!

Gamepad

Added gamepad support.

onGamepadButtonPress("south", () => {
    player.jump()
})

onGamepadStick("left", (v) => {
    player.move(v.scale(SPEED))
})

If you gamepad doesn't work, add your gamepad mappings to Kaboom's gamepad mapping json file.

Connect a gamepad and go nuts with this classic minigame!

Path Finding

The level API has been rewored and added path finding support. Use the new tile() component with option tile({ isObstacle: true }) to define obstacles in a level, and agent() component to

const map = addLevel([
    "##########",
    "#        #",
    "# @      #",
    "#.....   #",
    "#        #",
    "#    ....#",
    "#      $ #",
    "#        #",
    "##########",
], {
    tileWidth: 64,
    tileHeight: 64,
    tiles: {
        "$": () => [
            sprite("coin"),
            area(),
        ],
        "#": () => [
            sprite("wall"),
            tile({ isObstacle: true }),
        ],
        "@": () => [
            sprite("player"),
            tile(),
            agent({ speed: 640, allowDiagonals: true }),
            "player",
        ],
    },
})

const player = get("player")[0]

onClick(() => {
    bean.setTarget(mousePos())
})

Run this maze example to see for yourself. Click anywhere in the maze to travel to it!

Path finding is implemented by @mflerackers

More features

See complete list of changes in CHANGELOG

Migrating to v3000

You can check this doc