Bevy EngineはEntity Component Systemというアーキテクチャに従って設計されていますが、システムパラメータやクエリはその中核となる概念です。これを使いこなすことが Bevy習得の第一歩なので、ここで簡単に解説しておきます。
システムパラメータとは
Bevyにおけるシステムとは、特定のタイミングで呼び出される関数のことです。このシステムの引数には特定のパターンの引数のみが許されており、これをシステムパラメータといいます。ここを間違えるとかなり難解なコンパイルエラーが出ますし、実行時にエラーが起きたりする場合もあります。手を動かしながらよく理解しておくといいと思います。
Commands
もっともよく使うシステムパラメータにCommands
があります。Commands
のspawn
関数を呼ぶと、Bevyのワールドにエンティティを生成できます。たとえば、カメラを生成するには以下のようなシステムを記述します。
fn setup_camera(mut commands: Commands) {
commands.spawn(
Camera2d::default(),
);
}
また、エンティティを削除するには、commands.entity(entity).despawn()
のようにしてdespown
を呼びます。
Query
ワールドにエンティティを生成したら、次はそのエンティティをクエリで取り出して参照したり変更を加えていくことでゲームを進めていきます。イメージとしては、関係データベースにテーブルごとにクエリを送ってデータを取得している感覚に近いです。エンティティはツリー構造になっていると説明しましたが、あれはParent
コンポーネントやChildren
コンポーネントを使って論理的に親子関係を表現したもので、物理的にはコンポーネントが型ごとに配列として内部に保存されているようです。これにより、大量のエンティティがあっても効率よくデータにアクセスできるそうです。
クエリはQuery<QueryData, QueryFilter>
という形式になっており、どのコンポーネントを取り出すかと、コンポーネントを取り出すエンティティを絞り込む条件を指定します。QueryFilter
は省略することもできます。
たとえば、カメラの位置を移動したければ、位置を表すコンポーネントはTransform
なので、QueryData
には&mut Transform
を指定します。ここでもしQueryFilter
を省略すると、カメラの位置だけが欲しいのに、プレイヤーキャラクターなどを含めたTransform
を含むあらゆるエンティティからTransform
を集めてくることになってしまいます。それでは困るので、QueryFilter
にはWith<Camera2d>
を指定することで、Camera2d
コンポーネントを含むエンティティのみからTransform
を取り出すことができます。
fn update_camera(mut camera_query: Query<&mut Transform, With<Camera2d>>){
...
}
Query
に対しては、iter
やiter_mut
を呼びだすことで、クエリで取り出したそれぞれのコンポーネントに実際にアクセスできます。読み取りだけ行う場合はiter
、読み書きの両方を行う場合はiter_mut
です。このあたり使い分けがちょっと面倒ですね。
for mut transform in camera_query.iter_mut() {
...
}
また、クエリの結果が確実に1件のみである場合は、iter
やiter_mut
ではなく、single
やsingle_mut
を呼びだすことで、for
を使わずにコンポーネントを取り出すことができます。このとき、実行時に1件でなかった場合は実行時エラーになるので注意してください。
let mut transform = camera_query.single_mut();
クエリの結果が0件か1件である場合は、get_single
やget_single_mut
を使うといいでしょう。これらはResult
を返します。
if let Ok(mut transform) = camera_query.single_mut() {
...
}
また、一度に複数のコンポーネントを取り出す場合は、QueryData
にタプルを指定できます。さらに、クエリは複数定義することができます。私が実際のゲームで描いたupdate_camera
システムは、プレイヤーの位置やカメラの位置などを別々に参照しており、次のような定義になっています。
fn update_camera(
player_query: Query<(&Transform, &Actor), With<Player>>,
mut camera_query: Query<
(
&mut Transform,
&mut OrthographicProjection,
&mut CameraScaleFactor,
),
(With<Camera2d>, Without<Player>),
>,
...
) {
...
}
ここでちょっと注意しなければならないのは、player_query
とcamera_query
の両方にTransform
が含まれている点です。このとき、2つ目のクエリであるcamera_query
のほうには、Without<Player>
というフィルタを指定しなければ実行時エラーとなります。これは、特定のエンティティにもしPlayer
とCamera2d
の両方が含まれていた場合、そのエンティティのコンポーネントがplayer_query
とcamera_query
の両方に含まれることになってしまい、ひとつの構造体に対して変更可能な参照はひとつまで、というRustの原則に違反する可能性があるからです。実際にはPlayer
とCamera2d
の両方を含むエンティティは存在しないのですが、コンパイラはそのようなことを検知できないので、Without
で明示的に除外しなければならないわけです。このあたりは私も実際に実行時エラーを何度も踏んでようやく理解したところなので、ややこしいですが実際に開発を進めていくとわかると思います。
そのほかのシステムパラメータ
システムパラメータはCommands
やQuery
のほかにも、次のようなものがあります。
Query<'w, 's, Entity>,
Res<'w, SomeResource>,
ResMut<'w, SomeOtherResource>,
Local<'s, u8>,
Commands<'w, 's>,
EventReader<'w, 's, SomeEvent>,
EventWriter<'w, SomeEvent>
ただし、システムパラメータはマクロを使って拡張できるので、これ以外にも拡張は可能らしいです。