Bevy 遊戲引擎提供了一個簡潔而強大的架構,讓開發者能輕鬆地建立 2D 遊戲。本文將逐步講解如何使用 Bevy 和 Rust 語言實作貓咪角色的鍵盤控制移動,以及如何模擬球體的運動、重力影響和碰撞反彈,最後加入計分機制,使遊戲更具完整性。我們將探討 Bevy 的 ECS 架構、系統設計和元件管理,並提供清晰的程式碼範例和詳細的說明,幫助讀者理解遊戲開發的核心概念。
遊戲開發:實作貓咪移動功能
在前面的章節中,我們已經成功地渲染了貓咪角色,但它們仍然是靜止的。為了讓玩家能夠控制貓咪移動,我們需要實作鍵盤輸入控制。
定義 Side 列舉的方法
首先,我們需要在 Side 列舉中定義一些方法,以便根據目前的側邊傳回對應的鍵盤按鍵。我們希望左側玩家使用 A 和 D 鍵進行移動,而右側玩家則使用左右箭頭鍵。
impl Side {
// 取得向左移動的鍵盤按鍵
fn go_left_key(&self) -> KeyCode {
match self {
Side::Left => KeyCode::A,
Side::Right => KeyCode::Left,
}
}
// 取得向右移動的鍵盤按鍵
fn go_right_key(&self) -> KeyCode {
match self {
Side::Left => KeyCode::D,
Side::Right => KeyCode::Right,
}
}
// 定義貓咪的可移動範圍
fn range(&self) -> (f32, f32) {
match self {
Side::Left => (PLAYER_WIDTH / 2.0, ARENA_WIDTH / 2.0 - PLAYER_WIDTH / 2.0),
Side::Right => (ARENA_WIDTH / 2.0 + PLAYER_WIDTH / 2.0, ARENA_WIDTH - PLAYER_WIDTH / 2.0),
}
}
}
程式碼解析:
go_left_key和go_right_key方法:根據Side列舉的目前值,傳回對應的向左或向右移動的KeyCode。- 左側玩家使用
A和D鍵。 - 右側玩家使用左、右箭頭鍵。
- 左側玩家使用
range方法:定義每個貓咪角色的可移動範圍,確保它們不會超出自己的區域。- 左側玩家的移動範圍限制在競技場的左半邊。
- 右側玩家的移動範圍限制在競技場的右半邊。
實作 Player 系統
接下來,我們將建立一個名為 player 的系統,用於處理貓咪角色的移動。這個系統將會讀取鍵盤輸入,並根據輸入更新貓咪的位置。
const PLAYER_SPEED: f32 = 60.0;
fn player(
keyboard_input: Res<Input<KeyCode>>,
time: Res<Time>,
mut query: Query<(&Player, &mut Transform)>,
) {
for (player, mut transform) in query.iter_mut() {
let left = if keyboard_input.pressed(player.side.go_left_key()) {
-1.0f32
} else {
0.0
};
let right = if keyboard_input.pressed(player.side.go_right_key()) {
1.0f32
} else {
0.0
};
let direction = left + right;
let offset = direction * PLAYER_SPEED * time.raw_delta_seconds();
// 更新貓咪的 X 軸位置
transform.translation.x += offset;
// 限制貓咪在可移動範圍內
let (left_limit, right_limit) = player.side.range();
transform.translation.x = transform.translation.x.clamp(left_limit, right_limit);
}
}
程式碼解析:
player函式引數:keyboard_input:用於讀取目前按下的鍵盤按鍵。time:用於取得系統執行的時間間隔,以實作平滑移動。query:用於遍歷所有具有Player和Transform元件的實體。
計算移動方向:
- 如果按下向左的按鍵,則
left為-1.0,否則為0.0。 - 如果按下向右的按鍵,則
right為1.0,否則為0.0。 - 將
left和right相加得到最終的移動方向。
- 如果按下向左的按鍵,則
計算移動偏移量:
- 使用公式
offset = direction * PLAYER_SPEED * time.raw_delta_seconds()計算這段時間內的移動距離。
- 使用公式
更新貓咪位置:
- 將計算出的偏移量應用到貓咪的
Transform元件上,更新其 X 軸位置。 - 使用
clamp方法確保貓咪不會超出其可移動範圍。
- 將計算出的偏移量應用到貓咪的
將 Player 系統新增到 Bevy 應用程式中
最後,我們需要在 main 函式中將 player 系統新增到 Bevy 應用程式中,以便它能夠在遊戲迴圈中執行。
fn main() {
App::new()
// ... 其他系統和設定
.add_system(player)
.run();
}
系統註冊解析:
add_system(player):將player系統註冊到 Bevy 的 ECS 架構中,使其在每個遊戲迴圈中被執行,用於處理貓咪角色的移動。
建立球體運動系統與碰撞偵測
在前面的章節中,我們成功地將球體加入遊戲中,並賦予了初始速度。然而,球體只是簡單地向右移動並掉落至畫面外。為了使遊戲變得更有趣,我們需要實作重力模擬以及碰撞偵測,讓球體在碰到邊界或玩家角色時能夠彈回。
重力模擬的實作
首先,我們需要實作一個系統來模擬重力對球體的影響。我們定義了一個名為 move_ball 的系統,該系統會根據時間和重力加速度來更新球體的位置和速度。
程式碼實作
pub const GRAVITY_ACCELERATION: f32 = -40.0;
fn move_ball(
time: Res<Time>,
mut query: Query<(&mut Ball, &mut Transform)>
) {
for (mut ball, mut transform) in query.iter_mut() {
// 套用運動變化量
transform.translation.x += ball.velocity.x * time.raw_delta_seconds();
transform.translation.y += (ball.velocity.y + time.raw_delta_seconds() * GRAVITY_ACCELERATION / 2.0) * time.raw_delta_seconds();
ball.velocity.y += time.raw_delta_seconds() * GRAVITY_ACCELERATION;
}
}
內容解密:
GRAVITY_ACCELERATION定義了重力加速度的常數,單位為畫素/秒²。move_ball系統會查詢所有具有Ball和Transform元件的實體。- 在迴圈中,系統會根據球體的速度和重力加速度來更新其位置和速度。
- 使用了 Velocity Verlet 整合演算法來減少 Euler 整合的誤差,使模擬結果更加準確。
碰撞偵測與彈跳實作
接下來,我們需要實作一個系統來偵測球體與邊界或玩家角色之間的碰撞,並使球體在碰撞發生時彈回。
碰撞偵測邏輯
fn bounce_ball(
mut query_ball: Query<(&mut Ball, &mut Transform)>,
query_player: Query<&Transform, With<Player>>,
) {
for (mut ball, mut transform) in query_ball.iter_mut() {
// 檢查與玩家角色的碰撞
for player_transform in query_player.iter() {
if check_collision(ball, transform, player_transform) {
// 處理碰撞邏輯
handle_collision(&mut ball, &mut transform);
}
}
// 檢查與邊界的碰撞
if transform.translation.y < 0.0 {
// 處理與底部的碰撞
handle_collision(&mut ball, &mut transform);
}
}
}
fn check_collision(ball: &Ball, ball_transform: &Transform, player_transform: &Transform) -> bool {
// 簡化的碰撞偵測邏輯
let distance = ball_transform.translation - player_transform.translation;
let radius_sum = ball.radius + PLAYER_SIZE / 2.0;
distance.length_squared() < radius_sum * radius_sum
}
fn handle_collision(ball: &mut Ball, transform: &mut Transform) {
// 簡化的碰撞處理邏輯:反轉 Y 軸速度
ball.velocity.y = -ball.velocity.y;
}
內容解密:
bounce_ball系統負責偵測球體與其他實體之間的碰撞。check_collision函式檢查球體是否與玩家角色或其他邊界發生碰撞。handle_collision函式處理碰撞發生時的邏輯,目前簡化為反轉球體的 Y 軸速度。
系統整合
最後,我們需要在 Bevy 應用程式中加入 move_ball 和 bounce_ball 系統,使球體能夠正確地運動和彈跳。
fn main() {
App::new()
// ...
.add_system(move_ball)
.add_system(bounce_ball)
// ...
}
建立遊戲碰撞偵測與計分系統
在開發遊戲時,碰撞偵測和計分機制是不可或缺的元素。本章節將探討如何利用 Bevy 框架實作球體與玩家之間的碰撞偵測,以及如何建立計分系統。
碰撞偵測系統實作
首先,我們需要建立一個名為 bounce 的系統,用於處理球體與邊界及玩家之間的碰撞。這個系統將使用兩個查詢:一個用於球體,另一個用於玩家。
use rand::Rng;
fn bounce(
mut ball_query: Query<(&mut Ball, &Transform)>,
player_query: Query<(&Player, &Transform)>,
) {
for (mut ball, ball_transform) in ball_query.iter_mut() {
let ball_x = ball_transform.translation.x;
let ball_y = ball_transform.translation.y;
// 檢查球體是否碰到邊界並反彈
if ball_y <= ball.radius && ball.velocity.y < 0.0 {
ball.velocity.y = -ball.velocity.y;
} else if ball_y >= (ARENA_HEIGHT - ball.radius) && ball.velocity.y > 0.0 {
ball.velocity.y = -ball.velocity.y;
} else if ball_x <= ball.radius && ball.velocity.x < 0.0 {
ball.velocity.x = -ball.velocity.x;
} else if ball_x >= (ARENA_WIDTH - ball.radius) && ball.velocity.x > 0.0 {
ball.velocity.x = -ball.velocity.x;
}
// 檢查球體是否與玩家碰撞
for (player, player_trans) in player_query.iter() {
let player_x = player_trans.translation.x;
let player_y = player_trans.translation.y;
if point_in_rect(
ball_x,
ball_y,
player_x - PLAYER_WIDTH / 2.0 - ball.radius,
player_y - PLAYER_HEIGHT / 2.0 - ball.radius,
player_x + PLAYER_WIDTH / 2.0 + ball.radius,
player_y + PLAYER_HEIGHT / 2.0 + ball.radius,
) {
if ball.velocity.y < 0.0 {
// 球體碰到玩家時反彈並改變方向
ball.velocity.y = -ball.velocity.y;
let mut rng = rand::thread_rng();
match player.side {
Side::Left => {
ball.velocity.x = ball.velocity.x.abs() * rng.gen_range(0.6..1.4)
}
Side::Right => {
ball.velocity.x = -ball.velocity.x.abs() * rng.gen_range(0.6..1.4)
}
}
}
}
}
}
}
#### 內容解密:
1. **查詢機制**:使用 Bevy 的 `Query` 來取得球體和玩家的實體及其變換資訊。
2. **邊界碰撞偵測**:檢查球體是否碰到遊戲視窗的四個邊界,並根據其速度進行反彈處理。
3. **玩家碰撞偵測**:利用 `point_in_rect` 函式檢查球體是否進入玩家的矩形區域,若發生碰撞則進行反彈並改變球體的水平方向。
4. **隨機化處理**:在球體與玩家碰撞時引入隨機因素,使遊戲更具不可預測性。
### 計分系統實作
接下來,我們需要實作一個名為 `scoring` 的系統,用於處理球體落地時的計分邏輯。
```rust
fn scoring(mut query: Query<(&mut Ball, &mut Transform)>) {
for (mut ball, mut transform) in query.iter_mut() {
let ball_x = transform.translation.x;
let ball_y = transform.translation.y;
if ball_y < ball.radius {
// 球體落地,進行計分並重置球體位置
if ball_x <= ARENA_WIDTH / 2.0 {
println!("Right player scored");
ball.velocity.x = ball.velocity.x.abs();
} else {
println!("Left player scored");
ball.velocity.x = -ball.velocity.x.abs();
}
// 重置球體到初始位置
transform.translation.x = ARENA_WIDTH / 2.0;
transform.translation.y = ARENA_HEIGHT / 2.0;
ball.velocity.y = 0.0;
}
}
}
#### 內容解密:
1. **計分邏輯**:檢查球體是否落地,並根據其 x 座標判斷是哪一邊的玩家得分。
2. **重置球體**:將球體重置到遊戲區域的中心,並調整其速度以準備下一回合的發球。
3. **輸出得分資訊**:在控制檯輸出得分資訊,以通知玩家得分情況。