【Unity】Tiled Map Editor で作成したデータを読み込む

![001.png](https://qiita-image-store.s3.amazonaws.com/0/43772/774f53ae-3356-1c10-a82e-41acfe9d2ae2.png)

高機能なレベルエディタ「[Tiled Map Editor](http://www.mapeditor.org)」で作成したデータを読み込む方法を紹介したいと思います。

## 1. Tiled Map Editor の出力設定
まずは出力設定を変更します。
メニューから「Preferences ...」を選択して、設定画面を開きます。
![002.png](https://qiita-image-store.s3.amazonaws.com/0/43772/04201d34-d421-ebc5-3137-67f9f8abdfe5.png)
そして、レイヤーデータの保持方法を「CSV」に変更します。こうすると読み込み処理が簡単になるためです。

## 2. 拡張子を変更する
Tiled Map Editorで作成したデータは拡張子が「.tmx」となりますが、それを「.xml」に変更します。理由はUnityのテキストアセットは拡張子で判定をしているため、「.tmx」がテキストアセットと認識されません。そのためこの変更が必要となります。

※2014/1/14追記
AssetPostProcessorを使用して自動コピーを行うと、拡張時を*.tmxのまま運用することが可能です。詳細は「6. アセットの自動コピー」で説明しています。

## 3. プロジェクトへの登録
![003.png](https://qiita-image-store.s3.amazonaws.com/0/43772/d57d653d-85c3-c616-6a12-8694ca6e0572.png)
Projectビューに XMLファイル(Tiled Map Editorで作成したデータ)を登録します。Resources.Load()を使って読み込むので、「Resources」フォルダの下に配置する必要があります。ただここでは「Resources/Levels/test2」として配置しました。

なお読み込むレベルデータは以下のものとなります。

```xml:test2.xml
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.0" orientation="orthogonal" width="20" height="15" tilewidth="32" tileheight="32">
 <tileset firstgid="1" name="enemy" tilewidth="32" tileheight="32">
  <image source="enemy.bmp" trans="00ff00" width="512" height="128"/>
 </tileset>
 <layer name="タイル・レイヤー1" width="20" height="15">
  <data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,2,0,0,0,2,2,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,2,2,0,2,2,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
 </layer>
</map>

```

## 4. 読み込み処理
以下のスクリプトでレベルデータを読み込むことができます。

```csharp:TMLLoader.cs
using UnityEngine;
using System.Collections;
using System.Text;
using System.Xml;
using System.IO;

public class TMXLoader : MonoBehaviour {

	// レイヤー格納
	public class Layer2D {
		public int width; // 幅
		public int height; // 高さ
		private int[] _vals = null; // マップデータ

		// 作成
		public void Create(int width, int height) {
			this.width = width;
			this.height = height;
			_vals = new int[width * height];
		}

		// 値の取得
		// @param x X座標
		// @param y Y座標
		// @return 指定の座標の値 (領域外を指定したら-1)
		public int Get(int x, int y) {
			if(x < 0 || x >= width) { return -1; }
			if(y < 0 || y >= height) { return -1; }
			return _vals[y * width + x];
		}

		// 値の設定
		// @param x X座標
		// @param y Y座標
		// @param val 設定する値
		public void Set(int x, int y, int val) {
			if(x < 0 || x >= width) { return; }
			if(y < 0 || y >= height) { return; }
			_vals[y * width + x] = val;
		}

		// デバッグ出力
		public void Dump() {
			print ("[Layer2D] (w,h)=("+width+","+height+")");
			for(int y = 0; y < height; y++) {
				string s = "";
				for(int x = 0; x < width; x++) {
					s += Get (x, y) + ",";
				}
				print (s);
			}
		}
	}

	// レベルデータを読み込む
	void Start () {
		// レイヤー生成
		Layer2D layer = new Layer2D();
		// レベルデータ取得
		TextAsset tmx = Resources.Load ("Levels/test2") as TextAsset;

		// XML解析開始
		XmlDocument xmlDoc = new XmlDocument();
		xmlDoc.LoadXml (tmx.text);
		XmlNodeList mapList = xmlDoc.GetElementsByTagName("map");
		foreach(XmlNode map in mapList) {
			XmlNodeList childList = map.ChildNodes;
			foreach(XmlNode child in childList) {
				if(child.Name != "layer") { continue; } // layerノード以外は見ない

				// マップ属性を取得
				XmlAttributeCollection attrs = child.Attributes;
				int w = int.Parse(attrs.GetNamedItem("width").Value); // 幅を取得
				int h = int.Parse(attrs.GetNamedItem("height").Value); // 高さを取得
				// レイヤー生成
				layer.Create(w, h);
				XmlNode node = child.FirstChild; // 子ノードは<data>のみ
				XmlNode n = node.FirstChild; // テキストノードを取得
				string val = n.Value; // テキストを取得
				// CSV(マップデータ)を解析
				int y = 0;
				foreach(string line in val.Split('\n')) {
					int x = 0;
					foreach(string s in line.Split(',')) {
						int v = 0;
						// ","で終わるのでチェックが必要
						if(int.TryParse(s, out v) == false) { continue; }
						// 値を設定
						layer.Set (x, y, v);
						x++;
					}
					y++;
				}
			}
		}

		// デバッグ出力
		layer.Dump();

	}
}

```
上記例では、Layer2Dをローカル変数にしていますが、これをメンバ変数にすることで外部からレベルデータを取得できるようになると思います。

### 結果のログ
読み込んだ結果、以下のログが出ているのでちゃんと読めていますね。

```
[Layer2D] (w,h)=(20,15)
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,2,0,0,0,2,2,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,2,2,0,2,2,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
```

## 5. 課題
とりあえず敵の配置ができればいいかな、と思って作ったのでTiled Map Editorのすべての情報を取得していません。大きなところでは、

* マップチップのサイズを取得していない
* 複数レイヤーの読み込みに対応していない
* オブジェクトレイヤーの読み込みに対応していない

ですね。

## 6. アセットの自動コピー (2015/1/14追記)
AssetPostProcessorを使うとアセットに変更があった際、自動で更新を行うことができます。これを利用して、*.tmxに変更があった場合に、*.xmlへコピーすることができます。例えば以下のようにコードを記述します。

```csharp
#if UNITY_EDITOR // Unityエディタ上のみ有効

using UnityEditor;
using UnityEngine;

public class MyAssetPostprocessor : AssetPostprocessor
{
    /// <summary>
    /// すべてのアセットのインポートが終了した際に呼び出されます
    /// </summary>
    /// <param name="importedAssets">インポートされたアセットのパス</param>
    /// <param name="deletedAssets">削除されたアセットのパス</param>
    /// <param name="movedAssets">移動したアセットの移動後のパス</param>
    /// <param name="movedFromPath">移動したアセットの移動前のパス</param>
    private static void OnPostprocessAllAssets(
        string[] importedAssets, 
        string[] deletedAssets, 
        string[] movedAssets, 
        string[] movedFromPath)
    {
        foreach (var importedAsset in importedAssets)
        {
            if(IsTmxFile(importedAsset))
            {
                // TMXファイルなので拡張子を*.xmlにしてコピー
                var newAsset = importedAsset.Replace(".tmx", ".xml");
                // 古いXMLは削除
                AssetDatabase.DeleteAsset(newAsset);
                if(AssetDatabase.CopyAsset(importedAsset, newAsset))
                {
                    // コピー実行
                    Debug.Log ("Copy: " + importedAsset + " -> " + newAsset);
                }
            }
        }
    }

    /// TMXファイルかどうか調べる
    static bool IsTmxFile(string str)
    {
        return str.IndexOf(".tmx") > 0;
    }
 
}

#endif
```

このコードを任意のフォルダに配置することで、自動コピーをするようになるので、拡張子を*.tmxファイルのままで運用することが可能となります。