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