在 Amethyst 引擎中,音效系統的建構仰賴 AudioSink 與 DjSystem 等核心元件。AudioSink 提供了基礎的音訊控制介面,例如播放、暫停、停止及音量調整等功能。DjSystem 則負責管理音樂迴圈播放,當一首曲目結束時,它會自動載入下一首。碰撞和得分音效的實作則分別整合在 BounceSystem 和 WinnerSystem 中,透過呼叫 play_bounce() 和 play_score() 函式觸發對應的音效。這些函式會檢查音訊輸出裝置,並從資源函式庫載入指定的音效檔案進行播放。
音效系統的實作與整合
在遊戲開發中,音效的加入能夠大幅提升遊戲的沉浸感與真實感。本章節將詳細介紹如何在Amethyst遊戲引擎中實作音效系統,包括音樂迴圈播放、碰撞音效以及得分音效的處理。
音效系統的初始化
首先,我們需要建立一個initialize_audio()函式來初始化音效系統。在這個函式中,我們會建立一個AudioSink物件,它類別似於一個音樂播放器,提供了諸如.play(), .pause(), .stop(), 和.set_volume()等控制函式。接著,我們使用load_audio_track()輔助函式來載入指定的音訊檔案。
// src/audio.rs
use amethyst::audio::{AudioSink, Source};
fn initialize_audio(world: &mut World) {
let mut audio_sink = world.write_resource::<AudioSink>();
// 載入音訊檔案
let music = load_audio_track("path/to/music.ogg");
audio_sink.play(music);
}
內容解密:
AudioSink是Amethyst提供的音訊控制介面,可以用來播放、暫停或停止音訊。load_audio_track()函式負責載入音訊檔案並傳回一個Source物件。- 在
initialize_audio()中,我們將AudioSink加入到世界資源中,並載入音樂檔案。
音樂迴圈播放的實作
為了實作音樂的迴圈播放,我們使用了DjSystem。當AudioSink中的音樂播放完畢後,DjSystem會呼叫一個使用者定義的閉包來取得下一首音樂並播放。我們將音樂軌跡存放在一個Cycle迭代器中,這樣可以實作無限迴圈播放。
// src/main.rs
use amethyst::audio::{AudioBundle, DjSystemDesc};
fn main() -> amethyst::Result<()> {
// ...
let game_data = GameDataBuilder::default()
// ...
.with_bundle(AudioBundle::default())?
.with(
DjSystemDesc::new(|music: &mut Music| music.music.next()),
"dj_system",
&[],
)
// ...
}
內容解密:
DjSystemDesc用於建立一個DjSystem,它負責在音樂播放完畢後自動播放下一首。- 我們透過閉包
|music: &mut Music| music.music.next()來取得下一首音樂。 - 將
DjSystem加入到遊戲資料中,並命名為"dj_system"。
碰撞音效的新增
當球體碰撞到牆壁或球拍時,我們需要播放碰撞音效。首先,我們在src/audio.rs中定義了兩個函式:play_bounce()和play_score(),分別用於播放碰撞音效和得分音效。
// src/audio.rs
pub fn play_bounce(sounds: &Sounds, storage: &AssetStorage<Source>, output: Option<&Output>) {
if let Some(ref output) = output.as_ref() {
if let Some(sound) = storage.get(&sounds.bounce_sfx) {
output.play_once(sound, 1.0);
}
}
}
內容解密:
play_bounce()函式檢查是否有有效的音訊輸出裝置。- 如果有,它會從資產儲存中取得碰撞音效並以1.0的音量播放一次。
在BounceSystem中播放碰撞音效
我們修改了BounceSystem,使其在球體碰撞到牆壁或球拍時播放碰撞音效。
// src/systems/bounce.rs
use crate::audio::{play_bounce, Sounds};
#[derive(SystemDesc)]
pub struct BounceSystem;
impl<'s> System<'s> for BounceSystem {
type SystemData = (
WriteStorage<'s, Ball>,
ReadStorage<'s, Player>,
ReadStorage<'s, Transform>,
Read<'s, AssetStorage<Source>>,
ReadExpect<'s, Sounds>,
Option<Read<'s, Output>>,
);
fn run(&mut self, (mut balls, players, transforms, storage, sounds, audio_output): Self::SystemData) {
for (ball, transform) in (&mut balls, &transforms).join() {
// 碰撞邏輯...
play_bounce(&*sounds, &storage, audio_output.as_ref().map(|o| o.deref()));
}
}
}
內容解密:
- 我們在
BounceSystem中加入了必要的系統資料,包括AssetStorage<Source>、Sounds和Output。 - 當球體發生碰撞時,我們呼叫
play_bounce()函式來播放碰撞音效。
在WinnerSystem中播放得分音效
同樣地,我們在WinnerSystem中加入了播放得分音效的邏輯。
// src/systems/winner.rs
use crate::audio::{play_score, Sounds};
#[derive(SystemDesc)]
pub struct WinnerSystem;
impl<'s> System<'s> for WinnerSystem {
type SystemData = (
WriteStorage<'s, Ball>,
WriteStorage<'s, Transform>,
WriteStorage<'s, UiText>,
Write<'s, ScoreBoard>,
ReadExpect<'s, ScoreText>,
Read<'s, AssetStorage<Source>>,
ReadExpect<'s, Sounds>,
Option<Read<'s, Output>>,
);
fn run(&mut self, (mut balls, mut locals, mut ui_text, mut scores, score_text, storage, sounds, audio_output): Self::SystemData) {
for (ball, transform) in (&mut balls, &mut locals).join() {
// 得分邏輯...
play_score(&*sounds, &storage, audio_output.as_ref().map(|o| o.deref()));
}
}
}
內容解密:
- 我們在
WinnerSystem中加入了播放得分音效所需的系統資料。 - 當球體觸地得分時,我們呼叫
play_score()函式來播放得分音效。
使用Rust進行實體運算
在前面的章節中,我們所撰寫的程式都僅存在於虛擬世界。然而,現實世界中有很大一部分是由軟體控制的。交通燈、自駕車、飛機,甚至火箭和衛星等,都是由軟體控制的例子。這些軟體通常需要在與常見的Linux、Windows或MacOS桌面或筆記型電腦截然不同的環境中編譯和執行。它們通常需要在相對較弱的CPU和較少的記憶體下執行,有時甚至需要在沒有作業系統的情況下執行,或是在專為嵌入式系統設計的特殊作業系統上執行。
為何選擇Rust
傳統上,這些應用程式都是使用C或C++撰寫,以達到最佳的效能和對記憶體的低階控制。許多嵌入式平台由於資源有限,無法使用垃圾回收機制。然而,這正是Rust的強項所在。Rust不僅能提供與C或C++相同的效能和低階控制,還能保證更高的安全性。Rust程式可以被編譯以執行在多種不同的CPU架構上,如Intel、ARM和MIPS,並且支援多種主流作業系統,甚至是無作業系統環境。
在Raspberry Pi上使用Rust
本章節將重點放在使用Rust在Raspberry Pi上進行實體運算。Raspberry Pi是一款信用卡大小的廉價電腦,旨在使電腦教育更加普及。它具有幾個重要的特點,能夠展示本章節的重點:
- 它採用ARM CPU,能夠學習如何為ARM平台編譯和交叉編譯程式碼。
- 它具有GPIO針腳,可以用來控制實體電路,如LED燈和按鈕。
- 它足夠強大,可以執行完整的根據Debian的作業系統(Raspbian),因此可以在不深入裸機程式設計的情況下學習實體運算和交叉編譯。
首先,我們將在Raspberry Pi上安裝完整的作業系統。然後,我們將安裝完整的Rust工具鏈。接著,我們將構建兩個電路,一個用於輸出,一個用於輸入,並使用Rust與它們互動:
- 輸出:第一個電路將使我們能夠透過LED燈向實體世界輸出訊號。我們將建立一個簡單的LED電路,連線到GPIO輸出針腳。然後,我們可以撰寫Rust程式來開啟和關閉LED燈,並使其規律地閃爍。
- 輸入:我們也可以從實體世界接收輸入訊號。我們將在電路中新增一個按鈕。Rust程式可以檢測到按鈕的點選,並據此切換LED燈的開關狀態。
電路搭建與程式設計
// 使用Rust控制LED燈的範例程式碼
use std::thread;
use std::time::Duration;
fn main() {
// 初始化GPIO針腳
let led_pin = init_gpio_pin();
loop {
// 開啟LED燈
led_pin.set_high();
thread::sleep(Duration::from_millis(500));
// 關閉LED燈
led_pin.set_low();
thread::sleep(Duration::from_millis(500));
}
}
#### 內容解密:
1. `init_gpio_pin()` 函式用於初始化GPIO針腳,將其設定為輸出模式。
2. 在無窮迴圈中,我們交替開啟和關閉LED燈,每次持續500毫秒。
3. `thread::sleep(Duration::from_millis(500))` 用於使執行緒暫停執行,以達到閃爍的效果。
這些範例將幫助我們瞭解Rust程式碼如何與實體世界互動。然而,我們將直接在Raspberry Pi上編譯這些程式碼。在許多嵌入式應用中,目標平台(如Raspberry Pi或類別似的板子)可能不夠強大來編譯程式碼。因此,我們將把編譯工作移到另一台更強大的電腦上,這台電腦具有不同的CPU和作業系統。這種編譯方式被稱為交叉編譯。
交叉編譯
我們將設定交叉編譯工具鏈,並在該工具鏈上交叉編譯之前的範例。最後,為了讓大家提前瞭解GPIO針腳的工作原理,我們將使用底層API來控制它。這將使我們能夠瞭解高階GPIO函式庫的工作原理。
交叉編譯設定
- 安裝交叉編譯工具鏈。
- 設定Rust編譯目標為ARM架構。
- 編譯Rust程式碼以在Raspberry Pi上執行。
# 安裝ARM架構的交叉編譯工具鏈
rustup target add arm-unknown-linux-gnueabihf
# 編譯Rust程式碼
cargo build --target=arm-unknown-linux-gnueabihf
內容解密:
rustup target add arm-unknown-linux-gnueabihf命令用於安裝ARM架構的交叉編譯工具鏈。cargo build --target=arm-unknown-linux-gnueabihf命令用於編譯Rust程式碼,以在ARM架構的Raspberry Pi上執行。
透過本章節的學習,我們將能夠瞭解如何使用Rust進行實體運算,包括如何控制GPIO針腳、如何進行交叉編譯等。這將為我們進一步探索嵌入式系統和物聯網領域打下堅實的基礎。
樹莓派(Raspberry Pi)基礎與Rust安裝
樹莓派是一種類別似迷你電腦的開發板,具備電腦的基本元件,如CPU、記憶體、WiFi、藍牙、HDMI輸出、USB等。與傳統桌上型或筆記型電腦不同的是,樹莓派採用ARM架構的CPU,而非常見的Intel x86/x86_64架構。由於Rust是一種編譯成機器碼的語言,CPU架構決定了最終輸出的形式。
樹莓派的硬體特性
樹莓派具備多種周邊裝置,如SD卡讀卡器、micro-USB電源輸入、HDMI輸出、USB介面等。此外,樹莓派還有兩排金屬針腳(GPIO),用於與外部電路(如LED燈和按鈕)互動。
透過NOOBS安裝Raspbian作業系統
Raspbian是樹莓派的官方作業系統,根據Debian開發,具有友善的桌面環境和多種實用軟體套件。安裝Raspbian最簡單的方法是使用NOOBS(New Out Of the Box Software)安裝程式。
安裝步驟:
- 前往NOOBS下載頁面,下載離線和網路安裝的ZIP檔案。
- 準備至少8GB的SD卡,並格式化為FAT格式。
- 將NOOBS ZIP檔案解壓縮,並將所有檔案複製到SD卡中。
- 將SD卡插入樹莓派。
- 連線鍵盤、滑鼠和HDMI顯示器到樹莓派。
- 使用micro-USB電源開啟樹莓派。
- NOOBS啟動後,選擇Raspbian選項進行安裝。
- 安裝完成後,重啟樹莓派,即可進入Raspbian作業系統。
安裝Rust工具鏈
在Raspbian上安裝Rust編譯器和Cargo,可以使用rustup工具。執行以下命令:
curl https://sh.rustup.rs -sSf | sh
此命令會在樹莓派上安裝完整的Rust工具鏈。rustup會偵測到ARM CPU,並建議安裝對應的目標架構(armv7-unknown-linux-gnueabihf)。安裝完成後,記得將Cargo資料夾新增至PATH環境變數中,以便使用Cargo命令。
安裝過程中的注意事項:
- rustup會根據樹莓派的ARM CPU建議合適的目標架構。
- 將Cargo資料夾新增至PATH環境變數,以確保Cargo命令可用。
控制GPIO針腳
GPIO(General-Purpose Input/Output)針腳用於與外部世界溝通。當針腳作為輸出時,可以透過軟體控制輸出3.3V或0V。當針腳作為輸入時,可以偵測針腳電壓是高(3.3V)還是低(0V)。
GPIO針腳型別:
- 5V:5V電源供應
- 3V3:3.3V電源供應
- GND:接地
- 編號:GPIO針腳的BCM編號,用於在程式碼中參照針腳
GPIO針腳由硬體暫存器控制,暫存器就像電腦記憶體一樣,可以讀寫位元。透過寫入特定的位元模式到暫存器,可以設定針腳的模式(輸入、輸出或特殊協定)。
// 示例程式碼:控制GPIO針腳
use std::fs::OpenOptions;
use std::io::{Seek, SeekFrom, Write};
fn main() {
// 開啟GPIO記憶體對映檔
let mut gpio_mem = OpenOptions::new()
.read(true)
.write(true)
.open("/dev/gpiomem")
.unwrap();
// 設定GPIO針腳模式
gpio_mem.seek(SeekFrom::Start(0)).unwrap();
gpio_mem.write(&[0b00100000]).unwrap(); // 設定針腳為輸出模式
// 控制GPIO針腳輸出
gpio_mem.seek(SeekFrom::Start(4)).unwrap();
gpio_mem.write(&[0b00000001]).unwrap(); // 設定針腳輸出高電平
}
內容解密:
- 開啟
/dev/gpiomem檔案,該檔案代表GPIO記憶體對映。 - 使用
seek方法將檔案指標移到指定位置,準備寫入資料。 - 寫入特定的位元模式到暫存器,設定GPIO針腳的模式為輸出。
- 再次使用
seek方法將檔案指標移到另一個位置,準備寫入輸出資料。 - 寫入資料到暫存器,控制GPIO針腳輸出高電平。