1 /****************************************************************************
  2  Copyright (c) 2008-2010 Ricardo Quesada
  3  Copyright (c) 2011-2012 cocos2d-x.org
  4  Copyright (c) 2013-2014 Chukong Technologies 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  * @param {String} tmxFile tmxFile fileName or content string
101  * @param {String} resourcePath   If tmxFile is a file name ,it is not required.If tmxFile is content string ,it is must required.
102 
103  *
104  * @property {Array}    properties      - Properties from the map. They can be added using tilemap editors
105  * @property {Number}   mapOrientation  - Map orientation
106  * @property {Array}    objectGroups    - Object groups of the map
107  * @property {Number}   mapWidth        - Width of the map
108  * @property {Number}   mapHeight       - Height of the map
109  * @property {Number}   tileWidth       - Width of a tile
110  * @property {Number}   tileHeight      - Height of a tile
111  *
112  * @example
113  * //example
114  * 1.
115  * //create a TMXTiledMap with file name
116  * var tmxTiledMap = new cc.TMXTiledMap("res/orthogonal-test1.tmx");
117  * 2.
118  * //create a TMXTiledMap with content string and resource path
119  * var resources = "res/TileMaps";
120  * var filePath = "res/TileMaps/orthogonal-test1.tmx";
121  * var xmlStr = cc.loader.getRes(filePath);
122  * var tmxTiledMap = new cc.TMXTiledMap(xmlStr, resources);
123  */
124 cc.TMXTiledMap = cc.Node.extend(/** @lends cc.TMXTiledMap# */{
125 	properties: null,
126 	mapOrientation: null,
127 	objectGroups: null,
128 
129     //the map's size property measured in tiles
130     _mapSize: null,
131     _tileSize: null,
132     //tile properties
133     _tileProperties: null,
134     _className: "TMXTiledMap",
135 
136     /**
137      * Creates a TMX Tiled Map with a TMX file  or content string. <br/>
138      * Constructor of cc.TMXTiledMap
139      * @param {String} tmxFile tmxFile fileName or content string
140      * @param {String} resourcePath   If tmxFile is a file name ,it is not required.If tmxFile is content string ,it is must required.
141      */
142     ctor:function(tmxFile,resourcePath){
143         cc.Node.prototype.ctor.call(this);
144         this._mapSize = cc.size(0, 0);
145         this._tileSize = cc.size(0, 0);
146 
147         if(resourcePath !== undefined){
148             this.initWithXML(tmxFile,resourcePath);
149         }else if(tmxFile !== undefined){
150             this.initWithTMXFile(tmxFile);
151         }
152     },
153 
154     /**
155      * Gets the map size.
156      * @return {cc.Size}
157      */
158     getMapSize:function () {
159         return cc.size(this._mapSize.width, this._mapSize.height);
160     },
161 
162     /**
163      * Set the map size.
164      * @param {cc.Size} Var
165      */
166     setMapSize:function (Var) {
167         this._mapSize.width = Var.width;
168         this._mapSize.height = Var.height;
169     },
170 
171 	_getMapWidth: function () {
172 		return this._mapSize.width;
173 	},
174 	_setMapWidth: function (width) {
175 		this._mapSize.width = width;
176 	},
177 	_getMapHeight: function () {
178 		return this._mapSize.height;
179 	},
180 	_setMapHeight: function (height) {
181 		this._mapSize.height = height;
182 	},
183 
184     /**
185      * Gets the tile size.
186      * @return {cc.Size}
187      */
188     getTileSize:function () {
189         return cc.size(this._tileSize.width, this._tileSize.height);
190     },
191 
192     /**
193      * Set the tile size
194      * @param {cc.Size} Var
195      */
196     setTileSize:function (Var) {
197         this._tileSize.width = Var.width;
198         this._tileSize.height = Var.height;
199     },
200 
201 	_getTileWidth: function () {
202 		return this._tileSize.width;
203 	},
204 	_setTileWidth: function (width) {
205 		this._tileSize.width = width;
206 	},
207 	_getTileHeight: function () {
208 		return this._tileSize.height;
209 	},
210 	_setTileHeight: function (height) {
211 		this._tileSize.height = height;
212 	},
213 
214     /**
215      * map orientation
216      * @return {Number}
217      */
218     getMapOrientation:function () {
219         return this.mapOrientation;
220     },
221 
222     /**
223      * map orientation
224      * @param {Number} Var
225      */
226     setMapOrientation:function (Var) {
227         this.mapOrientation = Var;
228     },
229 
230     /**
231      * object groups
232      * @return {Array}
233      */
234     getObjectGroups:function () {
235         return this.objectGroups;
236     },
237 
238     /**
239      * object groups
240      * @param {Array} Var
241      */
242     setObjectGroups:function (Var) {
243         this.objectGroups = Var;
244     },
245 
246     /**
247      * Gets the properties
248      * @return {object}
249      */
250     getProperties:function () {
251         return this.properties;
252     },
253 
254     /**
255      * Set the properties
256      * @param {object} Var
257      */
258     setProperties:function (Var) {
259         this.properties = Var;
260     },
261 
262     /**
263      * Initializes the instance of cc.TMXTiledMap with tmxFile
264      * @param {String} tmxFile
265      * @return {Boolean} Whether the initialization was successful.
266      * @example
267      * //example
268      * var map = new cc.TMXTiledMap()
269      * map.initWithTMXFile("hello.tmx");
270      */
271     initWithTMXFile:function (tmxFile) {
272         if(!tmxFile || tmxFile.length === 0)
273             throw "cc.TMXTiledMap.initWithTMXFile(): tmxFile should be non-null or non-empty string.";
274 	    this.width = 0;
275 	    this.height = 0;
276         var mapInfo = new cc.TMXMapInfo(tmxFile);
277         if (!mapInfo)
278             return false;
279 
280         var locTilesets = mapInfo.getTilesets();
281         if(!locTilesets || locTilesets.length === 0)
282             cc.log("cc.TMXTiledMap.initWithTMXFile(): Map not found. Please check the filename.");
283         this._buildWithMapInfo(mapInfo);
284         return true;
285     },
286 
287     /**
288      * Initializes the instance of cc.TMXTiledMap with tmxString
289      * @param {String} tmxString
290      * @param {String} resourcePath
291      * @return {Boolean} Whether the initialization was successful.
292      */
293     initWithXML:function(tmxString, resourcePath){
294         this.width = 0;
295 	    this.height = 0;
296 
297         var mapInfo = new cc.TMXMapInfo(tmxString, resourcePath);
298         var locTilesets = mapInfo.getTilesets();
299         if(!locTilesets || locTilesets.length === 0)
300             cc.log("cc.TMXTiledMap.initWithXML(): Map not found. Please check the filename.");
301         this._buildWithMapInfo(mapInfo);
302         return true;
303     },
304 
305     _buildWithMapInfo:function (mapInfo) {
306         this._mapSize = mapInfo.getMapSize();
307         this._tileSize = mapInfo.getTileSize();
308         this.mapOrientation = mapInfo.orientation;
309         this.objectGroups = mapInfo.getObjectGroups();
310         this.properties = mapInfo.properties;
311         this._tileProperties = mapInfo.getTileProperties();
312 
313         var idx = 0;
314         var layers = mapInfo.getLayers();
315         if (layers) {
316             var layerInfo = null;
317             for (var i = 0, len = layers.length; i < len; i++) {
318                 layerInfo = layers[i];
319                 if (layerInfo && layerInfo.visible) {
320                     var child = this._parseLayer(layerInfo, mapInfo);
321                     this.addChild(child, idx, idx);
322                     // update content size with the max size
323 	                this.width = Math.max(this.width, child.width);
324 	                this.height = Math.max(this.height, child.height);
325                     idx++;
326                 }
327             }
328         }
329     },
330 
331     /**
332      * Return All layers array.
333      * @returns {Array}
334      */
335     allLayers: function () {
336         var retArr = [], locChildren = this._children;
337         for(var i = 0, len = locChildren.length;i< len;i++){
338             var layer = locChildren[i];
339             if(layer && layer instanceof cc.TMXLayer)
340                 retArr.push(layer);
341         }
342         return retArr;
343     },
344 
345     /**
346      * return the TMXLayer for the specific layer
347      * @param {String} layerName
348      * @return {cc.TMXLayer}
349      */
350     getLayer:function (layerName) {
351         if(!layerName || layerName.length === 0)
352             throw "cc.TMXTiledMap.getLayer(): layerName should be non-null or non-empty string.";
353         var locChildren = this._children;
354         for (var i = 0; i < locChildren.length; i++) {
355             var layer = locChildren[i];
356             if (layer && layer.layerName === layerName)
357                 return layer;
358         }
359         // layer not found
360         return null;
361     },
362 
363     /**
364      * Return the TMXObjectGroup for the specific group
365      * @param {String} groupName
366      * @return {cc.TMXObjectGroup}
367      */
368     getObjectGroup:function (groupName) {
369         if(!groupName || groupName.length === 0)
370             throw "cc.TMXTiledMap.getObjectGroup(): groupName should be non-null or non-empty string.";
371         if (this.objectGroups) {
372             for (var i = 0; i < this.objectGroups.length; i++) {
373                 var objectGroup = this.objectGroups[i];
374                 if (objectGroup && objectGroup.groupName === groupName) {
375                     return objectGroup;
376                 }
377             }
378         }
379         // objectGroup not found
380         return null;
381     },
382 
383     /**
384      * Return the value for the specific property name
385      * @param {String} propertyName
386      * @return {String}
387      */
388     getProperty:function (propertyName) {
389         return this.properties[propertyName.toString()];
390     },
391 
392     /**
393      * Return properties dictionary for tile GID
394      * @param {Number} GID
395      * @return {object}
396      * @deprecated
397      */
398     propertiesForGID:function (GID) {
399         cc.log("propertiesForGID is deprecated. Please use getPropertiesForGID instead.");
400         return this.getPropertiesForGID[GID];
401     },
402 
403     /**
404      * Return properties dictionary for tile GID
405      * @param {Number} GID
406      * @return {object}
407      */
408     getPropertiesForGID: function(GID) {
409         return this._tileProperties[GID];
410     },
411 
412     _parseLayer:function (layerInfo, mapInfo) {
413         var tileset = this._tilesetForLayer(layerInfo, mapInfo);
414         var layer = new cc.TMXLayer(tileset, layerInfo, mapInfo);
415         // tell the layerinfo to release the ownership of the tiles map.
416         layerInfo.ownTiles = false;
417         layer.setupTiles();
418         return layer;
419     },
420 
421     _tilesetForLayer:function (layerInfo, mapInfo) {
422         var size = layerInfo._layerSize;
423         var tilesets = mapInfo.getTilesets();
424         if (tilesets) {
425             for (var i = tilesets.length - 1; i >= 0; i--) {
426                 var tileset = tilesets[i];
427                 if (tileset) {
428                     for (var y = 0; y < size.height; y++) {
429                         for (var x = 0; x < size.width; x++) {
430                             var pos = x + size.width * y;
431                             var gid = layerInfo._tiles[pos];
432                             if (gid !== 0) {
433                                 // Optimization: quick return
434                                 // if the layer is invalid (more than 1 tileset per layer) an cc.assert will be thrown later
435                                 if (((gid & cc.TMX_TILE_FLIPPED_MASK)>>>0) >= tileset.firstGid) {
436                                     return tileset;
437                                 }
438                             }
439 
440                         }
441                     }
442                 }
443             }
444         }
445 
446         // If all the tiles are 0, return empty tileset
447         cc.log("cocos2d: Warning: TMX Layer " + layerInfo.name + " has no tiles");
448         return null;
449     }
450 });
451 
452 var _p = cc.TMXTiledMap.prototype;
453 
454 // Extended properties
455 /** @expose */
456 _p.mapWidth;
457 cc.defineGetterSetter(_p, "mapWidth", _p._getMapWidth, _p._setMapWidth);
458 /** @expose */
459 _p.mapHeight;
460 cc.defineGetterSetter(_p, "mapHeight", _p._getMapHeight, _p._setMapHeight);
461 /** @expose */
462 _p.tileWidth;
463 cc.defineGetterSetter(_p, "tileWidth", _p._getTileWidth, _p._setTileWidth);
464 /** @expose */
465 _p.tileHeight;
466 cc.defineGetterSetter(_p, "tileHeight", _p._getTileHeight, _p._setTileHeight);
467 
468 
469 /**
470  * Creates a TMX Tiled Map with a TMX file  or content string.
471  * Implementation cc.TMXTiledMap
472  * @deprecated since v3.0 please use new cc.TMXTiledMap(tmxFile,resourcePath) instead.
473  * @param {String} tmxFile tmxFile fileName or content string
474  * @param {String} resourcePath   If tmxFile is a file name ,it is not required.If tmxFile is content string ,it is must required.
475  * @return {cc.TMXTiledMap|undefined}
476  */
477 cc.TMXTiledMap.create = function (tmxFile,resourcePath) {
478     return new cc.TMXTiledMap(tmxFile,resourcePath);
479 };
480