Bevy Engineのすすめ (小さなUnity、大きなProcessing)

(英語版も書きました: https://dev.to/funatsufumiya/bevy-engine-as-a-small-unity-big-processing-3edg)

スクリーンショット 2024-01-22 17.35.32.png
1

はじめに: クリエイティブ・コーディングとProcessing

ゲームエンジンってたくさんありますが、あなたは何を使ってますか?

私はいわゆるクリエイティブ・コーディングが好きで、思いついたことを形にするために、例えばUnityやUE5、Touch DesignerNotch、Reactなど、その都度プロトタイピングにちょうど良さそうなツールを選んでは、思いついたものを作っていたりします。

そんなときふと、クリエイティブ・コーディングの自分にとっての源流ってなんだろうと思うと、やはりProcessingだろうなと思います。

Processingの何が良かったかと振り返ると、やはり気軽さでしょう。今でもp5.jsopenFrameworksのような形でその哲学は引き継がれ続けていますが、例えばp5.jsはオンラインエディタをブラウザで開くだけで気軽にコードが書けたりします 2

スクリーンショット 2024-01-22 17.05.15.png

当時から、Processingはエディタ一つ入れるだけで他に何もいらず、ただシンプルにコードを書くことを楽しむことができていました。

大規模ゲームエンジンとその狭間

ところで、クリエイティブ・コーディングというのは別に形態を問わない概念であって、私はBlenderやUnreal Engineなどに搭載されている、いわゆるビジュアルエディタも立派なクリエイティブ・コーディングだと思っています。

image01.png (図: Blenderのジオメトリノード 3)

material-editor-hero-image.png (図: Unreal Engine 5のマテリアルエディタ 4)

実際、私自身もこうしたものをフル活用してアイデアを形にしていますし、ノーコードという考え方があるように、もう今はコードというのは一つの概念になった感すらあります。

Webブラウザなどもエンジンの一つですが、ゲームエンジンに限らずエンジンというのは、私たちの制作を支える屋台骨であり、なくてはならないものになっています。

しかしながらときどき、Processingのように、軽量で使い勝手の良いものに原点回帰したいなという想いと、Reactのようなリアクティブプログラミングといった、近代的な概念と気軽さが融合したものが欲しい、つまりUnityとProcessingの狭間にあるものが欲しいと、ふと思うことがあります。何十GBもあるエディタのダウンロードを待つ必要がなく、すぐに手を動かせるくらいのものが欲しいな、と 5

Bevy Engineにみる、UnityとProcessingのあいだ

そう思っていたとき、ふと出会ったのがBevy Engineというものでした。

スクリーンショット 2024-01-22 17.39.48.png 6

Bevy Engineはオープンソースであり、まだ開発されてそんなに年数が経っていないのですが、まるでopenFrameworksのアドオン群のように、多数の有志によるプラグイン群が存在しています。

ProcessingやopenFrameworksと似ているのは、そうしたプラグインシステムを必要に応じて追加していく形式をとっており、コアは小さく書かれているということです。

そして何より、Rust (cargo) 以外のツールは基本的に要りません。まず $ cargo new bevy-hello 7 で空のプロジェクトを作り、$ cd bevy-hello でプロジェクトフォルダに移動し、$ cargo add bevy でbevyをライブラリとして追加し、あとは src/main.rs に好きなExampleにあるコード (例えば以下) を貼り付けたりして書き、$ cargo run するだけで動きます。

hello-world
use bevy::prelude::*;

fn main() {
    App::new()
        // .add_plugins(DefaultPlugins) // ※ この行を含むとウィンドウが出ます
        .add_systems(Startup, hello_world)
        .run();
}

fn hello_world() {
    println!("hello world");
}

スクリーンショット 2024-01-22 18.58.17a.png

初回の起動 (ビルド、コンパイル) には時間がかかりますが、2回目以降は驚くほど高速に再読み込みでき、必要に応じてアセットなどのホットリロードもできます。

ECS (Entity Component System) を眺める

IMG_0627.png (図: ECSの模試図 8)

まずは "System"

さて、いわゆるHello Worldが書けたところで、せっかくなので少しだけ他のコードも眺めてみましょう。

// (前略)

fn main() {
    App::new()
        .add_plugins(DefaultPlugins) // これはおまじない
        .add_systems(Startup, setup)
        .add_systems(Update, (system, rotate_camera, update_config))
        .run();
}

これは3D Gizmosというサンプルからとったコードの冒頭部ですが、add_systems(Startup, ...)add_systems(Update, ...) という部分が、Processingでいうところのsetup (一回だけ呼ばれる関数) とdraw (更新時に毎回呼ばれる関数) を登録しています。

実際に登録されている関数を一つ見てみましょう。

fn system(mut gizmos: Gizmos, time: Res<Time>) {
    gizmos.cuboid(
        Transform::from_translation(Vec3::Y * 0.5).with_scale(Vec3::splat(1.)),
        Color::BLACK,
    );
    gizmos.rect(
        Vec3::new(time.elapsed_seconds().cos() * 2.5, 1., 0.),
        Quat::from_rotation_y(PI / 2.),
        Vec2::splat(2.),
        Color::GREEN,
    );
    
    // (後略)
}

ここが驚くべきポイントですが、引数には欲しいものを指すを指定すると、自動でその値がバインドされるようになっています。まるでDI (依存性注入)とも呼べるトリックで、クールですね。

ちなみにStartupでは基本的に何を行うかというと、Reactなどでいうところの構成要素の登録を行います。いわばHTMLを書いているようなものです。HTMLでいうJavaScriptにあたる、要素の更新は、Updateに登録した関数群で行います。

この関数群を System と総称していますが、何か作業を行うものは基本的にすべてSystemに書きます 9

ちなみにProcessingでいうところの draw と違うところは、描画自体は基本的に自動で行われるという点にあります 10。だから draw ではなく update であって、updateでは基本的に描画自体ではなくて値などのパラメータや、構成要素だけを変えていきます。ここはReactなどと似ているところです。

すべてはEntity、属性はすべてComponent、データはすべてResource

そしてここがUnity (厳密にはUnity DOTS) と似ている部分ですが、すべての構成要素は Entity と呼ばれるものから派生しています。すべては Entity (Unityでいうところの Game Object) なので、統一的に扱いやすく、さらに、属性はすべて Component から派生しています。Unityでもコンポーネントは同じ概念で存在するので、読み替えがわかりやすいですね。

さらに、いわゆる変数にあたる保持しておきたいデータはすべて Resource になり、画像や3Dデータなどのアセットは Asset です。これもシンプルでわかりやすいです。

そしてこれらをどうやって System から取得するかといえば、先ほどと同じく、引数にを宣言すると勝手に取ってきてくれます 11。不思議ですね。

fn check_zero_health(
    // `Health`と`Transform`Componentを持つEntityにアクセスする
    // `Health`は読み取り専用で取得し、`Transform`は変更可能(mut)で取得する
    // オプション: `Player`Componetが存在するなら取得する
    mut query: Query<(&Health, &mut Transform, Option<&Player>)>,
) {
    // 一致する全てのEntityを取得する
    for (health, mut transform, player) in query.iter_mut() {
        eprintln!("Entity at {} has {} HP.", transform.translation, health.hp);

        // HPが0なら、中央に移動
        if health.hp <= 0.0 {
            transform.translation = Vec3::ZERO;
        }

        if let Some(player) = player {
            // ここではEntityは`Player`
        }
    }
}

// ※コードの引用元 (コメント等の解説含む):
// https://xianliang2032.hatenablog.com/entry/2021/09/28/Rust_Bevy_Query%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6

今回は紹介に留めておきたいので、ECS (Entity Component System) と呼ばれているこのアーキテクチャの紹介は、これくらいにしておきますが、Unityなどと同様に、汎用的な機構を備えていることがわかります。

UnityとBevyの用語対応表.png (図: UnityとBevyの用語対応表)

その他の特徴: クロスプラットフォーム、低レベル描画エンジンの抽象化などなど (割愛)

スクリーンショット 2024-01-22 19.50.31.png 12

他にもBevy Engineの面白い特徴はいろいろあり、例えばwgpuという仕組みでDirectXやMetal、Vulkan、WebGLなどに一つのコードで自動でクロスプラットフォーム対応できたりしますが、こうしたことはUnity等ではある意味で当たり前のことなので今回は割愛します。

最後に: プラグインシステムでどんどん拡張 / 例えばInspectorやGizmo、パーティクル …etc

最後に、プラグインについてちょっと触れておきます。例えば欲が出てくると、Unityのようなエディタがやっぱりほしいな、って思ったりしますよね。こうしたことはだいたいプラグインでできます。

0f18eacdfb26-20231117.png 13

例えば、GPUパーティクルシステムが欲しいと思えば、bevy_hanabiが使えますし、bevy-inspector-eguiなどを使うと、さっきより気軽なInspector (操作盤)を表示させたりできます。

スクリーンショット 2024-01-22 18.28.57.png

挙げればきりがないですが、例えばHTMLでUIを書きたい!とか思うと、belly というプラグインがそれを実現してくれたりします。

スクリーンショット 2024-01-22 18.34.40.png

こういう気軽な拡張ができるのが、やはりどんなエンジンでも楽しい部分でもあり、そしてECSアーキテクチャのおかげで、他の EntityComponent などの構造物と並列して、違和感なく使えるのが嬉しいですね。

追記: Bevyのプラグイン一覧は https://bevyengine.org/assets/#assetshttps://github.com/topics/bevy-plugin にリストアップされています。

まとめ: クリエイティブ・コーディングの過去と未来を融合していくと…

さて、今回はUnityやUnreal Engineなどのゲームエンジンにちょっと疲れた人や、プログラミングの授業でProcessingの次に何をしようかと迷っている人などにとって、その間くらいにある「ちょうどいい存在」として、Bevy Engineを紹介してみました。

ちなみに実際に書いてみると、Rustが出すエラーのわかりにくさにきっとビックリすることとは思います 笑 14

…これは半分冗談で、慣れの部分が大きいですが、Bevy EngineはECSというコンポーネントシステムの恩恵で、Rustの難しい部分である所有権やライフタイムをそこまで意識しなくて済むように工夫されています。ですがきっと今後は、このBevy EngineのようなものがTypeScriptなどの一般的な言語からも利用できるようになって、こういう姿がきっと未来のクリエイティブ・コーディングなんだろうな、と思ったりします。

Bevyはまだ登場して年数が浅いので、例えばp5.jsなどのように気軽に作品をオンラインでシェアするようなプラットフォームとか、アドオンが一覧になったサイトとかはありません (追記: Bevyプラグイン一覧は既に公式にありました!) が、既にExampleはWebGLで対話的に見ることができますし、そういう未来もすぐそこに来ていると思います。

こういうところから少しずつ、クリエイティブ・コーディングの過去と未来のそれぞれの良さが、うまく融合していくといいですね。


追記・補足など

  1. https://bevyengine.org/examples/2D%20Rendering/bloom-2d/ より引用、再構成。

  2. 蛇足ですが、つぶやきProcessing などの文化はワンライナーにも通じるものがあって、興味深いですね。

  3. https://forest.watch.impress.co.jp/docs/serial/blenderwthing/1373040.html より画像を引用。

  4. https://docs.unrealengine.com/5.0/ja/organizing-a-material-graph-in-unreal-engine/ より画像を引用。

  5. 容量について念の為フォローアップしておきますが、容量やスペックというのは相対的なものなので、シンプルさとは何か、の印象については常に変わっていくことと思います。実際、当時のProcessingも300MBくらいあって、容量は小さいとは言い難いものでした。そこでこの記事の文脈では、シンプルさというのは、プラグインシステムや抽象化などを使ってコアをシンプルに保っておくことと仮にしておきます(Unityなども既に充分シンプルであるとは思います)。

  6. https://bevyengine.org/examples/3D%20Rendering/3d-gizmos/ より引用、再構成。

  7. 以下、$ から始まるコマンドはターミナルに打ち込むものを指します。($は省いてそれ以降を入力します。)

  8. https://blog.mozvr.com/introducing-ecsy/ より図を引用。

  9. ちなみにSystemには依存関係を指定したり、セットと呼ばれる組み合わせも作ることができますが、冗長なので今回は割愛します。

  10. 注意としては、ECSなどの高レベルAPIから描画エンジン自体(wgpu)の低レベルAPI、そしてその中間に位置する中レベルAPIがバランスよく提供されているので、描画をカスタムできないという意味ではありません。シェーダーパイプラインや、各種バッファーオブジェクトなどはちゃんと操作できるのでご安心を。

  11. https://xianliang2032.hatenablog.com/entry/2021/09/28/Rust_Bevy_Query%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6 よりコードを引用。

  12. https://bevy-cheatbook.github.io/gpu/intro.html より表を引用。

  13. https://zenn.dev/eloy/articles/ea11899ee3dbe4 より画像を引用。

  14. 記事中では割愛しましたが、実際にコードを書くときはVSCodeにrust-analyzerを入れれば完璧に補完が効くので、もう準備はバッチリです。Have a happy coding!

  15. Bevy公式Discordコミュニティからいただいた情報です。コミュニティの皆さんの支援には本当に感謝します。