序文
Rustで書かれたRustで書けるRustなゲームエンジンであるAmethystのPong 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.toml
やsrc/main.rs
等が作成されますが、大きな違いは以下の通りです。
-
Cargo.toml
に[dependencies]
,[features]
が書き足されてる -
README.md
に"How to run"が書き足されてる -
resources/
という設定ファイル等を置くディレクトリ、およびウィンドウ設定ファイルdisplay_config.ron
が作成されている -
main.rs
にいっぱい書き足されてる
要はそのままRunさせることができます。
試しにRunさせてみよう
ここで早速cargo run
を打ちたい気持ちに駆られますがここはぐっと堪えてください。(二回目)
README.md
とGraphicalBackendErrorを見ていただければわかりますが、--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
列挙型を使って切り替えることができます。
モジュールのインポート
以下を書いておけばよし。
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トレイトを実装させます。
struct Pong;
impl SimpleState for Pong {}
SimpleState
の他にStateやEmptyStateがあります。
SimpleState
の方がState
より扱いやすいので先ずはこっちを使おう。
main関数を書いていこう
"?"演算子を使えるように
Rustではエラーハンドリングにunwrap()
を使ったりしますが、Result
型を返す関数内では代わりに?
演算子が使えます。
参考:https://qiita.com/kanna/items/a0c10a0563573d5b2ed0
これを利用するにあたってmain関数は以下のように書きます。
fn main() -> amethyst::Result<()> {
Ok(())
}
Loggerを表示させる
実行中のinfo, werning, errorをターミナル上で表示させるために以下文をmain関数に書きます。
amethyst::start_logger(Default::default());
わざわざ自分でprintln()
デバッグしなくても良くなります。
超便利。
display configファイルの作成
ゲームウィンドウの大きさ、ヘッダに書くタイトル等を設定するためにdisplay_config.ron
ファイルを作成します。
.ron
という得体の知れない拡張子ですが、これはRustに適したシリアライズフォーマットRONです。
(Jsonより、RONの方がRustプログラマにやさしいと考えておけばいい。フォーマットがほとんどRustのまんまだから。)
てきとーな場所に置いたら知らないおじさんに怒られてしまいそうなので[要出典]、resources
ディレクトリ内に置いておこう。
(
title: "<project_name>",
dimensions: Some((500, 500)),
)
その他の設定を書き足したければDisplayConfig
構造体に従って書いてください。
書き足さなくても動きます。
ここでmain.rs
に戻ってmain関数を書き進めよう。
後でdispley configファイルを読み込むために、ファイルへのパスの変数を書いておきます。
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関数に書き足します。
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
に新たにメソッドを書き足します。
let game_data = GameDataBuilder::default()
...
.with_thread_local(RenderingSystem::<DefaultBackend, _>::new(
RenderingGraph::default(),
));
以上。
次回予告
Pong Tutorialと言いながらPong要素の無かった今回。
このままではタイトル詐欺もいい所なので次回パドルを生成させます。
AssetやGraphCreatorの伏線回収なるか!?
次回 私の気分次第