UnityでコマンドRPGをキューを使って実装する

###はじめに
***
本記事は[Unity Advent Calendar2018 part3](https://qiita.com/advent-calendar/2018/unity3)、14日目の記事です。
本記事を投稿する背景として、[ターン制RPGを作るときに知っておきたい「ロジックと演出の分離」](http://www.lancarse.co.jp/blog/?p=194)様の記事がありました。この記事では、switch文のstep処理で組むのではなく、queueに行動内容を入れてdequeueしていくという手法がとられていました。そこで、RPGの戦闘処理をキューに入れて実装してみました。

###実装
***

```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Advent : MonoBehaviour {
    Queue<Action> battleQueue;
    [SerializeField] private GameObject attackObj;
    [SerializeField] private GameObject receiveObj;
    [SerializeField] AnimationClip preAnimationClip;
    [SerializeField] AnimationClip attackAnimationClip;
    [SerializeField] AnimationClip receiveAnimationClip;
    [SerializeField] ParticleSystem preParticle;
    [SerializeField] ParticleSystem attackParticle;
    [SerializeField] ParticleSystem receiveParticle;

    public enum AbnormalState
    {
        Poison,
        Paralize,
        Freeze,
        None
    };

    void Start() {
        battleQueue = new Queue<Action>();
        Action action1 = new Action() {
            p = new Performance { chara = attackObj, animationClip = preAnimationClip, particle = preParticle }
        };
        battleQueue.Enqueue(action1);

        Action action2 = new Action()
        {
            p = new Performance { chara = attackObj, animationClip = attackAnimationClip, particle = attackParticle }
        };
        battleQueue.Enqueue(action2);

        Action action3 = new Action()
        {
            p = new Performance { chara = receiveObj, animationClip = receiveAnimationClip, particle = receiveParticle }
        };
        battleQueue.Enqueue(action3);

        Action action4 = new Action()
        {
            d = new Damage { attackChara = attackObj, receiveChara = receiveObj, mp_use = 10, damage = 30, abnormalState = AbnormalState.Poison }
        };
        battleQueue.Enqueue(action4);

        StartCoroutine(ActionCoroutine());
	}
	
    IEnumerator ActionCoroutine()
    {
        Debug.Log(battleQueue.Count);
        while(battleQueue.Count > 0)
        {
            Action action = battleQueue.Dequeue();
            Debug.Log(action);
            if (action.p != null) { action.p.Method(); yield return new WaitForSeconds(2); }
            if (action.d != null) { action.d.Method(); yield return new WaitForSeconds(2); }
            
        }
    }

    public struct Action
    {
        public Performance p;
        public Damage d;
    }

    public class Performance
    {
        public GameObject chara;
        public AnimationClip animationClip;
        public ParticleSystem particle;

        public void Method()
        {
            animationClip.legacy = true;
            chara.GetComponent<Animation>().AddClip(animationClip,"Play");
            chara.GetComponent<Animation>().Play("Play");
            ParticleSystem tempParticle = Instantiate(particle) as ParticleSystem;
            tempParticle.transform.parent = chara.transform;
            tempParticle.Play();
        }
    }

    public class Damage
    {
        public GameObject attackChara;
        public GameObject receiveChara;
        public int mp_use;
        public int damage;
        public AbnormalState abnormalState;

        public void Method()
        {
            attackChara.GetComponent<CharaStatus>().mpSet -= mp_use;
            receiveChara.GetComponent<CharaStatus>().hpSet -= damage;
            if (abnormalState != AbnormalState.None)
            {
                List<AbnormalState> temp = receiveChara.GetComponent<CharaStatus>().abnormalSet;
                temp.Add(abnormalState);
                receiveChara.GetComponent<CharaStatus>().abnormalSet = temp;
            }
        }
    }
}

```

まず、こちらのAdventクラスのなかにはPerformanceクラスとDamageクラスが存在します。Perfomanceクラスは演出データとメソッドが書かれており、攻撃キャラ, アニメーション, パーティクルを操作します。Damageクラスは計算データとメソッドが書かれており、攻撃キャラ, 被攻撃キャラ, 使用mp, 与えるダメージ, 与える状態異常を操作します。
このスクリプトは空のゲームオブジェクトにアタッチし、インスペクタ上でSerializeField化された変数を設定します
***

```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CharaStatus : MonoBehaviour {

    [SerializeField] private int hp;
    [SerializeField] private int mp;
    [SerializeField] private List<Advent.AbnormalState> abnormalStateList;

    public int hpSet { get { return hp; } set { hp = value; } }
    public int mpSet { get { return mp; } set { mp = value; } }
    public List<Advent.AbnormalState> abnormalSet { get { return abnormalStateList; } set { abnormalStateList = value; } }

}
```

CharaStatusクラスには、hp, mp, 所持状態異常のリストが格納されています。このスクリプトはキャラクターにアタッチします。私はCubeを2つ作成し、片方を攻撃キャラ、もう片方を被攻撃キャラとしました。
***

```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ParticleObjDelete : MonoBehaviour {
    ParticleSystem particle;
	// Use this for initialization
	void Start () {
        particle = GetComponent<ParticleSystem>();
        StartCoroutine(DeleteCoroutine());
	}
	
	// Update is called once per frame
	IEnumerator DeleteCoroutine()
    {
        yield return new WaitWhile(() => particle.IsAlive(true));
        Destroy(this.gameObject);
    }
}
```

このスクリプトは、プレハブ化されたパーティクルに取り付けています。このスクリプトによって、作動が終わったParticleSystemを持つGameObjectが自動で削除されます。

###動作
***
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Unityアドベントカレンダー用の動画。<br>キューによるRPG戦闘の実現について。 <a href="https://t.co/oDi1Af7eOc">pic.twitter.com/oDi1Af7eOc</a></p>&mdash; もやし (@fms_moyashi) <a href="https://twitter.com/fms_moyashi/status/1072453394785337344?ref_src=twsrc%5Etfw">December 11, 2018</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

エフェクトとアニメーションが順序良く行われ、その後HPとMPが減っているのが確認できます。

###まとめ
***
やはり、switch文のstep処理で書くと、後から途中に挟み込みたい挙動が生まれてしまうと一つずつ直していく必要があります。そこをキューで処理することによって、簡単に仕様変更を行えるようになりました。ここからユーザーによるコマンド操作を可能にし、状態異常のダメージや麻痺によって動けないなどの処理を挟み込むと、よりゲームっぽくなると思います。

みなさんも、素晴らしいRPG作成ライフをお送りください！