返回文章列表

遊戲開發實作貓咪移動與碰撞偵測

本文講解如何使用 Bevy 遊戲引擎和 Rust 語言開發遊戲中的貓咪移動和球體碰撞偵測功能。文章涵蓋了鍵盤輸入控制、重力模擬、碰撞處理以及計分系統的實作細節,並提供了程式碼範例和說明。透過這些技術,可以讓遊戲角色具有更豐富的互動性和更逼真的物理效果。

遊戲開發 Rust

Bevy 遊戲引擎提供了一個簡潔而強大的架構,讓開發者能輕鬆地建立 2D 遊戲。本文將逐步講解如何使用 Bevy 和 Rust 語言實作貓咪角色的鍵盤控制移動,以及如何模擬球體的運動、重力影響和碰撞反彈,最後加入計分機制,使遊戲更具完整性。我們將探討 Bevy 的 ECS 架構、系統設計和元件管理,並提供清晰的程式碼範例和詳細的說明,幫助讀者理解遊戲開發的核心概念。

遊戲開發:實作貓咪移動功能

在前面的章節中,我們已經成功地渲染了貓咪角色,但它們仍然是靜止的。為了讓玩家能夠控制貓咪移動,我們需要實作鍵盤輸入控制。

定義 Side 列舉的方法

首先,我們需要在 Side 列舉中定義一些方法,以便根據目前的側邊傳回對應的鍵盤按鍵。我們希望左側玩家使用 AD 鍵進行移動,而右側玩家則使用左右箭頭鍵。

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),
        }
    }
}

程式碼解析:

  1. go_left_keygo_right_key 方法:根據 Side 列舉的目前值,傳回對應的向左或向右移動的 KeyCode

    • 左側玩家使用 AD 鍵。
    • 右側玩家使用左、右箭頭鍵。
  2. 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);
    }
}

程式碼解析:

  1. player 函式引數

    • keyboard_input:用於讀取目前按下的鍵盤按鍵。
    • time:用於取得系統執行的時間間隔,以實作平滑移動。
    • query:用於遍歷所有具有 PlayerTransform 元件的實體。
  2. 計算移動方向

    • 如果按下向左的按鍵,則 left-1.0,否則為 0.0
    • 如果按下向右的按鍵,則 right1.0,否則為 0.0
    • leftright 相加得到最終的移動方向。
  3. 計算移動偏移量

    • 使用公式 offset = direction * PLAYER_SPEED * time.raw_delta_seconds() 計算這段時間內的移動距離。
  4. 更新貓咪位置

    • 將計算出的偏移量應用到貓咪的 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;
    }
}

內容解密:

  1. GRAVITY_ACCELERATION 定義了重力加速度的常數,單位為畫素/秒²。
  2. move_ball 系統會查詢所有具有 BallTransform 元件的實體。
  3. 在迴圈中,系統會根據球體的速度和重力加速度來更新其位置和速度。
  4. 使用了 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;
}

內容解密:

  1. bounce_ball 系統負責偵測球體與其他實體之間的碰撞。
  2. check_collision 函式檢查球體是否與玩家角色或其他邊界發生碰撞。
  3. handle_collision 函式處理碰撞發生時的邏輯,目前簡化為反轉球體的 Y 軸速度。

系統整合

最後,我們需要在 Bevy 應用程式中加入 move_ballbounce_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. **輸出得分資訊**:在控制檯輸出得分資訊,以通知玩家得分情況。