LoginSignup
2
1

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-05-09

<< System編  #4 エンティティの作成・削除 >>

ご注意!

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

はじめに

今回は、マウスからの入力とTransformコンポーネントを扱ってみます。クリックした位置に画像を移動させるプログラムを作ってみます。

#1でウィンドウの設定ファイルについて紹介しました。今回は画像用の設定ファイルについても扱っていきます。

サンプルコードはこちら。今回はexamples/03_move_to_mouseになります。

spritesheet.ron

画像ファイルの1枚ごとに設定ファイルを用意します。

(
    spritesheet_width: 50,
    spritesheet_height: 50,
    sprites: [
        (
            x: 0,
            y: 0,
            width: 50,
            height: 50,
        ),
    ],
)

spritesheet_width, spritesheet_heightに画像ファイルのサイズを指定します。spritesには画像サイズ(左上のx座標、y座標、横幅、高さ)の配列を与えます。

main.rs

#2では最後にコード全体を紹介していましたが、見やすさのためにこれからは最初に載せようと思います。ただし、インポートやinitialise_camera関数など使いまわすものに関しては省略させていただきます。前回までの章、もしくはサンプルコードを見てください。
基本的に長いので最初は流し読みで大丈夫です。

struct Icon;

impl Component for Icon {
    type Storage = DenseVecStorage<Self>;
}

struct ExampleState;

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

    fn handle_event(
        &mut self,
        _: StateData<'_, GameData<'_, '_>>,
        event: StateEvent,
    ) -> SimpleTrans {
        /* body omitted */
    }
}

struct MoveSystem;

impl<'s> System<'s> for MoveSystem {
    type SystemData = (
        ReadStorage<'s, Icon>,
        WriteStorage<'s, Transform>,
        Read<'s, InputHandler<String, String>>,
    );

    fn run(&mut self, (icons, mut transforms, input): Self::SystemData) {
        if input.button_is_down(Button::Mouse(MouseButton::Left)) {
            if let Some((x, y)) = input.mouse_position() {
                for (_icon, transform) in (&icons, &mut transforms).join() {
                    transform.set_xyz(x as f32, 500.0 - y as f32, 0.0);
                }
            }
        }
    }
}

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

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

    let input_bundle = InputBundle::<String, String>::new();

    let transform_bundle = TransformBundle::new();

    let game_data = GameDataBuilder::new()
        .with_bundle(render_bundle.with_sprite_sheet_processor())?
        .with_bundle(input_bundle)?
        .with_bundle(transform_bundle)?
        .with(MoveSystem, "move-system", &[]);

    Application::new(
        "./examples/03_move_to_mouse/",
        ExampleState,
        game_data
    )?.run();

    Ok(())
}

fn initialise_camera(world: &mut World) {
    /* body omitted */
}

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

        let sprite_sheet_store = world.read_resource::<AssetStorage<SpriteSheet>>();
        loader.load(
            "spritesheet.ron",
            SpriteSheetFormat,
            texture_handle,
            (),
            &sprite_sheet_store,
        )
    };

    let sprite_render = SpriteRender {
        sprite_sheet: sprite_sheet_handle,
        sprite_number: 0,
    };

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

struct Icon

今回はIcon構造体を移動させるオブジェクトとします。

struct Icon;

impl Component for Icon {
    type Storage = DenseVecStorage<Self>;
}

System編で解説した通り、Componentトレイトを実装させます。これによりエンティティに付加させることができるようになります。

initialise_icon

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

        let sprite_sheet_store = world.read_resource::<AssetStorage<SpriteSheet>>();
        loader.load(
            "spritesheet.ron",
            SpriteSheetFormat,
            texture_handle,
            (),
            &sprite_sheet_store,
        )
    };

    let sprite_render = SpriteRender {
        sprite_sheet: sprite_sheet_handle,
        sprite_number: 0,
    };

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

Loaderに関しては以前触れましたね。いろいろなアセットを呼び出すことができるこのloadメソッドですが、今回はスプライトシートを呼び出しています。
余裕が出てくるまではコピペで良いと思います。"icon.png"が画像のパス、"spritesheet.ron"がスプライトシートの設定ファイルになり、sprite_numberがspritesheet.ronで設定した描画位置配列のインデックスになります。また今回はアニメーションはせずに1枚全体を使います(スプライトシートを使ったアニメーションをしてみたいと思った方は、examples/11_animation(名前変わるかもしれません)などを参考に頑張ってみてください。いつか記事にするとは思いますが)。

余談ですが、スプライトというパラパラ漫画のようにコマ送りでアニメーションを表現する技法があり、それを1枚にまとめて位置だけをずらして描画する方法をとるため、そのまとめた画像(シート)をスプライトシートというそうです。

MoveSystem

このシステムの中で、マウスボタンが押されている間の座標の取得、Iconの座標(Transform)の変更を行っています。

Read

System編ではReadWriteの説明は省略していました。これは型ごとに一つだけリソースを追加することができます。ストレージにエネミーオブジェクトを複数追加するような使い方ではなく、「時間」や「入力」といったリソースに対して使われます。~~Storageと同じように、Readは不変参照を、Writeは可変参照を呼び出します。
このプログラムではInputHandlerという、マウスやキーボードからの入力を読むためのリソースを呼び出しています。これは後ほど出てくるInputBundleを必要とします。

struct MoveSystem;

impl<'s> System<'s> for MoveSystem {
    type SystemData = (
        ReadStorage<'s, Icon>,
        WriteStorage<'s, Transform>,
        Read<'s, InputHandler<String, String>>,
    );

    fn run(&mut self, (icons, mut transforms, input): Self::SystemData) {
        if input.button_is_down(Button::Mouse(MouseButton::Left)) {
            if let Some((x, y)) = input.mouse_position() {
                for (_icon, transform) in (&icons, &mut transforms).join() {
                    transform.set_xyz(x as f32, 500.0 - y as f32, 0.0);
                }
            }
        }
    }
}

SystemDataは、Iconコンポーネントへの不変参照、Transformコンポーネントへの可変参照、そしてマウス入力を読み込むためのInputHandlerへの不変参照の3つを割り当てます。
runメソッド内にて、IconコンポーネントとTransformコンポーネントをもつエンティティにのみ作用させたいので、join()で選択します。今回は一つしか作っていませんのでfor文は1回でおわりますね。
input.mouse_position()transform.set_xyz()についてなのですが、y座標のみおかしなことになっています。というのも、Tranformコンポーネントにとってウィンドウのがy=0になりますが、マウス座標にとってはウィンドウがy=0なので、ウィンドウの縦サイズから引いて逆転させないと画面真ん中で鏡のような挙動になってしまいます。

main()

ここはこれといった変化はありません。InputHandlerを扱うためにInputBundleを用いていますね。

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

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

    let input_bundle = InputBundle::<String, String>::new();

    let transform_bundle = TransformBundle::new();

    let game_data = GameDataBuilder::new()
        .with_bundle(render_bundle.with_sprite_sheet_processor())?
        .with_bundle(input_bundle)?
        .with_bundle(transform_bundle)?
        .with(MoveSystem, "move-system", &[]);

    Application::new(
        "./examples/03_move_to_mouse/",
        ExampleState,
        game_data
    )?.run();

    Ok(())
}

上から7行目あたりで行っているpng画像の透過処理は忘れないようにしましょう。

お疲れさまでした!cargo run --example 03で実行されたら画面の適当なところをクリックしてみたり押しながら動かしたりしてみましょう。また別の画像を用意してspritesheet.ronの使い方に慣れてみてもいいですね。

 おわりに

以前デバッグのためにウィンドウタイトルを動的に変更できないかと試行錯誤していて結局できなかったのですが、ウィンドウサイズもおそらく取得できないかもしれません。そうなるとマウス座標から作用させるのにハードコーディングさせる必要が出てくるというのが厄介です...。けれどReadでいろいろリソースを取得できるので、これで何か得られるかもしれませんね?

次回は、何らかのアクションにより後からエンティティを追加してみようと思います。
ではでは。

2
1
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
2
1