【Unity】CSVからScriptableObjectにデータを流し込むEditor拡張サンプル
このブログではScriptableObjectの概要や作り方、ScriptableObjectを使うメリットについて記事を掲載しています。
その中でScriptableObjectおすすめだよ! と書いたので、より実践的に使える形で紹介したいと思います。今回はEditor拡張を使ってCSVファイルからScriptableObjectにデータを格納するサンプルをご紹介。
主にEditor拡張の部分に焦点を当てたサンプル……というかチュートリアルになっています。
環境
macOS 10.14 Mojave
Unity2018.2.20f1
偉大なる先達
Editor拡張について学ぶときは「Editor拡張入門」をぜひご覧ください。
マスタデータをScriptableObjectに流し込むEditor拡張はテラシュールブログさんのものを参考にしています。いつもお世話になっております。
ここではExcelが無い人向けに、CSVファイルからScriptableObjectにデータを格納する方法を考えます。
Editor拡張について
Editor拡張とは、Unityエディタの機能を拡張すること、またそのためのスクリプトのことを指します。
Unityエディタは多くの人にとってそのままでも使いやすい形式ですが、プロジェクトの要件に応じて、より詳細なデータを表示したいとか、デザイナーでも操作しやすいようにボタンを配置したいといった願望が生じることがあります。
その際、Unityエディタに機能を追加するスクリプトを書くことによって、独自のウィンドウを作ったり、ボタンの色を変えたり、はたまたデータをインポートしたりと、できることが広がっていきます。
作りたいゲームに応じた拡張を施すことによって、プロジェクトをより効率的に進めることができるんですね。素晴らしかー。
Editor拡張のスクリプトを書く際はUnityEditorの名前空間にあるクラスを使います。実はUnityエディタ自体もUnityEditorの名前空間にあるクラスで書かれているので、そこに自分で書き足していくイメージです。
UnityEditor側のスクリプトについては、なんとGitHubで公開されています。UnityでEditor拡張を行う際の勉強として閲覧できるので、慣れてきたらこっちもご覧ください。
今回の作業
今回は、CSVファイルとして出力した敵キャラのデータをUnityのScriptableObjectにセットするサンプルを作成してみます。
作業の流れとしては、
- CSVファイルの準備
- データを格納するScriptableObjectのスクリプトを作成
- データ読み込みボタンを表示するScriptableObjectを作成
- 読み込んだデータをScriptableObjectにセットするEditor拡張スクリプトを作成
となっています。作成するスクリプトファイルは計3個。
敵キャラデータ格納用のScriptableObjectにデータがセットされたことをInspectorウィンドウで確認できたら成功です。
CSVファイルの準備
以下のような表を準備します。Excelで作ってもいいですし、NumbersやGoogleスプレッドシートでも大丈夫です。
名前 | 最大HP | 攻撃力 | 防御力 | 経験値 | ゴールド |
スライム | 4 | 2 | 2 | 1 | 2 |
ゴブリン | 8 | 4 | 2 | 2 | 3 |
ジャイアントマウス | 12 | 5 | 3 | 3 | 5 |
作成したらCSV形式でエクスポートします。空白の行や列があるとそれも出力されるため、必要な行と列だけのデータにしておきます。
CSVファイルはProjectウィンドウに持ってきて任意の場所に配置します。
データ格納用のScriptableObjectを作成
続いてUnityでスクリプトを作成します。Projectウィンドウで任意のフォルダで右クリックして[Create] -> [C# Script]を選択します。
作成したファイルは「EnemyData」にリネームしました。CSVからインポートする対象のデータ種別に応じて分かりやすい名前をつけるといい感じです。
スクリプトの中身は以下のように編集します。MonoBehaviourではなくScriptableObjectを継承することに注意。
一応メニューバーからScriptableObjectのアセットファイルを作成できるようにはしていますが、今回はEditor拡張のスクリプトで自動的にアセットファイルを作成する予定です。
データ読み込みボタンを表示するScriptableObject
「ん?」となった方もいるかも。
今回は独自のウィンドウを作らず、データインポート用のScriptableObjectアセットを作成し、Inspectorウィンドウでデータ読み込み用のボタンを表示するようにします。インポートのお仕事をするScriptableObjectです。
Inspectorウィンドウの表示をカスタマイズ出来ちゃうところが今回の注目ポイントです。
また、このScriptableObjectアセットには読み込み対象のCSVファイルを参照させます。
まずはスクリプトファイルの作成から。上記の場合と同じようにProjectウィンドウで任意のフォルダで右クリックして[Create] -> [C# Script]を選択します。
作成したファイルは「CsvImporter」にリネームしました。
CsvImporterの中身は以下のように編集します。
こちらもScriptableObjectを継承します。
読み込む対象のCSVファイルへの参照をセットしたいので、そのためのフィールドを用意します。CSVファイルはTextAssetとして扱われます。
CreateAssetMenuでこのインポート用アセットを作成できるようにします。パスや名前は分かりやすいようにしておけばOK。
指定したパスからアセットファイルを作成します。上のスクリプトの例だと、[Create] -> [MyScriptable] ->[Create CSV Importer]です。
画面の上にあるメニューバーからも作成可能です。
どちらからでもこのアセットファイルが作成されます。名前は分かりやすく「CsvImporter」にしてみました。
このアセットファイルを選択してInspectorウィンドウを見るとこのようになっています。
CSVファイルの参照をセットするフィールドが表示されています。ここに読み込みたいCSVファイルをセットするイメージです。
今はまだ読み込みを実行するトリガーが何もないため、Editor拡張で読み込みボタンを作ります。そのボタンをクリックするとCSVファイルを読み込んで、受け皿用のクラスであるEnemyDataのアセットファイルを作成する処理を含めます。
CSV読み込み処理をEditor拡張で実装
Editor拡張を行うときはスクリプトファイルを「Editor」フォルダの中に置きます。この「Editor」フォルダは特殊なフォルダで、ここに置かれたスクリプトはUnityエディタでのみ使うスクリプトとして扱われます。
Unityエディタ以外の環境向けにビルドする際は「Editor」フォルダの中身が含まれません。まさにUnityエディタで使うためだけのスクリプト、Unityエディタでしか使えないスクリプトです。
「Editor」フォルダはAssetフォルダより下であればどこに配置してもOKです。好きな場所に作りましょ。私は今回のサンプル用に「DataConverter」という名前のフォルダを用意し、その中に「Editor」フォルダを作りました。
Inspectorでボタンを表示
続いて「Editor」フォルダの下に移動し、スクリプトファイルを作成します。右クリック -> [Create] -> [C# Script]を選択。
今回は上で作った「CsvImporter」の表示と処理を変更したいので、スクリプトの名前は「CsvImporterEditor」にしました(安直)
これからスクリプトの中身を編集しますが、最初はボタンを表示するところまでやってみます。
MonoBehaviourのスクリプトを書く時とは結構様子が違うため順番に補足説明を。
まずは4行目の「using UnityEditor;」を追加します。Editor拡張を行う際はUnityEditorの名前空間を入れます。
6行目には[CustomEditor]のAttributeを追加。typeofを使って、どのクラスに対してこのEditor拡張スクリプトを適用するか指定することができます。今回は「CsvImporter」の表示を変えたいのでこのクラス名を指定します。
7行目では継承元をMonoBehaviourからEditorに変更します。
8行目に記述したOnInspectorGUI()はカスタムされたInspectorを表示するときに使うメソッドです。この中にGUIを変更するパーツを記述していきます。
9行目のDrawDefaultInspector()はその名の通りデフォルトのInspectorと同じようにパーツを描画してくれるメソッドです。今回は読み込み対象のCSVファイルへの参照を表示させたいので、これを使っています。入れておかないと何も描画されないので、通常のフィールドを表示させたい場合は忘れずに。
11行目のGUILayout.Buttonはボタンを描画するメソッドで、引数にボタンのラベルを指定可能です。Inspectorウィンドウでボタンが押されたときにTrueを返します。If文でそれを検知しており、Trueを返されたフレームで分岐内の処理を実行します。ここではコンソールにメッセージを出力するようにしています。
スクリプトを保存してコンパイルが終わると、「CsvImporter」の表示が画像のようになります。
「敵データの作成」ボタンをクリックするとコンソールにメッセージが出力されます。
こんな感じでボタンが表示され、その中にボタンを押した時の処理を記述することができました。
CSVファイルの読み込みとマッピング
引き続き「CsvImporterEditor」に処理を追加します。今度はボタンの処理内容をCSVファイルの読み込み、パース、値のセット、アセットの保存に変えればOKです。
ちょっと長いですが順番に解説をさせてください。
9行目、DrawDefaultInspector()の前に、ターゲットとなるScriptableObjectである「CsvImporter」への参照をセットしました。これによって、InspectorウィンドウでセットしたCSVファイルを参照することができます。
12行目からのボタンの処理については、デバッグ文をコメントアウトし、新たに目的の処理を行うメソッドであるSetCsvDataToScriptableObjectを呼んでいます。引数として9行目でセットした「CsvImporter」への参照を渡します。
18行目からのSetCsvDataToScriptableObjectの処理でCSVファイルの読み込み、パース、値のセット、アセットの保存を行なっています。
メソッドが呼ばれたあとはCSVファイルがInspectorウィンドウでセットされているかを確認しています。確認後、ファイルの文字列を取得し、改行コードで分割して配列に格納します。
分割された行ごとにさらにカンマに分割し、マッピングを行なっていきます。ファイルの生成場所についてはサンプルなのでハードコーディングしちゃいました。
今回のCSVデータではIDに該当する列を作っていないため、行番号をIDとしてファイル名に設定しています。ToStringの引数に”D4″を指定しているのは、「0003」のように0で埋めた4桁にしたかったからです。この辺りは好みのフォーマットで。
読み込む列については35行目で宣言したcolumnに値を加算し、相対的に列をずらしています。というのも、元のデータで列の追加があった場合、修正が簡単になるためです。列番号決め打ちだと全部の番号を変えないといけなくなりますからね(1敗)
47行目でデータを格納するEnemyDataをインスタンス化し、そこにデータをセットしています。今回のデータはstringとして読み込まれているため、string以外のフィールドに値をセットする場合はパースする必要があります。このパース時に型についてのエラーが出やすいため、try-catchを入れておくとより丁寧です。
メモリ上のオブジェクトに値を格納したら今度はそれをアセットファイルとして保存します。
73行目では、44行目で宣言したpathにアセットがあるかどうかを確認しています。
アセットがない場合はnullが返ってくるので新しくアセットファイルを作ります。アセットが既に存在する場合は、既存のアセットを更新する形で保存します。
最初の頃はとにかくCreateAssetでアセットを作っていたのですが、既存のアセットを新規アセットとして上書きしちゃうとユニークIDも変わっちゃうみたいで、各シーンから参照されていたScriptableObjectへの参照が軒並み外れていたことがありました。なのでアセットの有無で処理を分けると安心です。
82行目でAssetDatabaseのリフレッシュを行うことで、作ったアセットがProjectウィンドウに表示されるようになります。
処理が全部終わったらデバッグ文でそれを通知しています。
ボタンを押してみる
先ほどのスクリプトを保存し、Unityエディタに戻ってコンパイルを終了させます。
「CsvImporter」を選択し、InspectorウィンドウからCSVファイルをセットしていない状態、セットした状態の両方の動きを確認してみましょ。
まずはセットしていない状態から。
ボタンを押すとコンソールに以下のメッセージが出力されます。メッセージをパッと見たときに出力元が分かるとちょっと嬉しいです。
続いて読み込むCSVファイルをセットします。このとき参照フィールドの右側にある丸ボタンを押すと、スクリプトファイルまでリストアップされるのでびっくりするかも。実はスクリプトファイルもTextAsset扱いで表示されるんです。
準備ができたらボタンを押してみましょ。
無事、作成が終わりました。
指定したパスにScriptableObjectのファイルが作成されました。今回は1種族につき1つのScriptableObjectのアセットファイルを作成するようにしているため、合計で3つのEnemyDataが作成されました。
作成されたScriptableObjectのアセットファイルを選択し、Inspectorウィンドウで中身を確認します。
冒頭で準備したCSVファイルの値が入っていました。よしよし、いい感じですね。
CSVからインポートする際の弱点
CSVファイルはカンマ区切りのテキストファイル、というのはみんなご存知かと思います。カンマで区切りを表現しているということは、セルの中にカンマがあるとそこで区切りだと認識されちゃうんです。
なのでセル内のカンマをエスケープする処理が必要です。カンマを含むセルがあった場合、ExcelやNumbersからCSVファイルを出力する際には”ダブルクォーテーション”で囲まれているため、その対応もしないといけません。また、セル内に改行が含まれていたらそれも対応しないと……。
ただ今回はそこまで扱うとかなーり長いページになってしまいそうだったので割愛しました。『お行儀の良い』CSVデータが来た前提でサンプルを書いているため、実際に使う場合はエスケープについても頭の片隅に置いといてもらえるとよかです。
エスケープ処理も別記事でサンプル考えてみようかな(やるとは言っていない)
まとめ
今回はExcelなどから出力したCSVファイルをUnityにインポートし、そのデータをScriptableObjectのアセットに格納するサンプルをご紹介しました。
ステップごとに画像を入れてみたので、サンプルというよりチュートリアルな感じですね。
さぁ、これであなたもEditor拡張が使えるようになりました!
思うがままにEditor拡張を楽しむのです!!
ゲーム開発の攻略チャートを作りました!
-
前の記事
【Unity】シリアライズされたフィールドの値はどこにあるの? 2019.02.01
-
次の記事
【Unity】ScriptableObjectで関連するフィールドをまとめる方法 2019.02.03
コメントを書く