【第14回】Bevy 入門:AI が書けない「今の書き方」で最小ゲームを動かす
AI に Bevy を頼むと SpriteBundle など古い API を吐く——Bevy 0.19 で実際に動く最小 ECS 構成と、AI が間違える「今の書き方」早見表。Rust ゲームエンジン入門。
← 前の記事: 【第13回】AI にゲームは作れる。でも「手触り」は作れない — LLM に欠けた game feel を Bevy 製大富豪で埋めた記録この記事は何か
Rust 製ゲームエンジン Bevy の入門です。ただし普通の入門とは狙いが違います。
Bevy に「四角を動かして」と AI に頼むと、動かないコードが返ってくることがよくあります。
Camera2dBundle、SpriteBundle、add_startup_system——どれも昔の Bevy の書き方で、今の
バージョンでは消えています。Bevy は 0.x のうちに破壊的変更を高頻度で重ねていて、LLM の学習データは古い Bevy に偏っているからです。
この記事は、今(0.19)動く最小構成を、実際に動いているゲームのコードから示します。 基礎(ECS)を掴みつつ、「AI が間違える所」を先に知っておくのが狙いです。
- 対象: これから Bevy を始める人/AI に Bevy を書かせて詰まった人
- シリーズ: 動かせたら次は 配信編(ブラウザ公開)と 手触り編(game feel の実装)へ
- 完成形デモ: /games/grimoire-royale/
1. Bevy = ECS(考え方だけ先に)
Bevy は ECS(Entity Component System) です。オブジェクト指向の「オブジェクトにメソッドが 生えている」とは発想が逆で、データと処理を分けます。
- Entity:ただの ID(「何か」の実体)
- Component:Entity に貼るデータ(位置、色、「これはプレイヤー」という印)
- System:毎フレーム走る関数。条件に合う Entity を拾って処理する
- Resource:ゲーム全体で1つのグローバル状態(スコア、設定など)
「プレイヤーというオブジェクト」ではなく「Player という印が付いた Entity を、move システムが 毎フレーム動かす」と考えます。
2. 最小構成:四角を動かす(0.19 で動く)
Cargo.toml:
[dependencies]
bevy = "0.19"
# ブラウザ配信するときの wasm 設定は「配信編」で。まずはネイティブで動かすmain.rs(これで、黄色い四角が右へ流れていきます):
use bevy::prelude::*;
#[derive(Component)]
struct Player;
fn main() {
App::new()
.add_plugins(DefaultPlugins) // ウィンドウ・描画・入力などの標準一式
.add_systems(Startup, setup) // 起動時に1回
.add_systems(Update, move_player) // 毎フレーム
.run();
}
fn setup(mut commands: Commands) {
commands.spawn(Camera2d); // 2D カメラ(※ Camera2dBundle ではない)
commands.spawn((
Sprite::from_color(Color::srgb(0.9, 0.7, 0.3), Vec2::new(80.0, 80.0)),
Transform::from_xyz(0.0, 0.0, 0.0),
Player, // 「これはプレイヤー」という印
));
}
fn move_player(time: Res<Time>, mut query: Query<&mut Transform, With<Player>>) {
for mut transform in &mut query {
// delta_secs() ぶんだけ動かす = フレームレート非依存
transform.translation.x += 150.0 * time.delta_secs();
}
}読み解き:
spawn((A, B, C))で「A・B・C というデータを持つ Entity」を1つ作る。Bundle は要らない (後述)。四角は「Sprite(見た目)+ Transform(位置)+ Player(印)」の3データの集合Query<&mut Transform, With<Player>>= 「Player の印が付いた Entity の Transform を全部よこせ」。 System はこう宣言するだけで、Bevy が該当 Entity を渡してくるRes<Time>= グローバルな時間リソース。delta_secs()で「前フレームからの経過秒」
3. AI が吐きがちな「古い書き方」早見表
ここが本題。同じことを AI に頼むと、次の左側(古い)が返りがちです。0.19 では右側です。
| AI が吐きがち(古い) | 0.19 の今 | 何が起きたか |
|---|---|---|
Camera2dBundle::default() | Camera2d | Bundle 廃止(0.15)→ required components |
SpriteBundle { texture, transform, ..default() } | (Sprite::from_image(h), Transform::…) のタプル | 同上 |
commands.spawn_bundle((…)) | commands.spawn((…)) | spawn_bundle は削除済み |
.add_startup_system(setup) | .add_systems(Startup, setup) | スケジュール API 刷新(0.10〜11) |
.add_system(move_player) | .add_systems(Update, move_player) | 同上 |
time.delta_seconds() | time.delta_secs() | メソッド改名(0.14) |
要点は Bundle 廃止です。以前は SpriteBundle・Camera2dBundle のような「よく使う
Component の詰め合わせ」がありましたが、0.15 で required components(ある Component を
spawn すると必要な相棒が自動で付く)に置き換わりました。AI は古い詰め合わせ前提のコードを
書くので、そのままだとコンパイルが通りません。
4. なぜ AI は古い Bevy を書くのか
Bevy は人気があって・破壊的変更が高頻度という、LLM にとって最悪の組み合わせです。
- 破壊的変更が多い → 「正しい書き方」が半年で変わる
- 人気で記事が多い → 古い書き方の記事が大量に学習データに残る
結果、LLM の分布は古い Bevy に引っ張られ、最新版で動かないコードを自信満々で出す。 だから Bevy こそ「動く最小構成を人間が確認して与える」必要があります。
これは姉妹記事(手触り編)の主張と同じ構造です。あちらは「LLM は game feel(手触り)を 知らない/気にしない」、こちらは「LLM は最新 API を追えない」。API も手触りも、最後は人間が知識として持ち込む——AI とゲームを作るとは、そういう分業です。
つぎは
四角が動かせたら、次の2本へ:
- 配信編:Bevy 製ゲームをブラウザで公開するまで(wasm ビルド・詰まりどころ)
- 手触り編:AI にゲームは作れる。でも「手触り」は作れない — ヒットストップ・シェイク・フラッシュを Bevy でどう足すか
参考
- Bevy 公式サイト / Bevy の Migration Guides(各 0.x の破壊的変更。ここを見れば「古いコード」が分かる)
- Unofficial Bevy Cheat Book
- 本記事の実 API 裏取り: grimoire-royale の
crates/client/src/main.rs/view.rs(Bevy 0.19)