Bevy 遊戲引擎提供便捷的音訊系統,允許開發者輕鬆地將音效和背景音樂整合至遊戲中,提升玩家體驗。本文示範如何在遊戲中新增球體碰撞、得分音效以及背景音樂,並說明如何在 Ball 元件中管理音效資源控制程式碼。接著,文章將焦點轉移到實體運算領域,說明如何使用 Rust 在 Raspberry Pi 上控制 GPIO 針腳。我們將逐步介紹 Raspberry Pi 環境設定、Rust 工具鏈安裝,以及使用 rust_gpiozero crate 簡化 GPIO 操作,例如控制 LED 燈的開關與讀取按鈕狀態。文章提供完整的程式碼範例,方便讀者快速上手並應用於實際專案。
為遊戲加入音效系統
在完成基本的遊戲功能後,我們需要為遊戲新增音效和背景音樂,以提升遊戲的整體體驗。Bevy 引擎提供了簡單易用的音訊系統,能夠滿足我們的基本需求。
更新 Ball 元件以支援音效
首先,我們需要更新 Ball 元件,使其包含音效的處理。我們將新增兩個新的欄位:bounce 和 score,分別用於儲存球體碰撞和得分時的音效資源。
#[derive(Component)]
pub struct Ball {
pub velocity: Vec2,
pub radius: f32,
pub bounce: Handle<AudioSource>, // 碰撞音效
pub score: Handle<AudioSource>, // 得分音效
}
內容解密:
- 更新
Ball元件:我們在Ball結構體中增加了bounce和score兩個欄位,它們都是Handle<AudioSource>型別,用於儲存音效資源的控制程式碼。 - 音效資源的意義:這些控制程式碼將在初始化球體時被指定,並在適當的時候用於播放對應的音效。
初始化球體時載入音效資源
接下來,我們需要在初始化球體時載入所需的音效資源,並將其指定給 Ball 元件的對應欄位。
fn initialize_ball(
// ...
) {
let bounce_audio = asset_server.load("audio/bounce.ogg");
let score_audio = asset_server.load("audio/score.ogg");
commands.spawn((
Ball {
velocity: Vec2::new(BALL_VELOCITY_X, BALL_VELOCITY_Y),
radius: BALL_RADIUS,
bounce: bounce_audio,
score: score_audio,
},
// ...
));
}
內容解密:
- 載入音效資源:使用
asset_server.load方法載入指定的音效檔案,並將傳回的控制程式碼指定給bounce_audio和score_audio。 - 初始化
Ball元件:在生成Ball元件時,將載入的音效控制程式碼指定給對應的欄位。
在遊戲設定中播放背景音樂
我們還需要在遊戲的設定函式中播放背景音樂,以為遊戲營造氛圍。
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
audio: Res<Audio>, // 音訊子系統
) {
audio.play_with_settings(
asset_server.load("audio/Computer_Music_All-Stars_-_Albatross_v2.ogg"),
PlaybackSettings::LOOP.with_volume(0.25),
);
// ...
}
內容解密:
- 播放背景音樂:使用
audio.play_with_settings方法播放背景音樂,並設定為迴圈播放,調整音量為 0.25。 - 載入背景音樂:使用
asset_server.load方法載入背景音樂檔案。
在碰撞偵測中播放音效
最後,我們需要在碰撞偵測中加入播放音效的邏輯,以增強遊戲的互動體驗。
fn bounce(
mut ball_query: Query<(&mut Ball, &Transform)>,
player_query: Query<(&Player, &Transform)>,
audio: Res<Audio>, // 音訊資源
) {
for (mut ball, ball_trans) in ball_query.iter_mut() {
// ...
if ball_y >= (ARENA_HEIGHT - ball.radius) && ball.velocity.y > 0.0 {
audio.play(ball.bounce.clone()); // 播放碰撞音效
ball.velocity.y = -ball.velocity.y;
}
// ...
}
}
fn scoring(
mut query: Query<(&mut Ball, &mut Transform)>,
mut score: ResMut<Score>,
audio: Res<Audio>, // 音訊資源
) {
for (mut ball, mut transform) in query.iter_mut() {
// ...
if ball_y < ball.radius {
// ...
audio.play(ball.score.clone()); // 播放得分音效
// ...
}
}
}
內容解密:
- 碰撞偵測中播放音效:在偵測到球體碰撞到牆壁或玩家桿時,播放碰撞音效。
- 得分時播放音效:在球體超出邊界,判定得分時,播放得分音效。
透過以上步驟,我們成功地為遊戲加入了音效系統,提升了遊戲的整體體驗。這些改進使得遊戲更加生動和有趣。
使用 Rust 進行實體運算
到目前為止,我們所撰寫的程式都僅存在於虛擬世界中。然而,我們所生活的實體世界中有很大一部分是由軟體控制的。交通燈、自駕車、飛機,甚至火箭和衛星,只是其中的幾個例子。這些軟體通常需要在與一般桌面環境截然不同的環境中構建和執行。它們通常需要在相對較弱的 CPU 上執行,並且可用記憶體較少。有時,它們甚至需要在沒有作業系統的情況下執行,或在專門的嵌入式作業系統上執行。
傳統上,這些應用程式都是使用 C 或 C++ 編寫的,以實作最佳效能和對記憶體的低階控制。許多嵌入式平台都非常有限,以至於垃圾回收是不可行的。但這正是 Rust 的強項所在。Rust 可以提供像 C 或 C++ 一樣的效能和低階控制,但同時也能保證更高的安全性。Rust 程式可以被編譯以在許多不同的中央處理器(CPU)架構上執行,例如 Intel、ARM、RISC-V 和 MIPS。它也支援各種主流作業系統,甚至可以在沒有作業系統的情況下執行。
實體運算專案:使用 Raspberry Pi
在本章中,我們將專注於在 Raspberry Pi 上使用 Rust。Raspberry Pi 是一款價格便宜、信用卡大小的電腦,旨在使電腦教育更加普及。它具有一些重要的功能,可以幫助我們在本章中闡述重點:
- 它具有通用輸入/輸出(GPIO)針腳,可以用來與實體電路(如 LED 和按鈕)互動。
使用 Rust 控制 GPIO
// 初始化 GPIO
let mut gpio = Gpio::new().unwrap();
// 設定 PIN 17 為輸出模式
let mut pin17 = gpio.get(17).unwrap().into_output();
// 設定 PIN 17 為高電位
pin17.set_high();
內容解密:
Gpio::new().unwrap():初始化 GPIO 模組,取得對其的控制權。gpio.get(17).unwrap().into_output():將 GPIO 的第 17 號針腳設定為輸出模式。pin17.set_high():將第 17 號針腳設為高電位,用於控制外部電路。
在本章中,我們將學習如何使用 Rust 與實體裝置互動,以及如何在嵌入式系統上執行 Rust 程式。我們將使用 Raspberry Pi 作為範例,展示如何使用 Rust 控制 GPIO 針腳,並與外部電路互動。
在Raspberry Pi上進行實體運算
實體運算對於那些習慣於網頁或桌面專案的人來說可能是一個巨大的變化。為了確保順利進行,我們需要花一些時間設定測試環境和工具鏈,以避免後續因為不知道是程式碼還是硬體出現問題而耗費大量時間。本章將先介紹如何設定Raspberry Pi 4硬體和作業系統,然後再開始撰寫程式碼。
認識你的Raspberry Pi
本章將使用Raspberry Pi 4 Model B開發板,但Raspberry Pi 3也應該適用。Raspberry Pi是一款微型電腦,具備所有必要的電腦元件:CPU、記憶體、Wi-Fi、藍牙、HDMI輸出、USB等。
Raspberry Pi 4使用ARM CPU,而大多數主流個人電腦使用x86架構的Intel或AMD CPU。由於Rust是一種編譯成機器碼的語言,因此編譯時必須針對正確的CPU架構,否則二進位制檔案將無法執行。
Raspberry Pi具有許多周邊裝置,包括SD卡讀卡器,可以將程式和作業系統載入SD卡。它還有一個USB-C電源輸入,可以使用手機充電器或行動式電源供應器。對於視訊輸出,可以使用其micro-HDMI輸出連線到HDMI顯示器。要控制裝置,可以使用USB滑鼠和鍵盤。最後,可以看到兩排金屬針腳(在圖8-1的左上角)。這些是GPIO(通用輸入/輸出)針腳,將用於與外部電氣元件(如LED和按鈕)互動。
使用Raspberry Pi Imager安裝Raspberry Pi OS
第一個範例將學習如何在Raspberry Pi上直接編譯和執行Rust程式。首先需要在SD卡上安裝作業系統映像,並讓Raspberry Pi從映像啟動。官方的Raspberry Pi作業系統稱為Raspberry Pi OS,它是一個根據Debian的作業系統,具有友好的桌面環境和許多預先安裝的有用軟體套件,如Firefox瀏覽器、文字編輯器、計算器和程式設計環境。
安裝Raspberry Pi OS最簡單的方法是使用名為Raspberry Pi Imager的安裝程式。它提供了一個逐步的精靈來引導您完成安裝過程。
以下是步驟:
- 前往Raspberry Pi Imager下載頁面(圖8-2),並按照安裝說明進行操作。例如,在Ubuntu x86 PC上,執行
sudo apt install rpi-imager。 - 將至少8GB且格式化為FAT格式的SD卡插入PC。
- 以root許可權啟動Raspberry Pi Imager,並按照螢幕上的指示將Raspberry Pi OS安裝到SD卡上(圖8-3)。
- 將SD卡插入Raspberry Pi。
- 將Raspberry Pi連線到鍵盤、滑鼠和HDMI顯示器。
- 將Raspberry Pi連線到5V/3A USB-C電源供應器(通常是手機充電器)。這將開啟Raspberry Pi。
- 一旦Raspberry Pi啟動,您應該會看到一個類別似於圖8-4的桌面環境。
內容解密:
此段落描述了安裝Raspberry Pi OS的步驟,包括下載Raspberry Pi Imager、準備SD卡、安裝作業系統、連線硬體裝置和啟動Raspberry Pi。每個步驟都非常重要,以確保順利安裝和使用Raspberry Pi。
sudo apt install rpi-imager
內容解密:
此命令用於在Ubuntu x86 PC上安裝Raspberry Pi Imager。sudo用於以root許可權執行命令,apt install用於安裝指定的套件,而rpi-imager則是要安裝的套件名稱。
設定與硬體互動
為了與硬體互動,需要建立兩個電路:一個用於輸出,一個用於輸入。輸出電路將允許我們產生光,而輸入電路將允許我們檢測按鈕點選。
輸出電路
首先,建立一個簡單的LED電路連線到GPIO輸出針腳。可以撰寫一個Rust程式來開啟和關閉LED,並以固定的間隔閃爍。
use std::thread;
use std::time::Duration;
fn main() {
// 初始化GPIO
let mut gpio = gpio::Gpio::new().unwrap();
// 設定GPIO針腳為輸出模式
let mut led = gpio.get(17).unwrap().into_output();
loop {
// 開啟LED
led.set_high();
thread::sleep(Duration::from_millis(500));
// 關閉LED
led.set_low();
thread::sleep(Duration::from_millis(500));
}
}
內容解密:
此Rust程式碼初始化GPIO,設定GPIO針腳為輸出模式,並進入無限迴圈。在迴圈中,它開啟和關閉LED,每次持續500毫秒。gpio::Gpio::new().unwrap()用於初始化GPIO,gpio.get(17).unwrap().into_output()用於設定GPIO針腳17為輸出模式。led.set_high()和led.set_low()分別用於開啟和關閉LED。
輸入電路
接下來,新增一個按鈕到電路中。Rust程式可以檢測按鈕點選並切換LED的狀態。
use std::thread;
use std::time::Duration;
fn main() {
// 初始化GPIO
let mut gpio = gpio::Gpio::new().unwrap();
// 設定GPIO針腳為輸出模式
let mut led = gpio.get(17).unwrap().into_output();
// 設定GPIO針腳為輸入模式
let button = gpio.get(23).unwrap().into_input();
loop {
// 檢測按鈕狀態
if button.is_high() {
// 開啟LED
led.set_high();
} else {
// 關閉LED
led.set_low();
}
thread::sleep(Duration::from_millis(100));
}
}
內容解密:
此Rust程式碼初始化GPIO,設定GPIO針腳17為輸出模式,設定GPIO針腳23為輸入模式,並進入無限迴圈。在迴圈中,它檢測按鈕狀態,如果按鈕被按下(高電平),則開啟LED,否則關閉LED。button.is_high()用於檢測按鈕狀態。
在樹莓派上安裝Rust工具鏈與控制GPIO
安裝Rust工具鏈
要在樹莓派(Raspberry Pi)上進行Rust開發,首先需要在其作業系統(Raspberry Pi OS)上安裝Rust編譯器和Cargo套件管理器。開啟終端機並執行以下命令,這個命令是從Rust官方安裝頁面取得的:
curl https://sh.rustup.rs -sSf | sh
這個命令會安裝Rust工具鏈到樹莓派上。rustup會偵測到ARM CPU並建議安裝不同的目標架構:armv7-unknown-linux-gnueabihf。
內容解密:
curl https://sh.rustup.rs -sSf | sh:這行命令會從指定的URL下載並執行一個Shell指令碼,用於安裝Rust工具鏈。- rustup會根據系統的CPU架構建議合適的目標架構,在樹莓派的情況下,通常是
armv7-unknown-linux-gnueabihf,用於編譯能在樹莓派上執行的二進位制檔案。 - 安裝完成後,記得將Cargo的資料夾新增到PATH環境變數中,以便能夠直接使用
cargo命令。
瞭解GPIO針腳
樹莓派的一側有兩排金屬針腳,被稱為GPIO(通用輸入/輸出)針腳,用於與外部世界通訊。這些針腳可以被設定為輸出或輸入,分別用於控制或讀取外部電路的狀態。
GPIO針腳的型別
- 5V:5伏特電源供應
- 3V3:3.3伏特電源供應
- GND:接地(0伏特)
- 編號針腳:GPIO針腳的編號是Broadcom SOC頻道(BCM)編號,這是用於在範例程式碼中識別針腳的編號。
內容解密:
- GPIO針腳可用於多種用途,包括輸出高或低電壓,或讀取外部電路的電壓狀態。
- 針腳的編號可能令人混淆,因為它們看起來是隨機的。BCM編號是樹莓派所使用的Broadcom品牌CPU內部的針腳編號。
- 有些針腳可以用於特定的通訊協定,如PWM、SPI、I2C或UART。
使用rust_gpiozero控制GPIO
為了簡化GPIO的操作,可以使用rust_gpiozero crate,它提供了像LED或Button這樣的易於使用的元件,讓你可以輕鬆地控制連線到GPIO的硬體元件。
建立LED電路
首先,使用樹莓派來點亮一個發光二極體(LED)。LED是一種小的電子元件,當電流透過它時會發出光。要保護LED不被過高的電流損壞,可以在電路中新增一個電阻。
電路連線步驟
- 將LED的陽極(通常是較長的引腳)連線到GPIO針腳2。
- 將LED的陰極連線到電阻的一端。
- 將電阻的另一端連線到麵包板的接地軌(標記為藍色和“-”)。
- 將麵包板的接地軌連線到樹莓派的GND針腳。
使用Rust控制LED
透過設定GPIO針腳2為高(3V3)或低(0V),你可以開啟或關閉LED。
use rust_gpiozero::GPIOOutput;
fn main() {
let mut led = GPIOOutput::new(2); // 使用BCM編號2
loop {
led.set_high(); // 開啟LED
std::thread::sleep(std::time::Duration::from_millis(1000));
led.set_low(); // 關閉LED
std::thread::sleep(std::time::Duration::from_millis(1000));
}
}
內容解密:
GPIOOutput::new(2):建立一個GPIO輸出物件,對應到BCM編號2的針腳。led.set_high()和led.set_low():用於設定LED的狀態,分別對應開啟和關閉LED。std::thread::sleep:使執行緒暫停一段時間,用於控制LED開啟和關閉的時間間隔。
透過上述步驟,你可以在樹莓派上使用Rust控制GPIO針腳,並實作對外部硬體(如LED)的控制。