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

Your First Game

Let’s create a simple game that draws a colored square and responds to input. This will introduce you to the core concepts of Emberware game development.

Create the Project

cargo new --lib my-first-game
cd my-first-game

Configure Cargo.toml

Replace the contents of Cargo.toml with:

[package]
name = "my-first-game"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[profile.release]
opt-level = "s"
lto = true

Key settings:

  • crate-type = ["cdylib"] - Builds a C-compatible dynamic library (required for WASM)
  • opt-level = "s" - Optimize for small binary size
  • lto = true - Link-time optimization for even smaller binaries

Write Your Game

Replace src/lib.rs with the following code:

#![allow(unused)]
#![no_std]
#![no_main]

fn main() {
use core::panic::PanicInfo;

// Panic handler required for no_std
#[panic_handler]
fn panic(_: &PanicInfo) -> ! {
    core::arch::wasm32::unreachable()
}

// FFI imports from the Emberware runtime
#[link(wasm_import_module = "env")]
extern "C" {
    fn set_clear_color(color: u32);
    fn button_pressed(player: u32, button: u32) -> u32;
    fn draw_rect(x: f32, y: f32, w: f32, h: f32, color: u32);
    fn draw_text(ptr: *const u8, len: u32, x: f32, y: f32, size: f32, color: u32);
}

// Game state - stored in static variables for rollback safety
static mut SQUARE_Y: f32 = 200.0;

// Button constants
const BUTTON_UP: u32 = 0;
const BUTTON_DOWN: u32 = 1;
const BUTTON_A: u32 = 4;

#[no_mangle]
pub extern "C" fn init() {
    unsafe {
        // Set the background color (dark blue)
        set_clear_color(0x1a1a2eFF);
    }
}

#[no_mangle]
pub extern "C" fn update() {
    unsafe {
        // Move square with D-pad
        if button_pressed(0, BUTTON_UP) != 0 {
            SQUARE_Y -= 10.0;
        }
        if button_pressed(0, BUTTON_DOWN) != 0 {
            SQUARE_Y += 10.0;
        }

        // Reset position with A button
        if button_pressed(0, BUTTON_A) != 0 {
            SQUARE_Y = 200.0;
        }

        // Keep square on screen
        SQUARE_Y = SQUARE_Y.clamp(20.0, 450.0);
    }
}

#[no_mangle]
pub extern "C" fn render() {
    unsafe {
        // Draw title text
        let title = b"Hello Emberware!";
        draw_text(
            title.as_ptr(),
            title.len() as u32,
            80.0,
            50.0,
            32.0,
            0xFFFFFFFF,
        );

        // Draw the moving square
        draw_rect(200.0, SQUARE_Y, 80.0, 80.0, 0xFF6B6BFF);

        // Draw instructions
        let hint = b"D-pad: Move   A: Reset";
        draw_text(
            hint.as_ptr(),
            hint.len() as u32,
            60.0,
            500.0,
            18.0,
            0x888888FF,
        );
    }
}
}

Understanding the Code

No Standard Library

#![allow(unused)]
#![no_std]
#![no_main]
fn main() {
}

Emberware games run in a minimal WebAssembly environment without the Rust standard library. This keeps binaries small and avoids OS dependencies.

FFI Imports

#![allow(unused)]
fn main() {
#[link(wasm_import_module = "env")]
extern "C" {
    fn set_clear_color(color: u32);
    // ...
}
}

Functions are imported from the Emberware runtime. See the Cheat Sheet for all available functions.

Static Game State

#![allow(unused)]
fn main() {
static mut SQUARE_Y: f32 = 200.0;
}

All game state lives in static mut variables. This is intentional - the Emberware runtime automatically snapshots all WASM memory for rollback netcode. No manual state serialization needed!

Colors

Colors are 32-bit RGBA values in hexadecimal:

  • 0xFFFFFFFF = White (R=255, G=255, B=255, A=255)
  • 0xFF6B6BFF = Salmon red (R=255, G=107, B=107, A=255)
  • 0x1a1a2eFF = Dark blue (R=26, G=26, B=46, A=255)

Build and Run

Build the WASM file:

cargo build --target wasm32-unknown-unknown --release

Output: target/wasm32-unknown-unknown/release/my_first_game.wasm

Run in the Emberware player:

ember run target/wasm32-unknown-unknown/release/my_first_game.wasm

Or load the .wasm file directly in the Emberware Library application.

What You’ve Learned

  • Setting up a Rust project for WASM
  • The #![no_std] environment
  • Importing FFI functions from the runtime
  • The three lifecycle functions: init(), update(), render()
  • Drawing 2D graphics with draw_rect() and draw_text()
  • Handling input with button_pressed()
  • Using static variables for game state

Next: Understanding the Game Loop