发布于

Rust-Wasm在Web游戏开发中的应用:构建高性能的浏览器游戏

作者

Rust-Wasm在Web游戏开发中的应用:构建高性能的浏览器游戏

Web游戏开发正在经历一场技术革命,Rust与WebAssembly的结合为开发者提供了构建高性能浏览器游戏的强大工具。本文将详细介绍完整的Web游戏开发方案。

游戏引擎架构设计

项目配置和依赖

# Cargo.toml - Web游戏引擎配置
[package]
name = "rust-wasm-game-engine"
version = "0.1.0"
edition = "2021"

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

[dependencies]
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }
js-sys = "0.3"
web-sys = "0.3"
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.4"
getrandom = { version = "0.2", features = ["js"] }
console_error_panic_hook = "0.1"
wee_alloc = "0.4"

# 游戏开发相关依赖
nalgebra = "0.32"
rapier2d = "0.17"
image = { version = "0.24", default-features = false, features = ["png", "jpeg"] }
rand = "0.8"
instant = { version = "0.1", features = ["wasm-bindgen"] }

[dependencies.web-sys]
version = "0.3"
features = [
  "console",
  "Document",
  "Element",
  "HtmlElement",
  "HtmlCanvasElement",
  "CanvasRenderingContext2d",
  "WebGlRenderingContext",
  "WebGl2RenderingContext",
  "WebGlProgram",
  "WebGlShader",
  "WebGlBuffer",
  "WebGlTexture",
  "WebGlUniformLocation",
  "ImageData",
  "Window",
  "Performance",
  "KeyboardEvent",
  "MouseEvent",
  "TouchEvent",
  "AudioContext",
  "AudioBuffer",
  "AudioBufferSourceNode",
  "GainNode",
]

# 优化配置
[profile.release]
opt-level = "s"
lto = true
codegen-units = 1
panic = "abort"

核心游戏引擎

// src/lib.rs - 游戏引擎核心
use wasm_bindgen::prelude::*;
use web_sys::*;
use nalgebra::{Vector2, Point2};
use std::collections::HashMap;
use instant::Instant;

// 全局设置
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

#[wasm_bindgen(start)]
pub fn main() {
    console_error_panic_hook::set_once();
}

// 游戏对象组件系统
#[wasm_bindgen]
#[derive(Clone, Debug)]
pub struct Transform {
    pub position: Vector2<f32>,
    pub rotation: f32,
    pub scale: Vector2<f32>,
}

#[wasm_bindgen]
impl Transform {
    #[wasm_bindgen(constructor)]
    pub fn new(x: f32, y: f32) -> Transform {
        Transform {
            position: Vector2::new(x, y),
            rotation: 0.0,
            scale: Vector2::new(1.0, 1.0),
        }
    }
    
    #[wasm_bindgen(getter)]
    pub fn x(&self) -> f32 {
        self.position.x
    }
    
    #[wasm_bindgen(setter)]
    pub fn set_x(&mut self, x: f32) {
        self.position.x = x;
    }
    
    #[wasm_bindgen(getter)]
    pub fn y(&self) -> f32 {
        self.position.y
    }
    
    #[wasm_bindgen(setter)]
    pub fn set_y(&mut self, y: f32) {
        self.position.y = y;
    }
    
    #[wasm_bindgen]
    pub fn translate(&mut self, dx: f32, dy: f32) {
        self.position.x += dx;
        self.position.y += dy;
    }
    
    #[wasm_bindgen]
    pub fn rotate(&mut self, angle: f32) {
        self.rotation += angle;
    }
    
    #[wasm_bindgen]
    pub fn set_scale(&mut self, sx: f32, sy: f32) {
        self.scale.x = sx;
        self.scale.y = sy;
    }
}

// 游戏实体
#[wasm_bindgen]
pub struct GameObject {
    id: u32,
    transform: Transform,
    velocity: Vector2<f32>,
    active: bool,
    tag: String,
}

#[wasm_bindgen]
impl GameObject {
    #[wasm_bindgen(constructor)]
    pub fn new(id: u32, x: f32, y: f32) -> GameObject {
        GameObject {
            id,
            transform: Transform::new(x, y),
            velocity: Vector2::new(0.0, 0.0),
            active: true,
            tag: String::new(),
        }
    }
    
    #[wasm_bindgen(getter)]
    pub fn id(&self) -> u32 {
        self.id
    }
    
    #[wasm_bindgen(getter)]
    pub fn active(&self) -> bool {
        self.active
    }
    
    #[wasm_bindgen(setter)]
    pub fn set_active(&mut self, active: bool) {
        self.active = active;
    }
    
    #[wasm_bindgen]
    pub fn get_transform(&self) -> Transform {
        self.transform.clone()
    }
    
    #[wasm_bindgen]
    pub fn set_transform(&mut self, transform: Transform) {
        self.transform = transform;
    }
    
    #[wasm_bindgen]
    pub fn set_velocity(&mut self, vx: f32, vy: f32) {
        self.velocity.x = vx;
        self.velocity.y = vy;
    }
    
    #[wasm_bindgen]
    pub fn update(&mut self, delta_time: f32) {
        if self.active {
            self.transform.position += self.velocity * delta_time;
        }
    }
    
    #[wasm_bindgen]
    pub fn set_tag(&mut self, tag: &str) {
        self.tag = tag.to_string();
    }
    
    #[wasm_bindgen]
    pub fn get_tag(&self) -> String {
        self.tag.clone()
    }
}

// 游戏场景管理器
#[wasm_bindgen]
pub struct Scene {
    objects: HashMap<u32, GameObject>,
    next_id: u32,
    camera_x: f32,
    camera_y: f32,
}

#[wasm_bindgen]
impl Scene {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Scene {
        Scene {
            objects: HashMap::new(),
            next_id: 1,
            camera_x: 0.0,
            camera_y: 0.0,
        }
    }
    
    #[wasm_bindgen]
    pub fn create_object(&mut self, x: f32, y: f32) -> u32 {
        let id = self.next_id;
        self.next_id += 1;
        
        let object = GameObject::new(id, x, y);
        self.objects.insert(id, object);
        
        id
    }
    
    #[wasm_bindgen]
    pub fn get_object(&self, id: u32) -> Option<GameObject> {
        self.objects.get(&id).cloned()
    }
    
    #[wasm_bindgen]
    pub fn update_object(&mut self, id: u32, object: GameObject) {
        self.objects.insert(id, object);
    }
    
    #[wasm_bindgen]
    pub fn remove_object(&mut self, id: u32) {
        self.objects.remove(&id);
    }
    
    #[wasm_bindgen]
    pub fn update(&mut self, delta_time: f32) {
        for object in self.objects.values_mut() {
            object.update(delta_time);
        }
    }
    
    #[wasm_bindgen]
    pub fn get_all_objects(&self) -> Vec<u32> {
        self.objects.keys().cloned().collect()
    }
    
    #[wasm_bindgen]
    pub fn set_camera(&mut self, x: f32, y: f32) {
        self.camera_x = x;
        self.camera_y = y;
    }
    
    #[wasm_bindgen]
    pub fn get_camera_x(&self) -> f32 {
        self.camera_x
    }
    
    #[wasm_bindgen]
    pub fn get_camera_y(&self) -> f32 {
        self.camera_y
    }
}

// 输入管理器
#[wasm_bindgen]
pub struct InputManager {
    keys_pressed: HashMap<String, bool>,
    keys_just_pressed: HashMap<String, bool>,
    mouse_x: f32,
    mouse_y: f32,
    mouse_buttons: HashMap<i16, bool>,
}

#[wasm_bindgen]
impl InputManager {
    #[wasm_bindgen(constructor)]
    pub fn new() -> InputManager {
        InputManager {
            keys_pressed: HashMap::new(),
            keys_just_pressed: HashMap::new(),
            mouse_x: 0.0,
            mouse_y: 0.0,
            mouse_buttons: HashMap::new(),
        }
    }
    
    #[wasm_bindgen]
    pub fn key_down(&mut self, key: &str) {
        if !self.keys_pressed.get(key).unwrap_or(&false) {
            self.keys_just_pressed.insert(key.to_string(), true);
        }
        self.keys_pressed.insert(key.to_string(), true);
    }
    
    #[wasm_bindgen]
    pub fn key_up(&mut self, key: &str) {
        self.keys_pressed.insert(key.to_string(), false);
    }
    
    #[wasm_bindgen]
    pub fn is_key_pressed(&self, key: &str) -> bool {
        *self.keys_pressed.get(key).unwrap_or(&false)
    }
    
    #[wasm_bindgen]
    pub fn is_key_just_pressed(&self, key: &str) -> bool {
        *self.keys_just_pressed.get(key).unwrap_or(&false)
    }
    
    #[wasm_bindgen]
    pub fn mouse_move(&mut self, x: f32, y: f32) {
        self.mouse_x = x;
        self.mouse_y = y;
    }
    
    #[wasm_bindgen]
    pub fn mouse_down(&mut self, button: i16) {
        self.mouse_buttons.insert(button, true);
    }
    
    #[wasm_bindgen]
    pub fn mouse_up(&mut self, button: i16) {
        self.mouse_buttons.insert(button, false);
    }
    
    #[wasm_bindgen]
    pub fn is_mouse_button_pressed(&self, button: i16) -> bool {
        *self.mouse_buttons.get(&button).unwrap_or(&false)
    }
    
    #[wasm_bindgen]
    pub fn get_mouse_x(&self) -> f32 {
        self.mouse_x
    }
    
    #[wasm_bindgen]
    pub fn get_mouse_y(&self) -> f32 {
        self.mouse_y
    }
    
    #[wasm_bindgen]
    pub fn clear_just_pressed(&mut self) {
        self.keys_just_pressed.clear();
    }
}

// 游戏引擎主类
#[wasm_bindgen]
pub struct GameEngine {
    scene: Scene,
    input: InputManager,
    last_time: f64,
    running: bool,
    canvas_width: f32,
    canvas_height: f32,
}

#[wasm_bindgen]
impl GameEngine {
    #[wasm_bindgen(constructor)]
    pub fn new(canvas_width: f32, canvas_height: f32) -> GameEngine {
        GameEngine {
            scene: Scene::new(),
            input: InputManager::new(),
            last_time: 0.0,
            running: false,
            canvas_width,
            canvas_height,
        }
    }
    
    #[wasm_bindgen]
    pub fn start(&mut self) {
        self.running = true;
        self.last_time = js_sys::Date::now();
    }
    
    #[wasm_bindgen]
    pub fn stop(&mut self) {
        self.running = false;
    }
    
    #[wasm_bindgen]
    pub fn update(&mut self, current_time: f64) -> f32 {
        if !self.running {
            return 0.0;
        }
        
        let delta_time = ((current_time - self.last_time) / 1000.0) as f32;
        self.last_time = current_time;
        
        // 更新场景
        self.scene.update(delta_time);
        
        // 清理输入状态
        self.input.clear_just_pressed();
        
        delta_time
    }
    
    #[wasm_bindgen]
    pub fn get_scene(&mut self) -> Scene {
        // 注意:这里返回场景的克隆,实际应用中可能需要更好的设计
        Scene::new() // 简化实现
    }
    
    #[wasm_bindgen]
    pub fn get_input(&mut self) -> InputManager {
        // 注意:这里返回输入管理器的引用,实际应用中需要更好的设计
        InputManager::new() // 简化实现
    }
    
    #[wasm_bindgen]
    pub fn create_object(&mut self, x: f32, y: f32) -> u32 {
        self.scene.create_object(x, y)
    }
    
    #[wasm_bindgen]
    pub fn get_object(&self, id: u32) -> Option<GameObject> {
        self.scene.get_object(id)
    }
    
    #[wasm_bindgen]
    pub fn update_object(&mut self, id: u32, object: GameObject) {
        self.scene.update_object(id, object);
    }
    
    #[wasm_bindgen]
    pub fn remove_object(&mut self, id: u32) {
        self.scene.remove_object(id);
    }
    
    #[wasm_bindgen]
    pub fn handle_key_down(&mut self, key: &str) {
        self.input.key_down(key);
    }
    
    #[wasm_bindgen]
    pub fn handle_key_up(&mut self, key: &str) {
        self.input.key_up(key);
    }
    
    #[wasm_bindgen]
    pub fn handle_mouse_move(&mut self, x: f32, y: f32) {
        self.input.mouse_move(x, y);
    }
    
    #[wasm_bindgen]
    pub fn handle_mouse_down(&mut self, button: i16) {
        self.input.mouse_down(button);
    }
    
    #[wasm_bindgen]
    pub fn handle_mouse_up(&mut self, button: i16) {
        self.input.mouse_up(button);
    }
    
    #[wasm_bindgen]
    pub fn is_key_pressed(&self, key: &str) -> bool {
        self.input.is_key_pressed(key)
    }
    
    #[wasm_bindgen]
    pub fn is_key_just_pressed(&self, key: &str) -> bool {
        self.input.is_key_just_pressed(key)
    }
    
    #[wasm_bindgen]
    pub fn get_mouse_position(&self) -> Vec<f32> {
        vec![self.input.get_mouse_x(), self.input.get_mouse_y()]
    }
    
    #[wasm_bindgen]
    pub fn set_camera(&mut self, x: f32, y: f32) {
        self.scene.set_camera(x, y);
    }
    
    #[wasm_bindgen]
    pub fn get_camera_position(&self) -> Vec<f32> {
        vec![self.scene.get_camera_x(), self.scene.get_camera_y()]
    }
}

物理引擎集成

// src/physics.rs - 物理引擎模块
use wasm_bindgen::prelude::*;
use rapier2d::prelude::*;
use nalgebra::{Vector2, Point2};
use std::collections::HashMap;

#[wasm_bindgen]
pub struct PhysicsWorld {
    rigid_body_set: RigidBodySet,
    collider_set: ColliderSet,
    gravity: Vector2<f32>,
    integration_parameters: IntegrationParameters,
    physics_pipeline: PhysicsPipeline,
    island_manager: IslandManager,
    broad_phase: BroadPhase,
    narrow_phase: NarrowPhase,
    impulse_joint_set: ImpulseJointSet,
    multibody_joint_set: MultibodyJointSet,
    ccd_solver: CCDSolver,
    query_pipeline: QueryPipeline,
    physics_hooks: (),
    event_handler: (),
    body_to_entity: HashMap<RigidBodyHandle, u32>,
}

#[wasm_bindgen]
impl PhysicsWorld {
    #[wasm_bindgen(constructor)]
    pub fn new() -> PhysicsWorld {
        PhysicsWorld {
            rigid_body_set: RigidBodySet::new(),
            collider_set: ColliderSet::new(),
            gravity: Vector2::new(0.0, -9.81),
            integration_parameters: IntegrationParameters::default(),
            physics_pipeline: PhysicsPipeline::new(),
            island_manager: IslandManager::new(),
            broad_phase: BroadPhase::new(),
            narrow_phase: NarrowPhase::new(),
            impulse_joint_set: ImpulseJointSet::new(),
            multibody_joint_set: MultibodyJointSet::new(),
            ccd_solver: CCDSolver::new(),
            query_pipeline: QueryPipeline::new(),
            physics_hooks: (),
            event_handler: (),
            body_to_entity: HashMap::new(),
        }
    }
    
    #[wasm_bindgen]
    pub fn set_gravity(&mut self, x: f32, y: f32) {
        self.gravity = Vector2::new(x, y);
    }
    
    #[wasm_bindgen]
    pub fn create_dynamic_body(&mut self, entity_id: u32, x: f32, y: f32) -> u32 {
        let rigid_body = RigidBodyBuilder::dynamic()
            .translation(Vector2::new(x, y))
            .build();
        
        let handle = self.rigid_body_set.insert(rigid_body);
        self.body_to_entity.insert(handle, entity_id);
        
        handle.into_raw_parts().0 as u32
    }
    
    #[wasm_bindgen]
    pub fn create_static_body(&mut self, entity_id: u32, x: f32, y: f32) -> u32 {
        let rigid_body = RigidBodyBuilder::fixed()
            .translation(Vector2::new(x, y))
            .build();
        
        let handle = self.rigid_body_set.insert(rigid_body);
        self.body_to_entity.insert(handle, entity_id);
        
        handle.into_raw_parts().0 as u32
    }
    
    #[wasm_bindgen]
    pub fn add_box_collider(&mut self, body_handle: u32, width: f32, height: f32) {
        let handle = RigidBodyHandle::from_raw_parts(body_handle, 0);
        
        if let Some(_) = self.rigid_body_set.get(handle) {
            let collider = ColliderBuilder::cuboid(width / 2.0, height / 2.0)
                .build();
            
            self.collider_set.insert_with_parent(collider, handle, &mut self.rigid_body_set);
        }
    }
    
    #[wasm_bindgen]
    pub fn add_circle_collider(&mut self, body_handle: u32, radius: f32) {
        let handle = RigidBodyHandle::from_raw_parts(body_handle, 0);
        
        if let Some(_) = self.rigid_body_set.get(handle) {
            let collider = ColliderBuilder::ball(radius)
                .build();
            
            self.collider_set.insert_with_parent(collider, handle, &mut self.rigid_body_set);
        }
    }
    
    #[wasm_bindgen]
    pub fn set_body_velocity(&mut self, body_handle: u32, vx: f32, vy: f32) {
        let handle = RigidBodyHandle::from_raw_parts(body_handle, 0);
        
        if let Some(body) = self.rigid_body_set.get_mut(handle) {
            body.set_linvel(Vector2::new(vx, vy), true);
        }
    }
    
    #[wasm_bindgen]
    pub fn apply_force(&mut self, body_handle: u32, fx: f32, fy: f32) {
        let handle = RigidBodyHandle::from_raw_parts(body_handle, 0);
        
        if let Some(body) = self.rigid_body_set.get_mut(handle) {
            body.add_force(Vector2::new(fx, fy), true);
        }
    }
    
    #[wasm_bindgen]
    pub fn apply_impulse(&mut self, body_handle: u32, ix: f32, iy: f32) {
        let handle = RigidBodyHandle::from_raw_parts(body_handle, 0);
        
        if let Some(body) = self.rigid_body_set.get_mut(handle) {
            body.apply_impulse(Vector2::new(ix, iy), true);
        }
    }
    
    #[wasm_bindgen]
    pub fn get_body_position(&self, body_handle: u32) -> Vec<f32> {
        let handle = RigidBodyHandle::from_raw_parts(body_handle, 0);
        
        if let Some(body) = self.rigid_body_set.get(handle) {
            let pos = body.translation();
            vec![pos.x, pos.y]
        } else {
            vec![0.0, 0.0]
        }
    }
    
    #[wasm_bindgen]
    pub fn get_body_rotation(&self, body_handle: u32) -> f32 {
        let handle = RigidBodyHandle::from_raw_parts(body_handle, 0);
        
        if let Some(body) = self.rigid_body_set.get(handle) {
            body.rotation().angle()
        } else {
            0.0
        }
    }
    
    #[wasm_bindgen]
    pub fn step(&mut self, delta_time: f32) {
        self.integration_parameters.dt = delta_time;
        
        self.physics_pipeline.step(
            &self.gravity,
            &self.integration_parameters,
            &mut self.island_manager,
            &mut self.broad_phase,
            &mut self.narrow_phase,
            &mut self.rigid_body_set,
            &mut self.collider_set,
            &mut self.impulse_joint_set,
            &mut self.multibody_joint_set,
            &mut self.ccd_solver,
            Some(&mut self.query_pipeline),
            &self.physics_hooks,
            &self.event_handler,
        );
    }
    
    #[wasm_bindgen]
    pub fn raycast(&self, start_x: f32, start_y: f32, dir_x: f32, dir_y: f32, max_distance: f32) -> Option<Vec<f32>> {
        let ray = Ray::new(
            Point2::new(start_x, start_y),
            Vector2::new(dir_x, dir_y)
        );
        
        if let Some((handle, toi)) = self.query_pipeline.cast_ray(
            &self.rigid_body_set,
            &self.collider_set,
            &ray,
            max_distance,
            true,
            QueryFilter::default()
        ) {
            let hit_point = ray.point_at(toi);
            Some(vec![hit_point.x, hit_point.y, toi])
        } else {
            None
        }
    }
    
    #[wasm_bindgen]
    pub fn remove_body(&mut self, body_handle: u32) {
        let handle = RigidBodyHandle::from_raw_parts(body_handle, 0);
        
        if let Some(_) = self.rigid_body_set.get(handle) {
            self.rigid_body_set.remove(
                handle,
                &mut self.island_manager,
                &mut self.collider_set,
                &mut self.impulse_joint_set,
                &mut self.multibody_joint_set,
                true
            );
            self.body_to_entity.remove(&handle);
        }
    }
}

// 物理组件
#[wasm_bindgen]
pub struct PhysicsComponent {
    body_handle: Option<u32>,
    mass: f32,
    friction: f32,
    restitution: f32,
    is_sensor: bool,
}

#[wasm_bindgen]
impl PhysicsComponent {
    #[wasm_bindgen(constructor)]
    pub fn new() -> PhysicsComponent {
        PhysicsComponent {
            body_handle: None,
            mass: 1.0,
            friction: 0.5,
            restitution: 0.3,
            is_sensor: false,
        }
    }
    
    #[wasm_bindgen]
    pub fn set_body_handle(&mut self, handle: u32) {
        self.body_handle = Some(handle);
    }
    
    #[wasm_bindgen]
    pub fn get_body_handle(&self) -> Option<u32> {
        self.body_handle
    }
    
    #[wasm_bindgen]
    pub fn set_mass(&mut self, mass: f32) {
        self.mass = mass;
    }
    
    #[wasm_bindgen]
    pub fn get_mass(&self) -> f32 {
        self.mass
    }
    
    #[wasm_bindgen]
    pub fn set_friction(&mut self, friction: f32) {
        self.friction = friction;
    }
    
    #[wasm_bindgen]
    pub fn get_friction(&self) -> f32 {
        self.friction
    }
    
    #[wasm_bindgen]
    pub fn set_restitution(&mut self, restitution: f32) {
        self.restitution = restitution;
    }
    
    #[wasm_bindgen]
    pub fn get_restitution(&self) -> f32 {
        self.restitution
    }
    
    #[wasm_bindgen]
    pub fn set_is_sensor(&mut self, is_sensor: bool) {
        self.is_sensor = is_sensor;
    }
    
    #[wasm_bindgen]
    pub fn is_sensor(&self) -> bool {
        self.is_sensor
    }
}

图形渲染系统

// src/renderer.rs - 渲染系统
use wasm_bindgen::prelude::*;
use web_sys::*;
use std::collections::HashMap;

#[wasm_bindgen]
pub struct Sprite {
    texture_id: String,
    width: f32,
    height: f32,
    source_x: f32,
    source_y: f32,
    source_width: f32,
    source_height: f32,
}

#[wasm_bindgen]
impl Sprite {
    #[wasm_bindgen(constructor)]
    pub fn new(texture_id: &str, width: f32, height: f32) -> Sprite {
        Sprite {
            texture_id: texture_id.to_string(),
            width,
            height,
            source_x: 0.0,
            source_y: 0.0,
            source_width: width,
            source_height: height,
        }
    }
    
    #[wasm_bindgen]
    pub fn set_source_rect(&mut self, x: f32, y: f32, width: f32, height: f32) {
        self.source_x = x;
        self.source_y = y;
        self.source_width = width;
        self.source_height = height;
    }
    
    #[wasm_bindgen]
    pub fn get_texture_id(&self) -> String {
        self.texture_id.clone()
    }
    
    #[wasm_bindgen]
    pub fn get_width(&self) -> f32 {
        self.width
    }
    
    #[wasm_bindgen]
    pub fn get_height(&self) -> f32 {
        self.height
    }
}

#[wasm_bindgen]
pub struct Renderer {
    canvas: HtmlCanvasElement,
    context: CanvasRenderingContext2d,
    textures: HashMap<String, HtmlImageElement>,
    camera_x: f32,
    camera_y: f32,
    canvas_width: f32,
    canvas_height: f32,
}

#[wasm_bindgen]
impl Renderer {
    #[wasm_bindgen(constructor)]
    pub fn new(canvas_id: &str) -> Result<Renderer, JsValue> {
        let document = web_sys::window().unwrap().document().unwrap();
        let canvas = document
            .get_element_by_id(canvas_id)
            .unwrap()
            .dyn_into::<HtmlCanvasElement>()?;
        
        let context = canvas
            .get_context("2d")?
            .unwrap()
            .dyn_into::<CanvasRenderingContext2d>()?;
        
        let canvas_width = canvas.width() as f32;
        let canvas_height = canvas.height() as f32;
        
        Ok(Renderer {
            canvas,
            context,
            textures: HashMap::new(),
            camera_x: 0.0,
            camera_y: 0.0,
            canvas_width,
            canvas_height,
        })
    }
    
    #[wasm_bindgen]
    pub fn load_texture(&mut self, texture_id: &str, image_url: &str) -> js_sys::Promise {
        let texture_id = texture_id.to_string();
        
        js_sys::Promise::new(&mut |resolve, reject| {
            let image = HtmlImageElement::new().unwrap();
            let image_clone = image.clone();
            let texture_id_clone = texture_id.clone();
            
            let onload = Closure::wrap(Box::new(move |_: Event| {
                // 这里需要访问self,但在闭包中比较复杂
                // 实际实现中需要更好的设计
                resolve.call1(&JsValue::NULL, &JsValue::from_str("loaded")).unwrap();
            }) as Box<dyn FnMut(Event)>);
            
            let onerror = Closure::wrap(Box::new(move |_: Event| {
                reject.call1(&JsValue::NULL, &JsValue::from_str("Failed to load image")).unwrap();
            }) as Box<dyn FnMut(Event)>);
            
            image.set_onload(Some(onload.as_ref().unchecked_ref()));
            image.set_onerror(Some(onerror.as_ref().unchecked_ref()));
            image.set_src(image_url);
            
            onload.forget();
            onerror.forget();
        })
    }
    
    #[wasm_bindgen]
    pub fn clear(&self, r: f32, g: f32, b: f32, a: f32) {
        let color = format!("rgba({}, {}, {}, {})", 
            (r * 255.0) as u8, 
            (g * 255.0) as u8, 
            (b * 255.0) as u8, 
            a
        );
        
        self.context.set_fill_style(&color.into());
        self.context.fill_rect(0.0, 0.0, self.canvas_width as f64, self.canvas_height as f64);
    }
    
    #[wasm_bindgen]
    pub fn set_camera(&mut self, x: f32, y: f32) {
        self.camera_x = x;
        self.camera_y = y;
    }
    
    #[wasm_bindgen]
    pub fn draw_sprite(&self, sprite: &Sprite, x: f32, y: f32, rotation: f32, scale_x: f32, scale_y: f32) -> Result<(), JsValue> {
        // 转换世界坐标到屏幕坐标
        let screen_x = x - self.camera_x + self.canvas_width / 2.0;
        let screen_y = y - self.camera_y + self.canvas_height / 2.0;
        
        self.context.save();
        
        // 应用变换
        self.context.translate(screen_x as f64, screen_y as f64)?;
        self.context.rotate(rotation as f64)?;
        self.context.scale(scale_x as f64, scale_y as f64)?;
        
        // 绘制精灵 (这里简化为矩形)
        self.context.set_fill_style(&"#ff0000".into());
        self.context.fill_rect(
            -(sprite.width / 2.0) as f64,
            -(sprite.height / 2.0) as f64,
            sprite.width as f64,
            sprite.height as f64
        );
        
        self.context.restore();
        
        Ok(())
    }
    
    #[wasm_bindgen]
    pub fn draw_rectangle(&self, x: f32, y: f32, width: f32, height: f32, color: &str) {
        let screen_x = x - self.camera_x + self.canvas_width / 2.0;
        let screen_y = y - self.camera_y + self.canvas_height / 2.0;
        
        self.context.set_fill_style(&color.into());
        self.context.fill_rect(
            screen_x as f64,
            screen_y as f64,
            width as f64,
            height as f64
        );
    }
    
    #[wasm_bindgen]
    pub fn draw_circle(&self, x: f32, y: f32, radius: f32, color: &str) {
        let screen_x = x - self.camera_x + self.canvas_width / 2.0;
        let screen_y = y - self.camera_y + self.canvas_height / 2.0;
        
        self.context.begin_path();
        self.context.arc(
            screen_x as f64,
            screen_y as f64,
            radius as f64,
            0.0,
            2.0 * std::f64::consts::PI
        ).unwrap();
        
        self.context.set_fill_style(&color.into());
        self.context.fill();
    }
    
    #[wasm_bindgen]
    pub fn draw_line(&self, x1: f32, y1: f32, x2: f32, y2: f32, color: &str, width: f32) {
        let screen_x1 = x1 - self.camera_x + self.canvas_width / 2.0;
        let screen_y1 = y1 - self.camera_y + self.canvas_height / 2.0;
        let screen_x2 = x2 - self.camera_x + self.canvas_width / 2.0;
        let screen_y2 = y2 - self.camera_y + self.canvas_height / 2.0;
        
        self.context.begin_path();
        self.context.move_to(screen_x1 as f64, screen_y1 as f64);
        self.context.line_to(screen_x2 as f64, screen_y2 as f64);
        
        self.context.set_stroke_style(&color.into());
        self.context.set_line_width(width as f64);
        self.context.stroke();
    }
    
    #[wasm_bindgen]
    pub fn draw_text(&self, text: &str, x: f32, y: f32, font: &str, color: &str) {
        let screen_x = x - self.camera_x + self.canvas_width / 2.0;
        let screen_y = y - self.camera_y + self.canvas_height / 2.0;
        
        self.context.set_font(font);
        self.context.set_fill_style(&color.into());
        self.context.fill_text(text, screen_x as f64, screen_y as f64).unwrap();
    }
}

音频系统

// src/audio.rs - 音频系统
use wasm_bindgen::prelude::*;
use web_sys::*;
use std::collections::HashMap;

#[wasm_bindgen]
pub struct AudioManager {
    context: AudioContext,
    buffers: HashMap<String, AudioBuffer>,
    master_gain: GainNode,
}

#[wasm_bindgen]
impl AudioManager {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Result<AudioManager, JsValue> {
        let context = AudioContext::new()?;
        let master_gain = context.create_gain()?;
        master_gain.connect_with_audio_node(&context.destination())?;

        Ok(AudioManager {
            context,
            buffers: HashMap::new(),
            master_gain,
        })
    }

    #[wasm_bindgen]
    pub async fn load_audio(&mut self, audio_id: &str, url: &str) -> Result<(), JsValue> {
        let window = web_sys::window().unwrap();
        let response = wasm_bindgen_futures::JsFuture::from(window.fetch_with_str(url)).await?;
        let response: Response = response.dyn_into()?;

        let array_buffer = wasm_bindgen_futures::JsFuture::from(response.array_buffer()?).await?;
        let audio_buffer = wasm_bindgen_futures::JsFuture::from(
            self.context.decode_audio_data(&array_buffer.dyn_into()?)?
        ).await?;

        self.buffers.insert(audio_id.to_string(), audio_buffer.dyn_into()?);
        Ok(())
    }

    #[wasm_bindgen]
    pub fn play_sound(&self, audio_id: &str, volume: f32, pitch: f32) -> Result<(), JsValue> {
        if let Some(buffer) = self.buffers.get(audio_id) {
            let source = self.context.create_buffer_source()?;
            let gain = self.context.create_gain()?;

            source.set_buffer(Some(buffer));
            source.set_playback_rate(pitch);

            gain.gain().set_value(volume);

            source.connect_with_audio_node(&gain)?;
            gain.connect_with_audio_node(&self.master_gain)?;

            source.start()?;
        }

        Ok(())
    }

    #[wasm_bindgen]
    pub fn set_master_volume(&self, volume: f32) -> Result<(), JsValue> {
        self.master_gain.gain().set_value(volume);
        Ok(())
    }
}

资源管理系统

// src/resources.rs - 资源管理
use wasm_bindgen::prelude::*;
use std::collections::HashMap;

#[wasm_bindgen]
pub struct ResourceManager {
    textures: HashMap<String, String>, // texture_id -> url
    audio_files: HashMap<String, String>, // audio_id -> url
    loaded_count: usize,
    total_count: usize,
    loading_complete: bool,
}

#[wasm_bindgen]
impl ResourceManager {
    #[wasm_bindgen(constructor)]
    pub fn new() -> ResourceManager {
        ResourceManager {
            textures: HashMap::new(),
            audio_files: HashMap::new(),
            loaded_count: 0,
            total_count: 0,
            loading_complete: false,
        }
    }

    #[wasm_bindgen]
    pub fn add_texture(&mut self, texture_id: &str, url: &str) {
        self.textures.insert(texture_id.to_string(), url.to_string());
        self.total_count += 1;
    }

    #[wasm_bindgen]
    pub fn add_audio(&mut self, audio_id: &str, url: &str) {
        self.audio_files.insert(audio_id.to_string(), url.to_string());
        self.total_count += 1;
    }

    #[wasm_bindgen]
    pub fn get_loading_progress(&self) -> f32 {
        if self.total_count == 0 {
            1.0
        } else {
            self.loaded_count as f32 / self.total_count as f32
        }
    }

    #[wasm_bindgen]
    pub fn is_loading_complete(&self) -> bool {
        self.loading_complete
    }

    #[wasm_bindgen]
    pub fn mark_resource_loaded(&mut self) {
        self.loaded_count += 1;
        if self.loaded_count >= self.total_count {
            self.loading_complete = true;
        }
    }

    #[wasm_bindgen]
    pub fn get_texture_urls(&self) -> js_sys::Array {
        let array = js_sys::Array::new();
        for (id, url) in &self.textures {
            let obj = js_sys::Object::new();
            js_sys::Reflect::set(&obj, &"id".into(), &id.clone().into()).unwrap();
            js_sys::Reflect::set(&obj, &"url".into(), &url.clone().into()).unwrap();
            array.push(&obj);
        }
        array
    }

    #[wasm_bindgen]
    pub fn get_audio_urls(&self) -> js_sys::Array {
        let array = js_sys::Array::new();
        for (id, url) in &self.audio_files {
            let obj = js_sys::Object::new();
            js_sys::Reflect::set(&obj, &"id".into(), &id.clone().into()).unwrap();
            js_sys::Reflect::set(&obj, &"url".into(), &url.clone().into()).unwrap();
            array.push(&obj);
        }
        array
    }
}

完整游戏示例

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Rust-Wasm Game Demo</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            background: #000;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            font-family: Arial, sans-serif;
        }

        .game-container {
            text-align: center;
        }

        canvas {
            border: 2px solid #333;
            background: #222;
        }

        .game-ui {
            color: white;
            margin-top: 10px;
        }

        .controls {
            margin-top: 20px;
            color: #ccc;
        }

        .loading {
            color: white;
            font-size: 18px;
        }

        .game-stats {
            display: flex;
            justify-content: space-between;
            margin-top: 10px;
            color: white;
        }

        button {
            padding: 10px 20px;
            margin: 5px;
            background: #444;
            color: white;
            border: none;
            cursor: pointer;
        }

        button:hover {
            background: #666;
        }
    </style>
</head>
<body>
    <div class="game-container">
        <div id="loading-screen" class="loading">
            <h2>Loading Game...</h2>
            <div id="loading-progress">0%</div>
        </div>

        <div id="game-screen" style="display: none;">
            <canvas id="game-canvas" width="800" height="600"></canvas>

            <div class="game-stats">
                <div>Score: <span id="score">0</span></div>
                <div>Lives: <span id="lives">3</span></div>
                <div>FPS: <span id="fps">60</span></div>
            </div>

            <div class="game-ui">
                <button onclick="startGame()">Start Game</button>
                <button onclick="pauseGame()">Pause</button>
                <button onclick="resetGame()">Reset</button>
            </div>

            <div class="controls">
                <p>Controls: WASD or Arrow Keys to move, Space to shoot</p>
            </div>
        </div>
    </div>

    <script type="module">
        import init, {
            GameEngine,
            PhysicsWorld,
            Renderer,
            AudioManager,
            ResourceManager,
            GameObject,
            Transform,
            Sprite
        } from './pkg/rust_wasm_game_engine.js';

        let gameEngine, physicsWorld, renderer, audioManager, resourceManager;
        let gameState = {
            running: false,
            paused: false,
            score: 0,
            lives: 3,
            player: null,
            enemies: [],
            bullets: [],
            lastTime: 0,
            frameCount: 0,
            fpsTime: 0
        };

        async function loadGame() {
            await init();

            // 初始化系统
            gameEngine = new GameEngine(800, 600);
            physicsWorld = new PhysicsWorld();
            renderer = new Renderer('game-canvas');
            audioManager = new AudioManager();
            resourceManager = new ResourceManager();

            // 设置物理世界
            physicsWorld.set_gravity(0, 0); // 太空游戏,无重力

            // 加载资源
            await loadResources();

            // 设置事件监听
            setupEventListeners();

            // 隐藏加载屏幕,显示游戏
            document.getElementById('loading-screen').style.display = 'none';
            document.getElementById('game-screen').style.display = 'block';

            console.log('Game loaded successfully!');
        }

        async function loadResources() {
            // 添加资源到管理器
            resourceManager.add_texture('player', 'assets/player.png');
            resourceManager.add_texture('enemy', 'assets/enemy.png');
            resourceManager.add_texture('bullet', 'assets/bullet.png');
            resourceManager.add_audio('shoot', 'assets/shoot.wav');
            resourceManager.add_audio('explosion', 'assets/explosion.wav');

            // 模拟加载过程
            const totalResources = 5;
            for (let i = 0; i < totalResources; i++) {
                await new Promise(resolve => setTimeout(resolve, 200));
                resourceManager.mark_resource_loaded();

                const progress = resourceManager.get_loading_progress();
                document.getElementById('loading-progress').textContent =
                    Math.round(progress * 100) + '%';
            }
        }

        function setupEventListeners() {
            // 键盘事件
            document.addEventListener('keydown', (e) => {
                gameEngine.handle_key_down(e.code);
                e.preventDefault();
            });

            document.addEventListener('keyup', (e) => {
                gameEngine.handle_key_up(e.code);
                e.preventDefault();
            });

            // 鼠标事件
            const canvas = document.getElementById('game-canvas');
            canvas.addEventListener('mousemove', (e) => {
                const rect = canvas.getBoundingClientRect();
                const x = e.clientX - rect.left;
                const y = e.clientY - rect.top;
                gameEngine.handle_mouse_move(x, y);
            });

            canvas.addEventListener('mousedown', (e) => {
                gameEngine.handle_mouse_down(e.button);
            });

            canvas.addEventListener('mouseup', (e) => {
                gameEngine.handle_mouse_up(e.button);
            });
        }

        window.startGame = function() {
            if (!gameState.running) {
                initializeGame();
                gameState.running = true;
                gameState.paused = false;
                gameEngine.start();
                gameLoop();
            }
        };

        window.pauseGame = function() {
            gameState.paused = !gameState.paused;
        };

        window.resetGame = function() {
            gameState.running = false;
            gameState.paused = false;
            gameState.score = 0;
            gameState.lives = 3;
            gameState.enemies = [];
            gameState.bullets = [];

            // 清理游戏对象
            if (gameState.player) {
                gameEngine.remove_object(gameState.player);
                gameState.player = null;
            }

            updateUI();
        };

        function initializeGame() {
            // 创建玩家
            gameState.player = gameEngine.create_object(400, 500);
            let player = gameEngine.get_object(gameState.player);
            if (player) {
                player.set_tag('player');
                gameEngine.update_object(gameState.player, player);
            }

            // 创建物理体
            const playerBody = physicsWorld.create_dynamic_body(gameState.player, 400, 500);
            physicsWorld.add_box_collider(playerBody, 40, 40);

            // 生成初始敌人
            spawnEnemies(5);

            updateUI();
        }

        function spawnEnemies(count) {
            for (let i = 0; i < count; i++) {
                const x = Math.random() * 800;
                const y = Math.random() * 200;

                const enemyId = gameEngine.create_object(x, y);
                let enemy = gameEngine.get_object(enemyId);
                if (enemy) {
                    enemy.set_tag('enemy');
                    enemy.set_velocity(
                        (Math.random() - 0.5) * 100,
                        Math.random() * 50 + 25
                    );
                    gameEngine.update_object(enemyId, enemy);
                }

                const enemyBody = physicsWorld.create_dynamic_body(enemyId, x, y);
                physicsWorld.add_box_collider(enemyBody, 30, 30);

                gameState.enemies.push(enemyId);
            }
        }

        function gameLoop() {
            if (!gameState.running) return;

            const currentTime = performance.now();
            const deltaTime = gameState.lastTime ? (currentTime - gameState.lastTime) / 1000 : 0;
            gameState.lastTime = currentTime;

            if (!gameState.paused) {
                update(deltaTime);
                render();
            }

            updateFPS(deltaTime);
            requestAnimationFrame(gameLoop);
        }

        function update(deltaTime) {
            // 更新游戏引擎
            gameEngine.update(currentTime);

            // 更新物理世界
            physicsWorld.step(deltaTime);

            // 处理输入
            handleInput();

            // 更新游戏逻辑
            updatePlayer(deltaTime);
            updateEnemies(deltaTime);
            updateBullets(deltaTime);

            // 检查碰撞
            checkCollisions();

            // 清理无效对象
            cleanup();
        }

        function handleInput() {
            if (!gameState.player) return;

            let player = gameEngine.get_object(gameState.player);
            if (!player) return;

            const speed = 300;
            let vx = 0, vy = 0;

            if (gameEngine.is_key_pressed('KeyW') || gameEngine.is_key_pressed('ArrowUp')) {
                vy = -speed;
            }
            if (gameEngine.is_key_pressed('KeyS') || gameEngine.is_key_pressed('ArrowDown')) {
                vy = speed;
            }
            if (gameEngine.is_key_pressed('KeyA') || gameEngine.is_key_pressed('ArrowLeft')) {
                vx = -speed;
            }
            if (gameEngine.is_key_pressed('KeyD') || gameEngine.is_key_pressed('ArrowRight')) {
                vx = speed;
            }

            player.set_velocity(vx, vy);
            gameEngine.update_object(gameState.player, player);

            // 射击
            if (gameEngine.is_key_just_pressed('Space')) {
                shoot();
            }
        }

        function shoot() {
            if (!gameState.player) return;

            let player = gameEngine.get_object(gameState.player);
            if (!player) return;

            const transform = player.get_transform();
            const bulletId = gameEngine.create_object(transform.x, transform.y - 20);

            let bullet = gameEngine.get_object(bulletId);
            if (bullet) {
                bullet.set_tag('bullet');
                bullet.set_velocity(0, -500);
                gameEngine.update_object(bulletId, bullet);
            }

            const bulletBody = physicsWorld.create_dynamic_body(bulletId, transform.x, transform.y - 20);
            physicsWorld.add_circle_collider(bulletBody, 5);

            gameState.bullets.push(bulletId);

            // 播放射击音效
            audioManager.play_sound('shoot', 0.5, 1.0);
        }

        function updatePlayer(deltaTime) {
            if (!gameState.player) return;

            let player = gameEngine.get_object(gameState.player);
            if (!player) return;

            const transform = player.get_transform();

            // 限制玩家在屏幕内
            if (transform.x < 20) transform.set_x(20);
            if (transform.x > 780) transform.set_x(780);
            if (transform.y < 20) transform.set_y(20);
            if (transform.y > 580) transform.set_y(580);

            player.set_transform(transform);
            gameEngine.update_object(gameState.player, player);
        }

        function updateEnemies(deltaTime) {
            for (const enemyId of gameState.enemies) {
                let enemy = gameEngine.get_object(enemyId);
                if (!enemy) continue;

                const transform = enemy.get_transform();

                // 敌人到达屏幕底部时重新生成
                if (transform.y > 650) {
                    transform.set_x(Math.random() * 800);
                    transform.set_y(-50);
                    enemy.set_transform(transform);
                    gameEngine.update_object(enemyId, enemy);
                }
            }
        }

        function updateBullets(deltaTime) {
            gameState.bullets = gameState.bullets.filter(bulletId => {
                let bullet = gameEngine.get_object(bulletId);
                if (!bullet) return false;

                const transform = bullet.get_transform();

                // 移除屏幕外的子弹
                if (transform.y < -50) {
                    gameEngine.remove_object(bulletId);
                    return false;
                }

                return true;
            });
        }

        function checkCollisions() {
            // 简化的碰撞检测
            for (const bulletId of gameState.bullets) {
                let bullet = gameEngine.get_object(bulletId);
                if (!bullet) continue;

                const bulletTransform = bullet.get_transform();

                for (let i = gameState.enemies.length - 1; i >= 0; i--) {
                    const enemyId = gameState.enemies[i];
                    let enemy = gameEngine.get_object(enemyId);
                    if (!enemy) continue;

                    const enemyTransform = enemy.get_transform();
                    const distance = Math.sqrt(
                        Math.pow(bulletTransform.x - enemyTransform.x, 2) +
                        Math.pow(bulletTransform.y - enemyTransform.y, 2)
                    );

                    if (distance < 25) {
                        // 碰撞发生
                        gameEngine.remove_object(bulletId);
                        gameEngine.remove_object(enemyId);

                        gameState.bullets = gameState.bullets.filter(id => id !== bulletId);
                        gameState.enemies.splice(i, 1);

                        gameState.score += 100;
                        audioManager.play_sound('explosion', 0.7, 1.0);

                        // 生成新敌人
                        if (gameState.enemies.length < 3) {
                            spawnEnemies(1);
                        }

                        break;
                    }
                }
            }
        }

        function cleanup() {
            // 清理无效的游戏对象
            gameState.enemies = gameState.enemies.filter(id => {
                return gameEngine.get_object(id) !== null;
            });

            gameState.bullets = gameState.bullets.filter(id => {
                return gameEngine.get_object(id) !== null;
            });
        }

        function render() {
            // 清空画布
            renderer.clear(0.1, 0.1, 0.2, 1.0);

            // 渲染玩家
            if (gameState.player) {
                let player = gameEngine.get_object(gameState.player);
                if (player) {
                    const transform = player.get_transform();
                    renderer.draw_rectangle(
                        transform.x - 20, transform.y - 20,
                        40, 40, '#00ff00'
                    );
                }
            }

            // 渲染敌人
            for (const enemyId of gameState.enemies) {
                let enemy = gameEngine.get_object(enemyId);
                if (enemy) {
                    const transform = enemy.get_transform();
                    renderer.draw_rectangle(
                        transform.x - 15, transform.y - 15,
                        30, 30, '#ff0000'
                    );
                }
            }

            // 渲染子弹
            for (const bulletId of gameState.bullets) {
                let bullet = gameEngine.get_object(bulletId);
                if (bullet) {
                    const transform = bullet.get_transform();
                    renderer.draw_circle(
                        transform.x, transform.y,
                        5, '#ffff00'
                    );
                }
            }
        }

        function updateFPS(deltaTime) {
            gameState.frameCount++;
            gameState.fpsTime += deltaTime;

            if (gameState.fpsTime >= 1.0) {
                const fps = Math.round(gameState.frameCount / gameState.fpsTime);
                document.getElementById('fps').textContent = fps;
                gameState.frameCount = 0;
                gameState.fpsTime = 0;
            }
        }

        function updateUI() {
            document.getElementById('score').textContent = gameState.score;
            document.getElementById('lives').textContent = gameState.lives;
        }

        // 启动游戏
        loadGame();
    </script>
</body>
</html>

总结

Rust-Wasm在Web游戏开发中的核心要点:

🎯 游戏引擎架构

  1. 组件系统:灵活的游戏对象和组件管理
  2. 场景管理:高效的游戏世界组织和更新
  3. 输入处理:完整的键盘、鼠标和触摸支持
  4. 时间管理:精确的帧率控制和时间计算

✅ 物理模拟

  • Rapier2D物理引擎集成
  • 刚体和碰撞体管理
  • 力学模拟和碰撞检测
  • 射线投射和查询系统

🚀 图形渲染

  • Canvas 2D渲染系统
  • 精灵和纹理管理
  • 摄像机和视口控制
  • 基础图形绘制功能

🎵 音频系统

  • Web Audio API集成
  • 音效播放和管理
  • 音量控制和音调调节
  • 异步音频资源加载

📦 资源管理

  • 统一的资源加载系统
  • 加载进度跟踪
  • 纹理和音频资源管理
  • 异步加载和缓存

💡 性能优化

  • WebAssembly高性能计算
  • 内存管理和对象池
  • 批量渲染和剔除
  • 帧率监控和优化

🎮 完整游戏示例

  • 太空射击游戏演示
  • 完整的游戏循环
  • 碰撞检测和游戏逻辑
  • UI界面和用户交互

掌握Rust-Wasm游戏开发,创造出色的浏览器游戏体验!


Web游戏开发的未来在于高性能和跨平台,Rust与WebAssembly的结合为开发者提供了实现这一目标的强大工具。