Development
Getting Started
Create games for Emberware hardware
Making games for Emberware hardware means working within the constraints of real (if obscure) hardware. This guide covers ZX development. Support for Chroma and z is coming.
Prerequisites
Before you start, make sure you have:
- Rust installed (rustup.rs)
- A code editor (VS Code, Sublime, Vim, whatever you prefer)
- Basic programming knowledge (any language is fine)
- An Emberware account for uploading games
Install the Emberware SDK
Clone the Emberware repository and build the development tools:
git clone https://github.com/emberware-io/emberware.git
cd emberware
cargo build --release This gives you the player, dev tools, and example games to learn from.
Create Your First Game
Create a new Rust library project:
cargo new --lib my-game
cd my-game Update Cargo.toml:
[package]
name = "my-game"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[profile.release]
opt-level = "s"
lto = true Understanding the Game Loop
Every Emberware game exports three functions. Replace src/lib.rs with:
#![no_std]
#![no_main]
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_: &PanicInfo) -> ! { core::arch::wasm32::unreachable() }
// Import runtime functions
#[link(wasm_import_module = "env")]
extern "C" {
fn set_clear_color(color: u32);
fn draw_rect(x: f32, y: f32, w: f32, h: f32, color: u32);
fn button_pressed(player: u32, button: u32) -> u32;
}
static mut Y: f32 = 200.0;
#[no_mangle]
pub extern "C" fn init() {
unsafe { set_clear_color(0x1a1a2eFF); }
}
#[no_mangle]
pub extern "C" fn update() {
unsafe {
if button_pressed(0, 0) != 0 { Y -= 10.0; } // Up
if button_pressed(0, 1) != 0 { Y += 10.0; } // Down
}
}
#[no_mangle]
pub extern "C" fn render() {
unsafe { draw_rect(200.0, Y, 80.0, 80.0, 0xFF6B6BFF); }
} All game state lives in static mut variables — this enables automatic rollback netcode for online multiplayer!
Drawing Graphics
Emberware provides 2D and 3D drawing APIs:
2D Drawing
// Draw rectangles and text
draw_rect(x, y, width, height, 0xFF0000FF); // Red
draw_text(b"Hello!".as_ptr(), 6, x, y, 24.0, 0xFFFFFFFF); 3D Graphics
// Set up camera
camera_set(0.0, 5.0, 10.0, 0.0, 0.0, 0.0);
// Create and draw a cube
let cube_handle = cube(1.0, 1.0, 1.0);
push_identity();
push_translate(0.0, 1.0, 0.0);
push_rotate_y(45.0);
draw_mesh(cube_handle); Handling Input
Check button states with the input API:
// Button constants
const BUTTON_A: u32 = 4;
const BUTTON_LEFT: u32 = 2;
// Check if A was just pressed this frame
if button_pressed(0, BUTTON_A) != 0 {
jump();
}
// Check if button is held down
if button_held(0, BUTTON_LEFT) != 0 {
player_x -= 5.0;
}
// Analog stick input (-1.0 to 1.0)
let stick_x = left_stick_x(0);
player_x += stick_x * 5.0; Adding Sound
Play sounds with simple procedural audio or loaded samples:
// Load PCM samples (22050 Hz, mono, i16)
let samples = generate_beep(); // Your audio data
let sfx = load_sound(samples.as_ptr(), (samples.len() * 2) as u32);
// Play with volume and stereo pan
play_sound(sfx, 0.8, 0.0); // volume=0.8, pan=center
// Or load from ROM assets
let jump = rom_sound(b"jump".as_ptr(), 4);
play_sound(jump, 1.0, -0.5); // pan left Build & Test
Compile your game to WebAssembly:
cargo build --target wasm32-unknown-unknown --release Run in the Emberware player:
ember run target/wasm32-unknown-unknown/release/my_game.wasm Package for Release
Create an ember.toml manifest and package your game:
[game]
id = "my-game"
title = "My Game"
author = "Your Name"
version = "1.0.0" Build and pack:
ember build && ember pack Submit to the Archive
- Sign in to your account
- Go to your Dashboard
- Click "Upload New Game"
- Fill in the details (title, description, category)
- Upload your
.embROM file - Add an icon (64x64 PNG) and screenshots (optional)
- Click "Publish"
Your game is now part of the Emberware Archive — playable by anyone with the emulator.
Continue
Next Steps
Reference
Common Patterns
How do I implement game states (menu, playing, game over)?
enum GameState { Menu, Playing, GameOver }
static mut STATE: GameState = GameState::Menu;
pub extern "C" fn update() {
unsafe {
match STATE {
GameState::Menu => update_menu(),
GameState::Playing => update_game(),
GameState::GameOver => update_game_over(),
}
}
} How do I handle collision detection?
fn aabb_collision(a: &Rect, b: &Rect) -> bool {
a.x < b.x + b.width &&
a.x + a.width > b.x &&
a.y < b.y + b.height &&
a.y + a.height > b.y
}
if aabb_collision(&player.bounds(), &enemy.bounds()) {
player.take_damage();
} How do I save player progress?
// 8 save slots available, up to 64KB each
const SLOT: u32 = 0;
// Save bytes
let data: [u8; 8] = [level, score, /* ... */];
save_write(SLOT, data.as_ptr(), data.len() as u32);
// Load bytes
let mut data = [0u8; 8];
let size = save_read(SLOT, data.as_mut_ptr(), 8);
if size > 0 { /* data loaded */ } How does online multiplayer work?
// Multiplayer is AUTOMATIC! Just write rollback-safe code:
// 1. All state in static variables
static mut PLAYER1_X: f32 = 0.0;
static mut PLAYER2_X: f32 = 0.0;
// 2. Read inputs for each player
let p1_input = left_stick_x(0);
let p2_input = left_stick_x(1); // Works online!
// 3. Deterministic update (no random system calls)
PLAYER1_X += p1_input * SPEED;
PLAYER2_X += p2_input * SPEED;
// The runtime handles networking, rollback, and prediction! Ready to Build?
You now have everything you need to create your first game for Emberware hardware.