LoginSignup
6
4

More than 3 years have passed since last update.

Amethyst: Pong Tutorialを読んでみた 其の一

Posted at

序文

Rustで書かれたRustで書けるRustなゲームエンジンであるAmethystPong Tutorialを読んでみた。

英文読んで要約するだけなので詳しい解説が欲しいなら他記事を読んでください。
またがんばって英語を読んでますが頑張らないと読めない程度なので所々間違いがあるかと思います。

訂正箇所があればやさしくコメントしてください。

バージョンは0.11.1

本記事では4.1 Setting up the projectについての内容である。

Projectの作成

さて先ずはcargo newを打ちたい気持ちに駆られますがここはぐっと堪えてください。

amethyst_toolsという超便利アイテムをインストールします。

以下のコマンドを打てばインストール完了。

cargo install amethyst_tools

次に以下コマンドでProject作成ができます。

amethyst new <project_name>

cargo newのようにCargo.tomlsrc/main.rs等が作成されますが、大きな違いは以下の通りです。

  • Cargo.toml[dependencies], [features]が書き足されてる
  • README.mdに"How to run"が書き足されてる
  • resources/という設定ファイル等を置くディレクトリ、およびウィンドウ設定ファイルdisplay_config.ronが作成されている
  • main.rsにいっぱい書き足されてる

要はそのままRunさせることができます。

試しにRunさせてみよう

ここで早速cargo runを打ちたい気持ちに駆られますがここはぐっと堪えてください。(二回目)

README.mdGraphicalBackendErrorを見ていただければわかりますが、--featuresオプションをつけないと走ってくれません。

つまりコマンドは以下のように打ちます。

Windows, Linuxの場合:

cargo run --features "vulkan"

macOSの場合:

cargo run --features "metal"

グラフィックバックエンド無しで走らせたい場合:

cargo run --features "empty"

ただしemptyを使った場合いずれ走らなくなるようなのでできるだけVulkanかMetalを使おう。

ようやく準備ができましたので上記に従ってRunさせてください。
(Vulkanの場合ここでエラーが出たりしたらこちらを参照してみてください。Metalは知らん。)

コンパイルが無事成功して実行されると、500×500サイズの淡い紺色のウィンドウが生成されるはずです。

さてこの時点で4.1 Setting up the projectの内容はコンプリートしましたので解説を終わります。

お疲れ様でした。

※まだ続きます

GameStateの作成

ここから先の内容はamethyst_toolsが自動で生成するので読まなくても問題ないです。

私が楽しいから書いてます。

GameStateとは

3.1 Stateは以下のように語ります。

A game state is a general and global section of the game.

ちょっとよくわかりにくいですが、例えば

  • ゲーム開始時のロードしてる状態 -> LoadingState
  • メインメニュー画面を表示してる状態 -> MainMenuState
  • ゲームをプレイできる状態 -> GameplayState
  • ゲームを一時停止してる状態 -> PauseState
  • ゲームが終わりスコア結果等を表示する状態 -> ResultState

といった具合です。

GameStateを1つだけ作ってプログラムを詰め込むのではなく、プログラムを各GameState毎にわけることでクオリティの高いゲームがより簡単に作れます。

実行中はTrans列挙型を使って切り替えることができます。

モジュールのインポート

以下を書いておけばよし。

main.rs
use amethyst::{
    core::transform::TransformBundle,
    ecs::prelude::{ReadExpect, Resources, SystemData},
    prelude::*,
    renderer::{
        pass::DrawShadedDesc,
        rendy::{
            factory::Factory,
            graph::{
                render::{RenderGroupDesc, SubpassBuilder},
                GraphBuilder,
            },
            hal::{format::Format, image},
        },
        types::DefaultBackend,
        GraphCreator, RenderingSystem,
    },
    utils::application_root_dir,
    window::{ScreenDimensions, Window, WindowBundle},
};

GameStateを作成

Pong構造体を作成し、SimpleStateトレイトを実装させます。

main.rs
struct Pong;

impl SimpleState for Pong {}

SimpleStateの他にStateEmptyStateがあります。

SimpleStateの方がStateより扱いやすいので先ずはこっちを使おう。

main関数を書いていこう

"?"演算子を使えるように

Rustではエラーハンドリングにunwrap()を使ったりしますが、Result型を返す関数内では代わりに?演算子が使えます。

参考:https://qiita.com/kanna/items/a0c10a0563573d5b2ed0

これを利用するにあたってmain関数は以下のように書きます。

main.rs
fn main() -> amethyst::Result<()> {

    Ok(())
}

Loggerを表示させる

実行中のinfo, werning, errorをターミナル上で表示させるために以下文をmain関数に書きます。

main.rs
amethyst::start_logger(Default::default());

わざわざ自分でprintln()デバッグしなくても良くなります。

超便利。

display configファイルの作成

ゲームウィンドウの大きさ、ヘッダに書くタイトル等を設定するためにdisplay_config.ronファイルを作成します。

.ronという得体の知れない拡張子ですが、これはRustに適したシリアライズフォーマットRONです。

(Jsonより、RONの方がRustプログラマにやさしいと考えておけばいい。フォーマットがほとんどRustのまんまだから。)

てきとーな場所に置いたら知らないおじさんに怒られてしまいそうなので[要出典]resourcesディレクトリ内に置いておこう。

resources/display_config.ron
(
  title: "<project_name>",
  dimensions: Some((500, 500)),
)

その他の設定を書き足したければDisplayConfig構造体に従って書いてください。

書き足さなくても動きます。

ここでmain.rsに戻ってmain関数を書き進めよう。

後でdispley configファイルを読み込むために、ファイルへのパスの変数を書いておきます。

main.rs
let app_root = application_root_dir()?;
let display_config_path = app_root.join("resources").join("display_config.ron");

application_root_dir()関数でCargoプロジェクトのパスが取得されます。

試しにprintln!("{:?}", app_root);とでも書いてみてください。

何も起きません。
(Runさせた時にCargoプロジェクトへのパスがターミナル上に表示されます)

ついでにmain関数の返り値とresourcesディレクトリの作成をすっとばした方に朗報です。

そのままでは後々動きません。読み返してください。

Windowを生成しよう

ようやくこのセッションでWindowが生成できます。

長かった...
読む側では5分くらいでしょうが書く側は昼食挟んで既に5時間くらい経ってます。

以下文をmain関数に書き足します。

main.rs
let game_data = GameDataBuilder::default()
    .with_bundle(WindowBundle::from_config_path(display_config_path))?;

let assets_dir = app_root.join("assets");
let mut game = Application::new(assets_dir, Pong, game_data)?;
game.run();

これを書けば後はCargoRunさせるだけですがここはぐっと堪えて(3回目)コードを読んでいきます。
(とっととRunさせたい人はここまで読んでないでしょう?)

Applicationはゲームエンジンのルートオブジェクトです。

これに必要なデータをぶち込めば、他のゲーム実行中に必要なグローバルな変数は必要ないです。(正確にはCoreApplication

Application::new()を使って、初期状態のAssetのロード, State, GameDataを変数としてぶち込みます。

Assetはテクスチャ, 音楽等のデータです。
今回全く触れなかったですがいずれ必要になります。
エラーが出るわけでもないので書くだけ書いときましょう。

Stateは最初の方で書いたやつです。
ようやく必要になりました。

GameDataはゲーム実行中のcentral repositoryになります。
要はゲームデータです((雑
with()with_bundle()を使ってSystemやECSバンドルをGameDataに追加していきます。
上コードではDisplayConfigを追加してます。

ここで.run()を呼ぶことでゲームが動きます。

Windowが生成します。
やったね。

レンダリングのベースとなるコードを書こう

Windowを生成するだけなら必要ありませんが、何かを表示させたりする時に必要になります。

今書かなくてええやろって?
だって4.1 Setting up the projectに書いてるもん。

コードは長いのでここからコピペしてください。
というかamethyst_toosで自動生成します。

しっかりコードを読んで行くと私の睡眠時間が消えてしまうのでウルトラざっくり読んで行きます。

ExampleGraph構造体を作成してGraphCreatorトレイトを実装させます。

ClearValue::Color内の変数を書き換えると背景色を変えられます。
好きな色に変えましょう。
(値は0.0-1.0で、RGBAです。例えば[0.34, 0.36, 0.52, 1.0]で淡い藍色になります。)

最後にmain関数に戻ってgame_dataに新たにメソッドを書き足します。

main.rs
let game_data = GameDataBuilder::default()
    ...
    .with_thread_local(RenderingSystem::<DefaultBackend, _>::new(
        RenderingGraph::default(),
    ));

以上。

次回予告

Pong Tutorialと言いながらPong要素の無かった今回。
このままではタイトル詐欺もいい所なので次回パドルを生成させます。
AssetやGraphCreatorの伏線回収なるか!?

次回 私の気分次第

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4