在 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)選項:emptymetalvulkan。由於本範例僅使用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建立遊戲資料,加入了RenderingBundleTransformBundle兩個套件(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();
}

內容解密:

  1. initialize_players 函式:該函式負責在遊戲世界中初始化兩個玩家實體。它接受一個可變的 World 參照和一個 sprite_sheet 的控制程式碼。
  2. sprite_render 的建立:透過 SpriteRender 結構體,將 sprite_sheetsprite_number(在本例中為 0,表示清單中的第一個 sprite)關聯起來,用於渲染玩家角色。
  3. 實體建立:使用 world.create_entity() 方法建立兩個實體,分別代表左側和右側的玩家。每個實體都被賦予 sprite_renderPlayer 元件(指定玩家的所屬方)和相應的 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,
    )
}

內容解密:

  1. load_sprite_sheet 函式:該函式負責載入精靈表及其相關資源。它傳回一個 Handle<SpriteSheet>,允許在多個實體間分享精靈表資源。
  2. 非同步載入:使用 loader.load() 方法非同步載入紋理和精靈表定義。這意味著資源可能不會在呼叫傳回後立即可用。
  3. SpriteSheetFormat:用於指定精靈表的格式,包括對應的紋理控制程式碼。

載入與使用輸入組態

為了避免在程式碼中硬編碼按鍵對映,可以使用組態檔案來定義按鍵與動作之間的對映關係。建立一個名為 resources/bindings_config.rs 的輸入組態檔案,如下所示:

// 輸入組態範例
// resources/bindings_config.rs

內容解密:

  1. 輸入組態的目的:透過組態檔案定義按鍵對映,提高遊戲的可組態性和使用者經驗。
  2. 檔案位置:將輸入組態檔案放置在 resources 目錄下,以便於管理和載入。