Rustで書かれたゲームエンジンamethyst勉強記

[#2 画像の表示](https://qiita.com/9laceef/items/383ca6525a4125f089eb) >>

### 2019/09/02 更新
この記事の内容は2019/09/02現在の最新版amethyst(v0.12.0)についての内容になります。

### はじめに
この記事はRust言語とゲーム作成に興味のある大学生が、Rust製のゲームエンジンamethystを使用した備忘録になります。
Rust製のゲームエンジンは有名なものでpistonやggezなどがありますが、amethystに関する記事がQiitaには（そもそもウェブ上に日本語の記事が）ほとんどなかったので、新たに始める方向けに残していこうと思います。

[こちら](https://github.com/glaceef/amethyst-example)に製作物があります。
今回は`examples/01_create_window`の内容になります。

## ronファイル

amethystでは、タイトルやウィンドウサイズなどの情報を設定ファイルとして別に書きだすようになっており、ronファイルという珍しいファイルを用いています。これは"Rusty Object Notation"の略で、Rustのオブジェクトと同じような感覚で書くことができるファイルになっています。詳しくは[こちら](https://github.com/ron-rs/ron)。

### 書き方の例

```display_config.ron
(
  title: "amethyst: 01_create_window",
  icon: None,
  fullscreen: false,
  dimensions: Some((500, 500)),
  max_dimensions: None,
  min_dimensions: None,
  visibility: true,
  multisampling: 0,
  vsync: false,
)
```
このように、`Some`や`None`、タプルを用いて書くことができます。記述しない箇所はデフォルトの設定になりますので、とりあえず必要最低限だけで良いです。

```display_config.ron
(
  title: "amethyst: 01_create_window",
  dimensions: Some((500, 500)),
)
```

## `main.rs`

```rust:main.rs
// examples/01_create_window/main.rs

use amethyst::{
    prelude::*,
    renderer::{
        RenderingBundle,
        types::DefaultBackend,
        plugins::{RenderToWindow, RenderFlat2D},
    },
    input::is_key_down,
    utils::application_dir,
    winit::VirtualKeyCode,
};

struct ExampleState;

impl SimpleState for ExampleState {
    fn handle_event(
        &mut self,
        _: StateData<'_, GameData<'_, '_>>,
        event: StateEvent,
    ) -> SimpleTrans {
        if let StateEvent::Window(event) = event {
            if is_key_down(&event, VirtualKeyCode::Escape) {
                Trans::Quit
            } else {
                Trans::None
            }
        } else {
            Trans::None
        }
    }
}

fn main() -> amethyst::Result<()> {
    amethyst::start_logger(Default::default());

    let app_root = application_dir("assets/")?;

    let display_config_path = app_root.join("display_config.ron");
    let render_bundle = RenderingBundle::<DefaultBackend>::new()
        .with_plugin(
            RenderToWindow::from_config_path(display_config_path)
                .with_clear([1.0; 4]),
        )
        .with_plugin(RenderFlat2D::default());

    let game_data = GameDataBuilder::default()
        .with_bundle(render_bundle)?;

    Application::new(app_root, ExampleState, game_data)?.run();

    Ok(())
}
```
では一つずつ簡単に解説します。`ExampleState`構造体は最後に解説します。

```rust
amethyst::start_logger(Default::default());
```
これにより、プログラムの情報がコンソールに表示されます。書かないと警告を出してくれますが特に動作に影響はありません。

```rust
let app_root = application_dir("examples/01_create_window/")?;
```

`utils`モジュールよりスコープに導入されます。
プロジェクトディレクトリ直下（サンプルの場合は`amethyst-example/`）からのパスを引数に与え、`PathBuf`として取得します。`app_root`の変数名が示すように、アプリケーションのルートをここで用意しておくといろいろ楽になります。もしくはアセットをまとめているディレクトリがおすすめ。
ほかにも、`amethyst_utils`クレートには便利な関数が用意されているので気になった人は調べてみてください（→[リファレンス](https://docs.amethyst.rs/stable/amethyst_utils/)）。

因みに、この`?`は`Result`型に対して用い、`Ok(v)`であれば`v`を取り出し（`.unwrap()`）、`Err(e)`であれば`e`を返す（`return e;`）糖衣構文です。よく見ると、この`main`関数は返り値がありますね。

```rust
let display_config_path = app_root.join("display_config.ron");
let render_bundle = RenderingBundle::<DefaultBackend>::new()
    .with_plugin(
        RenderToWindow::from_config_path(display_config_path)
            .with_clear([1.0; 4]),
    )
    .with_plugin(RenderFlat2D::default());
```

`display_config.ron`ファイルへのパスを用意し、それを用いて`RenderBundle`を作成しています。`.with_clear`は画面の初期化に用いる色を、`[f32; 4]`型で設定します。
この辺りは複雑な設定を使用するまでは「おまじない」として使ってよいでしょう。

```rust
let game_data = GameDataBuilder::default()
    .with_bundle(render_bundle)?;

Application::new(app_root, ExampleState, game_data)?.run();
```

`GameDataBuilder`を作成し、`with_bundle`メソッドで`WindowBundle`のインスタンスを与えます。`WindowBundle`構造体の関連関数により、設定ファイルのパスを元に作成しています。

`Application`構造体を作成する際、３つの引数を取ります。
１つ目は**ゲームの場所**です。この後amethystによって定義された関数にパスを与える場合ここがルートになります。例えば`examples/01_create_window/sample.ron`を読み込む場合、与えるパスは`./sample.ron`もしくは`sample.ron`になります。
２つ目は**初期のゲーム状態**です。ゲームには様々な状態があります（例：待機状態、ゲームプレイ状態、ポーズ状態、終了状態などなど...）。これら状態を表す構造体を今後作っていくことになります。今回のプログラムで用いる状態は`ExampleState`一つだけです。これはユニット構造体なのでこれでインスタンスを作成できています。もし`ExampleState(i32)`であれば、ここは`ExampleState(0)`のように記述する必要があります。
３つ目は、すぐ上で作った`game_data`を与えています。

今後の`main`関数はこの形がメインになります。

```rust
struct ExampleState;

impl SimpleState for ExampleState {
    fn handle_event(
        &mut self,
        _: StateData<'_, GameData<'_, '_>>,
        event: StateEvent,
    ) -> SimpleTrans {
        if let StateEvent::Window(e) = event {
            if is_key_down(&e, VirtualKeyCode::Escape) {
                return Trans::Quit;
            }
        }
        Trans::None
    }
}
```
最後になります。これが先ほど説明に出てきた**状態**を表す構造体です。
`SimpleState`トレイトで定義されている関数はすべてデフォルト実装がなされているので、必要なものだけを再定義します。今回はEscキーで終了する処理を実装します。
`is_key_down`関数は指定したキーが押されているか否かを返します。`VirtualKeyCode`列挙型のヴァリアントによってキーボードのキーを指定します。
`return Trans::Quit;`により、終了状態を伝えてゲームを終了させています。もしキーが押されていなかった場合、`Trans::None`が返るため何も起こりません。

以上で完成になります。
`cargo run --example 01`で実行してみましょう。真っ白なウィンドウが表示されれば成功です。フォーカスした状態でEscキーで終了させましょう。

## 終わりに

今後も進捗に合わせて記事を公開していこうと思います。
amethystでのゲーム制作を始めた人が、最初でつまづかないよう手助けになれればと思います。
また疑問や訂正などありましたらコメントしていただければ幸いです。