前回はプログラミング要素が少なめだったので、今回はBevyのECSアーキテクチャをちょっとだけ深掘りしたいと思います。
システム
Bevyにおけるシステムは、指定した特定のタイミングで呼ばれる関数です。誤解を恐れずにオブジェクト指向プログラミングに例えると、イベントに登録されたイベントハンドラ、イベントリスナーみたいなものです。
たとえば、タイトル画面をクリックしてゲームの本編が始まったときには、プレイヤーキャラクターのスプライト(画像)を表示したり、背景の画像を並べたりする必要があります。まずはそのときに呼ばれるシステム(つまり関数)setup_level
を次のように定義します。
fn setup_level(
mut commands: Commands,
...
) {
...
}
それから、ゲーム全体を表すapp.add_systems
を呼んで、setup_level
を登録します。ここでは、ゲーム本編GameState::InGame
が始まったとき(OnEnter
)に呼ばれる、ということを指定しています。
app.add_systems(OnEnter(GameState::InGame), setup_level);
OnEnter
以外にも、さまざまなスケジュールが定義されています。Cheat Bookが参考になります。
コマンドとspawn
上の関数はcommands
という引数をとっていますが、これはゲーム内にエンティティを生成するときに使うもっとも基本的なシステムパラメータです。このCommands
にはBevyのワールドに変更を加えるためのメソッドがいくつも定義されていて、たとえばcommands.spawn
を呼ぶと、ゲーム内にエンティティを生成できます。commands.spawn
は引数にタプルをとるので、エンティティに含めたいコンポーネントを最大16個まで指定できます。
以下は私が作っているゲームでプレイヤーキャラクターのエンティティを生成している実際のコードです。
commands.spawn((
Name::new("witch"),
StateScoped(GameState::InGame),
Actor {
uuid,
spell_delay: 0,
mana: 1000,
max_mana: 1000,
life,
max_life,
pointer: Vec2::from_angle(angle),
intensity,
move_direction: Vec2::ZERO,
move_force: PLAYER_MOVE_FORCE,
fire_state: ActorFireState::Idle,
group: WITCH_GROUP,
filter: ENTITY_GROUP | WALL_GROUP | WITCH_GROUP | ENEMY_GROUP,
current_wand: 0,
effects: default(),
wands,
},
ActorState::default(),
Witch,
controller,
EntityDepth,
Breakable {
life: 0,
amplitude: 0.0,
},
Transform::from_translation(position.extend(1.0)),
GlobalTransform::default(),
InheritedVisibility::default(),
(
RigidBody::Dynamic,
Velocity::default(),
Collider::ball(WITCH_COLLIDER_RADIUS),
GravityScale(0.0),
LockedAxes::ROTATION_LOCKED,
Damping {
linear_damping: 6.0,
angular_damping: 1.0,
},
ExternalForce::default(),
ExternalImpulse::default(),
CollisionGroups::new(
WITCH_GROUP,
ENTITY_GROUP
| WALL_GROUP
| WITCH_GROUP
| WITCH_BULLET_GROUP
| ENEMY_GROUP
| ENEMY_BULLET_GROUP
| MAGIC_CIRCLE_GROUP,
),
),
));
エンティティの名前を指定するコンポーネントName::new("witch")
や、座標を指定するコンポーネントTransform
、物理エンジンでの物体を表すコンポーネントRigidBody::Dynamic
など、さまざまなコンポーネントが詰め込まれているのがわかると思います。
実はこれでもまだ一部で、このエンティティの子として次のような別のエンティティを生成しています。
- プレイヤーキャラクターの画像。ダメージを受けた時に震える演出をするため、画像は子のコンポーネントにしています
- プレイヤーキャラクターの持つ杖の画像。杖はマウスカーソルに向かって回転して表示されるので、これも子のコンポーネントにして回転できるようにしています
- プレイヤーキャラクターの名前。オンライン時にキャラクターを見分けられるように、名前を表示できるようにしています
- 体力バー。キャラクターの残り体力を表示するバーも子のエンティティにしています
ちなみにコンポーネントを16個以上指定したい場合は、タプルをネストすればいくらでも追加できたりしますし、いったんエンティティを生成してから、あとからinsert
でさらにコンポーネントを動的に追加することもできます。上のコードでは16個では足りなくなったので、タプルをネストしているのがわかると思います。
それでアニメーションとか、マウスカーソルの位置に応じた回転とかいろいろ頑張って、次のような感じになりました(わかりやすいよう極端に拡大表示してます)。