Skip to content
Published on

Bevyゲームエンジン — RustとECSで作る現代ゲーム開発ハンズオン(深掘り、2026)

Authors

プロローグ — なぜRustとECSがゲーム開発に合うのか

ゲームエンジンの歴史は2行に圧縮できる。

  1. 継承の時代(1995〜2015) — UnityのMonoBehaviour、UnrealのUObject、GodotのNode。すべてのゲームオブジェクトはクラスを継承し、「GameObject is a Component」式の深い階層を作った。C++やC#といったOOP言語が自然だった。
  2. データ指向の時代(2015〜) — Naughty Dog、Insomniac、EpicのMass(Unreal 5)が「キャッシュミスはフレーム落ちと同義」と認めた。Data-Oriented Design(DOD)が台頭し、Entity-Component-System(ECS)が模範解答として現れた。

ECSは一段落でこうだ。

  • Entity = ただのID(64ビット整数)。データも振る舞いも持たない。
  • Component = 純粋なデータ(PositionVelocitySprite)。メソッドは無い。
  • System = 関数。「ある組み合わせのcomponentを持つすべてのentityに対して」動く。

継承ツリーは無い。Player extends Character extends Pawnのようなものは消える。「プレイヤー」は単にPlayerPositionVelocityHealthというcomponentを一緒に持つentityでしかない。

そこにRustを乗せると、ECSの弱点だった2つが即座に解決する。1つめは並行性。借用検査器(borrow checker)が「2つのsystemが同じcomponentを同時にmutateしない」をコンパイル時に保証する。Bevyのスケジューラはそれを利用してsystemを自動で並列化する。2つめは安定性。ゲームコードはポインタ・null・範囲外アクセスのバグでいつも傷ついてきた。Rustはそのカテゴリ自体を消す。

問題は「現実」だ。2026年5月、Unityはモバイル・インディ・VRの首位、UnrealはAAA・シネマティックの首位、Godotはインディ2D陣営で大きなシェア、Bevyはオープンソース陣営の星だが商用タイトルは数えられる程度。だからこの記事は「Year of Bevy」を叫ばない。代わりに正直に答える — Bevyは何が得意で、どこまで来ていて、どこで詰まっているか。

この記事の構成。

  1. ECSパラダイムを理解する — MonoBehaviourと何が違うか
  2. Bevyの構造 — App、Schedule、Plugin
  3. プラグインアーキテクチャ — すべてはプラグインだ
  4. wgpuでレンダリング — ブラウザ・デスクトップ・モバイルを一度に
  5. 最初のBevyゲーム30分 — 矢印キーで動く円
  6. Godot・Unity・Unreal・Macroquadとの比較
  7. 実際に出荷されたBevyゲーム — Foresight、Tunnet、そしてTiny Gladeの真相
  8. Bevyがまだ準備できていないところ

覚えておく一文: 「エンジンは道具であって信仰ではない。だがECSは道具の前にパラダイムであり、BevyはそれをRust上で最も正直に実装した答えだ」


1章 · ECSパラダイム — UnityやUnrealと何が違うか

1.1 継承モデルの動き方

Unityで敵キャラを作ると、たいていこうなる。

// Unity / MonoBehaviour 風
public class Enemy : MonoBehaviour {
    public int health = 100;
    public float speed = 2.0f;
    private Transform target;

    void Start() { target = GameObject.FindWithTag("Player").transform; }
    void Update() {
        transform.position = Vector3.MoveTowards(
            transform.position, target.position, speed * Time.deltaTime);
        if (health <= 0) Destroy(gameObject);
    }
}

EnemyMonoBehaviourを継承し、状態(体力・速度)と振る舞い(Update)を同じクラスに持つ。100体いればUpdateが100回呼ばれる。仮想関数呼び出し+キャッシュミス+分岐予測ミスが積み上がる。

UnrealのAActor+UCharacterMovementComponentの組み合わせも本質は同じ。AEnemy : public ACharacter : public APawn : public AActorという深い継承チェーン、各コンポーネントは親actorへのポインタを保持する。

1.2 ECSモデルの動き方

同じ敵をBevyで書くと、

// Bevy / ECS 風
use bevy::prelude::*;

#[derive(Component)]
struct Enemy;

#[derive(Component)]
struct Health(i32);

#[derive(Component)]
struct Speed(f32);

#[derive(Component)]
struct Target(Entity);

fn move_enemies(
    time: Res<Time>,
    mut enemies: Query<(&mut Transform, &Speed, &Target), With<Enemy>>,
    targets: Query<&Transform, Without<Enemy>>,
) {
    for (mut tf, speed, target) in &mut enemies {
        if let Ok(target_tf) = targets.get(target.0) {
            let dir = (target_tf.translation - tf.translation).normalize_or_zero();
            tf.translation += dir * speed.0 * time.delta_secs();
        }
    }
}

fn kill_dead_enemies(mut commands: Commands, q: Query<(Entity, &Health), With<Enemy>>) {
    for (e, hp) in &q {
        if hp.0 <= 0 { commands.entity(e).despawn(); }
    }
}

違いは明確だ。

  • Enemyマーカーcomponent(空構造体)。データも振る舞いも無い。
  • Health(i32)Speed(f32)純粋なデータ。メソッド無し。
  • move_enemiessystem。「Enemyマーカーを持つentity」だけを選んで一括処理する。
  • 100体のPosition・Speed・Targetはメモリ上で連続配置される(archetypeベースのストレージ)。CPUのキャッシュヒット率が高い。
  • move_enemieskill_dead_enemiesは異なるcomponentに触れるので、自動で並列実行される。

1.3 Archetypeとメモリ配置

Bevy(そしてFlecs、Unity DOTS、Unreal Mass)はarchetypeベースのECSだ。「どのcomponent集合を持つentityか」でメモリチャンクを作る。

archetype A: [Position, Velocity, Sprite] → 1000体のentityでPositionが連続、Velocityが連続、Spriteが連続。 archetype B: [Position, Velocity, Sprite, Health, Enemy] → 別のチャンク。

PositionVelocityへのクエリはAとB両方のチャンクをたどりながら、各チャンクの中では単純な配列走査で済む。仮想呼び出し無し、キャッシュミスもほとんど無し。これがECSが速い理由。

1.4 ECSの欠点も正直に

ECSは万能薬ではない。

  • ステートマシンや複雑な振る舞いツリーはcomponentで表現すると爆発する。IdleWalkingAttackingHurtDeadをそれぞれcomponentにすると、systemが全組み合わせを知る必要がある。1つのcomponent内にenumを置く方が現実的なことが多い。
  • 親子関係はECSの本来の概念には無い。BevyはParent/Children componentでツリーを擬似的に作るが、本質はECSとずれる。
  • 学習曲線が急だ。「なぜメソッドが呼べないのか」「このクエリは借用をどう分割するのか」のような問いに時間が取られる。

覚えておく一文: 「OOPは動詞を名詞の中に閉じ込める。ECSは名詞(データ)と動詞(system)を最後まで分離する」


2章 · Bevyの構造 — App、Schedule、System

2.1 App

BevyプログラムのエントリポイントはApp。main関数は通常こんな形。

use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, spawn_camera)
        .add_systems(Update, (move_player, count_fps).chain())
        .run();
}
  • App::new() — 空のECSワールドとスケジューラを作る。
  • .add_plugins(DefaultPlugins) — ウィンドウ、入力、レンダ、時間、アセットロードなど基本機能を丸ごとまとめたプラグイン束を追加する。
  • .add_systems(Startup, ...) — ゲーム開始時に一度だけ走るsystem。
  • .add_systems(Update, ...) — 毎フレーム走るsystem。
  • .run() — メインループを回す。

コード全体でclass GameManager : MonoBehaviour { void Awake() }のようなマネージャクラスが要らないという点が肝。App自体がマネージャで、ゲームロジックは自由関数だ。

2.2 Schedule — いつ実行されるか

Bevy 0.10で「ステージ」が消えてスケジュールが入った。0.15時点の主要スケジュールは次のとおり。

  • Startup — アプリ開始時に一度。
  • PreUpdate — 毎フレームの開始、入力収集後。
  • Update — 毎フレームのメインロジック。
  • PostUpdate — 毎フレーム、transform伝搬・物理後処理など。
  • FixedUpdate — 固定タイムステップ(既定64Hz)。物理・ネットワークロックステップ。
  • Last — フレームの最後。

system間の順序は.before().after().chain()SystemSetで制御する。

app.add_systems(
    Update,
    (
        input_system,
        movement_system.after(input_system),
        collision_system.after(movement_system),
    ),
);

2.3 Component・Resource・Event

ECSワールドにデータを入れる3通り。

  • Component — entityに付くデータ。#[derive(Component)]
  • Resource — ワールドに1つだけ存在するグローバル。スコア・設定・アセットハンドル。#[derive(Resource)]
  • Event — 1フレーム単位のメッセージ。system間通信。#[derive(Event)]
#[derive(Resource)]
struct Score(u32);

#[derive(Event)]
struct EnemyKilled { entity: Entity, by: Entity }

fn award_score(mut score: ResMut<Score>, mut events: EventReader<EnemyKilled>) {
    for _ in events.read() {
        score.0 += 100;
    }
}

Resは読み取り専用、ResMutは書き込み可能。2つのsystemが同じResMutを取ると自動で直列化され、両方がResなら並列。

2.4 Query — systemがデータを取りに行く方法

fn move_player(
    time: Res<Time>,
    mut q: Query<(&mut Transform, &Speed), With<Player>>,
) {
    for (mut tf, speed) in &mut q {
        tf.translation.x += speed.0 * time.delta_secs();
    }
}

Queryの型シグネチャがそのままsystemの「データ要求書」になる。With<Player>はフィルタ(componentを引っ張ってこないが、entityが持っていることを要求する)。2つのsystemが衝突しないクエリを持てば、Bevyは自動的に並列実行する。

覚えておく一文: 「Bevyコードの読み方 = systemのシグネチャだけで、何を読み何を書くかすべて見える」


3章 · プラグインアーキテクチャ — すべてはプラグインだ

Bevyで最大の美的決定は「エンジン自体がプラグインの集合体である」ことだ。

DefaultPluginsをほどくと約20個のプラグインが入っている。

  • WindowPlugin — ウィンドウ生成
  • InputPlugin — キーボード・マウス・ゲームパッド
  • RenderPlugin — wgpuベースのレンダ
  • SpritePlugin — 2Dスプライト
  • PbrPlugin — 3D物理ベースレンダリング
  • UiPlugin — bevy_ui
  • AudioPlugin — kiraベースのオーディオ
  • AssetPlugin — 非同期アセットロード
  • TimePluginTransformPluginLogPlugin、その他。

要らないものは丸ごと外せる。

// ヘッドレスサーバ — ウィンドウもレンダも無くECSだけ
App::new()
    .add_plugins(MinimalPlugins)
    .add_plugins(LogPlugin::default())
    .add_systems(Update, game_logic)
    .run();

3.1 自分のプラグインを書く

自分のゲームコードもプラグインに束ねるとモジュール化がきれいになる。

use bevy::prelude::*;

pub struct PlayerPlugin;

impl Plugin for PlayerPlugin {
    fn build(&self, app: &mut App) {
        app
            .add_event::<PlayerHurt>()
            .add_systems(Startup, spawn_player)
            .add_systems(Update, (
                player_input,
                player_movement.after(player_input),
                player_animation,
            ));
    }
}

#[derive(Event)]
pub struct PlayerHurt { pub amount: i32 }

#[derive(Component)]
pub struct Player;

fn spawn_player(mut commands: Commands) {
    commands.spawn((
        Player,
        Transform::default(),
        // sprite, collider など
    ));
}

fn player_input(/* ... */) {}
fn player_movement(/* ... */) {}
fn player_animation(/* ... */) {}

mainはこれだけ短くなる。

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins((PlayerPlugin, EnemyPlugin, AudioPlugin, UiPlugin))
        .run();
}

3.2 エコシステムのプラグイン(2026年5月時点)

  • Avian — 2D/3D物理。旧bevy_xpbdの後継。position-based dynamics、0.15対応。
  • bevy_egui — egui を Bevy に組み込むデバッグUIプラグイン。インスペクタやツール作成の標準。
  • bevy-inspector-egui — ワールドのcomponent・resourceをランタイムで即編集。
  • Lightyear — クライアント・サーバネットワーキング。replication、rollback、prediction。
  • bevy_rapier — Rapier物理エンジンのバインディング(Avianの代替候補)。
  • bevy_tweeningbevy_easings — アニメーション補間。
  • bevy_kira_audio — より高機能なオーディオ制御。
  • Big Brain — utility AIの決定ツリー。

bevy.orgbevy_assetsのGitHubリポジトリにキュレーション済みリストがある。

覚えておく一文: 「プラグインは単なるモジュールではなくBevyの思想 — エンジンとゲームコードの境界が無い」


4章 · wgpuレンダリング — ブラウザ・デスクトップ・モバイルを一度に

Bevyのレンダラはwgpuの上に乗っている。wgpuはRust陣営のWebGPU実装で、1つのコードでVulkan・Metal・DirectX 12・WebGPU・OpenGL ESをすべてターゲットにする。

4.1 良いところ

  • クロスプラットフォームが本物だ。 macOSではMetal、LinuxではVulkan、WindowsではDX12、モバイルではVulkan/Metal、ウェブではWebGPU。シェーダはWGSLで1回書けばどこでも動く。
  • WebGPUサポートが一級市民。 cargo build --target wasm32-unknown-unknownの後、wasm-bindgenで束ねるとブラウザで動く。0.15時点でitch.ioのような場所に出すのに十分。
  • モダンなシェーダ。 WGSLはGLSLより安全(型・範囲チェック)、HLSLよりシンプル。

4.2 弱いところ

  • 対応ブラウザがまだ狭い。 2026年5月時点でChromeとEdgeは安定、SafariはSafari 18から対応、Firefoxは一部プラットフォームのnightlyのみ。ウェブに出すならフォールバックを考える必要がある。
  • シェーダコンパイル時間とドライバ互換性はwgpu側の責務で、Vulkan SDKがネイティブに与えるツール群ほど滑らかではない。
  • PBRパイプラインはモダンだがUE5ほど豊かではない。 Lumen・Naniteのような最先端機能は無い(必要ならBevyを選ぶ理由はほぼ無い)。

4.3 カスタムシェーダ

WGSLでシェーダを書き、Material traitを実装してBevyのmaterialとして登録する。

use bevy::{
    prelude::*,
    render::render_resource::{AsBindGroup, ShaderRef},
    sprite::Material2d,
};

#[derive(Asset, TypePath, AsBindGroup, Clone, Debug)]
struct WaveMaterial {
    #[uniform(0)] time: f32,
    #[uniform(0)] color: LinearRgba,
}

impl Material2d for WaveMaterial {
    fn fragment_shader() -> ShaderRef { "shaders/wave.wgsl".into() }
}

そしてシェーダファイル。

struct Uniforms {
    time: f32,
    color: vec4<f32>,
};

@group(2) @binding(0) var<uniform> u: Uniforms;

@fragment
fn fragment(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
    let wave = sin(uv.x * 10.0 + u.time) * 0.5 + 0.5;
    return vec4<f32>(u.color.rgb * wave, 1.0);
}

覚えておく一文: 「wgpuのおかげでBevyは『1コードでどこでも動くレンダラ』をほぼ無償で得た — ただし『どこでも』が『最適』ではない」


5章 · 最初のBevyゲーム30分 — 矢印キーで動く円

ここからコードだ。「画面に円があり、矢印キーで動く」。30分で終わらせる。

5.1 プロジェクト準備

# Rustが入っていなければrustupで
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# 新規プロジェクト
cargo new moving_circle && cd moving_circle

Cargo.tomlを開く。

[package]
name = "moving_circle"
version = "0.1.0"
edition = "2021"

[dependencies]
bevy = "0.15"

# 高速コンパイル用のdevプロファイル — 推奨
[profile.dev]
opt-level = 1

[profile.dev.package."*"]
opt-level = 3

5.2 最初のコード — 空ウィンドウ

src/main.rs

use bevy::prelude::*;

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

fn setup(mut commands: Commands) {
    commands.spawn(Camera2d);
}
cargo run

黒い空ウィンドウが出る。初回ビルドは2〜3分(依存のコンパイル)。それ以降のインクリメンタルビルドは数秒。

5.3 円を描く

use bevy::prelude::*;

#[derive(Component)]
struct Player;

#[derive(Component)]
struct Speed(f32);

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

fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<ColorMaterial>>,
) {
    commands.spawn(Camera2d);

    commands.spawn((
        Player,
        Speed(300.0),
        Mesh2d(meshes.add(Circle::new(40.0))),
        MeshMaterial2d(materials.add(Color::srgb(0.2, 0.7, 1.0))),
        Transform::from_xyz(0.0, 0.0, 0.0),
    ));
}

再度cargo run。青い円が真ん中に表示される。

コードの肝。

  • commands.spawn((..., ..., ...)) — タプルでcomponentの束を一度に付ける。
  • Mesh2d + MeshMaterial2d — Bevy 0.15の新しい2Dマテリアル API。メッシュとマテリアルがそれぞれ独立したcomponent。
  • Transform — 位置・回転・スケール。

5.4 入力で動かす

Updateスケジュールにsystemを追加する。

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

fn move_player(
    time: Res<Time>,
    keyboard: Res<ButtonInput<KeyCode>>,
    mut q: Query<(&mut Transform, &Speed), With<Player>>,
) {
    let dt = time.delta_secs();
    for (mut tf, speed) in &mut q {
        let mut dir = Vec2::ZERO;
        if keyboard.pressed(KeyCode::ArrowLeft)  { dir.x -= 1.0; }
        if keyboard.pressed(KeyCode::ArrowRight) { dir.x += 1.0; }
        if keyboard.pressed(KeyCode::ArrowUp)    { dir.y += 1.0; }
        if keyboard.pressed(KeyCode::ArrowDown)  { dir.y -= 1.0; }
        if dir != Vec2::ZERO {
            tf.translation += (dir.normalize() * speed.0 * dt).extend(0.0);
        }
    }
}

cargo run。矢印キーで円が動く。終わり。

肝になるパターン。

  • Res<Time> — Bevyが自動提供する時間リソース。
  • Res<ButtonInput<KeyCode>> — キーボード状態リソース。
  • QueryにTransformとSpeed、With<Player>フィルタを付けてPlayerだけ取る。
  • time.delta_secs() — フレームレート独立な移動。

5.5 敵を1体追加

#[derive(Component)]
struct Enemy;

fn setup(/* ... */) {
    // 上と同じ

    commands.spawn((
        Enemy,
        Speed(150.0),
        Mesh2d(meshes.add(Circle::new(30.0))),
        MeshMaterial2d(materials.add(Color::srgb(1.0, 0.3, 0.3))),
        Transform::from_xyz(200.0, 200.0, 0.0),
    ));
}

fn chase_player(
    time: Res<Time>,
    player_q: Query<&Transform, With<Player>>,
    mut enemy_q: Query<(&mut Transform, &Speed), (With<Enemy>, Without<Player>)>,
) {
    let Ok(player_tf) = player_q.single() else { return };
    let dt = time.delta_secs();
    for (mut tf, speed) in &mut enemy_q {
        let dir = (player_tf.translation - tf.translation).normalize_or_zero();
        tf.translation += dir * speed.0 * dt;
    }
}

// main に .add_systems(Update, (move_player, chase_player)) を追加

赤い敵がプレイヤーを追ってくる。ECSの本領発揮 — 新しいクラス定義を増やさず、新しいcomponentとsystemを足すだけで新しい振る舞いが生まれる。

覚えておく一文: 「30分以内に『キーで動く円』まで届くことが、エンジンの第一印象テストだ。Bevyはそのテストを通る」


6章 · Godot・Unity・Unreal・Macroquadとの正直な比較

エンジンは宗教ではない。表を見よう。

6.1 一行サマリ

  • Unity (C#) — 業界標準。2Dインディ・モバイル・VR・軽量3Dで圧倒的1位。エディタとAsset Storeが武器。
  • Unreal Engine (C++/Blueprint) — AAA・シネマティック・高品質3Dの1位。Nanite、Lumen、MetaHuman、加えてエンタープライズソリューション。
  • Godot (GDScript/C#) — インディ2D・軽量3D。オープンソース、MIT、約50MBのエディタ。4.x以降は3Dも大きく改善。
  • Bevy (Rust) — ECSファースト、オープンソース、MIT/Apache。エディタ無し(BSN計画中)。ライブラリに近い。
  • Macroquad (Rust) — 「とりあえず2Dゲームを素早く始めたい」に答えるミニマルなRust製ゲームライブラリ。ECSは無い。

6.2 項目別の比較

言語とパラダイム

  • Unity: C# + OOP/コンポーネント
  • Unreal: C++ + Blueprint(ビジュアルスクリプティング)
  • Godot: GDScript(Python風) + C#
  • Bevy: Rust + ECS
  • Macroquad: Rust + イミディエートモードの手続き型

エディタ

  • Unity・Unreal・Godot: 豊富なGUIエディタが組み込み。
  • Bevy: 公式エディタは無い。0.15時点でBSN(Bevy Scene Notation)シーンフォーマットのRFCが進行中で、エディタはその上に作られる予定。それまではbevy-inspector-eguiによるランタイムインスペクタが代替。
  • Macroquad: エディタ無し。

コンパイル速度

  • Unity・Unreal: C++/C#のコンパイル+ホットリロードのオプションあり。
  • Godot: GDScriptは即実行、C#はコンパイル。
  • Bevy: Rustのコンパイルが遅い。devビルド最適化(opt-level=1)とcargo watch -x rundynamic_linkingフィーチャ(0.x.x限定)を使うと数秒〜十数秒。

ランタイム性能

  • Unreal: AAAタイトルで実証済みの最高性能、ただしビルドは重い。
  • Unity DOTS: ECSを導入したが学習曲線とエコシステム分断の代償あり。
  • Bevy: ECS + Rust が小さなコードで良好なマルチスレッド性能を出す。
  • Godot: 十分速いがAAAタイトル領域ではない。

モバイル/コンソールビルド

  • Unity・Unreal: 一級対応、コンソールSDKライセンス保有。
  • Godot: iOS・Android対応、コンソールはサードパーティ移植。
  • Bevy: デスクトップ・ウェブは滑らか。iOSは可能だが手間が要る。Androidはnightlyが多く実験的。コンソールは事実上不可(ライセンス)。

オープンソース方針

  • Unity・Unreal: 売上一定以上でライセンス/ロイヤリティ。
  • Godot・Bevy・Macroquad: MIT/Apache、完全自由。

6.3 何を選ぶか

  • モバイルインディ2D・軽量3D → Unity
  • AAAグラフィックス・シネマティック → Unreal
  • インディ2D、GDScript親和性が高いチーム → Godot
  • Rustが好き、ECSを学びたい、ツール・シミュレーション → Bevy
  • 「ゲームジャム5時間で2Dゲーム」 → Macroquad

覚えておく一文: 「Bevyは『Unityの代替』ではない。別の道具だ — Rust・ECS・オープンソース・モジュール性を同時に欲しいときだけ正解」


7章 · 実際に出荷されたBevyゲーム — 何が本当か

ここは正直であるべき領域だ。ネットに出回る「Bevyで作られたゲーム」リストには嘘が混ざることがある。

7.1 本当にBevyで作られた商用ゲーム

  • Foresight — インディRTS。開発元のCaptured by AliensがBevy採用を明示公開。
  • Roll It Up — インディカジュアル。
  • Tunnet — ネットワークシミュレーションゲーム。Bevyの0.x時代から開発・出荷。
  • Jarl — Steamで出ているグリッドベースRTS、Bevyベース。
  • 多数の itch.io ゲームジャム作品 — Bevy Jam(年1〜2回)の成果物。bevyengine.orgitch.io/jam/bevy-jamで検索すると数百件。

7.2 よく誤解されるケース — Tiny Glade

Pounce LightのTiny Gladeは2024年9月のリリース直後にSteam人気1位まで上がったインディ名作シミュレータだ。このゲームはBevyを使っていない — 自前のRustエンジンを作っており、Rustエコシステムのライブラリ(wgpuなど)は一部活用しているが、「Bevyのゲーム」と括ると間違い。開発元はいくつものインタビューやGDCトークで自前エンジンであることを明言している。

「Tiny GladeはBevyで作られた」と言う記事は無視してよい。Bevyコミュニティはその成功で「Rustゲームの可能性」を示せたことを喜んだが、エンジン自体は別だ。

7.3 非ゲーム用途

BevyはECS + wgpuの組み合わせのおかげで、ゲーム以外でも使われる。

  • Foresight Mining Software — 鉱山シミュレーションツール。
  • Komodo — 一部のインタラクティブ可視化に Bevy。
  • 多数のデータ可視化・ロボティクスシミュレーション・教育デモ。

エンジンをライブラリのようにインポートできるのはBevyの大きな特徴で、UnityやUnrealでは事実上不可能に近い。

覚えておく一文: 「Bevyは『Tiny Gladeのようなゲームを作っている』と嘘を言わない。『Foresight・Jarl・Tunnetのようなゲームを作り、データ可視化やシミュレーションにも使われている』と正直に語る」


8章 · Bevyがまだ準備できていないところ — 正直な限界

「Year of Bevy」は2024年にも、2025年にも、2026年にも毎年叫ばれた。その間に本当に成熟した部分とまだ荒い部分を分けよう。

8.1 成熟したもの

  • 2Dレンダリングと基本UI — 0.15からMesh2d/MeshMaterial2dで一貫性が整理。
  • 物理 — Avianが安定。2D/3D、コンストレイント、CCDまで。
  • オーディオbevy_audio(基本) + bevy_kira_audio(高機能)。
  • WebGPUビルド — デモ用途なら十分滑らか。
  • プラグインエコシステムbevy_assetsリポジトリに数百件。よく使われるものは毎リリースに追従。

8.2 まだ荒いもの

  • 公式エディタが無い。 0.15時点でBSN(Bevy Scene Notation)RFC進行中。これはゲームオブジェクト階層・アセット・マテリアルをデータで表現する新しいシーンフォーマットの提案で、採用されればその上にエディタができる。採用時期は2026〜2027年と見られる。
  • ホットリロード。 アセットはウォッチモードである程度、コードはdynamic_linkingフィーチャで部分的。JIT水準のホットリロードはまだ。
  • バージョン間の破壊的変更。 0.10・0.11・0.12・0.13・0.14・0.15のマイグレーションは毎回小さくない。0.15マイグレーションガイドは必読。1.0が出るまでこの状況は続く。
  • コンソールビルド。 Switch・PS5・Xboxは事実上不可。ライセンスと非公開SDKの壁。
  • モバイルビルド。 iOSは動くがツールが粗く、Androidはnightlyが多く実験的。UnityやUnrealとは比較にならない。
  • GUIデザイナーが無い。 bevy_uiがコードベースで、デザイナーがマウスでUIを組むワークフローが無い(BSNで解決予定)。
  • 学習曲線が急。 Rust + ECS + BevyのAPI。学習後の最初のゲームに到達する時間は他エンジンより長い。

8.3 0.15から1.0への道

Bevy公式ロードマップ(2026年5月更新)が1.0までに残している大きな項目。

  1. BSNの定着と公式エディタの最小版。
  2. 安定したABI(動的プラグインロード)。
  3. ホットリロードのシナリオ確立。
  4. マルチウィンドウ・高DPI・ドラッグ&ドロップなどデスクトップ仕上げ。
  5. ビジュアルスクリプティング(必要ならば外部プラグインとして)。

「1.0はいつか」と聞いてはいけない。答えは常に「準備ができたら」。ただし0.15は小さなインディゲームを始めるのに十分安定している。

覚えておく一文: 「Bevy 1.0を待つな。0.15で作れるゲームを今作れ。1.0マイグレーションはそのとき」


エピローグ — 始める人への案内

30分ハンズオン チェックリスト

  • rustupでRustをインストール(1.81+)
  • cargo new + Cargo.tomlbevy = "0.15"
  • Camera2dをspawn — 空ウィンドウ確認
  • Mesh2d(Circle::new(40.0)) + MeshMaterial2dで円を描く
  • Updateスケジュールにmove_player system、Res<ButtonInput<KeyCode>>でキー入力
  • Enemyマーカーcomponent +chase_player systemを追加
  • cargo run --releaseで最適化ビルドを1回
  • cargo build --target wasm32-unknown-unknownの後、wasm-bindgenでウェブビルドを試す
  • bevy-inspector-eguiプラグインを付けてランタイムインスペクタを起動
  • Avianを追加して2つの円にcolliderを付け、衝突を観察

Bevyのアンチパターン7つ

  1. system内でVec<Entity>を持ち回り毎フレームlookup — queryを上手に書けば不要。
  2. すべてをcomponentに — ステートマシンや小さなenumはcomponent内に入れる方が良い。
  3. どこからでもCommandsでmut — 可能だが反映は次フレーム。即時反映が必要ならWorldを直接受け取るexclusive systemを使う。
  4. Query::single()をpanicで解くlet Ok(...) = q.single() else { return };が安全。
  5. イベント取りこぼしEventReaderを毎フレーム持っていないとイベントが消える。2つのsystemが同じイベントを読むなら、add_event + 一貫したsystem順序。
  6. render systemをUpdateに置く — render systemは通常Bevy内部の責務。ユーザコードはデータを書くだけでよい。
  7. 0.x マイグレーションを無視 — Bevy 0.x → 0.(x+1)は毎回4〜12時間の作業。1.0前の大型プロジェクトはマイグレーションbufferを日程に入れること。

次回予告

  • Avianで2Dプラットフォーマー — ジャンプ・コヨーテタイム・重力調整・連続衝突判定。
  • Lightyearでマルチプレイヤーシューター — クライアントサーバモデルにおけるreplication・prediction・rollback。
  • Bevy + wasm + itch.io 出荷 — ウェブビルド、キーキャプチャ、モバイルタッチマッピング。
  • BSNシーンフォーマット RFC 精読 — Bevy 1.0が目指す形と、それがゲームコードをどう変えるか。

覚えておく一文: 「Rust + ECSはゲームエンジンパラダイムの答えのなかで最も正直な答え。Bevyはその答えを自分の手で直接書ける最速の道だ」


参考 / References