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