Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Drawing

There are two ways to put things on screen. Both end up in the same batched renderer.

Sprites as components

Give an entity a Transform2D and a Sprite, and the built-in draw_sprites system draws it every frame:

#![allow(unused)]
fn main() {
let crab = ctx.load_texture("crab.png");
ctx.world.spawn((
    Transform2D::at(vec2(400.0, 300.0)).rotated(0.3).scaled(vec2(2.0, 2.0)),
    Sprite::new(crab).with_color(Color::RED).with_z(1.0),
));
}
    auto crab = ctx.load_texture("crab.png");
    auto world = ctx.world();
    world.spawn(crabby::transform_at({400, 300}, 0.3f, {2, 2}),
                crabby::sprite_of(crab, crabby::colors::red, 1));
  • Transform2D: position, rotation (radians, clockwise on screen), scale (negative flips).
  • Sprite: texture, color (tint), z (higher is on top), pivot ((0.5, 0.5) is the center).

Immediate drawing

For UI, effects and quick prototypes, draw directly every frame:

#![allow(unused)]
fn main() {
ctx.clear(Color::hex(0x203040));
ctx.draw(&crab, vec2(100.0, 100.0));
ctx.draw_ex(&crab, vec2(200.0, 100.0), DrawParams {
    rotation: 0.5,
    scale: vec2(2.0, 2.0),
    color: Color::RED,
    ..Default::default()
});
}
    ctx.clear(crabby::rgb(0.125f, 0.19f, 0.25f));
    ctx.draw(crab, {100, 100});
    auto params = crabby::draw_defaults();
    params.rotation = 0.5f;
    params.scale = {2, 2};
    params.color = crabby::colors::red;
    ctx.draw(crab, {200, 100}, params);

Coordinates are world units, which are window pixels ((0, 0) at the top-left) until you move the camera; see Camera, text and UI.

Textures

  • ctx.load_texture("path.png") loads an image file.

  • ctx.create_texture(w, h, &rgba) makes one from pixels.

  • ctx.update_texture(&tex, &rgba) replaces a texture’s pixels, for images that change while the game runs (a minimap, a heat map, a simulated field). The ants example draws its pheromone field this way every frame.

Texture is a small Copy handle; store it in components freely.

    std::vector<std::uint8_t> rgba(64 * 64 * 4, 255);
    auto heat = ctx.create_texture(64, 64, rgba.data(), rgba.size());
    rgba[0] = 0;   // ...change pixels...
    ctx.update_texture(heat, rgba.data(), rgba.size());

In C++, Sprite values are crabby::sprite (sprite_of(texture, tint, z)). They can be spawned, read and set, but not used as query columns, because the engine stores sprites in a Rust layout. Transform2D can.

Hot reload

Save an image in your editor and the running game shows the new version within a quarter of a second: textures loaded with load_texture are reloaded when their file changes, under the same handle, so every sprite using them updates by itself. Fonts (load_font) and sounds (load_sound) reload the same way, and so do data files. A file that can’t be read or decoded mid-save keeps the old version. It’s on by default in debug builds and off in release builds; Game::hot_reload(bool) or CRABBY_HOT_RELOAD=1/0 changes that.

If you computed something from an image (a level read from its pixels, a collision mask), redo it when the texture shows up in reloaded_textures:

#![allow(unused)]
fn main() {
Game::new("Art test", 1280, 720)
    .hot_reload(true) // the default in debug builds
    .run(|ctx| {
        let level = ctx.load_texture("level.png");
        move |ctx: &mut Ctx| {
            if ctx.reloaded_textures().contains(&level) {
                println!("level.png changed: rebuilding the map");
            }
        }
    });
}
    crabby::texture level{};
    crabby::game("Art test", 1280, 720)
        .hot_reload(true)   // the default in debug builds of the engine
        .system([&](crabby::ctx& ctx) {
            for (crabby::texture t : ctx.reloaded_textures())
                if (t.id == level.id) std::puts("level.png changed: rebuilding the map");
        }, "watch_level")
        .run([&](crabby::ctx& ctx) { level = ctx.load_texture("level.png"); });

An image that changed size isn’t reloaded, because sprites keep the size their texture had; the console says so, and a restart picks it up. A file caught halfway through saving fails to decode and is tried again on the next save. Checking costs one file stat per loaded texture, four times a second.

Screenshots

#![allow(unused)]
fn main() {
if ctx.key_pressed(Key::F12) {
    ctx.save_screenshot("shot.png");
}
}
    if (ctx.key_pressed(CRABBY_KEY_F12)) ctx.save_screenshot("shot.png");

save_screenshot saves the next frame as a PNG once it has been drawn, even while the window is hidden. Headless runs have no frames, so it does nothing there.