Unity Excel Importerを作ってみた

マスターデータ等、Unityでテーブル形式のデータを扱う際はExcelを使うと便利なことがよくあります。その際は一般的に[NPOI](https://npoi.codeplex.com/)を利用して読み込みを行うのですが、データに合わせて毎回インポート処理を実装するのが手間でした。そこで、今回その仕組みを汎用化しプラグインにしてみました。

ちなみに、UnityでExcelを読み込むためのプラグインで有名なものに、テラシュールブログ・@tsubaki様（いつもお世話になっております）の[Unity Excel Importer Maker](https://github.com/tsubaki/Unity-Excel-Importer-Maker)がありますが、こちらはImporterを「Make」するためのプラグインです。本プラグインはImporter部分はリフレクションを用いて１つのまま、Excel内のデータを保持するScriptableObjectを作るという点で違いがあります。

2019/6/12: いくつか説明を加筆しました。

プラグインインポート
---------------

https://github.com/mikito/unity-excel-importer
こちらのリポジトリからソースコードをダウンロードし、Assets/ExcelImporter以下をプロジェクトに追加してください。

Excelファイルごとの事前準備
---------------------

Excelファイルごとに、それに紐づく必要なクラスの定義・生成を一度だけ行う必要があります。

### 1. Entityクラスの定義

まずは、Excelからロードしたいデータ構造のクラスを手動で定義します。
本プラグインではこのデータ構造クラスをEntityクラスと呼んでいます。
クラスにはpublicなフィールドで型と名前を定義し、System.Serializableアトリビュートを付与します。

```c#:MstItemEntity.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable] // アトリビュートを付与
public class MstItemEntity
{
	public int id; // publicでエクセルでインポートしたい型と名前を定義
	public string name;
	public int price; 
}
```

### 2. Excelファイルの作成と追加

次に、Excelファイルを作成します。
Excelファイルは**1行目にEntityクラスのフィールド名と同じ名称**を記述し、
そのフィールドの型に基づく値を2行目から入力します。
さらにシートの名前を適当な名称に変更します。

<img width="229" alt="34470078-7246b924-ef6d-11e7-8711-acb49ae2c349.png" src="https://qiita-image-store.s3.amazonaws.com/0/97161/7bbce2b2-fbce-5711-c675-10c9cb929b49.png">


<font color=red>**注:**
Excelファイルの1行目の名称は、Entityクラスのフィールド名と紐づいているため、どちらかを変更した場合はもう片方を合わせる必要があります。ちなみに並び順には影響されません。
</font>


作成したExcelファイルをAssets以下のどこのディレクトリでも良いので、
Unityにインポートします。
~~この時１度だけワーニングが出ますが、無視して構いません。~~（v1.1で出力しないようになりました）

<img width="247" alt="34470091-8bdc1c76-ef6d-11e7-88e5-76c76294a26c.png" src="https://qiita-image-store.s3.amazonaws.com/0/97161/004e601d-f1d3-4ec2-4ea0-00dc210311b8.png">

### 3. ExcelAssetスクリプトの生成

次にEntityクラスを保持するためのScriptableObjectクラスを生成します。
こちらは総じてExcelAssetクラスと呼んでいます。

Excelファイルを選択した状態でProjectビューのCreateメニューからExcelAssetScriptを実行すると、スクリプトファイルの保存先が聞かれるため、適当なディレクトリを選択します。

<img width="527" alt="34470096-abdadd0a-ef6d-11e7-9bf6-e9458c660264.png" src="https://qiita-image-store.s3.amazonaws.com/0/97161/cac38524-dd29-0f43-d577-653141ac8ed7.png">


すると、Excelファイルと同名のテンプレートスクリプトが生成されます。
生成されたスクリプト内には、コメントアウトされた行があり、
Excelファイル内のシート数と名前が反映されています。

```c#:MstItems.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExcelAsset]
public class MstItems : ScriptableObject
{
	//public List<EntityType> Entities; // Replace 'EntityType' to an actual type that is serializable.
}
```

ちなみに、ExcelAssetスクリプトの生成はプラグインの機能を利用すると楽ですが、大した内容ではないので手動で定義しても問題ありません。（ExcelAssetアトリビュートが付いたScriptableObjectで、Excelファイルと同名である必要があります。）

### 4. ExcelAssetスクリプトの修正

上記で生成したスクリプトを修正します。
コメントアウトを外し、EntityTypeを先ほど定義した実際のTypeに置き換えます。

```c#:MstItems.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExcelAsset]
public class MstItems : ScriptableObject // クラス名とExcelファイル名を合わせる
{
	public List<MstItemEntity> Entities; // シート名とフィールド名を合わせる
}
```

<font color=red>**注:**
上記ExcelAssetスクリプトのフィールド名、「Entities」の部分は**Excelファイル内のシート名**と紐づいています。シート名を変更した場合はこちらの定義も変更する必要があります。</font>

<font color=red>またクラス・スクリプトファイル名の「MstItems」はExcelファイル名と紐づいています。こちらも、Excelファイルの名称を変更した場合は追従して変更する必要があります。</font>

自動更新とデータの利用
-----------
一度上記作業を終えた後は、
Excelファイルを更新する度に自動で対応するExcelAssetのScriptableObjectを生成・更新します。

初回設定時はExcelファイルに対してReimportを行うと即時処理が実行され良いでしょう。

<img width="570" alt="34470102-cde2bcc4-ef6d-11e7-8083-a661b82ae707.png" src="https://qiita-image-store.s3.amazonaws.com/0/97161/e9411b58-1a89-079a-5d71-9923ef1fb21a.png">

このデータを利用するためには、
何かしらのMonoBehaviourクラスのSerializeFieldのフィールドからExcelAssetオブジェクトを参照してください。

その他
---------
### bool型の入力
Excelファイルのセルにて、FALSE、 TRUEの文字列を入力します（小文字で入力した場合、エクセルが自動的にbool値と判別して大文字に変換します）。

### コメント行
行の先頭のセルにて最初の文字を#にするとその行をコメント行とみなします。

<img width="432" alt="スクリーンショット 2019-06-12 23.28.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97161/1251f241-9a03-3bca-0001-9efc3c9ee399.png">

### アセット生成パスの変更

ExcelAssetアトリビュートにAssetPathを指定すると、
Excelファイルと同じディレクトリに生成されるAssetファイルのパスを変更できます。
例えば、Resources以下に変更すれば、
Resources.Load使って参照を取るということも可能です。

```c#:MstItems.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExcelAsset(AssetPath = "Resources/MasterData")]
public class MstItems : ScriptableObject
{
	public List<MstItemEntity> Entities;
}
```

### Enumの利用
Entityクラス内のフィールドの型にenumを指定し、Excelファイルのセル内にそのenumの要素名を文字列で入力すると、自動でenumに変換します。

このとき、Excelファイル内で「データの入力規則」の機能を使って
enum名のプルダウンを作ると便利です（以下の図はLibreOfficeのためUIが異なる場合があります）。

![enum.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97161/7fd13084-ff08-281d-7c5f-78b3ee639213.png)

### データ読み込みの終了位置
Excelファイルのシート内で、空の行もしくは空の列が出現した場合は、そこでデータの終了とみなしそこでセルの読み込みを打ち切ります。

以降のセルはコメントを残したり、計算式を記述するのに利用できます。

<img width="840" alt="スクリーンショット 2019-06-13 0.44.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97161/80f3c3c8-3d37-c4ea-5f67-e26725a818ac.png">

### 複数シートの対応

１つのExcelファイルで複数シートに対応したい場合は、ExcelAssetの定義にシートと同名の定義を追加します。


```c#:MstItems.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExcelAsset]
public class MyExcelAsset : ScriptableObject
{
	public List<Hoge> Sheet1; // シート名と変数名を合わせる
	public List<Fuga> Sheet2;
	public List<Piyo> Sheet3;
}
```

### 空のセルの挙動

セルを空にした場合は、Entityクラスで定義したフィールドの型のデフォルト値が入ります。