UnityECSで状態管理

# UnityECSで状態管理
UnityECSで状態管理をしようとすると以下のようにコンポーネントにEnumを取り入れてif文などで処理を分岐する方法が一般的だと思います。
```csharp
public struct PlyaerStatus :IComponentData
{
    public PlayerState PlayerState;
}

public enum PlayerState
{
    Normal = 0,
    Attack = 1,
    Dodge = 2,
    Damage = 3,
    Die = 4,
}
```
```csharp
[BurstCompile]
partial struct TestStateSystem : ISystem
{
    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        // 状態によって入力値を変えたいときとか
        foreach (var (playerStatus, input) in 
            SystemAPI.Query<PlayerStatus, PlayerInputs>())
        {
            if(playerStatus.PlayerState == PlayerState.Normal)
            {
                // 状態がNormalの時のinput処理など
            }
            else if(playerStatus.PlayerState == PlayerState.Attack)
            {
                // 状態がAttackの時のinput処理など
            }
        }
    }
}
```
という感じで`if`や`switch`で分岐していきます。

# 状態がいろいろ組合された場合
普通に大元の状態が単一で複数の状態が複合しないという部分には上記で十分ですが、ゲームによっていろいろな状態が重なり合うようなものもあると思います。
例えば、毒状態で体力がだんだん減っていくうえに、混乱状態になって入力の値がランダムになり、その上現在は水の中にいるので水中での処理が走ってほしい。みたいなときとかです。

ここで一つ立ち戻ってもらいたいのですがこのECSというフレームワーク実はこれが得意なのです。
それぞれの状態を`Component`として、そのそれぞれの状態の時のシステムを個別に作っておけば、その`Entity`についてる`Component`にそれぞれの処理が走るので簡単に複合できるわけです。
具体的に言うと`Poison`,`Confusion`,`Swim`コンポーネントとそれぞれに対する`System`を作っておけば、`Poison`と`Confusion`のコンポーネントがついたエンティティは`PoisonSystem`,`ConfusionSystem`で処理され、`Poison`,`Swim`のコンポーネントがついたエンティティは`PoisonSystem`,`Swim`システムで処理されます。
もちろん`Swim`しかついていないエンティティは`Swim`のみで処理されます。

こういうことなので、状態を持てないといわれるECSですが、実はばっちり状態を持って処理することができるのです。

また`Confusion`と`Poison`が競合していたら動かしたくない場合は`foreach`に`WithNone<Poison>()`などを付ければいいわけです。
```csharp
foreach (var (poison, entity) in 
    SystemAPI.Query<Poison>()
             .WithNone<Confusion>().WithEntityAccess())
{
    // Poison状態のときのみの処理
}
```

# 実際の例
では実際に書いていきます。
```csharp
public struct Poison : IComponentData
{ }

public struct Confusion : IComponentData
{ }

public struct Swim : IComponentData
{ }
```
`Component`はこんな形です。
他に必要なデータがあれば記述しても構いませんが、今回は分岐処理(ECSで言うQuery処理)のための`Component`なので何も入っていなくても問題ありません。
自分はこの状態のことをただ`タグ`を付けてるようなものなので`タグ`と言っています。(公式の呼び方ではないと思う)

```csharp
[BurstCompile]
partial struct PoisonSystem : ISystem
{
    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        foreach (var (poison, hp) in 
            SystemAPI.Query<Poison, PlayerHp>())
        {
            // PlayerのHPが減っていく処理
            //hp.currentHP -= 1;など
        }
    }
}

[BurstCompile]
partial struct ConfusionSystem : ISystem
{
    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        foreach (var (confusion, input) in 
            SystemAPI.Query<Confusion, PlayerInput>())
        {
            // Playerの入力がランダムになるなど
            // input.MoveVector = 〇〇...
        }
    }
}

[BurstCompile]
partial struct SwimSystem : ISystem
{
    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        foreach (var (swim, control) in 
            SystemAPI.Query<Swim, CharacterControl>())
        {
            // 水の中の処理
            // control.Velocity = 〇〇...など
        }
    }
}
```
これで`Entity`についてる状態`タグ`によってそれぞれの処理が実行されるようになりました。

# 状態変化を起こさせる
この状態になったときの処理は上記で書きました。
これをどう発生させるかですが、普通に`AddComponent()`でよいです。
`AddComponent()`など`Entity`に`Component`を追加する方法は`HybridAnimation`の時やりましたね.

```csharp
// EntityCommandBufferを作成
EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<EndSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(World.Unmanaged);

foreach (var (hybridData, entity) in 
    SystemAPI.Query<CharacterHybridData>()
             .WithNone<CharacterHybridLink>().WithEntityAccess())
{
    // EntityCommandBuffer.AddComponent({Entity}, new {Component});で`タグ`を追加する。
    ecb.AddComponent(entity, new Poison); or ecb.AddComponent<Poison>(entity);

    // 削除するとき(状態から抜けるとき)はEntityCommandBufferで
    // RemoveComponent<{Component}>({Entity});を呼ぶとコンポーネントが外れる
    ecb.RemoveComponent<Poision>(entity);
}
ecb.Dispose();
```
削除するとき(状態を抜けるとき)は`EntityCommandBuffer.RemoveCompoent<>()`を呼びましょう。

`JobSystem`でコンポーネントの追加や削除をするときは以下のようにします。

```csharp
public partial struct ExsamplePoisonJob : IJobEntity
{
    // Update()から入れ込まれてくるEntityCommandBufferを保持する
    // Jobは並列処理なのでParallelWriterにする必要がある
    internal EntityCommandBuffer.ParallelWriter ecbParallel;
    // JobSystemのQuery([ChunkIndexInQuery]がAddComponentするには必要)
    void Execute(RefRW<PlayerComponent> player, Entity entity, [ChunkIndexInQuery] int sortKey)
    {
        // Posionコンポーネントを追加する(sortKeyがいるので注意)
        ecbParallel.AddComponent<Poison>(sortKey,entity);
        
        // Poisonコンポーネントを削除する(sortKeyがいるので注意)
        ecbParallel.RemoveComponent<Poison>(sortKey,entity);
    }
}

[BurstCompile]
public void OnUpdate(ref SystemState state)
{
    // UpdateでEntityCommandBufferを作成し
    var ecb = new EntityCommandBuffer(Allocator.TempJob);

    // EntityCommandBufferをParallelWriterにしてJobに投げる
    new ExsamplePoisonJob
    {
        ecbParallel = ecb.AsParallelWriter()
    }
    .Schedule();

    state.Dependency.Complete();

    ecb.Playback(state.EntityManager);
    ecb.Dispose();
}
```

これで何かのイベントとかアクションとか状況から状態を変化させることができます。

# 注意点
`EntityCommandBuffer.AddComponent`は別に少量使うには問題ないのですが、一応内部では`ArcheType`を書き換えたりいろいろやってますので、毎フレーム状態を変えるなどの使用は避けた方がいいと思います。