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.