【Rustのまほう2】#10 システムパラメータとクエリ

Bevy EngineはEntity Component Systemというアーキテクチャに従って設計されていますが、システムパラメータやクエリはその中核となる概念です。これを使いこなすことが Bevy習得の第一歩なので、ここで簡単に解説しておきます。


# システムパラメータとは

Bevyにおける**システム**とは、特定のタイミングで呼び出される関数のことです。このシステムの引数には特定のパターンの引数のみが許されており、これを**システムパラメータ**といいます。ここを間違えるとかなり難解なコンパイルエラーが出ますし、実行時にエラーが起きたりする場合もあります。手を動かしながらよく理解しておくといいと思います。

## `Commands`

もっともよく使うシステムパラメータに`Commands`があります。`Commands`の`spawn`関数を呼ぶと、Bevyのワールドにエンティティを生成できます。たとえば、カメラを生成するには以下のようなシステムを記述します。

```rust
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`を取り出すことができます。

```rust
fn update_camera(mut camera_query: Query<&mut Transform, With<Camera2d>>){
    ...
}
```

`Query`に対しては、`iter`や`iter_mut`を呼びだすことで、クエリで取り出したそれぞれのコンポーネントに実際にアクセスできます。読み取りだけ行う場合は`iter`、読み書きの両方を行う場合は`iter_mut`です。このあたり使い分けがちょっと面倒ですね。

```rust
for mut transform in camera_query.iter_mut() {
   ...
}
```

また、クエリの結果が確実に1件のみである場合は、`iter`や`iter_mut`ではなく、`single`や`single_mut`を呼びだすことで、`for`を使わずにコンポーネントを取り出すことができます。このとき、実行時に1件でなかった場合は実行時エラーになるので注意してください。

```rust
let mut transform = camera_query.single_mut();
```

クエリの結果が0件か1件である場合は、`get_single`や`get_single_mut`を使うといいでしょう。これらは`Result`を返します。

```rust
if let Ok(mut transform) = camera_query.single_mut() {
    ...
}
```

また、一度に複数のコンポーネントを取り出す場合は、`QueryData`にタプルを指定できます。さらに、クエリは複数定義することができます。私が実際のゲームで描いた`update_camera`システムは、プレイヤーの位置やカメラの位置などを別々に参照しており、次のような定義になっています。

```rust
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`のほかにも、次のようなものがあります。

```rust
Query<'w, 's, Entity>,
Res<'w, SomeResource>,
ResMut<'w, SomeOtherResource>,
Local<'s, u8>,
Commands<'w, 's>,
EventReader<'w, 's, SomeEvent>,
EventWriter<'w, SomeEvent>
```

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



# 参考文献

- https://docs.rs/bevy/latest/bevy/ecs/system/trait.SystemParam.html