用ggez游戏框架学Rust:游戏开发实战指南(二)
第四部分:游戏主循环 (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