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

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

第四部分:游戏主循环 (EventHandler)

这是游戏运行的核心,继承自 ggez 的 EventHandler trait。游戏会不停地循环执行这update、draw两个方法。

impl EventHandler for MainState {
  fn update(&mut self, ctx:&mut Context) -> GameResult {}
  fn draw(&mut self, ctx:&mut Context) -> GameResult {}
}

1. update 方法:处理逻辑

相当于准备好数据,给draw绘制。

fn update(&mut self, ctx:&mut Context) -> GameResult {
    // 暂停逻辑:按 P 键切换暂停
    if ctx.keyboard.is_key_just_pressed(KeyCode::P) {
        self.paused =!self.paused;
    }
  if self.game_over {
            // 按 R 重新开始
            if ctx.keyboard.is_key_pressed(KeyCode::R) {
                self.reset();
            }
            returnOk(());
        }
   if self.paused {
            returnOk(());
        }

    let dt = ctx.time.delta().as_secs_f32();// 获取上一帧到这一帧的时间差

解释:dt (Delta Time) 非常重要,乘以它能让游戏速度在不同电脑上保持一致。

  // ===== 玩家移动 =====
    let mut movement =Vec2::ZERO;
    // 检测键盘方向键或 WASD
        if ctx.keyboard.is_key_pressed(KeyCode::Left) || ctx.keyboard.is_key_pressed(KeyCode::A) {
            movement.x -=1.0;
        }
        if ctx.keyboard.is_key_pressed(KeyCode::Right) || ctx.keyboard.is_key_pressed(KeyCode::D) {
            movement.x +=1.0;
        }
        if ctx.keyboard.is_key_pressed(KeyCode::Up) || ctx.keyboard.is_key_pressed(KeyCode::W) {
            movement.y -=1.0;
        }
        if ctx.keyboard.is_key_pressed(KeyCode::Down) || ctx.keyboard.is_key_pressed(KeyCode::S) {
            movement.y +=1.0;
        }
    
    // 归一化对角线移动,防止斜向移动过快
    if movement.length() >0.0{ movement = movement.normalize();}
    self.player.pos += movement * PLAYER_SPEED * dt;
    
    // 边界限制,不让玩家飞出屏幕
        self.player.pos.x =self.player.pos.x.clamp(20.0, SCREEN_WIDTH -20.0);
        self.player.pos.y =self.player.pos.y.clamp(20.0, SCREEN_HEIGHT -20.0);

解释:这是标准的向量移动逻辑,包含防对角线加速的归一化处理。

// ===== 射击(空格键)=====
// 简单的射击冷却,仅仅响应按下空格次数,一致按着无效
        if ctx.keyboard.is_key_just_pressed(KeyCode::Space) {
            self.shoot();//调用shoot方法
        }

// ===== 每一帧都更新子弹数据,实现子弹移动效果 =====
        for bullet in &mut self.bullets {
            bullet.pos += bullet.velocity * dt;

            // 超出屏幕
            if bullet.pos.y <-10.0{
                bullet.active =false;
            }
        }

// ===== 敌人生成 =====
   self.spawn_timer += dt;
   if self.spawn_timer >= SPAWN_INTERVAL {
        self.spawn_enemy();
        self.spawn_timer =0.0;
    }
// ===== 更新敌人 =====
   for enemy in &mut self.enemies {
            enemy.pos.y += enemy.speed * dt;

            // 超出屏幕底部
            if enemy.pos.y > SCREEN_HEIGHT +30.0{
                enemy.active =false;
                // 敌人逃脱,玩家是否扣血
                // self.player.hp -= 1;
                // if self.player.hp <= 0 {
                //     self.game_over = true;
                // }
            }
   }

解释:计时器控制敌人生成频率;循环更新所有子弹和敌人的位置。

// ===== 碰撞检测逻辑 =====
    // 打中敌人的子弹索引集合
 let mut bullets_to_deactivate =Vec::new();
// 被打中的敌人集合, 临时保存为三元组(idx, hp_change, deactivate)
 let mut enemy_changes =Vec::new();// 
 let mut score_to_add =0;
//遍历子弹
 for (bullet_idx, bullet) in self.bullets.iter().enumerate() {
     if !bullet.active {continue;}
//遍历敌人
     for (enemy_idx, enemy) in self.enemies.iter().enumerate() {
         if !enemy.active {continue;}
         if Self::check_collision(bullet.pos, 5.0, enemy.pos, enemy.size) {
                    bullets_to_deactivate.push(bullet_idx);
                    enemy_changes.push((enemy_idx, -1, false));
                    break;
                }
            }
        }
        // 应用变化
        for &idx in &bullets_to_deactivate {
            if idx <self.bullets.len() {
                self.bullets[idx].active =false;
            }
        }
        for (idx, hp_change, _) in enemy_changes {
            if idx <self.enemies.len() {
                self.enemies[idx].hp += hp_change;
                if self.enemies[idx].hp <=0{
                    self.enemies[idx].active =false;
                    score_to_add +=10;
                }
            }
        }
        self.add_score(score_to_add);
// 敌人撞玩家
        for enemy in &mutself.enemies {
            if enemy.active
                &&Self::check_collision(self.player.pos, self.player.size, enemy.pos, enemy.size)
            {
                enemy.active =false;
                self.player.hp -=1;
                if self.player.hp <=0{
                    self.game_over =true;
                }
            }
        }

        // 移除无效敌人
        self.enemies.retain(|e| e.active);
        // 移除无效子弹
        self.bullets.retain(|b| b.active); // ===== 更新星星背景 =====
        for star in &mutself.stars {
            star.y +=50.0* dt;// 星星下落
            if star.y > SCREEN_HEIGHT {
                star.y =0.0;
                star.x =rand::rng().random_range(0.0..SCREEN_WIDTH);
            }
        }
        Ok(())
    }//fn update结束

解释:这是游戏的“战斗系统”。检测到碰撞后,标记对象为非活跃,然后通过 retain 方法从列表中物理删除。

闭包:|b| ...

这是 Rust 的闭包(Closure)语法,类似于匿名函数。

o|b|:这是参数列表。b 代表 bullets 列表中的每一个子弹对象。

ob.active:这是闭包的主体。它返回一个布尔值。

借用(&)和消耗

for (idx, hp_change, _) in enemy_changes {
            if idx <self.enemies.len() {
                self.enemies[idx].hp += hp_change;
                if self.enemies[idx].hp <=0{
                    self.enemies[idx].active =false;
                    score_to_add +=10;
                }
            }
        }
//enemy_changes.clear();编译器提示错误!

不用像C++那样主动销毁临时指针delete bullets_to_deactivate、enemy_changes