【Rustのまほう2】#1 Bevy Engineでオンラインアクションゲームを作る

# はじめに

Tiny Gladeというゲームが9月末にSteamで販売開始されました。

https://store.steampowered.com/app/2198150/Tiny_Glade/?l=japanese

この記事を書いている時点でレビューが12,000件以上ついているという盛況ぶりです。こちらのゲームでは **Bevy Engine** というゲームエンジンが使われているそうです[^tinyglade]。

[^tinyglade]: ただしレンダリングはBevyのものではなく、Vulkan上で独自のレンダリングパイプラインが実装されているそうです。作者インタビュー→ https://80.lv/articles/exclusive-tiny-glade-developers-discuss-bevy-proceduralism-publishers-cozy-games/

https://bevyengine.org/

今回のアドベントカレンダーは、ちょっとしたゲームを作りながらこのBevy Engineの解説記事で完走を目指して走っていきたいと思います。もっとも、とてもすべては解説しきれないので、BevyやECSの大まかな概念やTipsなどに絞っていく予定です。

ちなみに2022年のアドベントカレンダーで書いた[Rustのまほう1期はこちら](https://qiita.com/advent-calendar/2022/wasm4)です。






# Bevy Engineについて

Bevyは次のような特徴のゲームエンジンです。

- プログラミング言語Rustのライブラリ
- 2Dと3Dの両方に対応
- クロスプラットフォーム (Windows, MacOS, Linux, Web, iOS, Android)
- オープンソース
- Entity Component System (ECS)というアーキテクチャ

Bevyはまだまだ開発途上の段階にあり、バージョンアップのたびにゴリゴリAPIが変わっているようです。なにしろ「まだAPIが不安定だしRustでゲーム作るならGodotとかを検討してもいいんじゃない？」と[公式が自分で言っている](https://bevyengine.org/learn/quick-start/introduction/)くらいです。あくまでゲームを完成させることが目的の人は、素直に Unity や Unreal Engine、あるいはオープンソースが好みなら Godot Engine などを使ったほうが安心できると思います。でも自分は今回、以下のような理由で Bevy にしました。

- プログラミング言語Rustを練習がてら何か作ってみたかった
- オブジェクト指向を脱却したデータドリブンなECSアーキテクチャを体験してみたかった
- UnityやUnreal Engineはすでに幾らでも資料や記事があるので、私が書くことはもうあまりない。日本語圏で資料が少ないもののほうが執筆のやる気が出る
- 多機能なGUIエディタつきのゲームエンジンより、シンプルなライブラリのほうが性に合っている気がする

Bevyはまだ若いエンジンのわりにはコミュニティが比較的大きく、ユーザーの熱意も感じます。探してみると良さげなプラグイン(ライブラリ)もたくさん見つかります。本体の開発も活発で、このあいだ[Bevy財団が発足して寄付を募っている](https://bevyengine.org/foundation/)などコアチームの開発体制づくりにも力が入っています。もしこの記事の読者のなかに大富豪がいらっしゃいましたら、ぜひとも寄付してもらえればコミュニティ全員が喜ぶと思います。








# 自分が作っているゲームについて

今回私が作っているのは、ジャンルでいうと「ツインスティックシューター」などと呼ばれている種類のものです。これは文字通り「左のスティックと右のスティック(またはWASDとマウス)でそれぞれ移動と攻撃の方向を指示」という操作の2Dのアクションゲームです。

私がプレイしたことのあるゲームでいうと「[Enter the Gungeon](https://store.steampowered.com/app/311690/Enter_the_Gungeon/?l=japanese)」が典型的で、「[Factorio](https://store.steampowered.com/app/427520/Factorio/?l=japanese)」「[Noita](https://store.steampowered.com/app/881100/Noita/?l=japanese)」のようなゲームも操作が同様です。FPSのような3次元的なゲームより操作が簡単でわかりやすく、私はこのジャンルのゲームが好きなのでこれにしました。

現状だとどうにか基本的なシステムは形になってきていて、プレイヤーキャラクターと敵キャラクターがなんとなくそれっぽく戦えたり、オンラインでプレイヤー同士が簡単なバトルをできるというところです。

![screenshot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64695/d87e7f66-2fcc-9d9d-feb8-ced1ff12eec6.png)

詳しい内容については、今後の記事に少しづつ書いていきたいと思います。







 # Bevyで画像を表示してみる

このシリーズはあくまでプログラミングの記事なので、早速ですがウィンドウを開いて何か画像を表示するコードを実際に書いて、Bevyの雰囲気を体験してみましょう。まずはいつものように `cargo init`でプロジェクトを初期化し、`bevy`クレートを依存関係に追加します。

```bash
$ cargo init

$ cargo add bevy
```

ウィンドウを開いてスプライト（画像）を表示する最低限のコードは以下のようになります[^next]。

[^next]: これはバージョン 0.14 のコードなのですが、実は次のバージョン 0.15 で書き方が変わりそうです。変化が激しい……。

```rust:src/main.rs
use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .run();
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
    commands.spawn(Camera2dBundle::default());
    commands.spawn(SpriteBundle {
        texture: asset_server.load("bevy_bird_dark.png"),
        ..default()
    });
}
```

それから`assets`フォルダ以下に画像や音声などの**アセット**を置きます。`assets`がBevyのデフォルトのアセットパスです。あとは `cargo run`で実行すれば、ウィンドウが開いて画像が表示されるはずです。


### main関数

少し詳しくコードの中身を見てみます。

まずは `app.add_plugins(DefaultPlugins)`でデフォルトのプラグインを設定しています。Bevyの個々の機能は、 **プラグイン** として個別に提供されます。ウィンドウを表示する機能やスプライトを表示する機能などの最小限の機能をまとめたのが`DefaultPlugins`で、これを登録することで最低限ウィンドウを開いて画像を表示することができます[^server]。

[^server]: サーバーサイドで動かすなど、ヘッドレスに実行したい場合はこの`DefaultPlugin`を登録しない場合もあるようです。

次の `app.add_systems(Startup, setup)`では、`Startup` という**スケジュール** で `setup` **システム** を登録しています。Bevyにおけるシステムとは、特定のパターンの引数を持った関数のことです。またスケジュールとはそのシステムが呼び出されるタイミングのことです。要するに、イベントにイベントハンドラを登録しているようなものだと考えればいいでしょう[^event]。

[^event]: ただしBevyの機能としての「イベント」はまた別に存在します。

最後に `app.run()` を呼び出して実行を開始します。




### setup関数

`Startup`はゲームの開始時に１度だけ実行されるスケジュールなので、先ほど登録された `setup` システムが一度だけ実行されます。まず`Camera2dBundle::default()`でカメラを作成し`commands.spawn`でゲーム世界に追加しています。ゲーム画面を表示するには、少なくともひとつのカメラが必要です。

次に `asset_server.load("bevy_bird_dark.png")`で画像のパスを指定し、画像のハンドルを取得しています[^load]。そしてそれを `SpriteBundle` に渡して初期化し、`commands.spawn`で追加しています。



# そしてBevy 0.15へ……

実はこの記事が投稿される前日に、[Bevy 0.15 が正式リリース](https://bevyengine.org/news/bevy-0-15/) されました。この変更により、上記のコード中の`Camera2dBundle`や`SpriteBundle`が非推奨になりました。0.15対応のコードだと以下のようになります。

```rs:src/main.rs
use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .run();
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
    commands.spawn(Camera2d::default());
    commands.spawn(Sprite::from_image(asset_server.load("bevy_bird_dark.png")));
}
```

自分も必死にリリースノートを読んでいるところです。**Required Components**という機能が追加されたのですが、これは「Bevyが始まって以来最も重大なAPI改善」だといいます。確かに今までのAPIよりもシンプルに書けるようになって、良い感じだとは思います。破壊的変更は恐ろしいですが、Bevy界隈は活発なので外部のBevyプラグインもすぐに追従してきてくれることでしょう。


# 次回予告

これだけならあまりECSっぽさもないので、とくに難しくないですね。次回以降は、Bevyの最大の特徴である **Entity Component System** について解説したり、Bevyでの開発について実践的な部分の解説をできたらと思います。

[^load]: 勘のいい人は「ファイルアクセスなのに同期的でいいのか？」と思うかもしれませんが、この`asset_server.load`は画像データそのものではなくハンドルを即座に返します。あとはエンジンが実際の読み込みを裏で行い、完了しだい画面に描画してくれるので、開発者がデータの非同期読み込みを意識する必要がなくなっています。