Unity
Unity2D

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

More than 3 years have passed since last update.

001.png

高機能なレベルエディタ「Tiled Map Editor」で作成したデータを読み込む方法を紹介したいと思います。


1. Tiled Map Editor の出力設定

まずは出力設定を変更します。

メニューから「Preferences ...」を選択して、設定画面を開きます。

002.png

そして、レイヤーデータの保持方法を「CSV」に変更します。こうすると読み込み処理が簡単になるためです。


2. 拡張子を変更する

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

※2014/1/14追記

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


3. プロジェクトへの登録

003.png

Projectビューに XMLファイル(Tiled Map Editorで作成したデータ)を登録します。Resources.Load()を使って読み込むので、「Resources」フォルダの下に配置する必要があります。ただここでは「Resources/Levels/test2」として配置しました。

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


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. 読み込み処理

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


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へコピーすることができます。例えば以下のようにコードを記述します。

#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ファイルのままで運用することが可能となります。