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 * @constant 29 * @type Number 30 */ 31 cc.TMX_LAYER_ATTRIB_NONE = 1 << 0; 32 /** 33 * @constant 34 * @type Number 35 */ 36 cc.TMX_LAYER_ATTRIB_BASE64 = 1 << 1; 37 /** 38 * @constant 39 * @type Number 40 */ 41 cc.TMX_LAYER_ATTRIB_GZIP = 1 << 2; 42 /** 43 * @constant 44 * @type Number 45 */ 46 cc.TMX_LAYER_ATTRIB_ZLIB = 1 << 3; 47 48 /** 49 * @constant 50 * @type Number 51 */ 52 cc.TMX_PROPERTY_NONE = 0; 53 54 /** 55 * @constant 56 * @type Number 57 */ 58 cc.TMX_PROPERTY_MAP = 1; 59 60 /** 61 * @constant 62 * @type Number 63 */ 64 cc.TMX_PROPERTY_LAYER = 2; 65 66 /** 67 * @constant 68 * @type Number 69 */ 70 cc.TMX_PROPERTY_OBJECTGROUP = 3; 71 72 /** 73 * @constant 74 * @type Number 75 */ 76 cc.TMX_PROPERTY_OBJECT = 4; 77 78 /** 79 * @constant 80 * @type Number 81 */ 82 cc.TMX_PROPERTY_TILE = 5; 83 84 /** 85 * @constant 86 * @type Number 87 */ 88 cc.TMX_TILE_HORIZONTAL_FLAG = 0x80000000; 89 90 91 /** 92 * @constant 93 * @type Number 94 */ 95 cc.TMX_TILE_VERTICAL_FLAG = 0x40000000; 96 97 /** 98 * @constant 99 * @type Number 100 */ 101 cc.TMX_TILE_DIAGONAL_FLAG = 0x20000000; 102 103 /** 104 * @constant 105 * @type Number 106 */ 107 cc.TMX_TILE_ALL_FLAGS = (cc.TMX_TILE_HORIZONTAL_FLAG | cc.TMX_TILE_VERTICAL_FLAG | cc.TMX_TILE_DIAGONAL_FLAG) >>> 0; 108 109 /** 110 * @constant 111 * @type Number 112 */ 113 cc.TMX_TILE_ALL_FLAGS_MASK = (~(cc.TMX_TILE_ALL_FLAGS)) >>> 0; 114 115 // Bits on the far end of the 32-bit global tile ID (GID's) are used for tile flags 116 117 /** 118 * <p>cc.TMXLayerInfo contains the information about the layers like: <br /> 119 * - Layer name<br /> 120 * - Layer size <br /> 121 * - Layer opacity at creation time (it can be modified at runtime) <br /> 122 * - Whether the layer is visible (if it's not visible, then the CocosNode won't be created) <br /> 123 * <br /> 124 * This information is obtained from the TMX file.</p> 125 * @class 126 * @extends cc.Class 127 */ 128 cc.TMXLayerInfo = cc.Class.extend(/** @lends cc.TMXLayerInfo# */{ 129 _properties:null, 130 name:"", 131 _layerSize:null, 132 _tiles:[], 133 visible:null, 134 _opacity:null, 135 ownTiles:true, 136 _minGID:100000, 137 _maxGID:0, 138 offset:cc.PointZero(), 139 ctor:function () { 140 this._properties = []; 141 }, 142 /** 143 * @return {Array} 144 */ 145 getProperties:function () { 146 return this._properties; 147 }, 148 149 /** 150 * @param {object} Var 151 */ 152 setProperties:function (Var) { 153 this._properties.push(Var); 154 } 155 }); 156 157 /** 158 * <p>cc.TMXTilesetInfo contains the information about the tilesets like: <br /> 159 * - Tileset name<br /> 160 * - Tileset spacing<br /> 161 * - Tileset margin<br /> 162 * - size of the tiles<br /> 163 * - Image used for the tiles<br /> 164 * - Image size<br /> 165 * 166 * This information is obtained from the TMX file. </p> 167 * @class 168 * @extends cc.Class 169 */ 170 cc.TMXTilesetInfo = cc.Class.extend(/** @lends cc.TMXTilesetInfo# */{ 171 172 /** 173 * Tileset name 174 */ 175 name:"", 176 177 /** 178 * First grid 179 */ 180 firstGid:0, 181 _tileSize:cc.SizeZero(), 182 183 /** 184 * Spacing 185 */ 186 spacing:0, 187 188 /** 189 * Margin 190 */ 191 margin:0, 192 193 /** 194 * Filename containing the tiles (should be sprite sheet / texture atlas) 195 */ 196 sourceImage:"", 197 198 /** 199 * Size in pixels of the image 200 */ 201 imageSize:cc.SizeZero(), 202 203 /** 204 * @param {Number} gid 205 * @return {cc.Rect} 206 */ 207 rectForGID:function (gid) { 208 var rect = cc.RectZero(); 209 rect.size = this._tileSize; 210 gid &= cc.TMX_TILE_ALL_FLAGS_MASK; 211 gid = gid - parseInt(this.firstGid, 10); 212 var max_x = parseInt((this.imageSize.width - this.margin * 2 + this.spacing) / (this._tileSize.width + this.spacing), 10); 213 rect.origin.x = parseInt((gid % max_x) * (this._tileSize.width + this.spacing) + this.margin, 10); 214 rect.origin.y = parseInt(parseInt(gid / max_x, 10) * (this._tileSize.height + this.spacing) + this.margin, 10); 215 return rect; 216 } 217 }); 218 219 /** 220 * <p>cc.TMXMapInfo contains the information about the map like: <br/> 221 *- Map orientation (hexagonal, isometric or orthogonal)<br/> 222 *- Tile size<br/> 223 *- Map size</p> 224 * 225 * <p>And it also contains: <br/> 226 * - Layers (an array of TMXLayerInfo objects)<br/> 227 * - Tilesets (an array of TMXTilesetInfo objects) <br/> 228 * - ObjectGroups (an array of TMXObjectGroupInfo objects) </p> 229 * 230 * <p>This information is obtained from the TMX file. </p> 231 * @class 232 * @extends cc.SAXParser 233 */ 234 cc.TMXMapInfo = cc.SAXParser.extend(/** @lends cc.TMXMapInfo# */{ 235 // map orientation 236 _orientation:null, 237 _mapSize:cc.SizeZero(), 238 _tileSize:cc.SizeZero(), 239 _layers:null, 240 _tileSets:null, 241 _objectGroups:null, 242 _parentElement:null, 243 _parentGID:null, 244 _layerAttribs:0, 245 _storingCharacters:false, 246 _properties:[], 247 // tmx filename 248 _TMXFileName:null, 249 //current string 250 _currentString:null, 251 // tile properties 252 _tileProperties:null, 253 _resources:null, 254 ctor:function () { 255 this._tileSets = []; 256 this._tileProperties = []; 257 this._properties = []; 258 }, 259 /** 260 * @return {Number} 261 */ 262 getOrientation:function () { 263 return this._orientation; 264 }, 265 266 /** 267 * @param {Number} Var 268 */ 269 setOrientation:function (Var) { 270 this._orientation = Var; 271 }, 272 273 /** 274 * Map width & height 275 * @return {cc.Size} 276 */ 277 getMapSize:function () { 278 return this._mapSize; 279 }, 280 281 /** 282 * @param {cc.Size} Var 283 */ 284 setMapSize:function (Var) { 285 this._mapSize = Var; 286 }, 287 288 /** 289 * Tiles width & height 290 * @return {cc.Size} 291 */ 292 getTileSize:function () { 293 return this._tileSize; 294 }, 295 296 /** 297 * @param {cc.Size} Var 298 */ 299 setTileSize:function (Var) { 300 this._tileSize = Var; 301 }, 302 303 /** 304 * Layers 305 * @return {Array} 306 */ 307 getLayers:function () { 308 return this._layers; 309 }, 310 311 /** 312 * @param {cc.TMXLayerInfo} Var 313 */ 314 setLayers:function (Var) { 315 this._layers.push(Var); 316 }, 317 318 /** 319 * tilesets 320 * @return {Array} 321 */ 322 getTilesets:function () { 323 return this._tileSets; 324 }, 325 326 /** 327 * @param {cc.TMXTilesetInfo} Var 328 */ 329 setTilesets:function (Var) { 330 this._tileSets.push(Var); 331 }, 332 333 /** 334 * ObjectGroups 335 * @return {Array} 336 */ 337 getObjectGroups:function () { 338 return this._objectGroups; 339 }, 340 341 /** 342 * @param {cc.TMXObjectGroup} Var 343 */ 344 setObjectGroups:function (Var) { 345 this._objectGroups.push(Var); 346 }, 347 348 /** 349 * parent element 350 * @return {Number} 351 */ 352 getParentElement:function () { 353 return this._parentElement; 354 }, 355 356 /** 357 * @param {Number} Var 358 */ 359 setParentElement:function (Var) { 360 this._parentElement = Var; 361 }, 362 363 /** 364 * parent GID 365 * @return {Number} 366 */ 367 getParentGID:function () { 368 return this._parentGID; 369 }, 370 371 /** 372 * @param {Number} Var 373 */ 374 setParentGID:function (Var) { 375 this._parentGID = Var; 376 }, 377 378 /** 379 * layer attribute 380 * @return {Number} 381 */ 382 getLayerAttribs:function () { 383 return this._layerAttribs; 384 }, 385 386 /** 387 * @param {Number} Var 388 */ 389 setLayerAttribs:function (Var) { 390 this._layerAttribs = Var; 391 }, 392 393 /** 394 * is string characters? 395 * @return {Boolean} 396 */ 397 getStoringCharacters:function () { 398 return this._storingCharacters; 399 }, 400 401 /** 402 * @param {Boolean} Var 403 */ 404 setStoringCharacters:function (Var) { 405 this._storingCharacters = Var; 406 }, 407 408 /** 409 * Properties 410 * @return {Array} 411 */ 412 getProperties:function () { 413 return this._properties; 414 }, 415 416 /** 417 * @param {object} Var 418 */ 419 setProperties:function (Var) { 420 this._properties.push(Var); 421 }, 422 423 /** 424 * Initializes a TMX format with a tmx file 425 * @param {String} tmxFile 426 * @return {String} 427 */ 428 initWithTMXFile:function (tmxFile, resourcePath) { 429 this._internalInit(tmxFile, resourcePath); 430 return this.parseXMLFile(this._TMXFileName); 431 }, 432 433 /** Initalises parsing of an XML file, either a tmx (Map) file or tsx (Tileset) file 434 * @param {String} tmxFile 435 * @return {Element} 436 */ 437 parseXMLFile:function (tmxFile) { 438 var mapXML = cc.SAXParser.getInstance().tmxParse(tmxFile); 439 var i, j; 440 441 // PARSE <map> 442 var map = mapXML.documentElement; 443 444 var version = map.getAttribute('version'); 445 var orientationStr = map.getAttribute('orientation'); 446 447 if (map.nodeName == "map") { 448 if (version != "1.0" && version !== null) 449 cc.log("cocos2d: TMXFormat: Unsupported TMX version:" + version); 450 451 if (orientationStr == "orthogonal") 452 this.setOrientation(cc.TMX_ORIENTATION_ORTHO); 453 else if (orientationStr == "isometric") 454 this.setOrientation(cc.TMX_ORIENTATION_ISO); 455 else if (orientationStr == "hexagonal") 456 this.setOrientation(cc.TMX_ORIENTATION_HEX); 457 else if (orientationStr !== null) 458 cc.log("cocos2d: TMXFomat: Unsupported orientation:" + this.getOrientation()); 459 460 var mapSize = cc.size(0, 0); 461 mapSize.width = parseFloat(map.getAttribute('width')); 462 mapSize.height = parseFloat(map.getAttribute('height')); 463 this.setMapSize(mapSize); 464 465 mapSize = cc.size(0, 0); 466 mapSize.width = parseFloat(map.getAttribute('tilewidth')); 467 mapSize.height = parseFloat(map.getAttribute('tileheight')); 468 this.setTileSize(mapSize); 469 470 // The parent element is the map 471 var propertyArr = map.querySelectorAll("map > properties > property"); 472 if (propertyArr) { 473 for (i = 0; i < propertyArr.length; i++) { 474 var aProperty = {}; 475 aProperty[propertyArr[i].getAttribute('name')] = propertyArr[i].getAttribute('value'); 476 this.setProperties(aProperty); 477 } 478 } 479 } 480 481 // PARSE <tileset> 482 var tilesets = map.getElementsByTagName('tileset'); 483 if (map.nodeName !== "map") { 484 tilesets = []; 485 tilesets.push(map); 486 } 487 488 for (i = 0; i < tilesets.length; i++) { 489 var selTileset = tilesets[i]; 490 // If this is an external tileset then start parsing that 491 var externalTilesetFilename = selTileset.getAttribute('source'); 492 if (externalTilesetFilename) { 493 this.parseXMLFile(cc.FileUtils.getInstance().fullPathFromRelativeFile(externalTilesetFilename, tmxFile)); 494 } else { 495 var tileset = new cc.TMXTilesetInfo(); 496 tileset.name = selTileset.getAttribute('name') || ""; 497 tileset.firstGid = parseInt(selTileset.getAttribute('firstgid')) || 1; 498 tileset.spacing = parseInt(selTileset.getAttribute('spacing')) || 0; 499 tileset.margin = parseInt(selTileset.getAttribute('margin')) || 0; 500 501 var tilesetSize = cc.size(0, 0); 502 tilesetSize.width = parseFloat(selTileset.getAttribute('tilewidth')); 503 tilesetSize.height = parseFloat(selTileset.getAttribute('tileheight')); 504 tileset._tileSize = tilesetSize; 505 506 var image = selTileset.getElementsByTagName('image')[0]; 507 var imgSource = image.getAttribute('source'); 508 if (imgSource) { 509 if (this._resources) 510 imgSource = this._resources + "/" + imgSource; 511 else 512 imgSource = cc.FileUtils.getInstance().fullPathFromRelativeFile(imgSource, tmxFile); 513 } 514 tileset.sourceImage = imgSource; 515 this.setTilesets(tileset); 516 } 517 } 518 519 // PARSE <tile> 520 var tiles = map.querySelectorAll('tile'); 521 if (tiles) { 522 for (i = 0; i < tiles.length; i++) { 523 var info = this._tileSets[0]; 524 var t = tiles[i]; 525 this.setParentGID(parseInt(info.firstGid) + parseInt(t.getAttribute('id') || 0)); 526 var tp = t.querySelectorAll("properties > property"); 527 if (tp) { 528 var dict = {}; 529 for (var j = 0; j < tp.length; j++) { 530 var name = tp[j].getAttribute('name'); 531 var value = tp[j].getAttribute('value'); 532 dict[name] = value; 533 } 534 this._tileProperties[this.getParentGID()] = dict; 535 } 536 } 537 } 538 539 // PARSE <layer> 540 var layers = map.getElementsByTagName('layer'); 541 if (layers) { 542 for (i = 0; i < layers.length; i++) { 543 var selLayer = layers[i]; 544 var data = selLayer.getElementsByTagName('data')[0]; 545 546 var layer = new cc.TMXLayerInfo(); 547 layer.name = selLayer.getAttribute('name'); 548 549 var layerSize = cc.size(0, 0); 550 layerSize.width = parseFloat(selLayer.getAttribute('width')); 551 layerSize.height = parseFloat(selLayer.getAttribute('height')); 552 layer._layerSize = layerSize; 553 554 var visible = selLayer.getAttribute('visible'); 555 layer.visible = !(visible == "0"); 556 557 var opacity = selLayer.getAttribute('opacity') || 1; 558 559 if (opacity) 560 layer._opacity = parseInt(255 * parseFloat(opacity)); 561 else 562 layer._opacity = 255; 563 layer.offset = cc.p(parseFloat(selLayer.getAttribute('x')) || 0, parseFloat(selLayer.getAttribute('y')) || 0); 564 565 var nodeValue = ''; 566 for (j = 0; j < data.childNodes.length; j++) { 567 nodeValue += data.childNodes[j].nodeValue 568 } 569 nodeValue = nodeValue.trim(); 570 571 // Unpack the tilemap data 572 var compression = data.getAttribute('compression'); 573 var encoding = data.getAttribute('encoding'); 574 cc.Assert(compression == null || compression === "gzip" || compression === "zlib", "TMX: unsupported compression method"); 575 switch (compression) { 576 case 'gzip': 577 layer._tiles = cc.unzipBase64AsArray(nodeValue, 4); 578 break; 579 case 'zlib': 580 var inflator = new Zlib.Inflate(cc.Codec.Base64.decodeAsArray(nodeValue, 1)); 581 layer._tiles = cc.uint8ArrayToUint32Array(inflator.decompress()); 582 break; 583 case null: 584 case '': 585 // Uncompressed 586 if (encoding == "base64") 587 layer._tiles = cc.Codec.Base64.decodeAsArray(nodeValue, 4); 588 else if (encoding === "csv") { 589 layer._tiles = []; 590 var csvTiles = nodeValue.split(','); 591 for (var csvIdx = 0; csvIdx < csvTiles.length; csvIdx++) 592 layer._tiles.push(parseInt(csvTiles[csvIdx])); 593 } else { 594 //XML format 595 var selDataTiles = data.getElementsByTagName("tile"); 596 layer._tiles = []; 597 for(var xmlIdx = 0; xmlIdx < selDataTiles.length; xmlIdx++) 598 layer._tiles.push(parseInt(selDataTiles[xmlIdx].getAttribute("gid"))); 599 } 600 break; 601 default: 602 cc.Assert(this.getLayerAttribs() != cc.TMX_LAYER_ATTRIB_NONE, "TMX tile map: Only base64 and/or gzip/zlib maps are supported"); 603 } 604 605 // The parent element is the last layer 606 var layerProps = selLayer.querySelectorAll("properties > property"); 607 if (layerProps) { 608 for (j = 0; j < layerProps.length; j++) { 609 var layerProp = {}; 610 layerProp[layerProps[j].getAttribute('name')] = layerProps[j].getAttribute('value'); 611 layer.setProperties(layerProp); 612 } 613 } 614 this.setLayers(layer); 615 } 616 } 617 618 // PARSE <objectgroup> 619 var objectGroups = map.getElementsByTagName('objectgroup'); 620 if (objectGroups) { 621 for (i = 0; i < objectGroups.length; i++) { 622 var selGroup = objectGroups[i]; 623 var objectGroup = new cc.TMXObjectGroup(); 624 objectGroup.setGroupName(selGroup.getAttribute('name')); 625 objectGroup.setPositionOffset(cc.p(parseFloat(selGroup.getAttribute('x')) * this.getTileSize().width || 0, 626 parseFloat(selGroup.getAttribute('y')) * this.getTileSize().height || 0)); 627 628 var groupProps = selGroup.querySelectorAll("objectgroup > properties > property"); 629 if (groupProps) { 630 for (j = 0; j < groupProps.length; j++) { 631 var groupProp = {}; 632 groupProp[groupProps[j].getAttribute('name')] = groupProps[j].getAttribute('value'); 633 // Add the property to the layer 634 objectGroup.setProperties(groupProp); 635 } 636 } 637 638 var objects = selGroup.querySelectorAll('object'); 639 if (objects) { 640 for (j = 0; j < objects.length; j++) { 641 var selObj = objects[j]; 642 // The value for "type" was blank or not a valid class name 643 // Create an instance of TMXObjectInfo to store the object and its properties 644 var objectProp = {}; 645 646 // Set the name of the object to the value for "name" 647 objectProp["name"] = selObj.getAttribute('name') || ""; 648 649 // Assign all the attributes as key/name pairs in the properties dictionary 650 objectProp["type"] = selObj.getAttribute('type') || ""; 651 652 objectProp["x"] = parseInt(selObj.getAttribute('x') || 0) + objectGroup.getPositionOffset().x; 653 var y = parseInt(selObj.getAttribute('y') || 0) + objectGroup.getPositionOffset().y; 654 655 objectProp["width"] = parseInt(selObj.getAttribute('width')) || 0; 656 objectProp["height"] = parseInt(selObj.getAttribute('height')) || 0; 657 658 // Correct y position. (Tiled uses Flipped, cocos2d uses Standard) 659 objectProp["y"] = parseInt(this.getMapSize().height * this.getTileSize().height) - y - objectProp["height"]; 660 661 var docObjProps = selObj.querySelectorAll("properties > property"); 662 if (docObjProps) { 663 for (var k = 0; k < docObjProps.length; k++) 664 objectProp[docObjProps[k].getAttribute('name')] = docObjProps[k].getAttribute('value'); 665 } 666 667 // Add the object to the objectGroup 668 objectGroup.setObjects(objectProp); 669 } 670 } 671 672 this.setObjectGroups(objectGroup); 673 } 674 } 675 return map; 676 }, 677 678 /** 679 * @return {object} 680 */ 681 getTileProperties:function () { 682 return this._tileProperties; 683 }, 684 685 /** 686 * @param {object} tileProperties 687 */ 688 setTileProperties:function (tileProperties) { 689 this._tileProperties.push(tileProperties); 690 }, 691 692 /** 693 * @return {String} 694 */ 695 getCurrentString:function () { 696 return this._currentString; 697 }, 698 699 /** 700 * @param {String} currentString 701 */ 702 setCurrentString:function (currentString) { 703 this._currentString = currentString; 704 }, 705 706 /** 707 * @return {String} 708 */ 709 getTMXFileName:function () { 710 return this._TMXFileName; 711 }, 712 713 /** 714 * @param {String} fileName 715 */ 716 setTMXFileName:function (fileName) { 717 this._TMXFileName = fileName; 718 }, 719 720 _internalInit:function (tmxFileName, resourcePath) { 721 this._tileSets = []; 722 this._layers = []; 723 724 this._TMXFileName = cc.FileUtils.getInstance().fullPathFromRelativePath(tmxFileName); 725 726 if (resourcePath) { 727 this._resources = resourcePath; 728 } 729 730 this._objectGroups = []; 731 732 this._properties = []; 733 this._tileProperties = []; 734 735 // tmp vars 736 this._currentString = ""; 737 this._storingCharacters = false; 738 this._layerAttribs = cc.TMX_LAYER_ATTRIB_NONE; 739 this._parentElement = cc.TMX_PROPERTY_NONE; 740 } 741 }); 742 743 /** 744 * Creates a TMX Format with a tmx file 745 * @param {String} tmxFile 746 * @param {String} resourcePath 747 * @return {cc.TMXMapInfo} 748 */ 749 cc.TMXMapInfo.create = function (tmxFile, resourcePath) { 750 var ret = new cc.TMXMapInfo(); 751 if (ret.initWithTMXFile(tmxFile, resourcePath)) { 752 return ret; 753 } 754 return null; 755 }; 756