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