Most of the functions if floaty closely resemble functions in Pico-8 . There is a bit of extra code needed due to the JS nature of floaty , so the quickest way to get started is to use the following snippet as a starting point:

import { Engine } from 'https://floaty.dev/engine-v1.js';

var engine = new Engine();
engine.expose();

var sprites = {
    // this is where your sprites go
};

var sounds = {
    // this is where your sounds go
};

function init() {
    // things to do when the game starts
}

function update() {
    // things to do each game tick:
    // updating the game state
    // moving the player
    // handling input
}

function draw() {
    // things to do each time the screen updates:
    // - clearing the canvas
    // - drawing on the canvas
}

engine.start({
    sprites,
    sounds,
    init,
    update,
    draw,
});

// code: https://codepen.io/assertchris/pen/yLZMVGO

One of the goals of floaty is that it should be easy to get started. That's why I've gone with a module importing approach. You can use this code in any modern browser without a build step. The code for the examples on this website actually works like this.

In the layout file:

<script type="module">
    import { Engine } from 'https://floaty.dev/engine-v1.js';
    window.Engine = Engine;
</script>

In each preview:

window.gameX = new window.Engine();
window.gameX.expose();

// game x code

window.gameX.start({
    sprites,
    sounds,
    init,
    update,
    draw,
    target: document.querySelector('.targetX'),
});

You don't actually need to assign the Engine instance to a variable, but I do because we need a way to stop one game when another is started, for performance reasons.

Global functions

To make floaty more like Pico-8, I made a way to expose commonly used functions to the top scope. They aren't global by default. You have to enable that by calling the expose method:

var engine = new Engine();

// the following will not work partly because
// spr is undefined at this point
spr('player', 16, 16);

engine.expose();
engine.start({ ... });

// the following will work as long as it's inside
// init or draw and the engine has been started
spr('player', 16, 16);

Every global function can also be used on the same Engine instance expose was called on:

var game1 = new Engine();
game1.expose();

// these two function calls do the same thing
spr('player', 16, 16);
game1.spr('player', 16, 16);

var game2 = new Engine();
game2.expose();

// this will happen in game2 not in game1
spr('player', 16, 16);

// if you want to do more stuff in game1 you will need
// to use the non-global form
game1.spr('player', 16, 16);

If you plan to have multiple games running on the same page, you need to write your games without using global functions. It's definitely possible to do this using the correct context:

var engine = new Engine();
var random;

function update() {
    random = this.rnd() * 127;
};

function draw() {
    this.spr('player', 16, random);
}

engine.start({ ... });

Animator

Animator is a small utility class that handles starting and stopping of animation loops. If you wanted to animate something in typical JS; you might write something like this:

function animate() {
    // animate all the things
    requestAnimationFrame(animate);
}

requestAnimationFrame(animate);

You might also build a way to cancel the animation. This is great code because it overcomes a lot of the downsides of traditional setTimeout / setInterval approaches, but it does make it harder to control things like max FPS.

Animator packages requestAnimation frame up with the ability to call:

You can call frameRate with no arguments to find out what the current framerate is.

Sound

All sounds are handled by the Tone.js library. Synthesizing sound in the browser is hard, and I don't want to maintain that kind of code. I'd much prefer to let Tone.js handle the heavy lifting. Heavy is the right word, though. At the time of writing, the engine source code is less than 10% of the size of Tone.js; so keep that in mind when building games with sound. You might want to create your own basic MP3/WAV playback functionality. The moment you use the sfx() function, you'll load all the Tone.js source code as well.

The easiest way to understand what the engine is using Tone.js for is by look at an example sound:

var sounds = {
    'explosion': [
        1,
        [40, 0.8, 2],
        [40, 0.8, 2],
        [40, 0.8, 2],
        [40, 0.8, 2],
        [30, 0.8, 2],
        [20, 0.8, 2],
        [10, 0.8, 2],
        [10, 0.8, 2],
        [10, 0.8, 2],
        [10, 0.8, 2],
    ],
};

In this 'explosion' sound, the first number is the length of time each note is played for. The greater this number, the longer the sound effect will be.

This is followed by arrays of [frequency, volume, instrument] . Frequency is the pitch of the note.

The available instruments are those supported by the Audio Context API . You can use the following instrument numbers:

You're unlikely to use this class directly, because it's cumbersome to use. Calling the sfx('explosion') function will pick up this array inside sounds , and handle all the instrument, frequency, and volume nonsense for you.

Preloader

Preloader is another small utility class that allows you to define your assets away from your game code. Say you're building a game in a team; where you are writing code another someone else is making sprites, and someone else is making sounds.

You might not all want to be working in the same JS file; and you probably don't want to be hand-coding sprites and sounds.

In this case, the Engine provides a preload function that takes a URL. This URL can be anywhere (provided you set up the appropriate CORS headers ). You can use Preloader directly, or via the engine:

var sounds = {
    'explosion': engine.preload('https://example.com/explosion'),
};

These are asynchronously loaded, so you need to remember to call the load method before you require any preloaded assets:

var mode = 'loading';

async function init() {
    // these don't need assets for spr() or sfx()
    // so they can do their thing before load()
    stars.init(this);
    particles.init(this);

    // this loads all the preloaded assets
    await this.load();

    // now we can begin using the assets in update() and draw()
    mode = 'start';
}

Versioning

Right now, the engine API isn't very stable. When it is, I will figure out a way to tag it, probably via different URLs for different versions. Expect change, but feel free to reach out with questions or for help.

Defaults

I have tried to stick as closely to the defaults of Pico-8 as possible.

Screen size

The screen is 128 pixels wide and 128 pixels high. If the container is bigger then the canvas on which everything is draw will scale to fit while maintaining a square aspect ratio. That means your game will always look pixelated, even if each pixel is actually a square block of pixels. You can inspect the ratio property if you're interested how many pixels wide and high each pixel is:

function update() {
    text('ratio: ' + this.ratio, 16, 16);
}

The rest of the screen, around the game canvas, will be the same as color 0 . If you want to override this, you can provide a different background color to the start() function:

engine.start({
    // ...
    background: 'white',
});

The background color can be any supported by CSS .

It can be tricky to get the container to stretch, especially if you're not overly familiar with CSS or HTML. Try something like this:

<!doctype html>
<html>
    <head>
        <style>
            html, body {
                margin: 0;
                padding: 0;
                width: 100%;
                height: 100%;
                overflow: hidden;
            }

            .game {
                width: 100vw;
                height: 100vh;
            }
        </style>
    </head>
    <body>
        <div class="game"></div>
        <script type="module">
            import {Engine} from "https://floaty.dev/engine-v1.js";

            var game = new Engine();

            // ...

            game.start({
                // ...
                target: document.querySelector('.game'),
            });
        </script>
    </body>
</html>

Sprites

In Pico-8, these are 8x8 or a multiple thereof; and larger sprites are drawn using optional parameters on the spr function. They're also 8x8 in floaty , though that is not enforced very well by the definition of the sprites variable. Use this pattern to avoid issues:

var sprites = {
    'player': [
        -1, -1, -1,  2,  2, -1, -1, -1,
        -1, -1,  2,  8,  8,  2, -1, -1,
        -1, -1,  2,  8,  8,  2, -1, -1,
        -1,  2, 14,  8,  8, 14,  2, -1,
         2, 14,  8,  7, 12,  8, 14,  2,
         2,  8,  8,  1,  1,  8,  8,  2,
        -1,  2,  8,  5,  5,  8,  2, -1,
        -1, -1,  2,  9,  9,  2, -1, -1,
    ],
};

In this array, -1 means no color is draw on that pixel. All other numbers are the numbers corresponding to palette colors.

Colors

I use the same colors as the default color palette in Pico-8:

floaty is developed by assertchris as part of sandcastle.games