返回文章列表

Amethyst 遊戲引擎得分與音效系統實作

本文介紹如何在 Amethyst 遊戲引擎中實作遊戲得分系統和音效系統,包含如何判斷勝負、更新得分、顯示得分,以及如何載入和播放音效和背景音樂,提升遊戲的互動性和使用者經驗。

遊戲開發 Rust

在遊戲開發中,得分系統和音效系統是提升遊戲體驗的關鍵要素。本文將詳細介紹如何在 Amethyst 遊戲引擎中實作這兩個系統。首先,我們會建立一個 WinnerSystem 來判斷遊戲勝負並更新得分,接著使用 Amethyst 的 UI 系統顯示得分。然後,我們將建立一個獨立的音訊模組來載入和管理音效和背景音樂,並將它們整合到遊戲中,使遊戲更加生動有趣。最後,我們將總結整個實作過程,並提供一些額外的技巧和建議,幫助讀者更好地理解和應用這些技術。

遊戲得分系統的建構與實作

在前面的章節中,我們已經成功地建立了一個可玩的遊戲,但得分仍然需要手動記錄。為了提升遊戲的體驗,我們將透過新增一個名為WinnerSystem的系統來自動記錄得分。

WinnerSystem 的基本架構

首先,我們需要建立WinnerSystem的基本架構,如下所示:

// src/systems/winner.rs
use amethyst::{
    core::transform::Transform,
    core::SystemDesc,
    derive::SystemDesc,
    ecs::prelude::{Join, System, SystemData, World, WriteStorage},
};
use crate::catvolleyball::{Ball, ARENA_HEIGHT, ARENA_WIDTH};

#[derive(SystemDesc)]
pub struct WinnerSystem;

impl<'s> System<'s> for WinnerSystem {
    type SystemData = (WriteStorage<'s, Ball>, WriteStorage<'s, Transform>);

    fn run(&mut self, (mut balls, mut transforms): Self::SystemData) {
        // 實作 WinnerSystem 的邏輯
    }
}

內容解密:

  • WinnerSystem是一個用於判斷遊戲勝負的系統。
  • 它需要存取BallTransform元件,以便取得球的位置和速度等資訊。
  • run函式是系統的核心,負責實作判斷勝負的邏輯。

勝負判斷邏輯的實作

run函式中,我們將實作勝負判斷的邏輯:

impl<'s> System<'s> for WinnerSystem {
    type SystemData = (WriteStorage<'s, Ball>, WriteStorage<'s, Transform>);

    fn run(&mut self, (mut balls, mut transforms): Self::SystemData) {
        for (ball, transform) in (&mut balls, &mut transforms).join() {
            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!("右邊球員得分");
                } else {
                    println!("左邊球員得分");
                }

                // 重置球的位置和速度
                transform.set_translation_x(ARENA_WIDTH / 2.0);
                transform.set_translation_y(ARENA_HEIGHT / 2.0);
                ball.velocity[0] = -ball.velocity[0];
                ball.velocity[1] = 0.0;
            }
        }
    }
}

內容解密:

  • 當球的Y座標小於或等於其半徑時,表示球觸地,得分條件觸發。
  • 根據球的X座標判斷是哪一邊的球員得分。
  • 重置球的位置到場地中央,並改變其X軸速度方向,以模擬發球。

使用 UI 系統顯示得分

為了更好地展示得分,我們需要使用 Amethyst 的 UI 系統。首先,新增UiBundle到遊戲資料建構器中:

// src/main.rs
use amethyst::ui::{RenderUi, UiBundle};

fn main() -> amethyst::Result<()> {
    // ...
    let game_data = GameDataBuilder::default()
        // ...
        .with_bundle(UiBundle::<StringBindings>::new())?
        .with_bundle(
            RenderingBundle::<DefaultBackend>::new()
                // ... 其他外掛
                .with_plugin(RenderUi::default()),
        )?;
    // ...
}

內容解密:

  • UiBundle提供了UI功能,必須新增到遊戲資料建構器中。
  • RenderUi外掛使遊戲能夠渲染UI元素。

建立 ScoreBoard 和 ScoreText

接下來,建立一個ScoreBoard結構體來儲存得分,並在WinnerSystem中使用它:

// src/catvolleyball.rs
#[derive(Default)]
pub struct ScoreBoard {
    pub score_left: i32,
    pub score_right: i32,
}

並修改WinnerSystem以更新ScoreBoard

impl<'s> System<'s> for WinnerSystem {
    type SystemData = (
        WriteStorage<'s, Ball>,
        WriteStorage<'s, Transform>,
        Write<'s, ScoreBoard>,
    );

    fn run(&mut self, (mut balls, mut transforms, mut score_board): Self::SystemData) {
        for (ball, transform) in (&mut balls, &mut transforms).join() {
            // ...
            if ball_y <= ball.radius {
                if ball_x <= (ARENA_WIDTH / 2.0) {
                    score_board.score_right = (score_board.score_right + 1).min(999);
                } else {
                    score_board.score_left = (score_board.score_left + 1).min(999);
                }
                // ...
            }
        }
    }
}

內容解密:

  • ScoreBoard結構體儲存兩邊球員的得分。
  • WinnerSystem中,根據得分條件更新ScoreBoard中的得分。

初始化 UiText 實體

最後,建立一個ScoreText結構體來持有顯示得分的UiText實體,並在遊戲開始時初始化它們:

// src/catvolleyball.rs
pub struct ScoreText {
    pub p1_score: Entity,
    pub p2_score: Entity,
}

fn initialize_scoreboard(world: &mut World) {
    // 載入字型並建立 UiTransform 和 UiText 實體
    // ...
}

內容解密:

  • ScoreText結構體用於儲存代表兩邊球員得分的UI實體。
  • initialize_scoreboard函式負責初始化這些UI實體,包括設定它們的位置、大小和顯示的文字。

透過上述步驟,我們成功地實作了遊戲的自動得分系統,並將得分顯示在螢幕上,大大增強了遊戲的互動性和使用者經驗。

在遊戲中加入音效與音樂

現在已經有了一個完整的遊戲,但如果沒有音效和背景音樂,就會感覺不夠完整。要在遊戲中播放音樂,需要在 src/main.rs 中的 GameData 新增 AudioBundle

載入音訊檔案

首先,需要將音訊檔案載入到適合 Amethyst 音訊系統的資料結構中。可以將它們新增到 src/catvolleyball.rs 檔案中,但由於該檔案已經相當龐大,因此可以建立一個新的 Rust 模組來存放所有與音訊相關的程式碼,並在 src/catvolleyball.rssrc/main.rs 中參照它。

建立音訊模組

建立一個名為 src/audio.rs 的檔案,並在其中建立一些函式來載入背景音樂。

// src/audio.rs
use amethyst::{
    assets::{AssetStorage, Loader},
    audio::{output::Output, AudioSink, OggFormat, Source, SourceHandle},
    ecs::prelude::{World, WorldExt},
};
use std::iter::Cycle;
use std::vec::IntoIter;

pub struct Music {
    pub music: Cycle<IntoIter<SourceHandle>>,
}

pub struct Sounds {
    pub score_sfx: SourceHandle,
    pub bounce_sfx: SourceHandle,
}

/// 載入 ogg 音訊軌道。
fn load_audio_track(loader: &Loader, world: &World, file: &str) -> SourceHandle {
    loader.load(file, OggFormat, (), &world.read_resource())
}

pub fn initialize_audio(world: &mut World) {
    use crate::catvolleyball::{AUDIO_BOUNCE, AUDIO_MUSIC, AUDIO_SCORE};

    let (sound_effects, music) = {
        let mut sink = world.write_resource::<AudioSink>();
        // 音樂聲音有點大,降低音量。
        sink.set_volume(0.25);
        let loader = world.read_resource::<Loader>();

        let music = AUDIO_MUSIC
            .iter()
            .map(|file| load_audio_track(&loader, &world, file))
            .collect::<Vec<_>>()
            .into_iter()
            .cycle();
        let music = Music { music };
        let sound = Sounds {
            bounce_sfx: load_audio_track(&loader, &world, AUDIO_BOUNCE),
            score_sfx: load_audio_track(&loader, &world, AUDIO_SCORE),
        };
        (sound, music)
    };

    world.insert(sound_effects);
    world.insert(music);
}

載入音訊檔案的解說

  1. 建立 MusicSounds 結構體Music 結構體用於存放背景音樂的迭代器,而 Sounds 結構體則用於存放音效。
  2. load_audio_track 函式:該函式用於載入 ogg 格式的音訊檔案。
  3. initialize_audio 函式:該函式用於初始化音訊系統,包括載入背景音樂和音效,並將它們插入到 World 中。

在遊戲中使用音訊

src/catvolleyball.rs 中,需要呼叫 initialize_audio 函式來初始化音訊系統。

// src/catvolleyball.rs
use crate::audio::initialize_audio;

pub const AUDIO_MUSIC: &'static [&'static str] = &[
    "./audio/Computer_Music_All-Stars_-_Wheres_My_Jetpack.ogg",
    "./audio/Computer_Music_All-Stars_-_Albatross_v2.ogg",
];

pub const AUDIO_BOUNCE: &'static str = "./audio/bounce.ogg";
pub const AUDIO_SCORE: &'static str = "./audio/score.ogg";

pub struct CatVolleyball;

impl SimpleState for CatVolleyball {
    fn on_start(&mut self, data: StateData<'_, GameData<'_, '_>>) {
        // ...
        initialize_audio(world);
    }
}

GameData 中新增 AudioBundle

最後,需要在 src/main.rs 中的 GameData 新增 AudioBundle

// src/main.rs
use amethyst::audio::{AudioBundle};

fn main() -> amethyst::Result<()> {
    // ...
    let game_data = GameDataBuilder::default()
        // ...
        .with_bundle(AudioBundle::default())?
        // ...
}

新增音訊後的遊戲體驗

新增音訊後,遊戲的體驗將會大大提升。背景音樂和音效將會使遊戲更加生動和有趣。

重點回顧

  1. 建立音訊模組:建立一個新的 Rust 模組來存放所有與音訊相關的程式碼。
  2. 載入音訊檔案:使用 LoaderOggFormat 來載入 ogg 格式的音訊檔案。
  3. 初始化音訊系統:使用 initialize_audio 函式來初始化音訊系統,包括載入背景音樂和音效。
  4. 在遊戲中使用音訊:在 src/catvolleyball.rs 中呼叫 initialize_audio 函式來初始化音訊系統,並在 GameData 中新增 AudioBundle

內容解密:

上述步驟闡述瞭如何在 Amethyst 遊戲引擎中新增和管理音訊資源,包括建立獨立的音訊模組、載入特定格式的音訊檔案,以及如何將這些音訊資源整合到遊戲邏輯中,最終提升遊戲的整體體驗。透過這種方式,開發者可以更方便地管理和使用音訊資源,使遊戲開發過程更加高效和有序。