読者です 読者をやめる 読者になる 読者になる

Unity / VRゲーム開発日記@長崎

Unityを使ったVRのゲーム開発をやってます。

Tiled map editorのxmlデータをクラスに流し込む

いま製作中のゲームで、マップの製作が必要なため、
フリーのマップ作成ツールで比較的高機能な
Tield map editorというのを使ってみました。


Tiled map editor
http://www.mapeditor.org/


今回は、このツールで出力したxmlデータを
Unityに取り込む為の手続きについてです。


最終的に画像と対応させるまでとなると、
画像とマップチップ番号を対応させる仕組み等も必要なので、
とりあえずは、Tiled map editor のxml形式を
プログラムで読めるようにするというところまでの段階の説明になります。



Tiled map editorで作ったデータをtmx形式で保存すると、
中身はxmlデータとなります。
ただし、unityでは、拡張子をみてTextAssetかを判断してるので、
「tmx」を「xml」に変更しておく必要があります。


また、ListとXml関連のクラスを利用するので
以下をusingしておいて下さい。

using System.Collections.Generic;
using System.Xml.Serialization;


そして、まずxmlを指定した型に流し込むクラス

	public class XmlReader
	{

		public static T LoadFromXml<T> (string path)
			where T : class
		{


			var xml = Resources.Load( path ) as TextAsset;

			if(xml == null){
				return null;
			}

			var ser = new XmlSerializer (typeof(T));

			var stringReader = new StringReader(tmx.text);

			var obj = ser.Deserialize(stringReader);

			var retClass = (T)obj;

			return retClass;
		}
	}

これは汎用的に利用できるクラスですが、
pathで指定されたResourcesフォルダにあるxmlファイルを
で指定されたclassとして認識させます。


そして、今回読み込みたいxmlファイルはこんな感じ


<?xml version="1.0" encoding="UTF-8"?>
<map version="1.0" orientation="orthogonal" width="10" height="10" tilewidth="16" tileheight="16">
 <tileset firstgid="1" name="char_16" tilewidth="16" tileheight="16">
  <image source="../../StageEdit/char_16.png" width="128" height="512"/>
 </tileset>
 <tileset firstgid="257" name="map_16" tilewidth="16" tileheight="16">
  <image source="../../StageEdit/map_16.png" width="512" height="512"/>
 </tileset>
 <layer name="layer_map" width="10" height="10">
  <data>
   <tile gid="258"/>
   <tile gid="258"/>
   <tile gid="258"/>
   <tile gid="258"/>
   <tile gid="258"/>

   〜 省略 〜

   <tile gid="260"/>
   <tile gid="260"/>
   <tile gid="260"/>
   <tile gid="260"/>
   <tile gid="260"/>
  </data>
 </layer>
 <layer name="layer_actor" width="10" height="10">
  <data>
   <tile gid="0"/>
   <tile gid="0"/>
   <tile gid="0"/>
   <tile gid="0"/>
   <tile gid="0"/>
  
   〜 省略 〜

   <tile gid="0"/>
   <tile gid="0"/>
   <tile gid="0"/>
   <tile gid="0"/>
   <tile gid="0"/>
  </data>
 </layer>
</map>

data内のtile要素は 10 x 10 = 100 個あるので省略してます。

そして、これをclassとして読み込みたいのですが、
このxmlに対応した構造のclassをあらかじめ作成しておかないといけません。

そのclassが以下の様な形になります。

	[XmlRoot("map")]
	public class Map
	{

		#region class

		public class TileSet
		{
			[XmlAttribute]
			public int
				firstgid;
			[XmlAttribute]
			public string
				name;
			[XmlAttribute]
			public int
				tilewidth;
			[XmlAttribute]
			public int
				tileheight;
			public Image imgae;
		}

		public class Layer
		{
			[XmlAttribute]
			public string
				name;
			[XmlAttribute]
			public int
				width;
			[XmlAttribute]
			public int
				height;
			public Data data;
		}

		public class Image
		{

			[XmlAttribute]
			public string
				source;
			[XmlAttribute]
			public int
				width;
			[XmlAttribute]
			public int
				height;
		}

		public class Data
		{
			[XmlAttribute]
			public string
				encoding;

			[XmlElement()]
			public List<Tile> tile;
		}

		public class Tile
		{
			[XmlAttribute]
			public int
				gid;
		}

		#endregion


		
		#region public property

		[XmlAttribute]
		public string
			version;
		[XmlAttribute]
		public string
			orientation;
		[XmlAttribute]
		public int
			width;
		[XmlAttribute]
		public int
			height;
		[XmlAttribute]
		public int
			tilewidth;
		[XmlAttribute]
		public int
			tileheight;

		[XmlElement()]
		public List<TileSet> tileset;

		[XmlElement()]
		public List<Layer> layer;





		#endregion
		

		
		
		#region public method




		public int[,] GetLayerData(string name){

			var oneLayer = GetLayer(name);

			if(oneLayer == null){
				return null;
			}

			var w = oneLayer.width;
			var h = oneLayer.height;

			var tiles = oneLayer.data.tile; 

			var grid = new int[w, h];

			for(int y=0; y<h; ++y){

				for(int x=0; x<w; ++x){

					var tileNo = x + (y * w);

					grid[x, y] = tiles[tileNo].gid;

				}


			}


			return grid;
		}




		#endregion
		
		
		#region private method
	
		private Layer GetLayer(string name){

			foreach(var l in layer){

				if(l.name == name){
					return l;
				}

			}

			return null;

		}




		#endregion
		

	}



なんか、結構複雑な構成です(^^;


xmlを見ながらクラスを逆構築していく作業は結構大変でした。
この変を自動化するツールとかないのかなぁ…


とりあえず、簡単に説明すると、
xmlの根になる要素がmapなのでMapクラスとしました。


そして、そこが根ということを示す為に
[XmlRoot("map")]
を宣言しています。


そのあと、#region class
内が、各要素の構成です。
アトリビュート項目は
[XmlAttribute]
で宣言します。


LayerやTileSet等、複数回登場する可能性のある要素は
[XmlElement()]
で宣言します。


また、重要な点として、変数名は
要素名と同じでないといけません。



とりあえず、構造だけあればデータは流し込めますが
ほとんどの場合、欲しいデータはレイヤーの情報だと思うので、
レイヤ名称を指定したらそのレイヤの数値データをintの配列で返す
GeetLayerData関数を用意しています。



以下は作成したクラスを実際に使って、
レイヤーの情報をDebug各値をDebug出力するサンプルです。

		public void ReadMap(){

			var path = "stageData";

			var map = XmlReader.LoadFromXml<Map>(path);

			var grid = _map.GetLayerData("layer_actor");



			foreach(var g in grid){
				Debug.Log(g);
			}

		}


とりあえず、今回はここまでです。
後は、複数のタイルを利用していた場合、
レイヤ内の値が全タイルでの通し番号となるようなので、
TileSetのfirstgidを使って番号を調整する必要がありそうです。


また、現段階では、オブジェクトレイヤーに対応できていないので、
その辺の追加などができたらまた修正版の記事を上げたいと思います。