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

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

# 目次
1. [概要](#概要)
2. [事前準備](#事前準備)
3. [使用方法](#使用方法)
4. [作業手順](#作業手順)
5. [実装方法](#実装方法)
6. [最後に](#最後に)



# 概要
以下のような、指定したスプレッドシートを読み込み、それをCSVファイルとして出力するEditorWindowの作成方法を解説します : 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480241/5cf13730-0818-f4a7-86e0-3a6c4452d406.png)
(GSSReaderTool : EditorWindow)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480241/fb4f0167-29ef-523a-5fda-abf0fc8d0e73.png)
(今回使用するスプレッドシート)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480241/0cb63ba8-dcf2-38bc-cbee-40133c602145.png)
(上記スプレッドシートから作成されるCSVファイル)

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



# 事前準備
事前にスプレッドシート(GSS)を作成しておきます。
1. GSSをGoogleドライブなどの共有可能な場所に作成
2. セルの値やページ名を上記のようにしておく。
3. GSSの画面右上の共有ボタンから、アクセス制限を以下のようにしておく :

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480241/6fd8f88e-e24a-11fb-4b34-c1f3540ff154.png)

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



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

##### 1. 下記スクリプトを作成する : 
<details><summary>GssReaderTool.cs (tap)</summary>

```
#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
```
</details>



##### 2. Unityエディタ上で"Tools/GSS Reader Tool"を選択する : 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480241/b7e8157c-a021-2605-f142-4309503c5609.png)


##### 3. すると、下記EditorWindowが出現するので、"GSSのシートID"と"GSSのシート名"と"作成するCSVファイル名"を指定し、実行ボタンを押すと、GSSを読み込み、そのCSVファイルが出力される : 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480241/5cf13730-0818-f4a7-86e0-3a6c4452d406.png)

:::note info
* GSSのシートIDの場所は、GSSのリンクの下記の部分 : 
　https://docs.google.com/spreadsheets/d/{GSSのシートID}/edit#gid=0
* CSVファイルの出力先フォルダ (なければ自動で作成) : 
　Assets/MasterData/CSV
:::



# 作業手順
#### 1. Unityエディタ上からEditorWindowを開けるように実装
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480241/d12fbbeb-0329-0f4d-2dc6-bdb84fb449aa.png)

#### 2. 動作に必要な最低限の描画処理の実装
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480241/de3ff3fc-ff4c-8e61-db9d-907ec2aeada3.png)

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

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

#### 5. 実行中の処理に関するログ出力の実装
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480241/d97b37d6-5cfa-c68d-1303-8b7f7fa89251.png)



# 実装方法
#### 1. Unityエディタ上からEditorWindowを開けるように実装
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480241/917e3152-780b-8bc8-3ee3-b16ae97c761c.png)

以下のように、GssReaderTool.csを作成する : 
<details><summary>GssReaderTool.cs (tap)</summary>

```
#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
```
</details>

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



#### 2. 動作に必要な最低限の描画処理の実装
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480241/de3ff3fc-ff4c-8e61-db9d-907ec2aeada3.png)

以下のように、GssReaderTool.csに追記する : 
<details><summary>GssReaderTool.cs (tap)</summary>

```diff_c_sharp
#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
```
</details>

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



#### 3. GSSの読み込み処理の実装
以下のように、GssReaderTool.csに追記する : 
<details><summary>GssReaderTool.cs (tap)</summary>

```diff_c_sharp
#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
```
</details>

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



#### 4. 読み込んだデータをCSVファイルとして出力する処理の実装
以下のように、GssReaderTool.csに追記する : 
<details><summary>GssReaderTool.cs (tap)</summary>

```diff_c_sharp
#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
```
</details>

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


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

#### 5. 実行中の処理に関するログ出力の実装
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480241/d97b37d6-5cfa-c68d-1303-8b7f7fa89251.png)

以下のように、GssReaderTool.csに追記する : 
<details><summary>GssReaderTool.cs (tap)</summary>

```diff_c_sharp
#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
```
</details>

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



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

また、以下のような多言語での文言の管理に適しています : 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480241/d0ca8b3d-22d3-4910-d348-7f067e7469f9.png)
これをCSVで出力することで、コンマ区切りの文字列として取得できるので、StringReaderクラスなどを使うことで、行ごとにstring配列(例 : {W001, 文言, wordings})としてリストに格納しておくことにより、容易にデータを参照することができるようになります。以下に、その実装を記したコードを置いておきます : 
<details><summary>CsvReader.cs (tap)</summary>

```
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
}
```
</details>

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

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

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