0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Unity] [Editor拡張] グーグルスプレッドシート(GSS)の内容を、CSVファイルとして出力するEditorWindowの作成方法

Last updated at Posted at 2024-05-30

五回目の投稿です。よろしくお願いします。

目次

  1. 概要
  2. 事前準備
  3. 使用方法
  4. 作業手順
  5. 実装方法
  6. 最後に

概要

以下のような、指定したスプレッドシートを読み込み、それをCSVファイルとして出力するEditorWindowの作成方法を解説します :
image.png
(GSSReaderTool : EditorWindow)

image.png
(今回使用するスプレッドシート)

image.png
(上記スプレッドシートから作成されるCSVファイル)

効用

  • Unity外でデータ管理をするので、非プログラマでも編集しやすく、共同編集も可能
  • アプリ配信後でもデータの編集が可能
  • 1列目を"id"にしておくことで、idからその行のデータセットを指定するといった運用が可能 (詳しくは"最後に"を参照)

事前準備

事前にスプレッドシート(GSS)を作成しておきます。

  1. GSSをGoogleドライブなどの共有可能な場所に作成
  2. セルの値やページ名を上記のようにしておく。
  3. GSSの画面右上の共有ボタンから、アクセス制限を以下のようにしておく :

image.png

また、エディタ上でコルーチンを実行するために"Editor Coroutines"パッケージを導入しています。パッケージマネージャーからインストールしておいてください。

使用方法

ここでは使用方法を説明します。作成方法に興味のない方は、ここを読むことで使用できるようになっています。

1. 下記スクリプトを作成する :
GssReaderTool.cs (tap)
#if UNITY_EDITOR
using System.Collections;
using UnityEngine.Networking;
using UnityEngine;
using System.IO;
using UnityEditor;
using Unity.EditorCoroutines.Editor;

namespace App.ReadCsv
{
    public class GssReaderTool : EditorWindow
    {
        private const string CSV_FOLDER_PATH = "Assets/MasterData/CSV/";
        private const string GSS_FORMAT = "tqx=out:csv";

        private string gssSheetId;
        private string gssSheetName;
        private string csvfileName;
        private string log;
        private bool isLoadingGss;


        [MenuItem("Tools/GSS Reader Tool")]
        public static void OpenWindow()
        {
            GetWindow<GssReaderTool>("GSS読み込みツール");
        }

        private void OnGUI() // 描画処理
        {
            GUILayout.Label("[GSSの読み込み, CSVファイルの作成]");

            gssSheetId = EditorGUILayout.TextField("・ GSSのシートID :", gssSheetId);
            gssSheetName = EditorGUILayout.TextField("・ GSSのシート名 :", gssSheetName);
            csvfileName = EditorGUILayout.TextField("・ 作成するファイル名 :", csvfileName);
            if (GUILayout.Button("実行"))
            {
                log = string.Empty;
                StartLoadGss(gssSheetId, gssSheetName);
            }
            DrawLogGUI();
        }

        /// <summary>
        /// GSSの読み込み
        /// </summary>
        public void StartLoadGss(string id, string name)
        {
            if (isLoadingGss) // GSS読み込み中は実行しない
            {
                AddLog("Error : GSSを読み込み中...");
                return;
            }
            else if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(csvfileName))
            {
                AddLog("Error : 設定項目をすべて記入してください。");
                return;
            }

            var url = "https://docs.google.com/spreadsheets/d/" + id + "/gviz/tq?" + GSS_FORMAT + "&sheet=" + name;
            EditorCoroutineUtility.StartCoroutine(LoadGss(url), this);
        }

        private void AddLog(string message)
        {
            log += $" - {message}\n";
        }

        private IEnumerator LoadGss(string url)
        {
            using (var request = UnityWebRequest.Get(url)) // GSSの読み込み
            {
                AddLog("GSSの読み込み開始");

                isLoadingGss = true;
                yield return request.SendWebRequest();
                isLoadingGss = false;

                if (request.result == UnityWebRequest.Result.ProtocolError)
                {
                    AddLog($"Error Load GSS : {request.error} (GSSの存在性と公開設定を確認して下さい)");
                    yield break;
                }
                else if (request.result == UnityWebRequest.Result.ConnectionError)
                {
                    AddLog($"Error Load GSS : {request.error} (ネットワークに接続されているかを確認して下さい)");
                    yield break;
                }
                AddLog("GSSの読み込み完了");

                CreateCsv(csvfileName, request.downloadHandler.text);
            }
        }

        /// <summary>
        /// CSVファイルの作成
        /// </summary>
        private void CreateCsv(string fileName, string data)
        {
            if (string.IsNullOrEmpty(data))
            {
                AddLog("Error Create CSV : データが空であるため、ファイルを作成できませんでした。");
                return;
            }

            AddLog("CSVファイルの作成開始");

            if (!Directory.Exists(CSV_FOLDER_PATH)) // フォルダがなければ、
            {
                Directory.CreateDirectory(CSV_FOLDER_PATH); // 新規作成
            }

            var path = CSV_FOLDER_PATH + fileName + ".csv";
            using (var sw = new StreamWriter(path, false)) // 新規作成 or 上書き
            {
                try
                {
                    sw.Write(data);
                    AssetDatabase.Refresh(); // エディタ上のアセットの更新
                    AddLog($"CSVファイルの作成完了 : {path}");
                }
                catch (System.Exception e)
                {
                    AddLog(e.ToString());
                }
            }
        }

        /// <summary>
        /// ログを出力
        /// </summary>
        private void DrawLogGUI()
        {
            if (string.IsNullOrEmpty(log)) { return; }

            GUILayout.FlexibleSpace();
            GUILayout.Box("", GUILayout.ExpandWidth(true), GUILayout.Height(3));

            GUILayout.Label(" [Log] ");
            GUILayout.Label(log, new GUIStyle() { wordWrap = true, normal = new GUIStyleState() { textColor = new Color(1, 1, 1, 1) } });

            if (GUILayout.Button("Clear Log"))
            {
                log = string.Empty;
            }
            GUILayout.Space(7);
        }
    }
}
#endif
2. Unityエディタ上で"Tools/GSS Reader Tool"を選択する :

image.png

3. すると、下記EditorWindowが出現するので、"GSSのシートID"と"GSSのシート名"と"作成するCSVファイル名"を指定し、実行ボタンを押すと、GSSを読み込み、そのCSVファイルが出力される :

image.png

作業手順

1. Unityエディタ上からEditorWindowを開けるように実装

image.png

2. 動作に必要な最低限の描画処理の実装

image.png

3. GSSの読み込み処理の実装

4. 読み込んだデータをCSVファイルとして出力する処理の実装

5. 実行中の処理に関するログ出力の実装

image.png

実装方法

1. Unityエディタ上からEditorWindowを開けるように実装

image.png

以下のように、GssReaderTool.csを作成する :

GssReaderTool.cs (tap)
#if UNITY_EDITOR
using UnityEditor;

namespace App.ReadCsv
{
    public class GssReaderTool : EditorWindow
    {
        [MenuItem("Tools/GSS Reader Tool")]
        public static void OpenWindow()
        {
            GetWindow<GssReaderTool>("GSS読み込みツール");
        }

        private void OnGUI() // 描画処理
        {
            
        }
    }
}
#endif

ポイント:

  • #if UNITY_EDITOR : Unityエディタ上でしか動作しないようにしている。
  • MenuItem属性 : Unityエディタ上のToolsに項目を追加している。
  • GetWindow() : EditorWindowを開けるようにしている。

2. 動作に必要な最低限の描画処理の実装

image.png

以下のように、GssReaderTool.csに追記する :

GssReaderTool.cs (tap)
#if UNITY_EDITOR
+ using UnityEngine;
using UnityEditor;

namespace App.ReadCsv
{
    public class GssReaderTool : EditorWindow
    {
+       private string gssSheetId;
+       private string gssSheetName;
+       private string csvfileName;
        
        [MenuItem("Tools/GSS Reader Tool")]
        public static void OpenWindow()
        {
            GetWindow<GssReaderTool>("GSS読み込みツール");
        }

        private void OnGUI() // 描画処理
        {
+           GUILayout.Label("[GSSの読み込み, CSVファイルの作成]");

+           gssSheetId = EditorGUILayout.TextField("・ GSSのシートID :", gssSheetId);
+           gssSheetName = EditorGUILayout.TextField("・ GSSのシート名 :", gssSheetName);
+           csvfileName = EditorGUILayout.TextField("・ 作成するファイル名 :", csvfileName);
            if (GUILayout.Button("実行"))
            {
                // GSSの読み込み処理
            }
        }
    }
}
#endif

ポイント:

  • EditorGUILayout.TextField() : インプットフィールドを用いて、データを渡せるようにしている。

3. GSSの読み込み処理の実装

以下のように、GssReaderTool.csに追記する :

GssReaderTool.cs (tap)
#if UNITY_EDITOR
+ using System.Collections;
using UnityEngine;
+ using UnityEngine.Networking;
using UnityEditor;
+ using Unity.EditorCoroutines.Editor;

namespace App.ReadCsv
{
    public class GssReaderTool : EditorWindow
    {
+       private const string GSS_FORMAT = "tqx=out:csv";

        private string gssSheetId;
        private string gssSheetName;
        private string csvfileName;
+       private bool isLoadingGss;
        
        [MenuItem("Tools/GSS Reader Tool")]
        public static void OpenWindow()
        {
            GetWindow<GssReaderTool>("GSS読み込みツール");
        }

        private void OnGUI() // 描画処理
        {
            GUILayout.Label("[GSSの読み込み, CSVファイルの作成]");

            gssSheetId = EditorGUILayout.TextField("・ GSSのシートID :", gssSheetId);
            gssSheetName = EditorGUILayout.TextField("・ GSSのシート名 :", gssSheetName);
            csvfileName = EditorGUILayout.TextField("・ 作成するファイル名 :", csvfileName);
            if (GUILayout.Button("実行"))
            {
+               StartLoadGss(gssSheetId, gssSheetName);
            }
        }

+       /// <summary>
+       /// GSSの読み込み
+       /// </summary>
+       public void StartLoadGss(string id, string name)
+       {
+           if (isLoadingGss) // GSS読み込み中は実行しない
+           {
+               return;
+           }
+           else if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(csvfileName))
+           {
+               return;
+           }
+
+           var url = "https://docs.google.com/spreadsheets/d/" + id + "/gviz/tq?" + GSS_FORMAT + "&sheet=" + name;
+           EditorCoroutineUtility.StartCoroutine(LoadGss(url), this);
+       }

+       private IEnumerator LoadGss(string url)
+       {
+           using (var request = UnityWebRequest.Get(url)) // GSSの読み込み
+           {
+               isLoadingGss = true;
+               yield return request.SendWebRequest();
+               isLoadingGss = false;
+
+               if (request.result == UnityWebRequest.Result.ProtocolError)
+               {
+                   yield break;
+               }
+               else if (request.result == UnityWebRequest.Result.ConnectionError)
+               {
+                   yield break;
+               }
+           }
+       }
    }
}
#endif

ポイント:

  • isLoadingGss : GSS読み込み中は、読み込みを開始できないようにするフラグ
  • UnityWebRequest.Get() : URLを指定し、GSSを取得
  • usingステートメントを用いて、外部リソースを解放

4. 読み込んだデータをCSVファイルとして出力する処理の実装

以下のように、GssReaderTool.csに追記する :

GssReaderTool.cs (tap)
#if UNITY_EDITOR
using System.Collections;
+ using System.IO;
using UnityEngine;
using UnityEngine.Networking;
using UnityEditor;
using Unity.EditorCoroutines.Editor;

namespace App.ReadCsv
{
    public class GssReaderTool : EditorWindow
    {
+       private const string CSV_FOLDER_PATH = "Assets/MasterData/CSV/";
        private const string GSS_FORMAT = "tqx=out:csv";

        private string gssSheetId;
        private string gssSheetName;
        private string csvfileName;
        private bool isLoadingGss;
        
        [MenuItem("Tools/GSS Reader Tool")]
        public static void OpenWindow()
        {
            GetWindow<GssReaderTool>("GSS読み込みツール");
        }

        private void OnGUI() // 描画処理
        {
            GUILayout.Label("[GSSの読み込み, CSVファイルの作成]");

            gssSheetId = EditorGUILayout.TextField("・ GSSのシートID :", gssSheetId);
            gssSheetName = EditorGUILayout.TextField("・ GSSのシート名 :", gssSheetName);
            csvfileName = EditorGUILayout.TextField("・ 作成するファイル名 :", csvfileName);
            if (GUILayout.Button("実行"))
            {
                StartLoadGss(gssSheetId, gssSheetName);
            }
        }

        /// <summary>
        /// GSSの読み込み
        /// </summary>
        public void StartLoadGss(string id, string name)
        {
            if (isLoadingGss) // GSS読み込み中は実行しない
            {
                return;
            }
            else if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(csvfileName))
            {
                return;
            }
 
            var url = "https://docs.google.com/spreadsheets/d/" + id + "/gviz/tq?" + GSS_FORMAT + "&sheet=" + name;
            EditorCoroutineUtility.StartCoroutine(LoadGss(url), this);
        }

        private IEnumerator LoadGss(string url)
        {
            using(var request = UnityWebRequest.Get(url)) // GSSの読み込み
            {
                isLoadingGss = true;
                yield return request.SendWebRequest();
                isLoadingGss = false;
 
                if (request.result == UnityWebRequest.Result.ProtocolError)
                {
                    yield break;
                }
                else if (request.result == UnityWebRequest.Result.ConnectionError)
                {
                    yield break;
                }
                
+               CreateCsv(csvfileName, request.downloadHandler.text);
            }
        }

+       /// <summary>
+       /// CSVファイルの作成
+       /// </summary>
+       private void CreateCsv(string fileName, string data)
+       {
+           if (string.IsNullOrEmpty(data))
+           {
+               return;
+           }
+
+           if (!Directory.Exists(CSV_FOLDER_PATH)) // フォルダがなければ、
+           {
+               Directory.CreateDirectory(CSV_FOLDER_PATH); // 新規作成
+           }
+
+           var path = CSV_FOLDER_PATH + fileName + ".csv";
+           using (var sw = new StreamWriter(path, false)) // 新規作成 or 上書き
+           {
+               try
+               {
+                   sw.Write(data);
+                   AssetDatabase.Refresh(); // エディタ上のアセットの更新
+               }
+               catch (System.Exception e)
+               {
+
+               }
+           }
+       }
    }
}
#endif

ポイント:

  • CSV_FOLDER_PATH : CSVファイルを保存するフォルダのパス
  • Directory.CreateDirectory() : フォルダの新規作成処理
  • StreamWriter() : ファイルの作成と上書き処理を実行するクラス
  • AssetDatabase.Refresh() : 作成されたファイルがすぐにエディタに表示されるようにする。

この時点で、GSSの読み込みと、CSVファイルの作成の実装は完了しているが、利便性向上のため、実行中の処理に関するログを表示する描画処理を、以下で実装する。

5. 実行中の処理に関するログ出力の実装

image.png

以下のように、GssReaderTool.csに追記する :

GssReaderTool.cs (tap)
#if UNITY_EDITOR
using System.Collections;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
using UnityEditor;
using Unity.EditorCoroutines.Editor;

namespace App.ReadCsv
{
    public class GssReaderTool : EditorWindow
    {
        private const string CSV_FOLDER_PATH = "Assets/MasterData/CSV/";
        private const string GSS_FORMAT = "tqx=out:csv";

        private string gssSheetId;
        private string gssSheetName;
        private string csvfileName;
+       private string log;
        private bool isLoadingGss;
        
        [MenuItem("Tools/GSS Reader Tool")]
        public static void OpenWindow()
        {
            GetWindow<GssReaderTool>("GSS読み込みツール");
        }

        private void OnGUI() // 描画処理
        {
            GUILayout.Label("[GSSの読み込み, CSVファイルの作成]");

            gssSheetId = EditorGUILayout.TextField("・ GSSのシートID :", gssSheetId);
            gssSheetName = EditorGUILayout.TextField("・ GSSのシート名 :", gssSheetName);
            csvfileName = EditorGUILayout.TextField("・ 作成するファイル名 :", csvfileName);
            if (GUILayout.Button("実行"))
            {
+               log = string.Empty;
                StartLoadGss(gssSheetId, gssSheetName);
            }
+           DrawLogGUI();
        }

        /// <summary>
        /// GSSの読み込み
        /// </summary>
        public void StartLoadGss(string id, string name)
        {
            if (isLoadingGss) // GSS読み込み中は実行しない
            {
+               AddLog("Error : GSSを読み込み中...");
                return;
            }
            else if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(csvfileName))
            {
+               AddLog("Error : 設定項目をすべて記入してください。");
                return;
            }
 
            var url = "https://docs.google.com/spreadsheets/d/" + id + "/gviz/tq?" + GSS_FORMAT + "&sheet=" + name;
            EditorCoroutineUtility.StartCoroutine(LoadGss(url), this);
        }

+       private void AddLog(string message)
+       {
+           log += $" - {message}\n";
+       }

        private IEnumerator LoadGss(string url)
        {
            using(var request = UnityWebRequest.Get(url)) // GSSの読み込み
            {
+               AddLog("GSSの読み込み開始");
            
                isLoadingGss = true;
                yield return request.SendWebRequest();
                isLoadingGss = false;
 
                if (request.result == UnityWebRequest.Result.ProtocolError)
                {
+                   AddLog($"Error Load GSS : {request.error} (GSSの存在性と公開設定を確認して下さい)");
                    yield break;
                }
                else if (request.result == UnityWebRequest.Result.ConnectionError)
                {
+                   AddLog($"Error Load GSS : {request.error} (ネットワークに接続されているかを確認して下さい)");
                    yield break;
                }
+               AddLog("GSSの読み込み完了");

                CreateCsv(csvfileName, request.downloadHandler.text);
            }
        }

        /// <summary>
        /// CSVファイルの作成
        /// </summary>
        private void CreateCsv(string fileName, string data)
        {
            if (string.IsNullOrEmpty(data))
            {
+               AddLog("Error Create CSV : データが空であるため、ファイルを作成できませんでした。");
                return;
            }

+           AddLog("CSVファイルの作成開始");
 
            if (!Directory.Exists(CSV_FOLDER_PATH)) // フォルダがなければ、
            {
                Directory.CreateDirectory(CSV_FOLDER_PATH); // 新規作成
            }
 
            var path = CSV_FOLDER_PATH + fileName + ".csv";
            using (var sw = new StreamWriter(path, false)) // 新規作成 or 上書き
            {
                try
                {
                    sw.Write(data);
                    AssetDatabase.Refresh(); // エディタ上のアセットの更新
+                   AddLog($"CSVファイルの作成完了 : {path}");
                }
                catch (System.Exception e)
                {
+                   AddLog(e.ToString());
                }
            }
        }

+       /// <summary>
+       /// ログを出力
+       /// </summary>
+       private void DrawLogGUI()
+       {
+           if (string.IsNullOrEmpty(log)) { return; }
+
+           GUILayout.FlexibleSpace();
+           GUILayout.Box("", GUILayout.ExpandWidth(true), GUILayout.Height(3));
+
+           GUILayout.Label(" [Log] ");
+           GUILayout.Label(log, new GUIStyle() { wordWrap = true, normal = new GUIStyleState() { textColor = new Color(1, 1, 1, 1) } });
+
+           if (GUILayout.Button("Clear Log"))
+           {
+               log = string.Empty;
+           }
+           GUILayout.Space(7);
+       }
    }
}
#endif

ポイント:

  • DrawLogGUI() : ログが空でなければ、ログ内容を描画
  • GUILayout.FlexibleSpace() : ログに関するUIを一番下に表示
  • GUILayout.Box() : 罫線を引く

最後に

どうでしたか、わかりやすかったでしょうか。GSSを用いてデータを管理することで、共同編集ができ、Gitでの競合の心配がなくなるので、積極的に使用したいところです。また、ネットワーク接続時は、アプリ起動時にGSSを取得してCSVファイルを上書きし、そうでないときは、すでにあるCSVファイルを参照するといった設計にすると、アプリ配信後にも対応が出来るので便利だと思われます。

また、以下のような多言語での文言の管理に適しています :
image.png
これをCSVで出力することで、コンマ区切りの文字列として取得できるので、StringReaderクラスなどを使うことで、行ごとにstring配列(例 : {W001, 文言, wordings})としてリストに格納しておくことにより、容易にデータを参照することができるようになります。以下に、その実装を記したコードを置いておきます :

CsvReader.cs (tap)
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using System.IO;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace App.ReadCsv
{
    public class CsvReader : MonoBehaviour
    {
        [SerializeField]
        private TextAsset csv;

        public List<string[]> ReadDatas { get; private set; } = new List<string[]>();

        /// <summary>
        /// idを用いて、データを検索
        /// </summary>
        public string[] FindDataWithId(string id)
        {
            var lineData = ReadDatas.FirstOrDefault(e => e[0] == id);
            if (lineData == default) { return lineData; }

            var lineDataExceptId = new string[lineData.Length - 1];
            for (var i = 0; i < lineData.Length - 1; i++) // idを取り除く
            {
                lineDataExceptId[i] = lineData[i + 1];
            }
            return lineDataExceptId;
        }

#if UNITY_EDITOR
        private void OnValidate()
        {
            if (!csv) { return; }

            var path = AssetDatabase.GetAssetPath(csv);
            if (!path.Contains(".csv"))
            {
                csv = null;
                Debug.LogError("CSVファイルを指定してください。");
                return;
            }

            ReadDatas = Read(csv.text);
        }

        /// <summary>
        /// データの読み込み
        /// </summary>
        private static List<string[]> Read(string text)
        {
            var readDatas = new List<string[]>();
            if (string.IsNullOrEmpty(text)) { return readDatas; }

            var reader = new StringReader(text);
            while (reader.Peek() != -1) // 読み込む文字がなくなるまで実行
            {
                var line = reader.ReadLine();   // 1行づつ読み込む
                readDatas.Add(line.Split(",")); // 行を分割して保存
            }
            Debug.Log($"Read CSV :\n {string.Join("\n", readDatas.Select(e => string.Join(", ", e)))}");

            return readDatas;
        }
#endif
    }

#if UNITY_EDITOR
    [CustomEditor(typeof(CsvReader))]
    public class CsvReaderEditor : Editor
    {
        private string[] data;
        private string id;
        private bool isFoldout;

        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();

            isFoldout = EditorGUILayout.Foldout(isFoldout, "データ確認用");
            if (isFoldout)
            {
                id = EditorGUILayout.TextField("ID:", id);
                if (GUILayout.Button("データの取得"))
                {
                    var creator = (CsvReader)target;
                    data = creator.FindDataWithId(id);
                }

                if (data != default)
                {
                    EditorGUILayout.TextField("取得したデータ:", string.Join(", ", data));
                }
            }
        }
    }
#endif
}
  • Read() : CSVファイルを行ごとに読み込み、リスト(List<string[]> ReadDatas)に格納
  • FindDataWithId(string id) : idから行データを取得
  • CsvReaderEditorクラス : インスペクター上から、データ取得確認ができるようにしている。
  • 上記スプレッドシートの場合、idをW001としてデータを取得すると、{文言, wordings}が返される。

また気が向いたら何か書きます。それでは。

宣伝 : CUPLEXという時間差アクションパズルゲームを作成しています。私は主にプログラムとプロジェクト管理を担当しています。よかったら、覗いてみてください↓
https://store.steampowered.com/app/2499100/CUPLEX/

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?