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 * <p>cc.TMXLayer represents the TMX layer. </p> 29 * 30 * <p>It is a subclass of cc.SpriteBatchNode. By default the tiles are rendered using a cc.TextureAtlas. <br /> 31 * If you modify a tile on runtime, then, that tile will become a cc.Sprite, otherwise no cc.Sprite objects are created. <br /> 32 * The benefits of using cc.Sprite objects as tiles are: <br /> 33 * - tiles (cc.Sprite) can be rotated/scaled/moved with a nice API </p> 34 * 35 * <p>If the layer contains a property named "cc.vertexz" with an integer (in can be positive or negative), <br /> 36 * then all the tiles belonging to the layer will use that value as their OpenGL vertex Z for depth. </p> 37 * 38 * <p>On the other hand, if the "cc.vertexz" property has the "automatic" value, then the tiles will use an automatic vertex Z value. <br /> 39 * Also before drawing the tiles, GL_ALPHA_TEST will be enabled, and disabled after drawing them. The used alpha func will be: </p> 40 * 41 * glAlphaFunc( GL_GREATER, value ) <br /> 42 * 43 * <p>"value" by default is 0, but you can change it from Tiled by adding the "cc_alpha_func" property to the layer. <br /> 44 * The value 0 should work for most cases, but if you have tiles that are semi-transparent, then you might want to use a different value, like 0.5.</p> 45 * @class 46 * @extends cc.SpriteBatchNode 47 * 48 * @property {Array} tiles - Tiles for layer 49 * @property {cc.TMXTilesetInfo} tileset - Tileset for layer 50 * @property {Number} layerOrientation - Layer orientation 51 * @property {Array} properties - Properties from the layer. They can be added using tilemap editors 52 * @property {String} layerName - Name of the layer 53 * @property {Number} layerWidth - Width of the layer 54 * @property {Number} layerHeight - Height of the layer 55 * @property {Number} tileWidth - Width of a tile 56 * @property {Number} tileHeight - Height of a tile 57 */ 58 cc.TMXLayer = cc.SpriteBatchNode.extend(/** @lends cc.TMXLayer# */{ 59 tiles: null, 60 tileset: null, 61 layerOrientation: null, 62 properties: null, 63 layerName: "", 64 65 //size of the layer in tiles 66 _layerSize: null, 67 _mapTileSize: null, 68 //TMX Layer supports opacity 69 _opacity: 255, 70 _minGID: null, 71 _maxGID: null, 72 //Only used when vertexZ is used 73 _vertexZvalue: null, 74 _useAutomaticVertexZ: null, 75 _alphaFuncValue: null, 76 //used for optimization 77 _reusedTile: null, 78 _atlasIndexArray: null, 79 //used for retina display 80 _contentScaleFactor: null, 81 82 _cacheCanvas:null, 83 _cacheContext:null, 84 _cacheTexture:null, 85 // Sub caches for avoid Chrome big image draw issue 86 _subCacheCanvas:null, 87 _subCacheContext:null, 88 _subCacheCount:0, 89 _subCacheWidth:0, 90 // Maximum pixel number by cache, a little more than 3072*3072, real limit is 4096*4096 91 _maxCachePixel:10000000, 92 _className:"TMXLayer", 93 94 /** 95 * Creates a cc.TMXLayer with an tile set info, a layer info and a map info <br/> 96 * Constructor of cc.TMXLayer 97 * @param {cc.TMXTilesetInfo} tilesetInfo 98 * @param {cc.TMXLayerInfo} layerInfo 99 * @param {cc.TMXMapInfo} mapInfo 100 */ 101 ctor:function (tilesetInfo, layerInfo, mapInfo) { 102 cc.SpriteBatchNode.prototype.ctor.call(this); 103 this._descendants = []; 104 105 this._layerSize = cc.size(0, 0); 106 this._mapTileSize = cc.size(0, 0); 107 108 if(cc._renderType === cc._RENDER_TYPE_CANVAS){ 109 var locCanvas = cc._canvas; 110 var tmpCanvas = cc.newElement('canvas'); 111 tmpCanvas.width = locCanvas.width; 112 tmpCanvas.height = locCanvas.height; 113 this._cacheCanvas = tmpCanvas; 114 this._cacheContext = this._cacheCanvas.getContext('2d'); 115 var tempTexture = new cc.Texture2D(); 116 tempTexture.initWithElement(tmpCanvas); 117 tempTexture.handleLoadedTexture(); 118 this._cacheTexture = tempTexture; 119 this.width = locCanvas.width; 120 this.height = locCanvas.height; 121 // This class uses cache, so its default cachedParent should be himself 122 this._cachedParent = this; 123 } 124 if(mapInfo !== undefined) 125 this.initWithTilesetInfo(tilesetInfo, layerInfo, mapInfo); 126 }, 127 128 /** 129 * Sets the untransformed size of the TMXLayer. 130 * @override 131 * @param {cc.Size|Number} size The untransformed size of the TMXLayer or The untransformed size's width of the TMXLayer. 132 * @param {Number} [height] The untransformed size's height of the TMXLayer. 133 */ 134 setContentSize:function (size, height) { 135 var locContentSize = this._contentSize; 136 cc.Node.prototype.setContentSize.call(this, size, height); 137 138 if(cc._renderType === cc._RENDER_TYPE_CANVAS){ 139 var locCanvas = this._cacheCanvas; 140 var scaleFactor = cc.contentScaleFactor(); 141 locCanvas.width = 0 | (locContentSize.width * 1.5 * scaleFactor); 142 locCanvas.height = 0 | (locContentSize.height * 1.5 * scaleFactor); 143 144 if(this.layerOrientation === cc.TMX_ORIENTATION_HEX) 145 this._cacheContext.translate(0, locCanvas.height - (this._mapTileSize.height * 0.5)); //translate for hexagonal 146 else 147 this._cacheContext.translate(0, locCanvas.height); 148 var locTexContentSize = this._cacheTexture._contentSize; 149 locTexContentSize.width = locCanvas.width; 150 locTexContentSize.height = locCanvas.height; 151 152 // Init sub caches if needed 153 var totalPixel = locCanvas.width * locCanvas.height; 154 if(totalPixel > this._maxCachePixel) { 155 if(!this._subCacheCanvas) this._subCacheCanvas = []; 156 if(!this._subCacheContext) this._subCacheContext = []; 157 158 this._subCacheCount = Math.ceil( totalPixel / this._maxCachePixel ); 159 var locSubCacheCanvas = this._subCacheCanvas, i; 160 for(i = 0; i < this._subCacheCount; i++) { 161 if(!locSubCacheCanvas[i]) { 162 locSubCacheCanvas[i] = document.createElement('canvas'); 163 this._subCacheContext[i] = locSubCacheCanvas[i].getContext('2d'); 164 } 165 var tmpCanvas = locSubCacheCanvas[i]; 166 tmpCanvas.width = this._subCacheWidth = Math.round( locCanvas.width / this._subCacheCount ); 167 tmpCanvas.height = locCanvas.height; 168 } 169 // Clear wasted cache to release memory 170 for(i = this._subCacheCount; i < locSubCacheCanvas.length; i++) { 171 tmpCanvas.width = 0; 172 tmpCanvas.height = 0; 173 } 174 } 175 // Otherwise use count as a flag to disable sub caches 176 else this._subCacheCount = 0; 177 } 178 }, 179 180 /** 181 * Return texture of cc.SpriteBatchNode 182 * @return {cc.Texture2D} 183 */ 184 getTexture: null, 185 186 _getTextureForCanvas:function () { 187 return this._cacheTexture; 188 }, 189 190 /** 191 * don't call visit on it's children ( override visit of cc.Node ) 192 * @override 193 * @param {CanvasRenderingContext2D} ctx 194 */ 195 visit: null, 196 197 _visitForCanvas: function (ctx) { 198 var context = ctx || cc._renderContext; 199 // quick return if not visible 200 if (!this._visible) 201 return; 202 203 context.save(); 204 this.transform(ctx); 205 var i, locChildren = this._children; 206 207 if (this._cacheDirty) { 208 // 209 var eglViewer = cc.view; 210 eglViewer._setScaleXYForRenderTexture(); 211 //add dirty region 212 var locCacheContext = this._cacheContext, locCacheCanvas = this._cacheCanvas; 213 locCacheContext.clearRect(0, 0, locCacheCanvas.width, -locCacheCanvas.height); 214 locCacheContext.save(); 215 locCacheContext.translate(this._anchorPointInPoints.x, -(this._anchorPointInPoints.y)); 216 if (locChildren) { 217 this.sortAllChildren(); 218 for (i = 0; i < locChildren.length; i++) { 219 if (locChildren[i]) 220 locChildren[i].visit(locCacheContext); 221 } 222 } 223 locCacheContext.restore(); 224 // Update sub caches if needed 225 if(this._subCacheCount > 0) { 226 var subCacheW = this._subCacheWidth, subCacheH = locCacheCanvas.height; 227 for(i = 0; i < this._subCacheCount; i++) { 228 this._subCacheContext[i].drawImage(locCacheCanvas, i * subCacheW, 0, subCacheW, subCacheH, 0, 0, subCacheW, subCacheH); 229 } 230 } 231 232 //reset Scale 233 eglViewer._resetScale(); 234 this._cacheDirty = false; 235 } 236 // draw RenderTexture 237 this.draw(ctx); 238 context.restore(); 239 }, 240 241 /** 242 * draw cc.SpriteBatchNode (override draw of cc.Node) 243 * @param {CanvasRenderingContext2D} ctx 244 */ 245 draw:null, 246 247 _drawForCanvas:function (ctx) { 248 var context = ctx || cc._renderContext; 249 //context.globalAlpha = this._opacity / 255; 250 var posX = 0 | ( -this._anchorPointInPoints.x), posY = 0 | ( -this._anchorPointInPoints.y); 251 var eglViewer = cc.view; 252 var locCacheCanvas = this._cacheCanvas; 253 //direct draw image by canvas drawImage 254 if (locCacheCanvas) { 255 var locSubCacheCount = this._subCacheCount, locCanvasHeight = locCacheCanvas.height * eglViewer._scaleY; 256 var halfTileSize = this._mapTileSize.height * 0.5 * eglViewer._scaleY; 257 if(locSubCacheCount > 0) { 258 var locSubCacheCanvasArr = this._subCacheCanvas; 259 for(var i = 0; i < locSubCacheCount; i++){ 260 var selSubCanvas = locSubCacheCanvasArr[i]; 261 if (this.layerOrientation === cc.TMX_ORIENTATION_HEX) 262 context.drawImage(locSubCacheCanvasArr[i], 0, 0, selSubCanvas.width, selSubCanvas.height, 263 posX + i * this._subCacheWidth, -(posY + locCanvasHeight) + halfTileSize, selSubCanvas.width * eglViewer._scaleX, locCanvasHeight); 264 else 265 context.drawImage(locSubCacheCanvasArr[i], 0, 0, selSubCanvas.width, selSubCanvas.height, 266 posX + i * this._subCacheWidth, -(posY + locCanvasHeight), selSubCanvas.width * eglViewer._scaleX, locCanvasHeight); 267 } 268 } else{ 269 if (this.layerOrientation === cc.TMX_ORIENTATION_HEX) 270 context.drawImage(locCacheCanvas, 0, 0, locCacheCanvas.width, locCacheCanvas.height, 271 posX, -(posY + locCanvasHeight) + halfTileSize, locCacheCanvas.width * eglViewer._scaleX, locCanvasHeight); 272 else 273 context.drawImage(locCacheCanvas, 0, 0, locCacheCanvas.width, locCacheCanvas.height, 274 posX, -(posY + locCanvasHeight), locCacheCanvas.width * eglViewer._scaleX, locCanvasHeight); 275 } 276 } 277 }, 278 279 /** 280 * @return {cc.Size} 281 */ 282 getLayerSize:function () { 283 return cc.size(this._layerSize.width, this._layerSize.height); 284 }, 285 286 /** 287 * @param {cc.Size} Var 288 */ 289 setLayerSize:function (Var) { 290 this._layerSize.width = Var.width; 291 this._layerSize.height = Var.height; 292 }, 293 294 _getLayerWidth: function () { 295 return this._layerSize.width; 296 }, 297 _setLayerWidth: function (width) { 298 this._layerSize.width = width; 299 }, 300 _getLayerHeight: function () { 301 return this._layerSize.height; 302 }, 303 _setLayerHeight: function (height) { 304 this._layerSize.height = height; 305 }, 306 307 /** 308 * Size of the map's tile (could be different from the tile's size) 309 * @return {cc.Size} 310 */ 311 getMapTileSize:function () { 312 return cc.size(this._mapTileSize.width,this._mapTileSize.height); 313 }, 314 315 /** 316 * @param {cc.Size} Var 317 */ 318 setMapTileSize:function (Var) { 319 this._mapTileSize.width = Var.width; 320 this._mapTileSize.height = Var.height; 321 }, 322 323 _getTileWidth: function () { 324 return this._mapTileSize.width; 325 }, 326 _setTileWidth: function (width) { 327 this._mapTileSize.width = width; 328 }, 329 _getTileHeight: function () { 330 return this._mapTileSize.height; 331 }, 332 _setTileHeight: function (height) { 333 this._mapTileSize.height = height; 334 }, 335 336 /** 337 * Pointer to the map of tiles 338 * @return {Array} 339 */ 340 getTiles:function () { 341 return this.tiles; 342 }, 343 344 /** 345 * @param {Array} Var 346 */ 347 setTiles:function (Var) { 348 this.tiles = Var; 349 }, 350 351 /** 352 * Tile set information for the layer 353 * @return {cc.TMXTilesetInfo} 354 */ 355 getTileset:function () { 356 return this.tileset; 357 }, 358 359 /** 360 * @param {cc.TMXTilesetInfo} Var 361 */ 362 setTileset:function (Var) { 363 this.tileset = Var; 364 }, 365 366 /** 367 * Layer orientation, which is the same as the map orientation 368 * @return {Number} 369 */ 370 getLayerOrientation:function () { 371 return this.layerOrientation; 372 }, 373 374 /** 375 * @param {Number} Var 376 */ 377 setLayerOrientation:function (Var) { 378 this.layerOrientation = Var; 379 }, 380 381 /** 382 * properties from the layer. They can be added using Tiled 383 * @return {Array} 384 */ 385 getProperties:function () { 386 return this.properties; 387 }, 388 389 /** 390 * @param {Array} Var 391 */ 392 setProperties:function (Var) { 393 this.properties = Var; 394 }, 395 396 /** 397 * Initializes a cc.TMXLayer with a tileset info, a layer info and a map info 398 * @param {cc.TMXTilesetInfo} tilesetInfo 399 * @param {cc.TMXLayerInfo} layerInfo 400 * @param {cc.TMXMapInfo} mapInfo 401 * @return {Boolean} 402 */ 403 initWithTilesetInfo:function (tilesetInfo, layerInfo, mapInfo) { 404 // XXX: is 35% a good estimate ? 405 var size = layerInfo._layerSize; 406 var totalNumberOfTiles = parseInt(size.width * size.height); 407 var capacity = totalNumberOfTiles * 0.35 + 1; // 35 percent is occupied ? 408 var texture; 409 if (tilesetInfo) 410 texture = cc.textureCache.addImage(tilesetInfo.sourceImage); 411 412 if (this.initWithTexture(texture, capacity)) { 413 // layerInfo 414 this.layerName = layerInfo.name; 415 this._layerSize = size; 416 this.tiles = layerInfo._tiles; 417 this._minGID = layerInfo._minGID; 418 this._maxGID = layerInfo._maxGID; 419 this._opacity = layerInfo._opacity; 420 this.properties = layerInfo.properties; 421 this._contentScaleFactor = cc.director.getContentScaleFactor(); 422 423 // tilesetInfo 424 this.tileset = tilesetInfo; 425 426 // mapInfo 427 this._mapTileSize = mapInfo.getTileSize(); 428 this.layerOrientation = mapInfo.orientation; 429 430 // offset (after layer orientation is set); 431 var offset = this._calculateLayerOffset(layerInfo.offset); 432 this.setPosition(cc.pointPixelsToPoints(offset)); 433 434 this._atlasIndexArray = []; 435 this.setContentSize(cc.sizePixelsToPoints(cc.size(this._layerSize.width * this._mapTileSize.width, 436 this._layerSize.height * this._mapTileSize.height))); 437 this._useAutomaticVertexZ = false; 438 this._vertexZvalue = 0; 439 return true; 440 } 441 return false; 442 }, 443 444 /** 445 * <p>Dealloc the map that contains the tile position from memory. <br /> 446 * Unless you want to know at runtime the tiles positions, you can safely call this method. <br /> 447 * If you are going to call layer.getTileGIDAt() then, don't release the map</p> 448 */ 449 releaseMap:function () { 450 if (this.tiles) 451 this.tiles = null; 452 453 if (this._atlasIndexArray) 454 this._atlasIndexArray = null; 455 }, 456 457 /** 458 * <p>Returns the tile (cc.Sprite) at a given a tile coordinate. <br/> 459 * The returned cc.Sprite will be already added to the cc.TMXLayer. Don't add it again.<br/> 460 * The cc.Sprite can be treated like any other cc.Sprite: rotated, scaled, translated, opacity, color, etc. <br/> 461 * You can remove either by calling: <br/> 462 * - layer.removeChild(sprite, cleanup); <br/> 463 * - or layer.removeTileAt(ccp(x,y)); </p> 464 * @param {cc.Point|Number} pos or x 465 * @param {Number} [y] 466 * @return {cc.Sprite} 467 */ 468 getTileAt: function (pos, y) { 469 if(!pos) 470 throw "cc.TMXLayer.getTileAt(): pos should be non-null"; 471 if(y !== undefined) 472 pos = cc.p(pos, y); 473 if(pos.x >= this._layerSize.width || pos.y >= this._layerSize.height || pos.x < 0 || pos.y < 0) 474 throw "cc.TMXLayer.getTileAt(): invalid position"; 475 if(!this.tiles || !this._atlasIndexArray){ 476 cc.log("cc.TMXLayer.getTileAt(): TMXLayer: the tiles map has been released"); 477 return null; 478 } 479 480 var tile = null, gid = this.getTileGIDAt(pos); 481 482 // if GID == 0, then no tile is present 483 if (gid === 0) 484 return tile; 485 486 var z = 0 | (pos.x + pos.y * this._layerSize.width); 487 tile = this.getChildByTag(z); 488 // tile not created yet. create it 489 if (!tile) { 490 var rect = this.tileset.rectForGID(gid); 491 rect = cc.rectPixelsToPoints(rect); 492 493 tile = new cc.Sprite(); 494 tile.initWithTexture(this.texture, rect); 495 tile.batchNode = this; 496 tile.setPosition(this.getPositionAt(pos)); 497 tile.vertexZ = this._vertexZForPos(pos); 498 tile.anchorX = 0; 499 tile.anchorY = 0; 500 tile.opacity = this._opacity; 501 502 var indexForZ = this._atlasIndexForExistantZ(z); 503 this.addSpriteWithoutQuad(tile, indexForZ, z); 504 } 505 return tile; 506 }, 507 508 /** 509 * Returns the tile gid at a given tile coordinate. <br /> 510 * if it returns 0, it means that the tile is empty. <br /> 511 * This method requires the the tile map has not been previously released (eg. don't call layer.releaseMap())<br /> 512 * @param {cc.Point|Number} pos or x 513 * @param {Number} [y] 514 * @return {Number} 515 */ 516 getTileGIDAt:function (pos, y) { 517 if(!pos) 518 throw "cc.TMXLayer.getTileGIDAt(): pos should be non-null"; 519 if(y !== undefined) 520 pos = cc.p(pos, y); 521 if(pos.x >= this._layerSize.width || pos.y >= this._layerSize.height || pos.x < 0 || pos.y < 0) 522 throw "cc.TMXLayer.getTileGIDAt(): invalid position"; 523 if(!this.tiles || !this._atlasIndexArray){ 524 cc.log("cc.TMXLayer.getTileGIDAt(): TMXLayer: the tiles map has been released"); 525 return null; 526 } 527 528 var idx = 0 | (pos.x + pos.y * this._layerSize.width); 529 // Bits on the far end of the 32-bit global tile ID are used for tile flags 530 var tile = this.tiles[idx]; 531 532 return (tile & cc.TMX_TILE_FLIPPED_MASK) >>> 0; 533 }, 534 // XXX: deprecated 535 // tileGIDAt:getTileGIDAt, 536 537 /** 538 * lipped tiles can be changed dynamically 539 * @param {cc.Point|Number} pos or x 540 * @param {Number} [y] 541 * @return {Number} 542 */ 543 getTileFlagsAt:function (pos, y) { 544 if(!pos) 545 throw "cc.TMXLayer.getTileFlagsAt(): pos should be non-null"; 546 if(y !== undefined) 547 pos = cc.p(pos, y); 548 if(pos.x >= this._layerSize.width || pos.y >= this._layerSize.height || pos.x < 0 || pos.y < 0) 549 throw "cc.TMXLayer.getTileFlagsAt(): invalid position"; 550 if(!this.tiles || !this._atlasIndexArray){ 551 cc.log("cc.TMXLayer.getTileFlagsAt(): TMXLayer: the tiles map has been released"); 552 return null; 553 } 554 555 var idx = 0 | (pos.x + pos.y * this._layerSize.width); 556 // Bits on the far end of the 32-bit global tile ID are used for tile flags 557 var tile = this.tiles[idx]; 558 559 return (tile & cc.TMX_TILE_FLIPPED_ALL) >>> 0; 560 }, 561 // XXX: deprecated 562 // tileFlagAt:getTileFlagsAt, 563 564 /** 565 * <p>Sets the tile gid (gid = tile global id) at a given tile coordinate.<br /> 566 * The Tile GID can be obtained by using the method "tileGIDAt" or by using the TMX editor . Tileset Mgr +1.<br /> 567 * If a tile is already placed at that position, then it will be removed.</p> 568 * @param {Number} gid 569 * @param {cc.Point|Number} posOrX position or x 570 * @param {Number} flagsOrY flags or y 571 * @param {Number} [flags] 572 */ 573 setTileGID: function(gid, posOrX, flagsOrY, flags) { 574 if(!posOrX) 575 throw "cc.TMXLayer.setTileGID(): pos should be non-null"; 576 var pos; 577 if (flags !== undefined) { 578 pos = cc.p(posOrX, flagsOrY); 579 } else { 580 pos = posOrX; 581 flags = flagsOrY; 582 } 583 if(pos.x >= this._layerSize.width || pos.y >= this._layerSize.height || pos.x < 0 || pos.y < 0) 584 throw "cc.TMXLayer.setTileGID(): invalid position"; 585 if(!this.tiles || !this._atlasIndexArray){ 586 cc.log("cc.TMXLayer.setTileGID(): TMXLayer: the tiles map has been released"); 587 return; 588 } 589 if(gid !== 0 && gid < this.tileset.firstGid){ 590 cc.log( "cc.TMXLayer.setTileGID(): invalid gid:" + gid); 591 return; 592 } 593 594 flags = flags || 0; 595 this._setNodeDirtyForCache(); 596 var currentFlags = this.getTileFlagsAt(pos); 597 var currentGID = this.getTileGIDAt(pos); 598 599 if (currentGID != gid || currentFlags != flags) { 600 var gidAndFlags = (gid | flags) >>> 0; 601 // setting gid=0 is equal to remove the tile 602 if (gid === 0) 603 this.removeTileAt(pos); 604 else if (currentGID === 0) // empty tile. create a new one 605 this._insertTileForGID(gidAndFlags, pos); 606 else { // modifying an existing tile with a non-empty tile 607 var z = pos.x + pos.y * this._layerSize.width; 608 var sprite = this.getChildByTag(z); 609 if (sprite) { 610 var rect = this.tileset.rectForGID(gid); 611 rect = cc.rectPixelsToPoints(rect); 612 613 sprite.setTextureRect(rect, false); 614 if (flags != null) 615 this._setupTileSprite(sprite, pos, gidAndFlags); 616 617 this.tiles[z] = gidAndFlags; 618 } else 619 this._updateTileForGID(gidAndFlags, pos); 620 } 621 } 622 }, 623 624 /** 625 * Removes a tile at given tile coordinate 626 * @param {cc.Point|Number} pos position or x 627 * @param {Number} [y] 628 */ 629 removeTileAt:function (pos, y) { 630 if(!pos) 631 throw "cc.TMXLayer.removeTileAt(): pos should be non-null"; 632 if(y !== undefined) 633 pos = cc.p(pos, y); 634 if(pos.x >= this._layerSize.width || pos.y >= this._layerSize.height || pos.x < 0 || pos.y < 0) 635 throw "cc.TMXLayer.removeTileAt(): invalid position"; 636 if(!this.tiles || !this._atlasIndexArray){ 637 cc.log("cc.TMXLayer.removeTileAt(): TMXLayer: the tiles map has been released"); 638 return; 639 } 640 641 var gid = this.getTileGIDAt(pos); 642 if (gid !== 0) { 643 if (cc._renderType === cc._RENDER_TYPE_CANVAS) 644 this._setNodeDirtyForCache(); 645 var z = 0 | (pos.x + pos.y * this._layerSize.width); 646 var atlasIndex = this._atlasIndexForExistantZ(z); 647 // remove tile from GID map 648 this.tiles[z] = 0; 649 650 // remove tile from atlas position array 651 this._atlasIndexArray.splice(atlasIndex, 1); 652 653 // remove it from sprites and/or texture atlas 654 var sprite = this.getChildByTag(z); 655 656 if (sprite) 657 cc.SpriteBatchNode.prototype.removeChild.call(this, sprite, true); //this.removeChild(sprite, true); 658 else { 659 if(cc._renderType === cc._RENDER_TYPE_WEBGL) 660 this.textureAtlas.removeQuadAtIndex(atlasIndex); 661 662 // update possible children 663 if (this._children) { 664 var locChildren = this._children; 665 for (var i = 0, len = locChildren.length; i < len; i++) { 666 var child = locChildren[i]; 667 if (child) { 668 var ai = child.atlasIndex; 669 if (ai >= atlasIndex) 670 child.atlasIndex = ai - 1; 671 } 672 } 673 } 674 } 675 } 676 }, 677 678 /** 679 * Returns the position in pixels of a given tile coordinate 680 * @param {cc.Point|Number} pos position or x 681 * @param {Number} [y] 682 * @return {cc.Point} 683 */ 684 getPositionAt:function (pos, y) { 685 if (y !== undefined) 686 pos = cc.p(pos, y); 687 var ret = cc.p(0,0); 688 switch (this.layerOrientation) { 689 case cc.TMX_ORIENTATION_ORTHO: 690 ret = this._positionForOrthoAt(pos); 691 break; 692 case cc.TMX_ORIENTATION_ISO: 693 ret = this._positionForIsoAt(pos); 694 break; 695 case cc.TMX_ORIENTATION_HEX: 696 ret = this._positionForHexAt(pos); 697 break; 698 } 699 return cc.pointPixelsToPoints(ret); 700 }, 701 // XXX: Deprecated. For backward compatibility only 702 // positionAt:getPositionAt, 703 704 /** 705 * Return the value for the specific property name 706 * @param {String} propertyName 707 * @return {*} 708 */ 709 getProperty:function (propertyName) { 710 return this.properties[propertyName]; 711 }, 712 713 /** 714 * Creates the tiles 715 */ 716 setupTiles:function () { 717 // Optimization: quick hack that sets the image size on the tileset 718 if (cc._renderType === cc._RENDER_TYPE_CANVAS) { 719 this.tileset.imageSize = this._originalTexture.getContentSizeInPixels(); 720 } else { 721 this.tileset.imageSize = this.textureAtlas.texture.getContentSizeInPixels(); 722 723 // By default all the tiles are aliased 724 // pros: 725 // - easier to render 726 // cons: 727 // - difficult to scale / rotate / etc. 728 this.textureAtlas.texture.setAliasTexParameters(); 729 } 730 731 // Parse cocos2d properties 732 this._parseInternalProperties(); 733 if (cc._renderType === cc._RENDER_TYPE_CANVAS) 734 this._setNodeDirtyForCache(); 735 736 var locLayerHeight = this._layerSize.height, locLayerWidth = this._layerSize.width; 737 for (var y = 0; y < locLayerHeight; y++) { 738 for (var x = 0; x < locLayerWidth; x++) { 739 var pos = x + locLayerWidth * y; 740 var gid = this.tiles[pos]; 741 742 // XXX: gid == 0 -. empty tile 743 if (gid !== 0) { 744 this._appendTileForGID(gid, cc.p(x, y)); 745 // Optimization: update min and max GID rendered by the layer 746 this._minGID = Math.min(gid, this._minGID); 747 this._maxGID = Math.max(gid, this._maxGID); 748 } 749 } 750 } 751 752 if (!((this._maxGID >= this.tileset.firstGid) && (this._minGID >= this.tileset.firstGid))) { 753 cc.log("cocos2d:TMX: Only 1 tileset per layer is supported"); 754 } 755 }, 756 757 /** 758 * cc.TMXLayer doesn't support adding a cc.Sprite manually. 759 * @warning addChild(child); is not supported on cc.TMXLayer. Instead of setTileGID. 760 * @param {cc.Node} child 761 * @param {number} zOrder 762 * @param {number} tag 763 */ 764 addChild:function (child, zOrder, tag) { 765 cc.log("addChild: is not supported on cc.TMXLayer. Instead use setTileGID or tileAt."); 766 }, 767 768 /** 769 * Remove child 770 * @param {cc.Sprite} sprite 771 * @param {Boolean} cleanup 772 */ 773 removeChild:function (sprite, cleanup) { 774 // allows removing nil objects 775 if (!sprite) 776 return; 777 778 if(this._children.indexOf(sprite) === -1){ 779 cc.log("cc.TMXLayer.removeChild(): Tile does not belong to TMXLayer"); 780 return; 781 } 782 783 if (cc._renderType === cc._RENDER_TYPE_CANVAS) 784 this._setNodeDirtyForCache(); 785 var atlasIndex = sprite.atlasIndex; 786 var zz = this._atlasIndexArray[atlasIndex]; 787 this.tiles[zz] = 0; 788 this._atlasIndexArray.splice(atlasIndex, 1); 789 cc.SpriteBatchNode.prototype.removeChild.call(this, sprite, cleanup); 790 }, 791 792 /** 793 * @return {String} 794 */ 795 getLayerName:function () { 796 return this.layerName; 797 }, 798 799 /** 800 * @param {String} layerName 801 */ 802 setLayerName:function (layerName) { 803 this.layerName = layerName; 804 }, 805 806 _positionForIsoAt:function (pos) { 807 return cc.p(this._mapTileSize.width / 2 * ( this._layerSize.width + pos.x - pos.y - 1), 808 this._mapTileSize.height / 2 * (( this._layerSize.height * 2 - pos.x - pos.y) - 2)); 809 }, 810 811 _positionForOrthoAt:function (pos) { 812 return cc.p(pos.x * this._mapTileSize.width, 813 (this._layerSize.height - pos.y - 1) * this._mapTileSize.height); 814 }, 815 816 _positionForHexAt:function (pos) { 817 var diffY = (pos.x % 2 == 1) ? (-this._mapTileSize.height / 2) : 0; 818 return cc.p(pos.x * this._mapTileSize.width * 3 / 4, 819 (this._layerSize.height - pos.y - 1) * this._mapTileSize.height + diffY); 820 }, 821 822 _calculateLayerOffset:function (pos) { 823 var ret = cc.p(0,0); 824 switch (this.layerOrientation) { 825 case cc.TMX_ORIENTATION_ORTHO: 826 ret = cc.p(pos.x * this._mapTileSize.width, -pos.y * this._mapTileSize.height); 827 break; 828 case cc.TMX_ORIENTATION_ISO: 829 ret = cc.p((this._mapTileSize.width / 2) * (pos.x - pos.y), 830 (this._mapTileSize.height / 2 ) * (-pos.x - pos.y)); 831 break; 832 case cc.TMX_ORIENTATION_HEX: 833 if(pos.x !== 0 || pos.y !== 0) 834 cc.log("offset for hexagonal map not implemented yet"); 835 break; 836 } 837 return ret; 838 }, 839 840 _appendTileForGID:function (gid, pos) { 841 var rect = this.tileset.rectForGID(gid); 842 rect = cc.rectPixelsToPoints(rect); 843 844 var z = 0 | (pos.x + pos.y * this._layerSize.width); 845 var tile = this._reusedTileWithRect(rect); 846 this._setupTileSprite(tile, pos, gid); 847 848 // optimization: 849 // The difference between appendTileForGID and insertTileforGID is that append is faster, since 850 // it appends the tile at the end of the texture atlas 851 var indexForZ = this._atlasIndexArray.length; 852 853 // don't add it using the "standard" way. 854 this.insertQuadFromSprite(tile, indexForZ); 855 856 // append should be after addQuadFromSprite since it modifies the quantity values 857 this._atlasIndexArray.splice(indexForZ, 0, z); 858 return tile; 859 }, 860 861 _insertTileForGID:function (gid, pos) { 862 var rect = this.tileset.rectForGID(gid); 863 rect = cc.rectPixelsToPoints(rect); 864 865 var z = 0 | (pos.x + pos.y * this._layerSize.width); 866 var tile = this._reusedTileWithRect(rect); 867 this._setupTileSprite(tile, pos, gid); 868 869 // get atlas index 870 var indexForZ = this._atlasIndexForNewZ(z); 871 872 // Optimization: add the quad without adding a child 873 this.insertQuadFromSprite(tile, indexForZ); 874 875 // insert it into the local atlasindex array 876 this._atlasIndexArray.splice(indexForZ, 0, z); 877 // update possible children 878 if (this._children) { 879 var locChildren = this._children; 880 for (var i = 0, len = locChildren.length; i < len; i++) { 881 var child = locChildren[i]; 882 if (child) { 883 var ai = child.atlasIndex; 884 if (ai >= indexForZ) 885 child.atlasIndex = ai + 1; 886 } 887 } 888 } 889 this.tiles[z] = gid; 890 return tile; 891 }, 892 893 _updateTileForGID:function (gid, pos) { 894 var rect = this.tileset.rectForGID(gid); 895 var locScaleFactor = this._contentScaleFactor; 896 rect = cc.rect(rect.x / locScaleFactor, rect.y / locScaleFactor, 897 rect.width / locScaleFactor, rect.height / locScaleFactor); 898 var z = pos.x + pos.y * this._layerSize.width; 899 900 var tile = this._reusedTileWithRect(rect); 901 this._setupTileSprite(tile, pos, gid); 902 903 // get atlas index 904 tile.atlasIndex = this._atlasIndexForExistantZ(z); 905 tile.dirty = true; 906 tile.updateTransform(); 907 this.tiles[z] = gid; 908 909 return tile; 910 }, 911 912 //The layer recognizes some special properties, like cc_vertez 913 _parseInternalProperties:function () { 914 // if cc_vertex=automatic, then tiles will be rendered using vertexz 915 var vertexz = this.getProperty("cc_vertexz"); 916 if (vertexz) { 917 if (vertexz == "automatic") { 918 this._useAutomaticVertexZ = true; 919 var alphaFuncVal = this.getProperty("cc_alpha_func"); 920 var alphaFuncValue = 0; 921 if (alphaFuncVal) 922 alphaFuncValue = parseFloat(alphaFuncVal); 923 924 if (cc._renderType === cc._RENDER_TYPE_WEBGL) { 925 this.shaderProgram = cc.shaderCache.programForKey(cc.SHADER_POSITION_TEXTURECOLORALPHATEST); 926 var alphaValueLocation = cc._renderContext.getUniformLocation(this.shaderProgram.getProgram(), cc.UNIFORM_ALPHA_TEST_VALUE_S); 927 // NOTE: alpha test shader is hard-coded to use the equivalent of a glAlphaFunc(GL_GREATER) comparison 928 this.shaderProgram.use(); 929 this.shaderProgram.setUniformLocationWith1f(alphaValueLocation, alphaFuncValue); 930 } 931 } else 932 this._vertexZvalue = parseInt(vertexz, 10); 933 } 934 }, 935 936 _setupTileSprite:function (sprite, pos, gid) { 937 var z = pos.x + pos.y * this._layerSize.width; 938 sprite.setPosition(this.getPositionAt(pos)); 939 if (cc._renderType === cc._RENDER_TYPE_WEBGL) 940 sprite.vertexZ = this._vertexZForPos(pos); 941 else 942 sprite.tag = z; 943 944 sprite.anchorX = 0; 945 sprite.anchorY = 0; 946 sprite.opacity = this._opacity; 947 if (cc._renderType === cc._RENDER_TYPE_WEBGL) { 948 sprite.rotation = 0.0; 949 } 950 951 sprite.setFlippedX(false); 952 sprite.setFlippedY(false); 953 954 // Rotation in tiled is achieved using 3 flipped states, flipping across the horizontal, vertical, and diagonal axes of the tiles. 955 if ((gid & cc.TMX_TILE_DIAGONAL_FLAG) >>> 0) { 956 // put the anchor in the middle for ease of rotation. 957 sprite.anchorX = 0.5; 958 sprite.anchorY = 0.5; 959 sprite.x = this.getPositionAt(pos).x + sprite.width / 2; 960 sprite.y = this.getPositionAt(pos).y + sprite.height / 2; 961 962 var flag = (gid & (cc.TMX_TILE_HORIZONTAL_FLAG | cc.TMX_TILE_VERTICAL_FLAG) >>> 0) >>> 0; 963 // handle the 4 diagonally flipped states. 964 if (flag == cc.TMX_TILE_HORIZONTAL_FLAG) 965 sprite.rotation = 90; 966 else if (flag == cc.TMX_TILE_VERTICAL_FLAG) 967 sprite.rotation = 270; 968 else if (flag == (cc.TMX_TILE_VERTICAL_FLAG | cc.TMX_TILE_HORIZONTAL_FLAG) >>> 0) { 969 sprite.rotation = 90; 970 sprite.setFlippedX(true); 971 } else { 972 sprite.rotation = 270; 973 sprite.setFlippedX(true); 974 } 975 } else { 976 if ((gid & cc.TMX_TILE_HORIZONTAL_FLAG) >>> 0) { 977 sprite.setFlippedX(true); 978 } 979 980 if ((gid & cc.TMX_TILE_VERTICAL_FLAG) >>> 0) { 981 sprite.setFlippedY(true); 982 } 983 } 984 }, 985 986 _reusedTileWithRect:function (rect) { 987 if(cc._renderType === cc._RENDER_TYPE_WEBGL){ 988 if (!this._reusedTile) { 989 this._reusedTile = new cc.Sprite(); 990 this._reusedTile.initWithTexture(this.texture, rect, false); 991 this._reusedTile.batchNode = this; 992 } else { 993 // XXX HACK: Needed because if "batch node" is nil, 994 // then the Sprite'squad will be reset 995 this._reusedTile.batchNode = null; 996 997 // Re-init the sprite 998 this._reusedTile.setTextureRect(rect, false); 999 1000 // restore the batch node 1001 this._reusedTile.batchNode = this; 1002 } 1003 } else { 1004 this._reusedTile = new cc.Sprite(); 1005 this._reusedTile.initWithTexture(this._textureForCanvas, rect, false); 1006 this._reusedTile.batchNode = this; 1007 this._reusedTile.parent = this; 1008 } 1009 return this._reusedTile; 1010 }, 1011 1012 _vertexZForPos:function (pos) { 1013 var ret = 0; 1014 var maxVal = 0; 1015 if (this._useAutomaticVertexZ) { 1016 switch (this.layerOrientation) { 1017 case cc.TMX_ORIENTATION_ISO: 1018 maxVal = this._layerSize.width + this._layerSize.height; 1019 ret = -(maxVal - (pos.x + pos.y)); 1020 break; 1021 case cc.TMX_ORIENTATION_ORTHO: 1022 ret = -(this._layerSize.height - pos.y); 1023 break; 1024 case cc.TMX_ORIENTATION_HEX: 1025 cc.log("TMX Hexa zOrder not supported"); 1026 break; 1027 default: 1028 cc.log("TMX invalid value"); 1029 break; 1030 } 1031 } else 1032 ret = this._vertexZvalue; 1033 return ret; 1034 }, 1035 1036 _atlasIndexForExistantZ:function (z) { 1037 var item; 1038 if (this._atlasIndexArray) { 1039 var locAtlasIndexArray = this._atlasIndexArray; 1040 for (var i = 0, len = locAtlasIndexArray.length; i < len; i++) { 1041 item = locAtlasIndexArray[i]; 1042 if (item == z) 1043 break; 1044 } 1045 } 1046 if(typeof item != "number") 1047 cc.log("cc.TMXLayer._atlasIndexForExistantZ(): TMX atlas index not found. Shall not happen"); 1048 return i; 1049 }, 1050 1051 _atlasIndexForNewZ:function (z) { 1052 var locAtlasIndexArray = this._atlasIndexArray; 1053 for (var i = 0, len = locAtlasIndexArray.length; i < len; i++) { 1054 var val = locAtlasIndexArray[i]; 1055 if (z < val) 1056 break; 1057 } 1058 return i; 1059 } 1060 }); 1061 1062 var _p = cc.TMXLayer.prototype; 1063 1064 if(cc._renderType == cc._RENDER_TYPE_WEBGL){ 1065 _p.draw = cc.SpriteBatchNode.prototype.draw; 1066 _p.visit = cc.SpriteBatchNode.prototype.visit; 1067 _p.getTexture = cc.SpriteBatchNode.prototype.getTexture; 1068 }else{ 1069 _p.draw = _p._drawForCanvas; 1070 _p.visit = _p._visitForCanvas; 1071 _p.getTexture = _p._getTextureForCanvas; 1072 } 1073 1074 /** @expose */ 1075 cc.defineGetterSetter(_p, "texture", _p.getTexture, _p.setTexture); 1076 1077 // Extended properties 1078 /** @expose */ 1079 _p.layerWidth; 1080 cc.defineGetterSetter(_p, "layerWidth", _p._getLayerWidth, _p._setLayerWidth); 1081 /** @expose */ 1082 _p.layerHeight; 1083 cc.defineGetterSetter(_p, "layerHeight", _p._getLayerHeight, _p._setLayerHeight); 1084 /** @expose */ 1085 _p.tileWidth; 1086 cc.defineGetterSetter(_p, "tileWidth", _p._getTileWidth, _p._setTileWidth); 1087 /** @expose */ 1088 _p.tileHeight; 1089 cc.defineGetterSetter(_p, "tileHeight", _p._getTileHeight, _p._setTileHeight); 1090 1091 1092 /** 1093 * Creates a cc.TMXLayer with an tile set info, a layer info and a map info 1094 * @deprecated 1095 * @param {cc.TMXTilesetInfo} tilesetInfo 1096 * @param {cc.TMXLayerInfo} layerInfo 1097 * @param {cc.TMXMapInfo} mapInfo 1098 * @return {cc.TMXLayer|Null} 1099 */ 1100 cc.TMXLayer.create = function (tilesetInfo, layerInfo, mapInfo) { 1101 return new cc.TMXLayer(tilesetInfo, layerInfo, mapInfo); 1102 }; 1103