LoginSignup
53
37

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-04-26

#2 画像の表示 >>

2019/09/02 更新

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

はじめに

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

こちらに製作物があります。
今回はexamples/01_create_windowの内容になります。

ronファイル

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

書き方の例

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,
)

このように、SomeNone、タプルを用いて書くことができます。記述しない箇所はデフォルトの設定になりますので、とりあえず必要最低限だけで良いです。

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

main.rs

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構造体は最後に解説します。

amethyst::start_logger(Default::default());

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

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

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

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

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]型で設定します。
この辺りは複雑な設定を使用するまでは「おまじない」として使ってよいでしょう。

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

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

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

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

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

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でのゲーム制作を始めた人が、最初でつまづかないよう手助けになれればと思います。
また疑問や訂正などありましたらコメントしていただければ幸いです。

53
37
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
53
37