FungusのFlowchart内にあるセリフデータをJSONに出力する【Unity】【Fungus】

はじめに

インディーズゲームを作っている中でNPCの会話を実装をすることがあります。はじめから外部にセリフデータを分離した実装を行っていればいいのですが、Unity内部で持ってしまうことがあります。

今回は以下のDreamIslandにて Fungusを使用してFlowchart内に入れた実装にしてしまいました。

store.steampowered.com

やりたいこと

以下の画像のように FungusのFlowchart内部にもっているセリフデータをjson形式にすべて一括でexportする (会話するNPCは100体以上存在するため、一つ一つだと時間がかかってしまいます)

Demo

Inspectorの Export Dialogue To Json のボタンを押すことで、以下の画像のように Inspector及びローカルのjsonに一覧化して見やすくしました

追加の拡張機能宣伝

以下では セリフデータを Google Sheetに保存する機能を作りました。 Unityを見ずらいjsonやInspectorではなく、使い慣れた Sheet形式で見れるのでかなり便利だと思います。 //別記事として作成中

環境

実装内容

一セリフデータを格納するクラスの作成

Flowchart内でセリフデータが入っているComponentは、以下画像のSayになります。
こちらのコンポーネントを見ると、以下の情報を持っていることが分かります。

  • ItemId(Commandの順番、本来は非表示)
  • Story Text (セリフ)
  • Character(セリフを話しているcharacterコンポーネント)

上の情報に セリフの文字数と都合上 nameの二つを追加したクラスを作成します。

    [Serializable]
    public class Dialogue
    {
        public string characterName;
        public string characterViewName;
        public string text; //セリフデータ
        public int commandIndex;
        public int dialogueCount;
    }

一キャラのセリフデータを格納するクラスの作成

一つのNPCに対して(FlowchartがアタッチされているNPC) 、セリフデータは以下のように複数存在しています。

そのため、複数のセリフを格納するために先ほどの Dialogue をリストにして NPCの名前と紐づけた 一キャラのセリフデータクラスを作成します。

    [Serializable]
    public class CardEventDialogue
    {
        public string dialogueNo;
        public List<Dialogue> dialogues;
    }

Flowchartコンポーネントから情報を取得する

クラスの作成は終わったので、一キャラのflowchart内から情報を取得していきます。

        private static CardEventDialogue GetCardEventDialogues(Component flowchart)
        {
            var dialogueList = new CardEventDialogue
            {
                dialogueNo = flowchart.name,
                dialogues = new List<Dialogue>()
            };
            var sayList = flowchart.GetComponents<Say>();
            foreach (var say in sayList)
            {
                if (say._Character == null) continue;
                var dialogue = new Dialogue
                {
                    characterName = say._Character.name,
                    characterViewName = say._Character.NameText,
                    text = say.GetStandardText(),
                    dialogueCount = say.GetStandardText().Length,
                    commandIndex = say.ItemId,
                };
                dialogueList.dialogues.Add(dialogue);
            }

            dialogueList.dialogues.Sort((a, b) => (a.commandIndex - b.commandIndex));

            return dialogueList;
        }

またセリフデータを取得する際に、セリフの順序がバラバラになると困るので ItemId でソートを行います

dialogueList.dialogues.Sort((a, b) => (a.commandIndex - b.commandIndex));

シーン上のすべての会話NPCの取得

ボタンを押したときにシーン(ヒエラルキー上)に存在する flowchartがアタッチされている NPCを取得します。

        private void GetAllDialogues()
        {
            _cardEventDialogueList.Clear();
            var dialogues = FindObjectsOfType<Flowchart>();
            foreach (var dialogue in dialogues)
            {
                _cardEventDialogueList.Add(GetCardEventDialogues(dialogue));
                _cardEventDialogueList.Sort((a, b) => (GetCardEventInt(a.dialogueNo) - GetCardEventInt(b.dialogueNo)));
            }
        }

Jsonにクラスを保存

クラス内がリスト型のため、JsonUtility.ToJson ではうまく変換ができないため以下のような関数を作り疑似的に保存できるように変更しています。

    public static class JsonExtension
    {
        public static string ListClassToJson<T>(T listClass, string className)
        {
            var tmpClass = new TempClass<T>
            {
                ClassName = className,
                Data = listClass
            };
            return JsonUtility.ToJson(tmpClass, true);
        }

        private class TempClass<T>
        {
            public string ClassName;
            public T Data;
        }
    }

取得してきたセリフデータをjsonに保存します。

        private async UniTask ExportJson()
        {
            var json = JsonExtension.ListClassToJson(_cardEventDialogueList, nameof(_cardEventDialogueList));
            Debug.Log("json: " + json);
            var writer = new StreamWriter(JsonSavePath, false);
            await writer.WriteAsync(json);
            await writer.FlushAsync();
            writer.Close();
        }