Google Sheetを使ったFungusのテキストデータの管理【Fungus】【Unity】【GoogleAppsScript】

概要

私たちが制作した「DreamIsland」(*1)というゲームでは、Fungusを使ってNPCやオブジェクトとの会話イベントを実装しています。その中でFungusのデータ管理(会話テキストやフロー)についていろいろ調査と試作をしていったん解決ができました。

今回は Fungusの会話フローやテキストデータを Google Sheetを使うことによって、開発時の開発フローの改善と生産性向上を行うことができると思っています。

(*1) 宣言もかねて、載せていただきます!是非プレイしてみてください

store.steampowered.com

デモ

デモ動画では、以下の流れで操作を行っています。

  1. Unity画面で会話テキストの表示
  2. Google Sheet側で会話テキストの変更
  3. 変更後の会話テキストの表示確認

リポジトリ

github.com

ターゲット

  • FungusとUnityをある程度使っている人
  • Fungusのデータ管理について困っている人

開発環境

  • Unity 2021.3.4f1
  • Fungus v3.13.8
  • UniTask 2.3.1
  • UniRx 7.1.0

課題と解決方法実装

これまでの課題

Fungusを使うときのデータ管理として、Fungus側は以下が準備されています。

  1. Flowchartにデータをそのまま埋め込む
  2. textデータをimport/exportして管理をする
  3. CSVデータをexportする(importはうまくいなかった)

上記の3つともローカルでは編集できるのですが、チームで開発を行うときにだれが更新するのか・そもそもどのファイルが最新版なのかがわからなくなってしまいます。また テキストデータを編集する人はUnityや実装のことをわかっていないポジションの人かもしれません。

解決方法

概要にもありますが、今回は Google Sheetで会話テキストと会話フローを管理することにしました。また Fungus(Unity)のデータ更新は Unity起動時に 最新版に更新することでシナリオを書く人と実装する人のコミュニケーションコストを無くすようにしました。

以下が大まかな構成図になります。

  1. Google Sheet上で会話テキストや会話フローを編集
  2. GASを使って特定のエンドポイントから最新版の会話データをjsonで取得
  3. Unity起動時に http経由で最新の会話データを取得
  4. Fungusのflowchartにipmortできる形にコンバートを行い、flowchartを最新に更新

実装方法

準備編

いったんいま実装している会話データをGoogle Sheet(CSV)の場合どのような書き方になるのかを確認します。

1 . Fungus(flowchart)で会話を実装

今回は以下のような会話を作りました。

2 . Localizationコンポーネントの追加

以下のMenuからCSVとしてexportできるための機能(Localization) コンポーネントを追加することができます。Tool -> Fungus -> Create -> LocalizationLocalization コンポーネントヒエラルキー上に追加することができます。

3. 会話データをCSVで出力

Localization コンポーネントExport Localization File というボタンがあるので、こちらを押すと 任意のパスに 会話データを CSVファイルとして出力することができます。

Google Sheet側(会話データ側)

1. 外部から会話データを取得できるGASスクリプトの作成

先ほど出力した CSVのフォーマットを そのまま Google Sheetにコピーします。

次にこのデータを Unity側で取得できるように GASを使ってjsonとして渡せるようにします。メニューから 拡張機能 -> App Script を開きます。

開くとスクリプトを書くことができるので、以下のようにして 任意のシート名のデータをjsonとして渡すスクリプトを作成します。

function doGet(e) {
  var sheetName = e.parameter.sheetName;
  var d = {"textData":getData(sheetName)};
  var out = ContentService.createTextOutput();
  out.setMimeType(ContentService.MimeType.JSON);
  out.setContent(JSON.stringify(d));
  return out;
}

function getData(sheetName) {
  const sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
  const rows = sheet.getDataRange().getValues();
  const keys = rows.splice(0, 1)[0];
  return rows.map(row => {
    const obj = {};
    row.map((item, index) => {
      obj[String(keys[index])] = String(item);
    });
    return obj;
  });
}

2. GASスクリプトのデプロイ

デプロイから新しいデプロイを選び、ウェブアプリを選択します。ユーザーは 自分を選択します。

このときの表示されるデプロイ URLは使うためどこかに控えておきます

Unity側(ゲーム側)

会話データ用をロードするためのクラスを以下のように作成します。

[Serializable]
    public class TextData
    {
        public string Key;
        public string Description;
        public string Standard;
    }

    [Serializable]
    public class TextDataList
    {
        public List<TextData> textData;
    }

次に GAS(http)経由で Google Sheetのデータを取得します。またFungusに沿ったテキストフォーマットに変更する必要があるため、二つの関数を用意しておきます。

/// <summary>
        /// ゲーム情報をスプレッドシートから取得
        /// </summary>
        /// <returns></returns>
        public static async UniTask<T> GetGameInfo<T>()
        {
            var request = UnityWebRequest.Get($"{url}?sheetName={sheetName}");
            await request.SendWebRequest();
            if (request.result is UnityWebRequest.Result.ConnectionError or UnityWebRequest.Result.ProtocolError or UnityWebRequest.Result.DataProcessingError)
            {
                Debug.Log("fail to get card info from google sheet");
            }
            else
            {
                var json = request.downloadHandler.text;
                var data = JsonUtility.FromJson<T>(json);
                return data;
            }

            return default;
        }

        public static string ConvertFungusTextFormat(TextDataList data)
        {
            var fungusText = "";
            foreach (var info in data.textData)
            {
                fungusText += $"#{info.Key}\n";
                fungusText += $"{info.Standard}\n";
                fungusText += "\n";
            }

            return fungusText;
        }

最後にこれらのスクリプトを呼び出し、Fungus側に起動時に反映すれば終了です

public class UpdateFungusText : MonoBehaviour
    {
        private Localization _localization;
        [SerializeField] private GameStarted gameStarted;

        private void Awake()
        {
            _localization = GetComponent<Localization>();
        }

        private async void Start()
        {
            var data = await LoadTextData.GetGameInfo<TextDataList>();
            var fungusText = LoadTextData.ConvertFungusTextFormat(data);
            _localization.SetStandardText(fungusText);
            gameStarted.enabled = true;
        }
    }

省略している部分や少し変更されている部分もありますので、最新版はRepositoryをご確認ください

終わりに

FungusのデータをGoogle Sheetで管理することによって、テキストのローカライズの対応がより容易、テキストの確認作業の高速化、エンジニア側とシナリオ側のコミュニケーションの削減など多くのメリットがあります。

またFungusを使ったときの会話テストをEditor Windowのみで完結させる拡張機能は以下で紹介していますので、ご覧ください

ayousanz.hatenadiary.jp