jugar-web
Web platform bindings for running Jugar games in the browser.
Overview
jugar-web provides the WASM bindings for running Jugar games in web browsers. It handles:
- WASM module initialization
- Event forwarding from browser to game
- Render command output to JavaScript
- WebAudio integration
- Touch/mouse/keyboard input
WebPlatform
The main interface exposed to JavaScript:
#![allow(unused)] fn main() { use wasm_bindgen::prelude::*; #[wasm_bindgen] pub struct WebPlatform { game: GameState, config: WebConfig, } #[wasm_bindgen] impl WebPlatform { #[wasm_bindgen(constructor)] pub fn new(width: u32, height: u32) -> Self { // Initialize platform } pub fn frame(&mut self, timestamp: f64, input_json: &str) -> String { // Process frame, return render commands as JSON } pub fn key_down(&mut self, code: &str) { // Handle key press } pub fn key_up(&mut self, code: &str) { // Handle key release } pub fn mouse_move(&mut self, x: f32, y: f32) { // Handle mouse movement } pub fn touch_start(&mut self, id: u32, x: f32, y: f32) { // Handle touch start } } }
Building
# Build with wasm-pack
wasm-pack build crates/jugar-web --target web --out-dir pkg
# Or use make
make build-web
HTML Integration
Minimal loader (no game logic in JS):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Jugar Game</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script type="module">
import init, { WebPlatform } from './pkg/jugar_web.js';
async function main() {
await init();
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const platform = new WebPlatform(800, 600);
// Event forwarding only
document.addEventListener('keydown', e => {
platform.key_down(e.code);
});
document.addEventListener('keyup', e => {
platform.key_up(e.code);
});
canvas.addEventListener('mousemove', e => {
platform.mouse_move(e.offsetX, e.offsetY);
});
function frame(timestamp) {
const commands = platform.frame(timestamp, '[]');
render(ctx, JSON.parse(commands));
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
}
function render(ctx, commands) {
for (const cmd of commands) {
switch (cmd.type) {
case 'clear':
ctx.fillStyle = cmd.color;
ctx.fillRect(0, 0, 800, 600);
break;
case 'rect':
ctx.fillStyle = cmd.color;
ctx.fillRect(cmd.x, cmd.y, cmd.w, cmd.h);
break;
case 'text':
ctx.fillStyle = cmd.color;
ctx.font = `${cmd.size}px sans-serif`;
ctx.fillText(cmd.text, cmd.x, cmd.y);
break;
}
}
}
main();
</script>
</body>
</html>
Render Commands
JSON format for render commands:
[
{"type": "clear", "color": "#1a1a2e"},
{"type": "rect", "x": 100, "y": 200, "w": 50, "h": 50, "color": "#ffffff"},
{"type": "text", "x": 400, "y": 50, "text": "Score: 100", "size": 24, "color": "#ffffff"},
{"type": "circle", "x": 300, "y": 300, "r": 10, "color": "#ff0000"}
]
Audio Commands
[
{"type": "playTone", "frequency": 440, "duration": 0.1},
{"type": "playSound", "id": "explosion", "volume": 0.8}
]
Testing
E2E tests using Probar:
# Run Probar tests (replaces Playwright)
make test-e2e
# Or directly
cargo test -p jugar-web --test probar_pong
Configuration
#![allow(unused)] fn main() { pub struct WebConfig { pub width: u32, pub height: u32, pub canvas_id: String, pub audio_enabled: bool, pub touch_enabled: bool, } }
Feature Flags
[features]
default = ["audio"]
audio = [] # Enable WebAudio support
debug = [] # Enable debug overlays