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

<< [#1 ウィンドウの作成](https://qiita.com/9laceef/items/380ac0944199de7aad8e)　 [System編](https://qiita.com/9laceef/items/b1e9496717e0a60e9b7e) >>　　　[番外編(画像の色を変化させる)](https://qiita.com/9laceef/items/50fe0a262fb5f15315ab) >>

# ご注意！
この記事の内容は2019/06/17現在の最新版amethystクレート(v0.11.0)では仕様が変わっており動きません！随時更新していきますので、しばしお待ちください。
もし更新が待てなかったり直接尋ねたいことなどありましたらコメントにてお願いいたします。

### はじめに

[前回の記事](https://qiita.com/9laceef/items/380ac0944199de7aad8e)（ウィンドウ作成編）にたくさんのいいねがついていました。ありがとうございます。
モチベが上がったので、頑張って続けていこうと思います。

今回は、前回作成したウィンドウに画像を表示してみようと思います。[サンプルコード](https://github.com/glaceef/amethyst-test)の`examples/02_image/`にあたります。

## main.rs

今回からはコード量が多くなるので、部分的に説明してから最後に全部見せようと思います。

```rust
struct ExampleState;

impl SimpleState for ExampleState {
    fn on_start(&mut self, data: StateData<'_, GameData<'_, '_>>) {
        let world = data.world;
        init_camera(world);
        init_image(world);
    }
}
```

これは前回も紹介した、`SinpleState`トレイトを実装した`ExampleState`を定義しています。今回は`handle_event`メソッドではなく、`on_start`メソッドのみ実装します。
`data.world`により、この世界への可変参照を得ます。その後それを用いてカメラと画像の登録をします。

```rust
fn init_camera(world: &mut World) {
    let camera = Camera::from(Projection::orthographic(
        -250.0, 250.0, -250.0, 250.0
    ));
    let mut transform = Transform::default();
    transform.set_xyz(250.0, 250.0, 1.0);
    world
        .create_entity()
        .with(camera)
        .with(transform)
        .build();
}
```

先ほどの世界への可変参照を引数として、カメラの登録を行います。
`Camera::from()`により`Camera`構造体を作成します。引数となるのは`Projection`列挙子(=>[ドキュメント](https://docs.rs/amethyst_renderer/0.10.4/amethyst_renderer/enum.Projection.html))で、`Perspective`と`Orthograpgic`の二つがあり、それぞれ`Projection::perspecive()`と`Projection::orthographic()`で作成できます。前者は遠近感有の描画、後者は無しの描画をします。今回は後者で問題ありません。
`Projection::orthographic()`により、描画範囲をカメラから見て上下左右250pxずつにします。引数は、(左、右、下、上)となっています。この世界では右・上が正になっています（関数によっては例外がありますがここでは省略します。後ほど出てきます）。
カメラに(0,0)から(500,500)までを描画してもらうために、オブジェクトの座標を(250,250)に設定します。`Transform`はUnityのtransformコンポーネントとと同じように、rotationやscaleも持っています。
さて、これがこの関数の最後です。
`create_entity()`で空のエンティティを作成し、`with()`で様々なコンポーネントを付与していきます。やっぱりUnityみたいですね。最後に`build()`で登録完了です。

```rust
fn init_image(world: &mut World) {
    let loader = world.read_resource::<Loader>();
    let texture_storage = world.read_resource::<AssetStorage<Texture>>();
    let texture_handle = loader.load(
        "logo.png",
        PngFormat,
        TextureMetadata::srgb_scale(),
        (),
        &texture_storage,
    );
    
    let mut transform = Transform::default();
    transform.set_xyz(250.0, 250.0, 0.0);
    world
        .create_entity()
        .with(texture_handle)
        .with(transform)
        .build();
}
```

`load_resource()`により、型パラメータで指定したリソースを得ることができ、今回必要な`Loader`と`AssetStorage`を取得しています。
`load()`によって`TextureHandle`を作成しており、ここでは画像パスと画像形式などを設定します。しかしなぜ画像のファイル名だけでよいのでしょう？ 答えは前回ちょっとだけ触れましたが、"ゲームの場所"を決めていたんですね。`Application::new()`に与えた第一引数です。普通のパス指定では`./examples/02_image/logo.png`になるはずなのですが(後ほどコード全体を紹介します)、`./examples/02_image/`にゲームが存在しているので、`logo.png`だけで良いということなのです。
後は先ほどのようにエンティティを登録して終了です。

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

    let config = DisplayConfig::load("./examples/02_image/config.ron");
    let pipe = Pipeline::build().with_stage(
        Stage::with_backbuffer()
            .clear_target([0.0, 0.0, 0.0, 1.0], 1.0)
            .with_pass(DrawFlat2D::new().with_transparency( // ... (1)
                ColorMask::all(),
                ALPHA,
                Some(DepthMode::LessEqualWrite),
            )),
    );
    let render_bundle = RenderBundle::new(pipe, Some(config));

    let transform_bundle = TransformBundle::new(); // ... (2)

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

    let mut game = Application::new("./examples/02_image/", ExampleState, game_data)?;

    game.run();

    Ok(())
}
```

`main`関数も所々変わっています。
(1)では、png画像のため透過度を有効にしています。今後png画像を利用する場合はこのままコピペでいいでしょう。
(2)では、新しく`TransfromBundle`を追加しています。`Transform`を利用するために必要になります。

これでコードの説明は終了です。完成したプログラムはこちらになります。

```rust
// examples/02_image/main.rs

use amethyst::{
    prelude::*,
    core::{
        Transform, TransformBundle
    },
    renderer::{
        DisplayConfig, Pipeline, Stage, DrawFlat2D, ColorMask, ALPHA, DepthMode,
        Camera, Projection,
        Texture, PngFormat, TextureMetadata,
        RenderBundle
    },
    assets::{
        Loader, AssetStorage
    },
};

struct ExampleState;

impl SimpleState for ExampleState {
    fn on_start(&mut self, data: StateData<'_, GameData<'_, '_>>) {
        let world = data.world;
        init_camera(world);
        init_image(world);
    }
}

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

    let config = DisplayConfig::load("./examples/02_image/config.ron");
    let pipe = Pipeline::build().with_stage(
        Stage::with_backbuffer()
            .clear_target([0.0, 0.0, 0.0, 1.0], 1.0)
            .with_pass(DrawFlat2D::new().with_transparency(
                ColorMask::all(),
                ALPHA,
                Some(DepthMode::LessEqualWrite),
            )),
    );
    let render_bundle = RenderBundle::new(pipe, Some(config));

    let transform_bundle = TransformBundle::new();

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

    let mut game = Application::new("./examples/02_image/", ExampleState, game_data)?;

    game.run();

    Ok(())
}

fn init_camera(world: &mut World) {
    let camera = Camera::from(Projection::orthographic(
        -250.0, 250.0, -250.0, 250.0
    ));
    let mut transform = Transform::default();
    transform.set_xyz(250.0, 250.0, 1.0);
    world
        .create_entity()
        .with(camera)
        .with(transform)
        .build();
}

fn init_image(world: &mut World) {
    let texture_handle = {
        let loader = world.read_resource::<Loader>();
        let texture_storage = world.read_resource::<AssetStorage<Texture>>();
        loader.load(
            "logo.png",
            PngFormat,
            TextureMetadata::srgb_scale(),
            (),
            &texture_storage,
        )
    };

    let mut transform = Transform::default();
    transform.set_xyz(250.0, 250.0, 0.0);
    world
        .create_entity()
        .with(texture_handle)
        .with(transform)
        .build();
}
```

お疲れさまでした！
`cargo run --example 02`でamethystのロゴがでっかく表示されたら成功です。