用ggez游戏框架学Rust:游戏开发实战指南(一)

📅 2026-05-28 📁 游戏编程 👁 49 次阅读

下面讲解的是一份非常经典的 Rust + ggez 2D 射击游戏代码。我将按照代码的逻辑顺序,从上到下逐行拆解游戏代码的“前世今生”。

代码实现了一个简单的“打飞机”游戏,包含玩家控制、射击、敌人生成、碰撞检测和游戏状态管理。

第一部分:依赖引入与常量定义

这部分代码告诉编译器我们需要用到哪些外部库,并定义了游戏的“物理规则”。

//引入event模块和EventHandler
use ggez::event::{self, EventHandler};
use ggez::graphics::{self, Color, DrawMode, Mesh, Rect, Text, TextFragment};
use ggez::input::keyboard::KeyCode;
use ggez::timer;
use ggez::{Context, ContextBuilder, GameResult};
use ggez::glam::Vec2;
use rand::Rng;

解释:引入了 ggez 框架的核心模块(事件处理、图形绘制、键盘输入、时间管理)和 rand 随机数库。Vec2 用于处理二维坐标。

// 游戏常量
const SCREEN_WIDTH:f32=800.0;//屏幕宽度
const SCREEN_HEIGHT:f32=600.0;
const PLAYER_SPEED:f32=300.0;//玩家移动速度
const BULLET_SPEED:f32=500.0;//子弹速度
const ENEMY_SPEED:f32=100.0;//敌人速度
const SPAWN_INTERVAL:f32=1.5;// 敌人生成间隔(秒)

解释:定义了游戏窗口大小和各种移动速度。这些是游戏的“平衡性参数”。

第二部分:数据结构定义 (Structs)

这里定义了游戏中所有物体的“蓝图”。

// 游戏实体
#[derive(Clone)]
struct Player {
    pos: Vec2,            
    size:f32,            
    hp:i32,            
}

解释:定义了玩家飞船,包含位置 (pos)、大小 (size) 和血量 (hp)。

#[derive(Clone)]
struct Bullet {
    pos: Vec2,            
    velocity: Vec2,            
    active:bool,            
}

解释:定义了子弹,包含位置、速度向量 (velocity) 和一个活跃状态标记。

#[derive(Clone)]
struct Enemy {
    pos: Vec2,            
    size:f32,            
    speed:f32,            
    hp:i32,            
    active:bool,            
}

解释:定义了敌人,结构与玩家类似,但多了独立的速度属性(让每一个敌人有自己的速度)

// 游戏状态
struct MainState {
    player: Player,            
    bullets:Vec<Bullet>,//字段集合
    enemies:Vec<Enemy>,//敌人集合
    score:u32,            
    spawn_timer:f32, //生成敌人间隔计时器
    game_over:bool,            
    stars:Vec<Vec2>,// 背景星星
    paused:bool,     // 游戏是否暂停
    fire_counts:u32,  // 射击计数器(用于演示)
}

解释:这是游戏的“大脑”。它持有了所有当前存在的对象(玩家、子弹列表、敌人列表)以及全局状态(分数、计时器、游戏是否结束)。

第三部分:核心逻辑实现 (Impl MainState)

这里定义了游戏对象的行为方法。

impl MainState {
    fn new() -> GameResult<MainState>{
        //mut关键字让变量stars在后面的代码可改变
        let mut stars =Vec::new();
        let mut rng =rand::rng();// 用来随机生成背景星星
        for _ in 0..50{
            stars.push(Vec2::new( 
                rng.random_range(0.0..SCREEN_WIDTH), 
                rng.random_range(0.0..SCREEN_HEIGHT), 
            ));
        }
        // 初始化 MainState 结构体
        let s = MainState {            
            player: Player {
                pos:Vec2::new(SCREEN_WIDTH /2.0, SCREEN_HEIGHT -50.0),            
                size:20.0,            
                hp:30,            
            },
            bullets:Vec::new(),            
            enemies:Vec::new(),            
            score:0,            
            spawn_timer:0.0,            
            game_over:false,            
            stars,            
            paused:false,            
            fire_counts:0,            
        };
        Ok(s)
    }
// 以下是函数定义...
    
}

解释:new 方法是游戏的“出生点”。它创建了 50 颗随机位置的背景星星,并初始化玩家位置。左上角为原点(0,0)坐标。

// 重置游戏
    fn reset(&mut self) {
        self.player = Player {
            pos:Vec2::new(SCREEN_WIDTH /2.0, SCREEN_HEIGHT -50.0),   
            size:20.0,            
            hp:3,            
        };
        self.bullets.clear();
        self.enemies.clear();
        self.score =0;
        self.spawn_timer =0.0;
        self.game_over =false;
        self.paused =false;// 重置时游戏直接执行
        self.fire_counts=0;
    }

    // 生成敌人
    fn spawn_enemy(&mut self) {
        let mut rng =rand::rng();
        let x = rng.random_range(30.0..SCREEN_WIDTH -30.0);

        self.enemies.push(Enemy {
            pos:Vec2::new(x, -30.0),//敌人初始化在屏幕外
            size:25.0,            
            speed: ENEMY_SPEED + rng.random_range(0.0..100.0),           
            hp:2,            
            active:true,            
        });
    }

    // 射击
    fn shoot(&mut self) {
        self.fire_counts+=2;
        self.bullets.push(Bullet {
            pos:self.player.pos +Vec2::new(0.0, -20.0),            
            velocity:Vec2::new(0.0, -BULLET_SPEED),            
            active:true,            
        });
        self.fire_counts-=1;
    }

    // 碰撞检测
    fn check_collision(a_pos: Vec2, a_size:f32, b_pos: Vec2, b_size:f32) ->bool{
        let distance = a_pos.distance(b_pos);
        distance < (a_size + b_size) /2.0
    }
    // 增加分数
    fn add_score(&mut self, points:u32) {
        self.score += points;
    }

解释:&T (不可变引用) 允许多个“读者”同时读取数据,但不能修改。&mut T (可变引用)允许“写者”修改数据,但同一时间只能有一个可变引用,且不能有其他不可变引用共存。这保证了数据在修改时不会被其他代码意外读取(即“数据竞争”)。

shoot 方法中:fire_counts 先加 2 再减 1,最终结果是加 1。这是一段故意设计的控制代码,方便研究汇编代码。

碰撞逻辑,这是最基础的圆形碰撞检测。计算两个物体中心点的距离,如果距离小于它们半径之和,则判定为碰撞。

可以把MainState看作一个类,其中定义了属性字段和方法(fn)