在 Rust 環境下,使用 Amethyst 遊戲引擎開發遊戲,首先需確認系統環境並安裝圖形驅動程式。Amethyst 提供 CLI 工具簡化專案建立流程,透過 cargo install amethyst_tools 安裝 CLI 後,即可使用 amethyst new 指令建立新專案。執行 cargo run --features=vulkan 啟動專案,並可觀察到引擎初始化過程、DPI 設定等資訊。設定 Cargo.toml 可指定 Vulkan 後端,簡化專案組態。src/main.rs 檔案包含渲染視窗的樣板程式碼,定義了 MyState 結構體和 main 函式,用於初始化引擎、設定資源目錄、建立遊戲資料和啟動遊戲迴圈。Amethyst 的架構圍繞狀態和遊戲資料,透過套件提供模組化功能,實作高度彈性。
建立遊戲視窗需要 RenderToWindow 外掛和 display_config.ron 設定檔,設定視窗標題和大小。初始化攝影機使用 Camera::standard_2d 建立正交投影,並透過 Transform 設定位置。玩家角色以 Player 結構體表示,包含所屬方、寬度和高度等資訊。initialize_players 函式建立兩個玩家實體,並設定其位置。設定遊戲角色時,initialize_players 函式利用 SpriteRender 渲染角色,並使用 Transform 設定位置。載入精靈表使用 load_sprite_sheet 函式,非同步載入紋理和精靈表定義。輸入組態則透過 resources/bindings_config.rs 檔案設定,提升遊戲可組態性。
開發Amethyst遊戲引擎專案的基礎設定與初始架構解析
在開始使用Amethyst遊戲引擎開發專案之前,需要先確保系統環境符合基本要求。首先,必須安裝最新版本的圖形卡驅動程式。若使用的是整合式Intel HD Graphics 620 GPU,則無需額外設定,可以直接使用。
建立專案結構
雖然可以手動使用cargo new指令建立一個空的Rust專案,並自行加入所需的依賴套件和程式碼,但Amethyst提供了便捷的命令列工具(CLI)來簡化這一流程。首先,需要透過Cargo安裝Amethyst CLI:
cargo install amethyst_tools
接著,使用以下指令建立新的Amethyst專案:
amethyst new cat_volleyball
此指令會在cat_volleyball資料夾中建立專案的基本結構,包括Cargo.toml檔案和一些範本檔案。執行以下指令即可執行專案:
cargo run --features=vulkan
此時,會開啟一個空白視窗,並在控制檯中輸出類別似於以下的記錄:
內容解密:
- 執行
cargo run --features=vulkan指令,Cargo會編譯並執行專案,啟動Amethyst遊戲引擎。 - 輸出的記錄顯示了Amethyst引擎的初始化過程,包括版本資訊、平台資訊等。
[INFO][winit::platform::platform::x11::window] Guessed window DPI factor: 1.75表示視窗的DPI縮放比例被設定為1.75。[INFO][rendy_util::wrap] Slow safety checks are enabled! You can disable them in production by enabling the 'no-slow-safety-checks' feature!提示在生產環境中可以透過啟用no-slow-safety-checks功能來關閉緩慢的安全檢查,以提升效能。
設定Cargo.toml
檢視Cargo.toml檔案,可以看到Amethyst CLI自動加入了對Amethyst的依賴,並設定了三個可能的後端(backend)選項:empty、metal和vulkan。由於本範例僅使用Vulkan後端,可以將其硬編碼至依賴設定中,如下所示:
[dependencies]
amethyst = { version = "0.13.2", features = ["vulkan"] }
內容解密:
- 將Amethyst的依賴設定為僅使用Vulkan後端,簡化了專案組態。
features = ["vulkan"]明確指定了使用Vulkan渲染後端。
分析初始main.rs程式碼
在src/main.rs檔案中,包含了一些用於渲染視窗的樣板程式碼。主要結構包括定義一個名為MyState的結構體,並實作SimpleState特徵(trait)。此外,還有一個main函式負責初始化Amethyst引擎並啟動遊戲迴圈。
use amethyst::{
core::transform::TransformBundle,
ecs::prelude::{ReadExpect, Resource, SystemData},
prelude::*,
renderer::{
plugins::{RenderFlat2D, RenderToWindow},
types::DefaultBackend,
RenderingBundle,
},
utils::application_root_dir,
};
struct MyState;
impl SimpleState for MyState {
fn on_start(&mut self, _data: StateData<'_, GameData<'_, '_>>) {}
}
fn main() -> amethyst::Result<()> {
amethyst::start_logger(Default::default());
let app_root = application_root_dir()?;
let assets_dir = app_root.join("assets");
let game_data = GameDataBuilder::default()
.with_bundle(
RenderingBundle::<DefaultBackend>::new()
.with_plugin(RenderToWindow::from_config_path(display_config_path).with_clear([0.0, 0.0, 0.0, 1.0]))
.with_plugin(RenderFlat2D::default()),
)?
.with_bundle(TransformBundle::new())?;
let mut game = Application::new(assets_dir, MyState, game_data)?;
game.run();
Ok(())
}
內容解密:
MyState結構體實作了SimpleState特徵,使其成為一個簡單的遊戲狀態,用於管理遊戲的不同階段。main函式中,首先初始化了Amethyst的日誌系統,接著設定了資源目錄(assets directory)的路徑。- 使用
GameDataBuilder建立遊戲資料,加入了RenderingBundle和TransformBundle兩個套件(bundle)。RenderingBundle負責渲染相關的功能,包括將內容渲染至視窗(RenderToWindow)和2D平面渲染(RenderFlat2D)。TransformBundle提供了實體(entity)的變換功能,使得可以在遊戲中移動或旋轉物件。
- 最後,建立了一個
Application例項,並呼叫其run方法來啟動遊戲迴圈。
高階架構解析
整體而言,Amethyst的遊戲架構圍繞著狀態(state)和遊戲資料(game data)兩大核心概念。狀態管理了遊戲的不同階段,例如載入畫面、角色選擇和遊戲進行中等。而遊戲資料則包含了使遊戲運作所需的所有元件和系統,例如渲染、變換和物理模擬等。
透過將相關功能模組化至不同的套件中,Amethyst提供了高度的彈性和可擴充套件性。在本範例中,僅實作了一個簡單的遊戲狀態,但實際上可以根據需求加入更多狀態,以管理遊戲的複雜流程。
內容解密:
- Amethyst的遊戲架構採用了狀態管理的概念,使得開發者可以清晰地組織遊戲的不同階段。
- 透過使用不同的套件,可以靈活地加入或移除功能,滿足專案的需求。
- 這種模組化的設計使得遊戲開發變得更加有條理和易於維護。
建立遊戲視窗與基本元素
在建立遊戲的過程中,首先需要設定遊戲視窗並加入基本元素,如攝影機和玩家角色。本章節將引導你完成這些步驟。
設定遊戲視窗
首先,你需要新增 RenderToWindow 外掛來建立遊戲視窗。此外掛需要一個 display_config_path 引數,指向 display_config.ron 檔案,該檔案包含了視窗的設定。
(
title: "cat_volleyball",
dimensions: Some((500, 500)),
)
這個設定檔案定義了視窗的標題和大小。在這個例子中,視窗的大小被設定為 500x500 畫素。
// 在遊戲初始化時加入 RenderToWindow 外掛
// 並設定視窗背景顏色為純黑色
.with_plugin(RenderToWindow::new("assets/display_config.ron").with_clear([0.0, 0.0, 0.0, 1.0]))
內容解密:
RenderToWindow外掛用於建立遊戲視窗。display_config.ron檔案定義了視窗的組態,包括標題和大小。.with_clear([0.0, 0.0, 0.0, 1.0])設定了視窗的背景顏色為純黑色,使用 RGBA 值表示。
初始化攝影機
接下來,你需要在遊戲世界中新增一個攝影機,以決定哪些部分應該被顯示在螢幕上。
fn initialize_camera(world: &mut World) {
let mut transform = Transform::default();
transform.set_translation_xyz(ARENA_WIDTH * 0.5, ARENA_HEIGHT * 0.5, 1.0);
world.create_entity()
.with(Camera::standard_2d(ARENA_WIDTH, ARENA_HEIGHT))
.with(transform)
.build();
}
內容解密:
initialize_camera函式負責在遊戲世界中建立攝影機實體。Camera::standard_2d(ARENA_WIDTH, ARENA_HEIGHT)建立了一個正交投影攝影機,覆寫了整個遊戲區域。transform.set_translation_xyz將攝影機移動到遊戲區域的中心。
新增玩家角色
最後,你需要在遊戲中新增玩家角色。
pub struct Player {
pub side: Side,
pub width: f32,
pub height: f32,
}
impl Player {
fn new(side: Side) -> Player {
Player {
side,
width: PLAYER_WIDTH,
height: PLAYER_HEIGHT,
}
}
}
impl Component for Player {
type Storage = DenseVecStorage<Self>;
}
內容解密:
Player結構體代表玩家角色,包含了所屬方、寬度和高度等資訊。Side列舉用於區分玩家所屬的方。DenseVecStorage用於儲存元件,能夠有效地利用記憶體。
初始化玩家
fn initialize_players(world: &mut World) {
let mut left_transform = Transform::default();
let mut right_transform = Transform::default();
let y = PLAYER_HEIGHT / 2.0;
left_transform.set_translation_xyz(PLAYER_WIDTH * 0.5, y, 0.0);
right_transform.set_translation_xyz(ARENA_WIDTH - PLAYER_WIDTH * 0.5, y, 0.0);
world.create_entity()
.with(Player::new(Side::Left))
.with(left_transform)
.build();
world.create_entity()
.with(Player::new(Side::Right))
.with(right_transform)
.build();
}
內容解密:
initialize_players函式負責在遊戲世界中建立兩個玩家角色實體。- 每個玩家角色都有對應的
Transform元件,用於設定其在遊戲世界中的位置。 - 玩家角色根據所屬方被放置在遊戲區域的左右兩側。
設定遊戲角色與資源載入
在開發遊戲的過程中,如何有效地設定遊戲角色並載入所需的資源是一個重要的課題。本章節將介紹如何在 Amethyst 遊戲引擎中實作這些功能。
初始化遊戲角色
首先,您需要定義遊戲角色的內部資料結構。在本例中,遊戲角色被定義為 Player,包含其所屬的 Side(左側或右側)。利用 Transform::set_translation_xyz() 設定角色的位置,並在 World 中建立兩個 Player 實體,分別代表左側和右側的角色。
// 初始化遊戲角色
fn initialize_players(world: &mut World, sprite_sheet: Handle<SpriteSheet>) {
// 準備 left_transform 和 right_transform
let sprite_render = SpriteRender {
sprite_sheet: sprite_sheet.clone(),
sprite_number: 0, // 貓是 sprites 清單中的第一個 sprite
};
world
.create_entity()
.with(sprite_render.clone()) // 使用 sprite renderer
.with(Player::new(Side::Left))
.with(left_transform)
.build();
world
.create_entity()
.with(sprite_render.clone())
.with(Player::new(Side::Right))
.with(right_transform)
.build();
}
內容解密:
initialize_players函式:該函式負責在遊戲世界中初始化兩個玩家實體。它接受一個可變的World參照和一個sprite_sheet的控制程式碼。sprite_render的建立:透過SpriteRender結構體,將sprite_sheet和sprite_number(在本例中為 0,表示清單中的第一個 sprite)關聯起來,用於渲染玩家角色。- 實體建立:使用
world.create_entity()方法建立兩個實體,分別代表左側和右側的玩家。每個實體都被賦予sprite_render、Player元件(指定玩家的所屬方)和相應的Transform元件(指定位置)。
載入精靈表(Spritesheet)
為了提高效率,通常不會為每個遊戲物件載入單獨的圖片,而是將多個相關圖片合併成一個大圖片,即精靈表(Spritesheet)。在本例中,由於目前只有貓的圖片,因此直接使用這張圖片作為精靈表。
// 載入精靈表
fn load_sprite_sheet(world: &mut World) -> Handle<SpriteSheet> {
let texture_handle = {
let loader = world.read_resource::<Loader>();
let texture_storage = world.read_resource::<AssetStorage<Texture>>();
loader.load(
"texture/spritesheet.png",
ImageFormat::default(),
(),
&texture_storage,
)
};
let loader = world.read_resource::<Loader>();
let sprite_sheet_store = world.read_resource::<AssetStorage<SpriteSheet>>();
loader.load(
"texture/spritesheet.ron",
SpriteSheetFormat(texture_handle),
(),
&sprite_sheet_store,
)
}
內容解密:
load_sprite_sheet函式:該函式負責載入精靈表及其相關資源。它傳回一個Handle<SpriteSheet>,允許在多個實體間分享精靈表資源。- 非同步載入:使用
loader.load()方法非同步載入紋理和精靈表定義。這意味著資源可能不會在呼叫傳回後立即可用。 SpriteSheetFormat:用於指定精靈表的格式,包括對應的紋理控制程式碼。
載入與使用輸入組態
為了避免在程式碼中硬編碼按鍵對映,可以使用組態檔案來定義按鍵與動作之間的對映關係。建立一個名為 resources/bindings_config.rs 的輸入組態檔案,如下所示:
// 輸入組態範例
// resources/bindings_config.rs
內容解密:
- 輸入組態的目的:透過組態檔案定義按鍵對映,提高遊戲的可組態性和使用者經驗。
- 檔案位置:將輸入組態檔案放置在
resources目錄下,以便於管理和載入。