[Unity] Project Tinyでのミニゲーム作成 - 当たり判定・アニメーション

# はじめに
Project Tinyでミニゲームを作成時に使用した機能やトラブルを説明していきます。
今回はゲーム中の当たり判定・アニメーションの実装についてです。
リポジトリは[こちら](https://github.com/chocolate-ice-cream/TinyRun)。

## 当たり判定

使用するコンポーネントはRectHitBox2Dです。

マニュアル
[https://docs.unity3d.com/Packages/com.unity.tiny@0.16/manual/module-hitbox2d.html](https://docs.unity3d.com/Packages/com.unity.tiny@0.16/manual/module-hitbox2d.html)

### 判定方法

Sprite2DRnederer,RectHitBox2Dが設定されているEntity同士が接触すると
HitBoxOverlapコンポーネントが追加され、非接触の状態になると削除されます。
このHitBoxOverlapコンポーネントの存在の有無で当たり判定を行います。
今回のゲームではキャラクターが障害物に接触したら即ゲームオーバーなので、
HitBoxOverlapが存在したらゲームオーバーフラグを立てる処理を行っています。

```C#:GameMain.cs
Entities.ForEach((Entity entity, ref Character character, ref RectHitBox2D rectHitBox2d) => {
    if (EntityManager.HasComponent<HitBoxOverlap>(entity)) {
        isGameOver = true;
    }
});
```

### 不便な点

Colliderであれば設定したサイズをSceneビューで見ることが出来ますが、
RectHitBox2Dではそういうプレビュー的なものが存在しないためサイズ調整がなかなか難しいです。
なので、このランゲームではけっこういい加減な判定になっています。

## アニメーション

Sprite2DSequenceとSprite2DSequencePlayerの2つのコンポーネントを使用します。

リファレンス
[Sprite2DSequence](https://docs.unity3d.com/Packages/com.unity.tiny@0.16/api/Unity.Tiny.Core2D.Sprite2DSequence.html)
[Sprite2DSequencePlayer](https://docs.unity3d.com/Packages/com.unity.tiny@0.16/api/Unity.Tiny.Core2D.Sprite2DSequencePlayer.html)

### 設定手順

1. 適当なEntityにSprite2DSequenceをAdd Componentする
1. アニメーションに使用するスプライトを各フレームに設定する![Sprite2DSequence.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490737/af195822-9e20-5f1d-c86b-f9ec07fb3801.jpeg)

1. Sprite2DRendererが設定されているEntityにSprite2DSequencePlayerをAdd Componentする
1. Sprite2DSequencePlayerのsequenceにSprite2DSequenceのEnitityを設定する![Sprite2DSequencePlayer.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490737/142ed792-18a7-3fa9-f485-48994d7a84e5.jpeg)

### Sprite2DSequencePlayerのフィールドの説明

**sequence**
Sprite2DSequenceが設定されているEntity

**speed**
再生スピード
1が通常スピード
1未満でスロー再生、1以上で倍速再生

**time**
現在の時間
値を設定することで途中からの再生を行うことができる

**paused**
一時停止

**ループタイプ**

|Name  |Description|
|---|---|
|ClampFoever  |再生後最終フレームを維持する  |
|Loop  |通常ループ  |
|Once  |一度だけ再生  |
|PingPong  |いったり来たりをする再生を繰り返す  |
|PingPongOnce  |PingPongを一度だけ行う  |

### アニメーションの切り替え

Animationsコンポーネントを作成しその中に各アニメーションのSprite2DSequenceのEntityを設定できるようにしてあります。

```C#:Animations.cs
public struct Animations : IComponentData {
    public Entity SequenceIdleEntity;
    public Entity SequenceRunEntity;
    public Entity SequenceJumpEntity;
    public Entity SequenceDeathEntity;
}
```

あとはCharacterAnimationSystemで入力や状態を見て、Sprite2DSequencePlayerのsequenceのEntityを変更することでアニメーション切り替えを行っています。

```C#:CharacterAnimationSystem.cs
public class CharacterAnimationSystem : ComponentSystem {
    protected override void OnUpdate() {
        Entities.ForEach((ref Animations anims) => {
            var sequenceIdleEntity = anims.SequenceIdleEntity;
            var sequenceRunEntity = anims.SequenceRunEntity;
            var sequenceJumpEntity = anims.SequenceJumpEntity;
            var sequenceDeathEntity = anims.SequenceDeathEntity;
            var isGameOver = false;
            var isPlaying = false;
            var begun = false;
            var retry = false;

            Entities.ForEach((ref GameState game) => {
                isGameOver = (!game.GameOverPrev && game.GameOver);
                isPlaying = game.Playing;
                begun = game.Begun;
                retry = game.Retry;
            });

            Entities.ForEach((ref Character character, ref Sprite2DSequencePlayer sequencePlayer) => {
                if (true) {
                    if (isGameOver) {
                        // ゲームオーバー
                        sequencePlayer.time = 0.0f;
                        sequencePlayer.loop = LoopMode.ClampForever;
                        sequencePlayer.sequence = sequenceDeathEntity;
                    }
                    else if (begun) {
                        // ジャンプ
                        sequencePlayer.time = 0.0f;
                        sequencePlayer.loop = LoopMode.ClampForever;
                        sequencePlayer.sequence = sequenceJumpEntity;
                    }
                    else if (retry) {
                        // 走り
                        sequencePlayer.time = 0.0f;
                        sequencePlayer.loop = LoopMode.Loop;
                        sequencePlayer.sequence = sequenceRunEntity;
                    }
                    else {
                        if (!character.jumpingPrev && character.jumping) {
                            // ジャンプ
                            sequencePlayer.time = 0.0f;
                            sequencePlayer.loop = LoopMode.ClampForever;
                            sequencePlayer.sequence = sequenceJumpEntity;
                        }
                        else if (character.jumpingPrev && !character.jumping) {
                            // 着地
                            sequencePlayer.time = 0.0f;
                            sequencePlayer.loop = LoopMode.Loop;
                            sequencePlayer.sequence = sequenceRunEntity;
                        }
                    }
                }
            });
        });
    }
}
```