[Unity]クォータービューの円形すごろくを作る

# 概要
Unityでクォータービューのすごろくゲームを作ろうとしたので、その実装方法をまとめておきます。
この記事で紹介するものの完成形は、ダイスを振るボタンを押すとダイスの目が表示され、キャラクターがその分だけ移動する、円形のすごろくです。
![qiita_card.gif](https://qiita-image-store.s3.amazonaws.com/0/217930/eb6c57bd-5f77-a16a-a844-e310aaeb97ad.gif)

# 実行環境
Unity 2017.3.1f1

# 使用アセット
- [UniRx 5.7.0](https://assetstore.unity.com/packages/tools/unirx-reactive-extensions-for-unity-17276)
- [DOTween 1.1.640](https://assetstore.unity.com/packages/tools/animation/dotween-hotween-v2-27676)

使わせていただいた素材アセット

- [ファンタジー ドット キャラクターパック2](https://assetstore.unity.com/packages/2d/characters/fantasy-dot-character-pack-2-103187)
- [2D PixelArt - Isometric Blocks](https://assetstore.unity.com/packages/3d/environments/2d-pixelart-isometric-blocks-115039)

# ダイスロールの実装
ダイスを振るイベントを実装するスクリプト、ボタンを押した時にそのイベントを実行するスクリプト、ダイスの結果を表示するスクリプトの３つを作ります。

```c#:DiceRollObserver.cs
// ダイスを振るイベント

using UniRx;
using UnityEngine;
using Random = UnityEngine.Random;

public class DiceRollObserver : MonoBehaviour
{
    public Subject<int> OnDiceRolledObservable = new Subject<int>();

    public void RollDice()
    {
        // 今回は１〜６の目が出るダイス
        this.OnDiceRolledObservable.OnNext(Random.Range(1, 7));
    }
}
```

```c#:RollDiceButtonDispacher.cs
// ダイスを振るボタンを押した時の処理

using UnityEngine;
using UniRx;
using UnityEngine.UI;

[RequireComponent(typeof(Button))]
public class RollDiceButtonDispatcher : MonoBehaviour
{
    public DiceRollObserver DiceRollObserver;

    void Start()
    {
        this.GetComponent<Button>().OnClickAsObservable()
            .Subscribe(_ => this.DiceRollObserver.RollDice())
            .AddTo(this);
    }
}
```

```c#:DiceRenderer.cs
// ダイスの数字を表示する

using UnityEngine;
using UnityEngine.UI;
using UniRx;

[RequireComponent(typeof(DiceRollObserver))]
[RequireComponent(typeof(Text))]
public class DiceRenderer : MonoBehaviour
{
    void Start()
    {
        // ダイスを振るイベントを受けて、テキストの描画
        this.GetComponent<DiceRollObserver>()
            .OnDiceRolledObservable
            .Subscribe(value => this.Render(value))
            .AddTo(this);
    }

    // テキストの描画
    private void Render(int value)
    {
        this.GetComponent<Text>().text = value.ToString();
    }
}
```

UniRxでダイスを振るイベントと、ボタンを監視する処理と、ダイスを振るイベントを監視する処理を書きました。

# キャラクター移動処理の実装
すごろくのマス目とキャラクターの位置情報を格納するスクリプトと、ダイスが振られたイベントに反応してキャラクターを移動させるスクリプトを作ります。

```c#:Field.cs
// すごろくのマス情報

using UnityEngine;

public class Field : MonoBehaviour {
    // すごろくの座標を取得するために各マスのオブジェクトを格納(transformのリストでもいい)
    public GameObject[] MassGameObjects;
}
```

```c#:Charactor.cs
// キャラクターの情報

using UnityEngine;

public class Charactor: MonoBehaviour
{
    // キャラクターの位置
    public int Place;
}
```

```c#:CharactorRenderer.cs
// キャラクターの表示

using UnityEngine;
using UniRx;
using DG.Tweening;

[RequireComponent(typeof(Charactor))]
public class CharactorRenderer : MonoBehaviour
{
    public DiceRollObserver DiceRollObserver;
    public Field Field;
    private Charactor Charactor;

    void Start()
    {
        this.Charactor = this.GetComponent<Charactor>();

        // ダイスを振るイベントを受けて、移動処理を実行
        this.DiceRollObserver.OnDiceRolledObservable
            .Subscribe(value => this.Move(value))
            .AddTo(this);
    }

    // ダイスの目だけ移動
    private void Move(int moveCount)
    {
        Sequence moveSequence = DOTween.Sequence();
        for (int i = 1; i <= moveCount; i++)
        {
            int nextPlace = i + this.Charactor.Place;
            // 一周した場合は0に戻す
            if (nextPlace >= this.Field.MassGameObjects.Length)
            {
                nextPlace -= this.Field.MassGameObjects.Length;
            }

            // すごろくのマスオブジェクトの座標を元に、キャラクターの次の移動先を決める
            Vector3 nextPosition = Field.MassGameObjects[nextPlace].transform.localPosition;
            this.AppendMove(moveSequence, nextPosition);
        }
    }

    // キャラクターの移動処理を追加
    private void AppendMove(Sequence sequence, Vector3 newPosition)
    {
        sequence.Append(
            this.GetComponent<RectTransform>()
                .DOLocalMove(newPosition, 0.5f)
                .OnComplete(() =>
                {
                    // 移動後の処理
                    // キャラクターの位置の更新
                    this.Charactor.Place = this.GetNewPlace(this.Charactor.Place);
                    // 位置によってキャラクターの向きを変える処理
                    this.ChangeDirection(this.Charactor.Place);
                })
        );
    }

    // 位置を一つ進めた新しい位置を返す
    private int GetNewPlace(int place)
    {
        int _place = place;
        if (Field.MassGameObjects.Length - 1 != place)
        {
            _place++;
        }
        else // 一周した場合
        {
            _place = 0;
        }

        return _place;
    }

    // キャラクターの向きを変える
    private void ChangeDirection(int place) {
        // 省略
    }
```

すごろくのマス目に0〜19の値(`Field.MassGameObjects`の添字)を割り振って、出たすごろくの目の値だけ、キャラクターの現在の位置情報(`Charactor.Place`)を参照しつつ、`DOLocalMove`で移動させていきます。
DOTweenのSequenceを使うことでアニメーションを結合することができ、また、Callback処理で移動後に現在位置の更新と向きの更新を行うことができます。
円形以外のすごろくを作る時はまた考える必要がありそうですが、UniRxとDOTweenを使って円形のすごろくを実装することができました。

# 参考記事
[作りながら理解するUniRx](https://qiita.com/mattak/items/106dfd0974653aa06fbc)
[[Unity] DOTweenのSequenceを使ってアニメーションを結合する](https://qiita.com/lycoris102/items/6a9e1e39bfa69880eaba)