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>
ただし、システムパラメータはマクロを使って拡張できるので、これ以外にも拡張は可能らしいです。