Gaming & Canvas dengan Wasm — WebAssembly

Gaming & Canvas dengan WebAssembly Game development adalah salah satu motivasi awal WebAssembly. Dengan Wasm, kamu bisa membuat game yang berjalan di browser…

Gaming & Canvas dengan WebAssembly

Game development adalah salah satu motivasi awal WebAssembly. Dengan Wasm, kamu bisa membuat game yang berjalan di browser dengan performa mendekati native — tanpa plugin.

Game Loop Pattern

// JavaScript: requestAnimationFrame + Wasm game logic
import init, { GameState } from "./pkg/game.js";

await init();
const game = GameState.new(800, 600);

let lastTime = 0;
function gameLoop(timestamp) {
  const dt = (timestamp - lastTime) / 1000; // delta time in seconds
  lastTime = timestamp;

  // Update game state (Wasm — cepat)
  game.update(dt);

  // Render (JS Canvas API atau Wasm direct pixel)
  game.render();

  requestAnimationFrame(gameLoop);
}

requestAnimationFrame(gameLoop);

Rust: Game State & Physics

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct GameState {
    width: f64,
    height: f64,
    player_x: f64,
    player_y: f64,
    velocity_x: f64,
    velocity_y: f64,
    enemies: Vec<Entity>,
    score: u32,
}

struct Entity {
    x: f64, y: f64,
    vx: f64, vy: f64,
    radius: f64,
    active: bool,
}

#[wasm_bindgen]
impl GameState {
    pub fn new(width: f64, height: f64) -> Self {
        Self {
            width, height,
            player_x: width / 2.0,
            player_y: height / 2.0,
            velocity_x: 0.0,
            velocity_y: 0.0,
            enemies: Vec::new(),
            score: 0,
        }
    }

    pub fn update(&mut self, dt: f64) {
        // Physics: update posisi
        self.player_x += self.velocity_x * dt;
        self.player_y += self.velocity_y * dt;

        // Boundary check
        self.player_x = self.player_x.clamp(0.0, self.width);
        self.player_y = self.player_y.clamp(0.0, self.height);

        // Update enemies
        for enemy in &mut self.enemies {
            if !enemy.active { continue; }
            enemy.x += enemy.vx * dt;
            enemy.y += enemy.vy * dt;

            // Collision detection (circle vs circle)
            let dx = enemy.x - self.player_x;
            let dy = enemy.y - self.player_y;
            let dist = (dx * dx + dy * dy).sqrt();
            if dist < enemy.radius + 10.0 {
                enemy.active = false;
                self.score += 10;
            }
        }
    }

    pub fn set_input(&mut self, key: &str) {
        match key {
            "ArrowUp" => self.velocity_y = -200.0,
            "ArrowDown" => self.velocity_y = 200.0,
            "ArrowLeft" => self.velocity_x = -200.0,
            "ArrowRight" => self.velocity_x = 200.0,
            _ => {}
        }
    }
}

Canvas Rendering

use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};

#[wasm_bindgen]
impl GameState {
    pub fn render(&self) {
        let document = web_sys::window().unwrap().document().unwrap();
        let canvas = document
            .get_element_by_id("game")
            .unwrap()
            .dyn_into::<HtmlCanvasElement>()
            .unwrap();
        let ctx = canvas
            .get_context("2d").unwrap().unwrap()
            .dyn_into::<CanvasRenderingContext2d>()
            .unwrap();

        // Clear
        ctx.clear_rect(0.0, 0.0, self.width, self.height);

        // Draw player
        ctx.set_fill_style_str("#00ff88");
        ctx.begin_path();
        ctx.arc(self.player_x, self.player_y, 10.0,
                0.0, std::f64::consts::PI * 2.0).unwrap();
        ctx.fill();

        // Draw score
        ctx.set_fill_style_str("#ffffff");
        ctx.set_font("16px monospace");
        ctx.fill_text(
            &format!("Score: {}", self.score), 10.0, 25.0
        ).unwrap();
    }
}

Input Handling (JavaScript Side)

document.addEventListener("keydown", (e) => {
  game.set_input(e.key);
});
document.addEventListener("keyup", () => {
  game.set_input("stop");
});

Real-World Game Engines + Wasm

Yang akan kamu pelajari