1 /****************************************************************************
  2  Copyright (c) 2010-2012 cocos2d-x.org
  3  Copyright (c) 2008-2010 Ricardo Quesada
  4  Copyright (c) 2011      Zynga Inc.
  5 
  6  http://www.cocos2d-x.org
  7 
  8  Permission is hereby granted, free of charge, to any person obtaining a copy
  9  of this software and associated documentation files (the "Software"), to deal
 10  in the Software without restriction, including without limitation the rights
 11  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 12  copies of the Software, and to permit persons to whom the Software is
 13  furnished to do so, subject to the following conditions:
 14 
 15  The above copyright notice and this permission notice shall be included in
 16  all copies or substantial portions of the Software.
 17 
 18  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 19  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 20  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 21  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 22  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 23  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 24  THE SOFTWARE.
 25  ****************************************************************************/
 26 
 27 /**
 28  Orthogonal orientation
 29  * @constant
 30  * @type Number
 31  */
 32 cc.TMX_ORIENTATION_ORTHO = 0;
 33 
 34 /**
 35  * Hexagonal orientation
 36  * @constant
 37  * @type Number
 38  */
 39 
 40 cc.TMX_ORIENTATION_HEX = 1;
 41 
 42 /**
 43  * Isometric orientation
 44  * @constant
 45  * @type Number
 46  */
 47 cc.TMX_ORIENTATION_ISO = 2;
 48 
 49 /**
 50  * <p>cc.TMXTiledMap knows how to parse and render a TMX map.</p>
 51  *
 52  * <p>It adds support for the TMX tiled map format used by http://www.mapeditor.org <br />
 53  * It supports isometric, hexagonal and orthogonal tiles.<br />
 54  * It also supports object groups, objects, and properties.</p>
 55  *
 56  * <p>Features: <br />
 57  * - Each tile will be treated as an cc.Sprite<br />
 58  * - The sprites are created on demand. They will be created only when you call "layer.getTileAt(position)" <br />
 59  * - Each tile can be rotated / moved / scaled / tinted / "opacitied", since each tile is a cc.Sprite<br />
 60  * - Tiles can be added/removed in runtime<br />
 61  * - The z-order of the tiles can be modified in runtime<br />
 62  * - Each tile has an anchorPoint of (0,0) <br />
 63  * - The anchorPoint of the TMXTileMap is (0,0) <br />
 64  * - The TMX layers will be added as a child <br />
 65  * - The TMX layers will be aliased by default <br />
 66  * - The tileset image will be loaded using the cc.TextureCache <br />
 67  * - Each tile will have a unique tag<br />
 68  * - Each tile will have a unique z value. top-left: z=1, bottom-right: z=max z<br />
 69  * - Each object group will be treated as an cc.MutableArray <br />
 70  * - Object class which will contain all the properties in a dictionary<br />
 71  * - Properties can be assigned to the Map, Layer, Object Group, and Object</p>
 72  *
 73  * <p>Limitations: <br />
 74  * - It only supports one tileset per layer. <br />
 75  * - Embeded images are not supported <br />
 76  * - It only supports the XML format (the JSON format is not supported)</p>
 77  *
 78  * <p>Technical description: <br />
 79  * Each layer is created using an cc.TMXLayer (subclass of cc.SpriteBatchNode). If you have 5 layers, then 5 cc.TMXLayer will be created, <br />
 80  * unless the layer visibility is off. In that case, the layer won't be created at all. <br />
 81  * You can obtain the layers (cc.TMXLayer objects) at runtime by: <br />
 82  * - map.getChildByTag(tag_number);  // 0=1st layer, 1=2nd layer, 2=3rd layer, etc...<br />
 83  * - map.getLayer(name_of_the_layer); </p>
 84  *
 85  * <p>Each object group is created using a cc.TMXObjectGroup which is a subclass of cc.MutableArray.<br />
 86  * You can obtain the object groups at runtime by: <br />
 87  * - map.getObjectGroup(name_of_the_object_group); </p>
 88  *
 89  * <p>Each object is a cc.TMXObject.</p>
 90  *
 91  * <p>Each property is stored as a key-value pair in an cc.MutableDictionary.<br />
 92  * You can obtain the properties at runtime by: </p>
 93  *
 94  * <p>map.getProperty(name_of_the_property); <br />
 95  * layer.getProperty(name_of_the_property); <br />
 96  * objectGroup.getProperty(name_of_the_property); <br />
 97  * object.getProperty(name_of_the_property);</p>
 98  * @class
 99  * @extends cc.Node
100  *
101  * @property {Array}    properties      - Properties from the map. They can be added using tilemap editors
102  * @property {Number}   mapOrientation  - Map orientation
103  * @property {Array}    objectGroups    - Object groups of the map
104  * @property {Number}   mapWidth        - Width of the map
105  * @property {Number}   mapHeight       - Height of the map
106  * @property {Number}   tileWidth       - Width of a tile
107  * @property {Number}   tileHeight      - Height of a tile
108  */
109 cc.TMXTiledMap = cc.NodeRGBA.extend(/** @lends cc.TMXTiledMap# */{
110 	properties: null,
111 	mapOrientation: null,
112 	objectGroups: null,
113 
114     //the map's size property measured in tiles
115     _mapSize: null,
116     _tileSize: null,
117     //tile properties
118     _tileProperties: null,
119     _className: "TMXTiledMap",
120 
121     /**
122      * Creates a TMX Tiled Map with a TMX file  or content string.
123      * Implementation cc.TMXTiledMap
124      * @constructor
125      * @param {String} tmxFile tmxFile fileName or content string
126      * @param {String} resourcePath   If tmxFile is a file name ,it is not required.If tmxFile is content string ,it is must required.
127      * @example
128      * //example
129      * 1.
130      * //create a TMXTiledMap with file name
131      * var tmxTiledMap = new cc.TMXTiledMap("res/orthogonal-test1.tmx");
132      * 2.
133      * //create a TMXTiledMap with content string and resource path
134      * var resources = "res/TileMaps";
135      * var filePath = "res/TileMaps/orthogonal-test1.tmx";
136      * var xmlStr = cc.loader.getRes(filePath);
137      * var tmxTiledMap = new cc.TMXTiledMap(xmlStr, resources);
138      */
139     ctor:function(tmxFile,resourcePath){
140         cc.Node.prototype.ctor.call(this);
141         this._mapSize = cc.size(0, 0);
142         this._tileSize = cc.size(0, 0);
143 
144         if(resourcePath !== undefined){
145             this.initWithXML(tmxFile,resourcePath);
146         }else if(tmxFile !== undefined){
147             this.initWithTMXFile(tmxFile);
148         }
149     },
150 
151     /**
152      * @return {cc.Size}
153      */
154     getMapSize:function () {
155         return cc.size(this._mapSize.width, this._mapSize.height);
156     },
157 
158     /**
159      * @param {cc.Size} Var
160      */
161     setMapSize:function (Var) {
162         this._mapSize.width = Var.width;
163         this._mapSize.height = Var.height;
164     },
165 
166 	_getMapWidth: function () {
167 		return this._mapSize.width;
168 	},
169 	_setMapWidth: function (width) {
170 		this._mapSize.width = width;
171 	},
172 	_getMapHeight: function () {
173 		return this._mapSize.height;
174 	},
175 	_setMapHeight: function (height) {
176 		this._mapSize.height = height;
177 	},
178 
179     /**
180      * @return {cc.Size}
181      */
182     getTileSize:function () {
183         return cc.size(this._tileSize.width, this._tileSize.height);
184     },
185 
186     /**
187      * @param {cc.Size} Var
188      */
189     setTileSize:function (Var) {
190         this._tileSize.width = Var.width;
191         this._tileSize.height = Var.height;
192     },
193 
194 	_getTileWidth: function () {
195 		return this._tileSize.width;
196 	},
197 	_setTileWidth: function (width) {
198 		this._tileSize.width = width;
199 	},
200 	_getTileHeight: function () {
201 		return this._tileSize.height;
202 	},
203 	_setTileHeight: function (height) {
204 		this._tileSize.height = height;
205 	},
206 
207     /**
208      * map orientation
209      * @return {Number}
210      */
211     getMapOrientation:function () {
212         return this.mapOrientation;
213     },
214 
215     /**
216      * @param {Number} Var
217      */
218     setMapOrientation:function (Var) {
219         this.mapOrientation = Var;
220     },
221 
222     /**
223      * object groups
224      * @return {Array}
225      */
226     getObjectGroups:function () {
227         return this.objectGroups;
228     },
229 
230     /**
231      * @param {Array} Var
232      */
233     setObjectGroups:function (Var) {
234         this.objectGroups = Var;
235     },
236 
237     /**
238      * properties
239      * @return {object}
240      */
241     getProperties:function () {
242         return this.properties;
243     },
244 
245     /**
246      * @param {object} Var
247      */
248     setProperties:function (Var) {
249         this.properties = Var;
250     },
251 
252     /**
253      * @param {String} tmxFile
254      * @return {Boolean}
255      * @example
256      * //example
257      * var map = new cc.TMXTiledMap()
258      * map.initWithTMXFile("hello.tmx");
259      */
260     initWithTMXFile:function (tmxFile) {
261         if(!tmxFile || tmxFile.length == 0)
262             throw "cc.TMXTiledMap.initWithTMXFile(): tmxFile should be non-null or non-empty string.";
263 	    this.width = 0;
264 	    this.height = 0;
265         var mapInfo = cc.TMXMapInfo.create(tmxFile);
266         if (!mapInfo)
267             return false;
268 
269         var locTilesets = mapInfo.getTilesets();
270         if(!locTilesets || locTilesets.length === 0)
271             cc.log("cc.TMXTiledMap.initWithTMXFile(): Map not found. Please check the filename.");
272         this._buildWithMapInfo(mapInfo);
273         return true;
274     },
275 
276     initWithXML:function(tmxString, resourcePath){
277         this.width = 0;
278 	    this.height = 0;
279 
280         var mapInfo = cc.TMXMapInfo.create(tmxString, resourcePath);
281         var locTilesets = mapInfo.getTilesets();
282         if(!locTilesets || locTilesets.length === 0)
283             cc.log("cc.TMXTiledMap.initWithXML(): Map not found. Please check the filename.");
284         this._buildWithMapInfo(mapInfo);
285         return true;
286     },
287 
288     _buildWithMapInfo:function (mapInfo) {
289         this._mapSize = mapInfo.getMapSize();
290         this._tileSize = mapInfo.getTileSize();
291         this.mapOrientation = mapInfo.orientation;
292         this.objectGroups = mapInfo.getObjectGroups();
293         this.properties = mapInfo.properties;
294         this._tileProperties = mapInfo.getTileProperties();
295 
296         var idx = 0;
297         var layers = mapInfo.getLayers();
298         if (layers) {
299             var layerInfo = null;
300             for (var i = 0, len = layers.length; i < len; i++) {
301                 layerInfo = layers[i];
302                 if (layerInfo && layerInfo.visible) {
303                     var child = this._parseLayer(layerInfo, mapInfo);
304                     this.addChild(child, idx, idx);
305                     // update content size with the max size
306 	                this.width = Math.max(this.width, child.width);
307 	                this.height = Math.max(this.height, child.height);
308                     idx++;
309                 }
310             }
311         }
312     },
313 
314     allLayers: function () {
315         var retArr = [], locChildren = this._children;
316         for(var i = 0, len = locChildren.length;i< len;i++){
317             var layer = locChildren[i];
318             if(layer && layer instanceof cc.TMXLayer)
319                 retArr.push(layer);
320         }
321         return retArr;
322     },
323 
324     /**
325      * return the TMXLayer for the specific layer
326      * @param {String} layerName
327      * @return {cc.TMXLayer}
328      */
329     getLayer:function (layerName) {
330         if(!layerName || layerName.length === 0)
331             throw "cc.TMXTiledMap.getLayer(): layerName should be non-null or non-empty string.";
332         var locChildren = this._children;
333         for (var i = 0; i < locChildren.length; i++) {
334             var layer = locChildren[i];
335             if (layer && layer.layerName == layerName)
336                 return layer;
337         }
338         // layer not found
339         return null;
340     },
341 
342     /**
343      * Return the TMXObjectGroup for the specific group
344      * @param {String} groupName
345      * @return {cc.TMXObjectGroup}
346      */
347     getObjectGroup:function (groupName) {
348         if(!groupName || groupName.length === 0)
349             throw "cc.TMXTiledMap.getObjectGroup(): groupName should be non-null or non-empty string.";
350         if (this.objectGroups) {
351             for (var i = 0; i < this.objectGroups.length; i++) {
352                 var objectGroup = this.objectGroups[i];
353                 if (objectGroup && objectGroup.groupName == groupName) {
354                     return objectGroup;
355                 }
356             }
357         }
358         // objectGroup not found
359         return null;
360     },
361 
362     /**
363      * Return the value for the specific property name
364      * @param {String} propertyName
365      * @return {String}
366      */
367     getProperty:function (propertyName) {
368         return this.properties[propertyName.toString()];
369     },
370 
371     /**
372      * Return properties dictionary for tile GID
373      * @param {Number} GID
374      * @return {object}
375      */
376     propertiesForGID:function (GID) {
377         return this._tileProperties[GID];
378     },
379 
380     _parseLayer:function (layerInfo, mapInfo) {
381         var tileset = this._tilesetForLayer(layerInfo, mapInfo);
382         var layer = cc.TMXLayer.create(tileset, layerInfo, mapInfo);
383         // tell the layerinfo to release the ownership of the tiles map.
384         layerInfo.ownTiles = false;
385         layer.setupTiles();
386         return layer;
387     },
388 
389     _tilesetForLayer:function (layerInfo, mapInfo) {
390         var size = layerInfo._layerSize;
391         var tilesets = mapInfo.getTilesets();
392         if (tilesets) {
393             for (var i = tilesets.length - 1; i >= 0; i--) {
394                 var tileset = tilesets[i];
395                 if (tileset) {
396                     for (var y = 0; y < size.height; y++) {
397                         for (var x = 0; x < size.width; x++) {
398                             var pos = x + size.width * y;
399                             var gid = layerInfo._tiles[pos];
400                             if (gid != 0) {
401                                 // Optimization: quick return
402                                 // if the layer is invalid (more than 1 tileset per layer) an cc.assert will be thrown later
403                                 if (((gid & cc.TMX_TILE_FLIPPED_MASK)>>>0) >= tileset.firstGid) {
404                                     return tileset;
405                                 }
406                             }
407 
408                         }
409                     }
410                 }
411             }
412         }
413 
414         // If all the tiles are 0, return empty tileset
415         cc.log("cocos2d: Warning: TMX Layer " + layerInfo.name + " has no tiles");
416         return null;
417     }
418 });
419 
420 var _p = cc.TMXTiledMap.prototype;
421 
422 // Extended properties
423 /** @expose */
424 _p.mapWidth;
425 cc.defineGetterSetter(_p, "mapWidth", _p._getMapWidth, _p._setMapWidth);
426 /** @expose */
427 _p.mapHeight;
428 cc.defineGetterSetter(_p, "mapHeight", _p._getMapHeight, _p._setMapHeight);
429 /** @expose */
430 _p.tileWidth;
431 cc.defineGetterSetter(_p, "tileWidth", _p._getTileWidth, _p._setTileWidth);
432 /** @expose */
433 _p.tileHeight;
434 cc.defineGetterSetter(_p, "tileHeight", _p._getTileHeight, _p._setTileHeight);
435 
436 
437 /**
438  * Creates a TMX Tiled Map with a TMX file  or content string.
439  * Implementation cc.TMXTiledMap
440  * @param {String} tmxFile tmxFile fileName or content string
441  * @param {String} resourcePath   If tmxFile is a file name ,it is not required.If tmxFile is content string ,it is must required.
442  * @return {cc.TMXTiledMap|undefined}
443  * @example
444  * //example
445  * 1.
446  * //create a TMXTiledMap with file name
447  * var tmxTiledMap = cc.TMXTiledMap.create("res/orthogonal-test1.tmx");
448  * 2.
449  * //create a TMXTiledMap with content string and resource path
450  * var resources = "res/TileMaps";
451  * var filePath = "res/TileMaps/orthogonal-test1.tmx";
452  * var xmlStr = cc.loader.getRes(filePath);
453  * var tmxTiledMap = cc.TMXTiledMap.create(xmlStr, resources);
454  */
455 cc.TMXTiledMap.create = function (tmxFile,resourcePath) {
456     return new cc.TMXTiledMap(tmxFile,resourcePath);
457 };
458