- Authors

- Name
- Youngju Kim
- @fjvbn20031
プロローグ — なぜRustとECSがゲーム開発に合うのか
ゲームエンジンの歴史は2行に圧縮できる。
- 継承の時代(1995〜2015) — Unityの
MonoBehaviour、UnrealのUObject、GodotのNode。すべてのゲームオブジェクトはクラスを継承し、「GameObject is a Component」式の深い階層を作った。C++やC#といったOOP言語が自然だった。 - データ指向の時代(2015〜) — Naughty Dog、Insomniac、EpicのMass(Unreal 5)が「キャッシュミスはフレーム落ちと同義」と認めた。Data-Oriented Design(DOD)が台頭し、Entity-Component-System(ECS)が模範解答として現れた。
ECSは一段落でこうだ。
- Entity = ただのID(64ビット整数)。データも振る舞いも持たない。
- Component = 純粋なデータ(
Position、Velocity、Sprite)。メソッドは無い。 - System = 関数。「ある組み合わせのcomponentを持つすべてのentityに対して」動く。
継承ツリーは無い。Player extends Character extends Pawnのようなものは消える。「プレイヤー」は単にPlayer、Position、Velocity、Healthという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は何が得意で、どこまで来ていて、どこで詰まっているか。
この記事の構成。
- ECSパラダイムを理解する —
MonoBehaviourと何が違うか - Bevyの構造 — App、Schedule、Plugin
- プラグインアーキテクチャ — すべてはプラグインだ
- wgpuでレンダリング — ブラウザ・デスクトップ・モバイルを一度に
- 最初のBevyゲーム30分 — 矢印キーで動く円
- Godot・Unity・Unreal・Macroquadとの比較
- 実際に出荷されたBevyゲーム — Foresight、Tunnet、そしてTiny Gladeの真相
- 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);
}
}
EnemyはMonoBehaviourを継承し、状態(体力・速度)と振る舞い(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_enemiesはsystem。「Enemyマーカーを持つentity」だけを選んで一括処理する。- 100体のPosition・Speed・Targetはメモリ上で連続配置される(archetypeベースのストレージ)。CPUのキャッシュヒット率が高い。
move_enemiesとkill_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] → 別のチャンク。
PositionとVelocityへのクエリはAとB両方のチャンクをたどりながら、各チャンクの中では単純な配列走査で済む。仮想呼び出し無し、キャッシュミスもほとんど無し。これがECSが速い理由。
1.4 ECSの欠点も正直に
ECSは万能薬ではない。
- ステートマシンや複雑な振る舞いツリーはcomponentで表現すると爆発する。
Idle、Walking、Attacking、Hurt、Deadをそれぞれcomponentにすると、systemが全組み合わせを知る必要がある。1つのcomponent内にenumを置く方が現実的なことが多い。 - 親子関係はECSの本来の概念には無い。Bevyは
Parent/Childrencomponentでツリーを擬似的に作るが、本質は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_uiAudioPlugin— kiraベースのオーディオAssetPlugin— 非同期アセットロードTimePlugin、TransformPlugin、LogPlugin、その他。
要らないものは丸ごと外せる。
// ヘッドレスサーバ — ウィンドウもレンダも無く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_tweening、bevy_easings — アニメーション補間。
- bevy_kira_audio — より高機能なオーディオ制御。
- Big Brain — utility AIの決定ツリー。
bevy.orgやbevy_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 run、dynamic_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.orgやitch.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までに残している大きな項目。
- BSNの定着と公式エディタの最小版。
- 安定したABI(動的プラグインロード)。
- ホットリロードのシナリオ確立。
- マルチウィンドウ・高DPI・ドラッグ&ドロップなどデスクトップ仕上げ。
- ビジュアルスクリプティング(必要ならば外部プラグインとして)。
「1.0はいつか」と聞いてはいけない。答えは常に「準備ができたら」。ただし0.15は小さなインディゲームを始めるのに十分安定している。
覚えておく一文: 「Bevy 1.0を待つな。0.15で作れるゲームを今作れ。1.0マイグレーションはそのとき」
エピローグ — 始める人への案内
30分ハンズオン チェックリスト
-
rustupでRustをインストール(1.81+) -
cargo new+Cargo.tomlにbevy = "0.15" -
Camera2dをspawn — 空ウィンドウ確認 -
Mesh2d(Circle::new(40.0))+MeshMaterial2dで円を描く -
Updateスケジュールにmove_playersystem、Res<ButtonInput<KeyCode>>でキー入力 - Enemyマーカーcomponent +
chase_playersystemを追加 -
cargo run --releaseで最適化ビルドを1回 -
cargo build --target wasm32-unknown-unknownの後、wasm-bindgenでウェブビルドを試す -
bevy-inspector-eguiプラグインを付けてランタイムインスペクタを起動 - Avianを追加して2つの円にcolliderを付け、衝突を観察
Bevyのアンチパターン7つ
- system内で
Vec<Entity>を持ち回り毎フレームlookup — queryを上手に書けば不要。 - すべてをcomponentに — ステートマシンや小さなenumはcomponent内に入れる方が良い。
- どこからでも
Commandsでmut — 可能だが反映は次フレーム。即時反映が必要ならWorldを直接受け取るexclusive systemを使う。 Query::single()をpanicで解く —let Ok(...) = q.single() else { return };が安全。- イベント取りこぼし —
EventReaderを毎フレーム持っていないとイベントが消える。2つのsystemが同じイベントを読むなら、add_event+ 一貫したsystem順序。 - render systemを
Updateに置く — render systemは通常Bevy内部の責務。ユーザコードはデータを書くだけでよい。 - 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
- Bevy公式サイト
- Bevy GitHubリポジトリ
- Bevy 0.15リリースノート
- Bevy Book — 公式チュートリアル
- Bevy Cheatbook(コミュニティ)
- BSN(Bevy Scene Notation)RFC討論
- wgpuプロジェクト
- WebGPU仕様(W3C)
- Avian物理エンジン
- bevy_eguiプラグイン
- Lightyearネットワーキング
- Bevy Jamアーカイブ(itch.io)
- Foresight on Steam — Captured by Aliens
- Pounce Light — Tiny Glade開発元、自前エンジン明言
- Macroquad — ミニマルRustゲームライブラリ
- Godot Engine
- Unity Engine
- Unreal Engine 5
- Data-Oriented Design — Mike Acton, CppCon 2014
- The Rust Programming Language Book