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 cc.TMXTiledMap = cc.NodeRGBA.extend(/** @lends cc.TMXTiledMap# */{
102     //the map's size property measured in tiles
103     _mapSize:null,
104     _tileSize:null,
105     _properties:null,
106     _objectGroups:null,
107     _mapOrientation:null,
108     //tile properties
109     _tileProperties:null,
110 
111     ctor:function(){
112         cc.Node.prototype.ctor.call(this);
113         this._mapSize = cc.SizeZero();
114         this._tileSize = cc.SizeZero();
115         this._properties = null;
116         this._objectGroups = null;
117         this._mapOrientation = null;
118 
119         this._tileProperties = null;
120     },
121 
122     /**
123      * @return {cc.Size}
124      */
125     getMapSize:function () {
126         return cc.size(this._mapSize.width, this._mapSize.height);
127     },
128 
129     /**
130      * @param {cc.Size} Var
131      */
132     setMapSize:function (Var) {
133         this._mapSize.width = Var.width;
134         this._mapSize.height = Var.height;
135     },
136 
137     /**
138      * @return {cc.Size}
139      */
140     getTileSize:function () {
141         return cc.size(this._tileSize.width, this._tileSize.height);
142     },
143 
144     /**
145      * @param {cc.Size} Var
146      */
147     setTileSize:function (Var) {
148         this._tileSize.width = Var.width;
149         this._tileSize.height = Var.height;
150     },
151 
152     /**
153      * map orientation
154      * @return {Number}
155      */
156     getMapOrientation:function () {
157         return this._mapOrientation;
158     },
159 
160     /**
161      * @param {Number} Var
162      */
163     setMapOrientation:function (Var) {
164         this._mapOrientation = Var;
165     },
166 
167     /**
168      * object groups
169      * @return {Array}
170      */
171     getObjectGroups:function () {
172         return this._objectGroups;
173     },
174 
175     /**
176      * @param {Array} Var
177      */
178     setObjectGroups:function (Var) {
179         this._objectGroups = Var;
180     },
181 
182     /**
183      * properties
184      * @return {object}
185      */
186     getProperties:function () {
187         return this._properties;
188     },
189 
190     /**
191      * @param {object} Var
192      */
193     setProperties:function (Var) {
194         this._properties = Var;
195     },
196 
197     /**
198      * @param {String} tmxFile
199      * @param {String} [resourcePath=]
200      * @return {Boolean}
201      * @example
202      * //example
203      * var map = new cc.TMXTiledMap()
204      * map.initWithTMXFile("hello.tmx");
205      */
206     initWithTMXFile:function (tmxFile,resourcePath) {
207         if(!tmxFile || tmxFile.length == 0)
208             throw "cc.TMXTiledMap.initWithTMXFile(): tmxFile should be non-null or non-empty string.";
209         this.setContentSize(0, 0);
210         var mapInfo = cc.TMXMapInfo.create(tmxFile,resourcePath);
211         if (!mapInfo)
212             return false;
213 
214         var locTilesets = mapInfo.getTilesets();
215         if(!locTilesets || locTilesets.length === 0)
216             cc.log("cc.TMXTiledMap.initWithTMXFile(): Map not found. Please check the filename.");
217         this._buildWithMapInfo(mapInfo);
218         return true;
219     },
220 
221     initWithXML:function(tmxString, resourcePath){
222         this.setContentSize(0, 0);
223 
224         var mapInfo = cc.TMXMapInfo.createWithXML(tmxString, resourcePath);
225         var locTilesets = mapInfo.getTilesets();
226         if(!locTilesets || locTilesets.length === 0)
227             cc.log("cc.TMXTiledMap.initWithXML(): Map not found. Please check the filename.");
228         this._buildWithMapInfo(mapInfo);
229         return true;
230     },
231 
232     _buildWithMapInfo:function (mapInfo) {
233         this._mapSize = mapInfo.getMapSize();
234         this._tileSize = mapInfo.getTileSize();
235         this._mapOrientation = mapInfo.getOrientation();
236         this._objectGroups = mapInfo.getObjectGroups();
237         this._properties = mapInfo.getProperties();
238         this._tileProperties = mapInfo.getTileProperties();
239 
240         var idx = 0;
241         var layers = mapInfo.getLayers();
242         if (layers) {
243             var layerInfo = null;
244             for (var i = 0, len = layers.length; i < len; i++) {
245                 layerInfo = layers[i];
246                 if (layerInfo && layerInfo.visible) {
247                     var child = this._parseLayer(layerInfo, mapInfo);
248                     this.addChild(child, idx, idx);
249                     // update content size with the max size
250                     var childSize = child.getContentSize();
251                     var currentSize = this.getContentSize();
252                     this.setContentSize(cc.size(Math.max(currentSize.width, childSize.width), Math.max(currentSize.height, childSize.height)));
253                     idx++;
254                 }
255             }
256         }
257     },
258 
259     allLayers: function () {
260         var retArr = [], locChildren = this._children;
261         for(var i = 0, len = locChildren.length;i< len;i++){
262             var layer = locChildren[i];
263             if(layer && layer instanceof cc.TMXLayer)
264                 retArr.push(layer);
265         }
266         return retArr;
267     },
268 
269     /**
270      * return the TMXLayer for the specific layer
271      * @param {String} layerName
272      * @return {cc.TMXLayer}
273      */
274     getLayer:function (layerName) {
275         if(!layerName || layerName.length === 0)
276             throw "cc.TMXTiledMap.getLayer(): layerName should be non-null or non-empty string.";
277         var locChildren = this._children;
278         for (var i = 0; i < locChildren.length; i++) {
279             var layer = locChildren[i];
280             if (layer && layer.getLayerName() == layerName)
281                 return layer;
282         }
283         // layer not found
284         return null;
285     },
286 
287     /**
288      * Return the TMXObjectGroup for the specific group
289      * @param {String} groupName
290      * @return {cc.TMXObjectGroup}
291      */
292     getObjectGroup:function (groupName) {
293         if(!groupName || groupName.length === 0)
294             throw "cc.TMXTiledMap.getObjectGroup(): groupName should be non-null or non-empty string.";
295         if (this._objectGroups) {
296             for (var i = 0; i < this._objectGroups.length; i++) {
297                 var objectGroup = this._objectGroups[i];
298                 if (objectGroup && objectGroup.getGroupName() == groupName) {
299                     return objectGroup;
300                 }
301             }
302         }
303         // objectGroup not found
304         return null;
305     },
306 
307     /**
308      * Return the value for the specific property name
309      * @param {String} propertyName
310      * @return {String}
311      */
312     getProperty:function (propertyName) {
313         return this._properties[propertyName.toString()];
314     },
315 
316     /**
317      * Return properties dictionary for tile GID
318      * @param {Number} GID
319      * @return {object}
320      */
321     propertiesForGID:function (GID) {
322         return this._tileProperties[GID];
323     },
324 
325     _parseLayer:function (layerInfo, mapInfo) {
326         var tileset = this._tilesetForLayer(layerInfo, mapInfo);
327         var layer = cc.TMXLayer.create(tileset, layerInfo, mapInfo);
328         // tell the layerinfo to release the ownership of the tiles map.
329         layerInfo.ownTiles = false;
330         layer.setupTiles();
331         return layer;
332     },
333 
334     _tilesetForLayer:function (layerInfo, mapInfo) {
335         var size = layerInfo._layerSize;
336         var tilesets = mapInfo.getTilesets();
337         if (tilesets) {
338             for (var i = tilesets.length - 1; i >= 0; i--) {
339                 var tileset = tilesets[i];
340                 if (tileset) {
341                     for (var y = 0; y < size.height; y++) {
342                         for (var x = 0; x < size.width; x++) {
343                             var pos = x + size.width * y;
344                             var gid = layerInfo._tiles[pos];
345                             if (gid != 0) {
346                                 // Optimization: quick return
347                                 // if the layer is invalid (more than 1 tileset per layer) an cc.Assert will be thrown later
348                                 if (((gid & cc.TMX_TILE_FLIPPED_MASK)>>>0) >= tileset.firstGid) {
349                                     return tileset;
350                                 }
351                             }
352 
353                         }
354                     }
355                 }
356             }
357         }
358 
359         // If all the tiles are 0, return empty tileset
360         cc.log("cocos2d: Warning: TMX Layer " + layerInfo.name + " has no tiles");
361         return null;
362     }
363 });
364 
365 /**
366  * Creates a TMX Tiled Map with a TMX file.
367  * Implementation cc.TMXTiledMap
368  * @param {String} tmxFile
369  * @param {String} resourcePath
370  * @return {cc.TMXTiledMap|undefined}
371  * @example
372  * //example
373  * var map = cc.TMXTiledMap.create("hello.tmx");
374  */
375 cc.TMXTiledMap.create = function (tmxFile, resourcePath) {
376     var ret = new cc.TMXTiledMap();
377     if (ret.initWithTMXFile(tmxFile,resourcePath)) {
378         return ret;
379     }
380     return null;
381 };
382 
383 /**
384  * initializes a TMX Tiled Map with a TMX formatted XML string and a path to TMX resources
385  * @param {String} tmxString
386  * @param {String} resourcePath
387  * @return {cc.TMXTiledMap|undefined}
388  */
389 cc.TMXTiledMap.createWithXML = function(tmxString, resourcePath){
390     var tileMap = new cc.TMXTiledMap();
391     if(tileMap.initWithXML(tmxString,resourcePath))
392         return tileMap;
393     return null;
394 };
395