小時候在大型機台玩《雷電》時,總覺得能自己做出一個射擊遊戲一定很酷。最近我正好在研究 Rust 的遊戲引擎 Bevy,挑戰做出一個簡化版的經典射擊遊戲 雷電 (Raiden)。
這個 Demo 包含以下功能:
玩家可以用 WASD / 方向鍵 移動飛機
按 空白鍵 發射子彈
敵人會自動生成並從上方往下移動
子彈擊中敵人 → 敵人與子彈同時消失
超出畫面的物件會自動清理
最終效果大概是這樣:
Bevy 為什麼適合這個題材?
Bevy 採用 ECS(Entity Component System) 架構,把遊戲物件拆成三個元素:
Entity(實體):玩家、敵人、子彈
Component(元件):位置、速度、角色類型
System(系統):移動、生成、碰撞、清理
對「射擊遊戲」來說,這個架構超直觀:敵人就是一個 entity,子彈也是,給它一個 velocity component,它們就會自己動起來。
玩家控制
玩家飛機的操作就是 方向鍵 / WASD。這裡我加了一個小細節:玩家不能飛出螢幕邊界。
transform.translation.x = transform.translation.x
.clamp(-WINDOW_WIDTH / 2.0 + 25.0, WINDOW_WIDTH / 2.0 - 25.0);
這行程式碼就是用來「把你擋在畫面裡」。我一開始還忘了加,結果飛機直接飛出視窗消失XD
射擊
只要按空白鍵,玩家就能發射一顆黃色小子彈往上飛。
commands.spawn((
Sprite {
color: Color::srgb(1.0, 1.0, 0.0),
custom_size: Some(Vec2::new(5.0, 10.0)),
..default()
},
Transform::from_xyz(player_x, player_y + 30.0, 0.0),
Bullet,
Velocity(Vec3::new(0.0, BULLET_SPEED, 0.0)),
));
子彈就是一個矩形精靈,加上 Velocity,剩下交給移動系統就好。 第一次按下空白鍵,看到畫面裡飛出黃色子彈的瞬間,彷彿夢回童年。
產生敵人
敵人每隔兩秒會在隨機位置生成,然後慢慢往下移動:
let x = (rand::random::<f32>() - 0.5) * (WINDOW_WIDTH - 100.0);
commands.spawn((
Sprite::from_image(asset_server.load("player2.png")),
Transform::from_xyz(x, WINDOW_HEIGHT / 2.0 + 50.0, 0.0),
Enemy,
Velocity(Vec3::new(0.0, -ENEMY_SPEED, 0.0)),
));
當我第一次看到敵人真的「一架一架掉下來」時,瞬間覺得好像真的可以玩了 😎。
碰撞檢測
子彈跟敵人碰到就同歸於盡,這裡我用了最直白的寫法:算距離。
if bullet_transform.translation.distance(enemy_transform.translation) < 30.0 {
commands.entity(bullet_entity).despawn();
commands.entity(enemy_entity).despawn();
}
完全沒有物理引擎,單純距離小於 30 就消失。但就算這麼陽春,打掉第一架敵機的瞬間還是很讚。
自動清理
最後一件必須要處理的是,子彈飛出畫面、敵人掉下去,都要刪掉。不然遊戲跑久了會 lag。
if transform.translation.y > WINDOW_HEIGHT / 2.0 + 100.0
|| transform.translation.y < -WINDOW_HEIGHT / 2.0 - 100.0 {
commands.entity(entity).despawn();
}
記得把這段加上去,不然最後會卡QQ
小結
總之,這是一個運用 Bevy 就能寫出來的一個遊戲,雖然很簡單,但是應該可以有很多可能性,例如可以再加上特效、聲音,可能還可以加上計分系統。
這篇文章只是我用 Bevy 寫遊戲的一個小實驗。其實我還在寫一個完整的 Bevy 系列文,會一步一步帶大家從 ECS 入門,到做出一個 Rogue-lite 小遊戲。 如果你對 Rust 遊戲開發有興趣,歡迎看一下鐵人賽系列