Fungusの会話テストができるEditorWindow機能【Unity,Fungus,Editor拡張】

はじめに

私が作っているゲーム (DreamIsland) Unityでの会話機能を作る際に OSSで公開されている Fungusを使用しています。

しかし、どのゲームでもそうかもしれませんが各NPCとの会話はNPCの前まで移動して会話テストする必要があります。このテストがかなり時間がかかっているため、今回は 場所を移動せずとも会話テストができる Editor拡張機能の作成を行いました。

GitHub ActionsでのCI/CD環境で Fungusのテストについては、以下の記事で書いていますので自動テストは以下の記事をご覧ください。

ayousanz.hatenadiary.jp

成果物

  • Editor実行時にシーン上にあるNPC(FungusがアタッチされているNPC)一覧の取得とリスト表示(NPC Numberにソート表示)
  • 会話テストしたいNPCを選択時に選択したNPCのボタンの色を変更
  • EditorWindowからNPC情報の確認と Phaseの変更、会話テスト実行機能

(注) * 内部で GetComponent をしているため、Editor実行したときしか使用することができません。

環境とゲーム内の会話処理周りの情報

環境

  • Unity 2021.3.0f1
  • Fungus v3.13.7
  • UniTask

会話処理周り

Fungusの会話処理は NPCController 内に実装が書かれていて、テスト用の Interfaceをして IDebugNPC を作成しています。

その他のクラス図は以下のようになっています。

機能詳細と実装

今回実装した機能は大きく分けると以下になります。

  1. NPC一覧の取得とソートでの一覧表示
  2. 選択したNPC Buttonのハイライトと情報の表示
  3. fungusのphaseを指定した会話テストの実行

なお、IDebugNPC は以下のように定義しています。

using Cysharp.Threading.Tasks;

namespace _DreamIsland.Card
{
    public interface IDebugNpc
    {
        string GetNpcName();
        string GetNpcObjectName();
        UniTask Talk();
        void SetTalkPhase(int phase);
        int GetTalkPhase();
    }
}

1. NPC一覧の取得とソートでの一覧表示

まず NPCの一覧取得には、FindGameObjectWithTag を使っています。 その後、名前の中から数字情報をとってきてソートをかけています。

        private static int NPCNumber(string npcObjectName)
        {
            if (npcObjectName.Contains("NPC"))
            {
                return Convert.ToInt32(npcObjectName.Substring(2, npcObjectName.IndexOf("NPC", StringComparison.Ordinal) - 2));
            }

            return 100;
        }

        private static List<IDebugNpc> GetNpcList()
        {
            var npcList = GameObject.FindGameObjectsWithTag("NPC");
            var conversationNpcList = new List<IDebugNpc>();
            foreach (var npc in npcList)
            {
                if (!npc.TryGetComponent<IDebugNpc>(out var debugNpc)) continue;
                conversationNpcList.Add(debugNpc);
            }

            conversationNpcList.Sort((a, b) => NPCNumber(a.GetNpcObjectName()) - NPCNumber(b.GetNpcObjectName()));
            return conversationNpcList;
        }

2. 選択したNPC Buttonのハイライトと情報の表示

NPC一覧表示と選択時の色変更は、以下のようにしています。

ボタンの背景は、OnEnable 時に色の取得をして保存しています。

また 内部で GetComponent を使用しているため、エディタ実行中のみ (EditorApplication.isPlaying == true )処理されるようにしています。

                using (new EditorGUILayout.VerticalScope(GUILayout.Width(200f)))
                {
                    _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
                    {
                        foreach (var npc in _npcList.Where(_ => EditorApplication.isPlaying))
                        {
                            var npcObjectName = npc.GetNpcObjectName();
                            GUI.backgroundColor = _selectNpcGameObjectName == npcObjectName ? Color.green : _editorBackgroundColor;

                            if (GUILayout.Button($"{npcObjectName}"))
                            {
                                _selectedNpc = npc;
                                _selectNpcGameObjectName = npc.GetNpcObjectName();
                                _selectNpcName = npc.GetNpcName();
                            }
                        }
                    }
                    EditorGUILayout.EndScrollView();
                }

参考にさせていただいたサイトは以下です。

www.hanachiru-blog.com

zenn.dev

www.codetd.com

3. fungusのphaseを指定した会話テストの実行

  1. SetPhase(int phase) で任意の会話フェーズを指定する
  2. 会話テストを開始(内部で Fungus ≒ Flowchartの SendFungusMessage を呼んでいる)
  3. phaseを初期状態に戻す ( set phaseしたデータが残ってしまうため)

会話テスト開始ボタンを押した後のコードは以下のようになっています。

                        var prePhase = _selectedNpc.GetTalkPhase();
                        _selectedNpc.SetTalkPhase(_selectNpcTalkPhase);
                        await _selectedNpc.Talk();
                        _selectedNpc.SetTalkPhase(prePhase);