【Unity】XMLファイルをCSV形式にコンバートするEditor拡張
Editor拡張を使った処理として、XMLファイルをCSV形式にコンバートするサンプルを考えます。
上の記事ではUnityでマスタデータを持つ時のファイル形式について比較を行ったのですが、そこで紹介したのがCSV、XML、JSON、YAMLでした。
CSVからXMLに変換するEditor拡張を以前作ったのですが、「あれ? 逆もあった方がいいかな?」と思い立ったので作ってみることに。
ぶっちゃけEditor拡張スクリプトを書きたいだけです。
環境
macOS 10.14 Mojave
Unity2018.2.20f1
ファイル形式について
今回はXMLファイルからCSVファイルへの変換を行います。
「Editor拡張やってみようかな〜」なんて思っている方の中に「CSVって何?」という方はそんなにいないかと思いますが念の為説明を。
CSVファイル
CSVはComma-Separated Values、その名の通りカンマで区切られた値が並ぶテキストファイルです。
Excelなどの表計算ソフトからだとCSVファイルでエクスポートする機能があるかと思うので、それを使ってCSVファイルを用意します。
XMLファイル
XMLはExtensible Markup Language、拡張可能なマークアップ言語です。タグを使ってデータ構造を表現しており、データと意味との対応が分かりやすい形式です。
UnityでXMLを使うメリットはそんなにないのですが、例えば敵キャラのデータをエクスポートしてWebで公開したい、なんてときにはXMLで表現されていると便利……かもしれません。
あるいは逆にWebに掲載した敵のデータをXMLで引っこ抜いた後にUnityにインポートしたい時には使える……かも……?
変換元のデータ
マスタデータとして以下の以下の表データがある場合は考えます。ExcelなどからXML形式でエクスポートし、Unityのプロジェクトにインポートします。このファイルをUnityプロジェクト内でCSV形式に変換します。
また、ExcelからはCSV形式で出力しておいて、上の記事でCSVからXMLに変換したものを使ってもいいかも。
今回のサンプルでは、敵キャラのデータをXMLからCSVに変換する例を考えます。
enemyName | maxHp | atk | def | exp | gold |
スライム | 4 | 2 | 2 | 1 | 2 |
ゴブリン | 8 | 4 | 2 | 2 | 3 |
ジャイアントマウス | 12 | 5 | 3 | 3 | 5 |
上の表をXMLにしたものは以下の「// XML」から下の部分です。今回のサンプルを動かす上では、XML部分をテキストエディタなどから.xml形式で保存すると楽ちんです。
なお、今回のサンプルでは<Data></Data>の子要素までが存在するケースを考えます。以下のように、<Data></Data>の孫要素がある場合はスクリプトの複雑さがえらいこっちゃになるので、仕様として対応しないこととします。
複雑かつ仕様が決まっているXMLの場合はそれ用にカスタマイズした方が良いかも。
作業の流れ
今回のサンプルで行う作業は以下の通り。
- コンバート対象のXMLファイルを参照し、変換ボタンを表示させるScriptableObjectを作成
- 上記ファイルのInspector表示と変換の動作を定義するEditor拡張スクリプトを作成
- ボタンを押してXMLファイルの内容がCSVファイルとして書き出されたことを確認
作成したいのはScriptableObjectが1つ、そのScriptableObjectに適用するEditor拡張スクリプトが1つ、合計2つのスクリプトです。
ScriptableObjectの概要や作り方については以下の記事で扱っているので、こちらも併せてご参照ください。
作業の流れはCSVからXMLに変換するときとほぼ同じで、Editor拡張スクリプトの内容が違うくらいでしょうか。
ファイル変換用のScriptableObjectを作成
今回のEditor拡張では独自のウィンドウを作成せず、作成したScriptableObjectのInspector表示をカスタマイズします。
まずはスクリプトファイルの作成から。Projectウィンドウで任意のフォルダで右クリックして[Create] -> [C# Script]を選択します。
作成されたスクリプトは「XmlToCsvConverter」とリネームしました。
今回もフォルダ作成については省略しちゃいましたが、Projectウィンドウ内でスクリプトを整理したかったので、「Assets」フォルダの下に「Convert」フォルダを作成し、さらにその中に「XMLtoCSV」フォルダを作成しています。
XmlToCsvConverterの中身は以下のように編集します。
ScriptableObjectを継承し、データ変換で使うフィールドを用意します。
フィールドは変換したいXMLファイルへの参照、変換後のCSVファイルの名前をセットするのに使います。
CreateAssetMenuのAttributeを付けることで、メニューからこのScriptableObjectのアセットを作成することができるようになります。Projectウィンドウ内で右クリックするか、メニューバーからメニューを開き、[Create] -> [MyScriptable] -> [Create CSV to XML Converter]と選択することでアセットファイルが作成されます。
アセットファイルが作成されたら好きな名前にリネームします。画像の例では「XmlToCsvConverter」とリネームしました。
Projectウィンドウでこのアセットファイルを選択すると、Inspectorウィンドウでは以下の画像のように表示されます。
この時点でXMLファイルもプロジェクトにインポートしておきましょ。ファイル名は任意の値で大丈夫です。
早速「XmlToCsvConverter」を選択して、フィールドで参照させておきます。変換後のファイル名も入力しておきましょうか。Editor拡張スクリプトの中で拡張子も付けるので、このフィールドに入力するときは拡張子は付けなくて大丈夫です。
これで準備ができたので、このInspectorウィンドウにボタンを追加し、変換処理を実装します。
XMLからCSVへの変換処理をEditor拡張で実装
Editor拡張用のスクリプトは「Editor」フォルダに入れます。プロジェクト内の好きな場所に配置できるため、今回のスクリプトを配置している「XMLtoCSV」に新しく「Editor」フォルダを作成します。
作成したら「Editor」にリネームします。
次に「Editor」フォルダに移動し、Editor拡張用のスクリプトを作成します。名前は「XmlToCsvConverterEditor」としました。
このスクリプトの中で、「XmlToCsvConverter」をInspectorウィンドウで表示する際にボタンを表示させる処理と、変換処理を追加します。
「うわぁ……」と呟きたくなる長さ。順番に説明させてください。
4行目の「UnityEditor」はUnityのエディタの動作を定義するクラスが含まれる名前空間、5行目の「System.IO」はファイルを読み書きするクラスが含まれている名前空間、6行目の「System.Xml.Linq」はC#でXMLを扱うときに使うクラスが含まれる名前空間です。
8行目のCustomEditorのAttributeは、どのScriptableObjectに対してカスタマイズを施すか、という指定です。今回カスタマイズしたいのは「XmlToCsvConverter」クラスのアセットですから、この型を指定します。
11行目から始まるOnInspectorGUI()では、表示をカスタマイズしたいScriptableObjectに含まれるフィールドをDrawDefaultInspector()で描画した後、ボタンを表示させています。ボタンが押された時の処理として、25行目から始まるConvertXmlToCsvのメソッドを呼んでいます。
ConvertXmlToCsvのメソッド内で何か例外が起きたらエラーメッセージを拾うようにしています。が、拾い方が雑なのは目を瞑ってくださいすみません。
25行目から変換の処理メソッドとなっています。
27行目から36行目までは、ScriptableObjectのフィールドで値がセットされているかどうかを確認しています。どちらかのチェックに引っかかったら処理は行わないようにしています。
39行目から42行目ではInspectorウィンドウでセットしたXMLファイルの内容を読み取って解析しています。何らかの例外が発生するのは多分ここがほとんどです。試しにXMLファイルではなくスクリプトファイルへの参照をセットすると、「XMLじゃないんだけど……?」と怒られて例外処理に移動します。
45行目から53行目では、XMLに存在するタグ名からCSVファイルのヘッダー行を生成しています。今回サンプルとして読み込んでいるXMLの構造は以下のようになっています。Root要素があり、その子要素としてData、さらにその子要素としてenemyNameやmaxHpが存在しています。
42行目のdataElementsにはRootの子要素であるDataが入っています。実際のファイルだとDataが3つ分になっているので、dataElementsにはそれらのDataが格納されているんです。
46行目のforeachではそのうち1つのData(一番上の要素)から読み込まれ、その要素の子要素の名前、つまりemenyNameやmaxHpなどのタグ名をもうひとつのforeachで取得し、リストに格納しています。最初のDataに含まれる要素名が取得できればOKなので、47行目から51行目までのforeachを回した後、breakで抜けています。
56行目から59行目ではファイルの出力先を指定しています。今回ファイルを保存する先はScriptableObjectのアセットファイルと同じフォルダにしています。ここで拡張子”.xml”を付けているので、Inspectorウィンドウでは拡張子の入力は必要ありません。
62行目ではリストに格納したヘッダーの文字列を配列に変換しています。63行目でファイルに書き込む際に、string.Joinを使ってカンマ区切りの行を生成しています。この引数は配列となっているため、Listから配列に変換する必要があったんですね。
66行目からはDataの子要素の値を読み取っています。ヘッダー行となる列ごとに辞書を作成し、対応する値を格納していきます。もし対応する行がなかったら空文字列が出力されるようになっています。
79行目ではヘッダー行の要素と同じ順番になるようにデータ行をリストに格納後、配列に変換してからstring.Joinを使ってカンマ区切りの行を生成し、ファイルに書き込んでいます。
全てのDataの処理が終わったらファイルを閉じ、AssetDatabaseを更新して処理終了です。
実際にやってみよう
上のスクリプトを保存してコンパイルを終わらせると、「XmlToCsvConverter」のアセットを選択したときにInspectorウィンドウでの表示が変わります。
「CSVに変換」ボタンを押すと、以下の画像のように同じフォルダにCSVファイルが出力されます。
Inspectorウィンドウでファイルの中身を見ると以下のようになっています。
XMLとCSVの内容を比較してみても、どうやらうまくいってそう。良かった!
今回のスクリプトの弱点
途中でちょっと触れましたが、Root要素の孫要素まではパースできますが、孫要素がさらに入れ子になってひ孫要素が存在しているとうまくパースできません。
なので、もしひ孫要素がある場合はカスタマイズが必要です。
その場合は46行目の処理でひ孫要素のタグ名を取得するようにして、74行目のforeachの中で「element.HasElements」を使って孫要素がその子要素(Rootのひ孫要素)を持っているかどうかを確認して処理を分ける感じですかね。
まとめ
XMLファイルをCSVファイルに変換するEditor拡張のサンプルを紹介しました。
大元のデータからXML形式でエクスポートして、さらにCSV形式に変換するとか割とレアケースかもしれませんが、ファイル形式間の変換を自力で実装するのも勉強になりますね。
CSVからXMLへの変換も以下の記事で扱っているので、良かったら違いを見比べてみてください。
例のごとくスクリプトにツッコミがあったらぜひコメントでお願いします。
ゲーム開発の攻略チャートを作りました!
-
前の記事
【Unity】CSVファイルをXML形式にコンバートするEditor拡張 2019.02.08
-
次の記事
イーサネットってエーテルネットの英語読み。名付け親は厨二だな? 2019.02.28
コメントを書く