【Unity】CSV SerializerでCSVファイルを読み込む

# はじめに
Unityのアセットの1つ[「CSV Serializer」](https://assetstore.unity.com/packages/tools/integration/csv-serialize-135763?locale=ja-JP)を使えば外部のCSVファイルに記述されたデータを自作クラスの配列に流し込めます。例えばRPGの敵キャラのデータベースをCSVとして用意し、それを直接読み込んで使ったりできます。

ただこのアセットは付属のReadmeがやや簡略にすぎる感があり、またWeb上を探しても手取り足取りの解説をしている記事はなかなかなく、プログラミングにあまり慣れていないと扱いが難しいかもしれないのではと思いました。
そこで、本記事ではCSV Serializerの各機能の使い方について詳しく説明していきます。

（※C#コードの基本的な書き方やクラスに関する知識、書いたC#コードをUnityのシーン内で使う方法に関しては習得していることを前提とします）

# アウトライン

- CSVを配列に流し込む
- CSVをScriptableObjectに流し込む
- 単純な値や文字列以外のデータを流し込む

# CSVを配列に流し込む
まずCSVを流し込む対象として、下記のように**[System.Serializable]**属性をつけたクラスを用意します。

```cs:MonsterData.cs
[System.Serializable]
public class MonsterData
{
    public string name;
    public int HP;
    public int ATK;
    public int DEF;
}
```

次に流し込むCSVを書きます。1行目に列名を書いていますが、これが上記クラスの持つ変数の名前と一致していることに注意してください。

```csvs:CSVTestData.csv
name,HP,ATK,DEF
あかスライム,100,10,10
あおスライム,200,20,20
きいろスライム,300,30,30 
```

このCSVをstring型（文字列）として読み込めば、後はそれをCSV Serializerの機能でクラスの配列に流し込むことができます。下記はそれを行うためのコードです。

```cs:CSVProcessingTest.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CSVProcessingTest : MonoBehaviour
{
    //　流し込む配列
    public MonsterData[] monsterData;

    void Start()
    {
        //　テキストファイルの読み込みを行ってくれるクラス
        TextAsset textasset = new TextAsset();
        //　先ほど用意したcsvファイルを読み込ませる。
        //　ファイルは「Resources」フォルダを作り、そこに入れておくこと。また"CSVTestData"の部分はファイル名に合わせて変更する。
        textasset = Resources.Load("CSVTestData", typeof(TextAsset)) as TextAsset;
        //　CSVSerializerを用いてcsvファイルを配列に流し込む。
        monsterData = CSVSerializer.Deserialize<MonsterData>(textasset.text);
    }

    void Update()
    {

    }
}
```

このコードをシーン中の何かしらのGameObjectにアタッチして実行し、インスペクタから下図のように配列にデータが正しく流し込まれていることを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63319/2d97ae23-6f27-e361-ef7a-60d65a02c378.png)

# CSVをScriptableObjectに流し込む
Unityにはゲーム中で使うデータを保存しておくための仕組みとしてScriptableObjectというものがあります。詳細については他の記事に譲りますが、ScriptableObjectはプロジェクト内のどこからでも読み込んで使うことができ、ゲーム中のメモリの節約にもつながる便利なものです。
CSV SerializerではこのScriptableObjectにCSVファイルの中身を流し込むこともできます。

ここでは先程の配列にCSVファイルを流し込むコードをScriptableObject版に書き直してみます。
まずは以下のように、ScriptableObjectになるクラスを定義します。CSVを流し込みたいクラスの配列を保持させています。

```cs:MonsterData.cs
public class MonsterDataBase : ScriptableObject {
    public MonsterData[] datas;
}
```

次にScriptableObjectに流し込むコードを用意します。まずはusingに

```cs:CSVProcessingTest.cs
#if UNITY_EDITOR
using UnityEditor;
#endif
```

を追加したうえで、以下のように書きます。

```cs:CSVProcessingTest.cs
#if UNITY_EDITOR
public class PostProcessingTest : AssetPostprocessor
{
  static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
  {
    foreach (string str in importedAssets)
    {
      //　IndexOfの引数は"/(読み込ませたいファイル名)"とする。
      if (str.IndexOf("/CSVTestData.csv") != -1)
      {
        //　エディタ内で読み込むならResource.Loadではなくこちらを使うこともできる。
        TextAsset textasset = AssetDatabase.LoadAssetAtPath<TextAsset>(str);
        //　同名のScriptableObjectファイルを読み込む。ない場合は新たに作る。
        string assetfile = str.Replace(".csv", ".asset");
        //　※"MonsterDataBase"はScriptableObjectのクラス名に合わせて変更する。
        MonsterDataBase md = AssetDatabase.LoadAssetAtPath<MonsterDataBase>(assetfile);
        if (md == null){
          md = new MonsterDataBase();
          AssetDatabase.CreateAsset(md, assetfile);
        }
        //　※"MonsterData"はScriptableObjectに入れるデータのクラス名に合わせて変更。
        //　※"datas"もScriptableObjectが保有する配列名に合わせる。
        md.datas = CSVSerializer.Deserialize<MonsterData>(textasset.text);
        EditorUtility.SetDirty(md);
        AssetDatabase.SaveAssets();
      }
    }
  }
}
#endif
```

書けたらCSVファイルにデータを追加し保存してみましょう。データの入ったScriptableObjectが自動的に作られたり更新されたりするはずです。

ScriptableObjectは**Unityエディタ上でデータを更新したときにのみその結果が保存され、ビルド後の実行で更新した際は保存されない**という性質があります。よって、コードもエディタ上で実行されるものとして書くのが望ましいです。
上記のクラスは**AssetPostprocessor**というものを継承していますが、これを使うことで**プロジェクトのファイルになにか変更があったときに行う処理**が書けます。**OnPostprocessAllAssets**関数がファイル変更時に呼ばれるので、その中にScriptableObjectの編集（なければ作成）を行う処理を入れているというわけです。
（※なおこの部分はCSV Serializerの付属サンプルに倣ったコードになっています。）

# 単純な値や文字列以外のデータを流し込む
CSV Serializerには以下のような機能もあります。

- 流し込んだデータを自分で定義した列挙型（enum）に直す
- コンマで区切った文字列を配列として流し込む（もちろんただ区切ったのではそれぞれ別なセルとして扱われてしまうので、文字列全体がダブルクォートで囲まれている必要があります）
- 画像のアドレスを読み込ませ、直接Spriteに変換する

例えば以下のようなクラスを考えてみましょう。

```cs:MonsterData.cs
[System.Serializable]
public class MonsterData
{
    public string name;
    public int HP;
    public int ATK;
    public int DEF;

    public MonsterCategory category;
    public Sprite sprite;
    public string[] skill;
}

public enum MonsterCategory{
  amorphous = 1,
  demihuman = 2,
  beast = 3
}
```

これに対してこのようなデータを読み込ませてみます。

```csvs:CSVTestData.csv
name,HP,ATK,DEF,category,sprite,skill
スライム,100,10,10,amorphous,Assets/Resources/fantasy_game_character_slime.png,"たいあたり"
ウルフ,200,20,20,beast,Assets/Resources/animal_ookami.png,"かみつき,とおぼえ"
ゴブリン,300,30,30,demihuman,Assets/Resources/fantasy_goblin.png,"なぐる,投石,挑発"
```

生成されたScriptableObjectを見ると、下記のように列挙型、Sprite、配列も読み込めているのがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63319/9eae87a2-debd-1b2b-0073-3fea899e903c.png)
CSV Serializerのコードを直接書き換えることでSprite以外の型についても文字列から何かしらのデータに変換するということも可能になっているのですが、それについてはここでは省略させていただきます。