【Rustのまほう2】#6 プレイヤーキャラクターのエンティティ生成

前回はプログラミング要素が少なめだったので、今回はBevyのECSアーキテクチャをちょっとだけ深掘りしたいと思います。

# システム

Bevyにおける**システム**は、指定した特定のタイミングで呼ばれる関数です。誤解を恐れずにオブジェクト指向プログラミングに例えると、イベントに登録されたイベントハンドラ、イベントリスナーみたいなものです。

たとえば、タイトル画面をクリックしてゲームの本編が始まったときには、プレイヤーキャラクターのスプライト(画像)を表示したり、背景の画像を並べたりする必要があります。まずはそのときに呼ばれるシステム(つまり関数)`setup_level`を次のように定義します。

```rs
fn setup_level(
    mut commands: Commands,
    ...
) {
 ...
}
```

それから、ゲーム全体を表す`app.add_systems`を呼んで、`setup_level`を登録します。ここでは、ゲーム本編`GameState::InGame`が始まったとき(`OnEnter`)に呼ばれる、ということを指定しています。

```rs
app.add_systems(OnEnter(GameState::InGame), setup_level);
```

`OnEnter`以外にも、さまざまな**スケジュール**が定義されています。Cheat Bookが参考になります。

https://bevy-cheatbook.github.io/programming/schedules.html



# コマンドとspawn

上の関数は`commands`という引数をとっていますが、これはゲーム内にエンティティを生成するときに使うもっとも基本的な**システムパラメータ**です。この`Commands`にはBevyのワールドに変更を加えるためのメソッドがいくつも定義されていて、たとえば`commands.spawn`を呼ぶと、ゲーム内にエンティティを生成できます。`commands.spawn`は引数にタプルをとるので、エンティティに含めたいコンポーネントを最大16個まで指定できます。

以下は私が作っているゲームでプレイヤーキャラクターのエンティティを生成している実際のコードです。

```rust
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個では足りなくなったので、タプルをネストしているのがわかると思います。

それでアニメーションとか、マウスカーソルの位置に応じた回転とかいろいろ頑張って、次のような感じになりました(わかりやすいよう極端に拡大表示してます)。

![magiaforge 0.1 2024-12-04 01-26-15.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64695/80d02bfa-2e69-2c28-2140-51ab3135e4f9.gif)