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 29 /** 30 * @constant 31 * @type Number 32 */ 33 cc.DEFAULT_SPRITE_BATCH_CAPACITY = 29; 34 35 /** 36 * <p> 37 * In WebGL render mode ,cc.SpriteBatchNode is like a batch node: if it contains children, it will draw them in 1 single OpenGL call<br/> 38 * (often known as "batch draw").<br/> 39 * <br/> 40 * A cc.SpriteBatchNode can reference one and only one texture (one image file, one texture atlas).<br/> 41 * Only the cc.Sprites that are contained in that texture can be added to the cc.SpriteBatchNode.<br/> 42 * All cc.Sprites added to a cc.SpriteBatchNode are drawn in one OpenGL ES draw call. <br/> 43 * If the CCSprites are not added to a CCSpriteBatchNode then an OpenGL ES draw call will be needed for each one, which is less efficient. <br/> 44 * <br/> 45 * Limitations:<br/> 46 * - The only object that is accepted as child (or grandchild, grand-grandchild, etc...) is CCSprite or any subclass of CCSprite. <br/> 47 * eg: particles, labels and layer can't be added to a CCSpriteBatchNode. <br/> 48 * - Either all its children are Aliased or Antialiased. It can't be a mix. <br/> 49 * This is because "alias" is a property of the texture, and all the sprites share the same texture. </br> 50 * </p> 51 * @class 52 * @extends cc.Node 53 * @example 54 * //create a SpriteBatchNode 55 * var parent2 = cc.SpriteBatchNode.create("res/animations/grossini.png", 50); 56 */ 57 cc.SpriteBatchNode = cc.Node.extend(/** @lends cc.SpriteBatchNode# */{ 58 _textureAtlas:new cc.TextureAtlas(), 59 _blendFunc:new cc.BlendFunc(0, 0), 60 // all descendants: chlidren, gran children, etc... 61 _descendants:[], 62 _renderTexture:null, 63 _isUseCache:false, 64 _originalTexture:null, 65 /** 66 * Constructor 67 * @param {String} fileImage 68 */ 69 ctor:function (fileImage) { 70 this._super(); 71 if (fileImage) { 72 this.init(fileImage, cc.DEFAULT_SPRITE_BATCH_CAPACITY); 73 } 74 this._renderTexture = cc.RenderTexture.create(cc.canvas.width, cc.canvas.height); 75 this.setContentSize(cc.size(cc.canvas.width, cc.canvas.height)); 76 }, 77 setContentSize:function (size) { 78 if (!size) { 79 return; 80 } 81 82 this._super(size); 83 this._renderTexture.setContentSize(size); 84 }, 85 _updateBlendFunc:function () { 86 if (!this._textureAtlas.getTexture().hasPremultipliedAlpha()) { 87 this._blendFunc.src = gl.SRC_ALPHA; 88 this._blendFunc.dst = gl.ONE_MINUS_SRC_ALPHA; 89 } 90 }, 91 92 _updateAtlasIndex:function (sprite, curIndex) { 93 var count = 0; 94 var pArray = sprite.getChildren(); 95 if (pArray) { 96 count = pArray.length; 97 } 98 99 var oldIndex = 0; 100 if (count == 0) { 101 oldIndex = sprite.getAtlasIndex(); 102 sprite.setAtlasIndex(curIndex); 103 sprite.setOrderOfArrival(0); 104 if (oldIndex != curIndex) { 105 this._swap(oldIndex, curIndex); 106 } 107 curIndex++; 108 } else { 109 var needNewIndex = true; 110 if (pArray[0].getZOrder() >= 0) { 111 //all children are in front of the parent 112 oldIndex = sprite.getAtlasIndex(); 113 sprite.setAtlasIndex(curIndex); 114 sprite.setOrderOfArrival(0); 115 if (oldIndex != curIndex) { 116 this._swap(oldIndex, curIndex); 117 } 118 curIndex++; 119 needNewIndex = false; 120 } 121 122 for (var i = 0; i < pArray.length; i++) { 123 var child = pArray[i]; 124 if (needNewIndex && child.getZOrder() >= 0) { 125 oldIndex = sprite.getAtlasIndex(); 126 sprite.setAtlasIndex(curIndex); 127 sprite.setOrderOfArrival(0); 128 if (oldIndex != curIndex) { 129 this._swap(oldIndex, curIndex); 130 } 131 curIndex++; 132 needNewIndex = false; 133 } 134 this._updateAtlasIndex(child, curIndex); 135 } 136 137 if (needNewIndex) { 138 //all children have a zOrder < 0) 139 oldIndex = sprite.getAtlasIndex(); 140 sprite.setAtlasIndex(curIndex); 141 sprite.setOrderOfArrival(0); 142 if (oldIndex != curIndex) { 143 this._swap(oldIndex, curIndex); 144 } 145 curIndex++; 146 } 147 } 148 149 return curIndex; 150 }, 151 152 _swap:function (oldIndex, newIndex) { 153 var quads = this._textureAtlas.getQuads(); 154 var tempItem = this._descendants[oldIndex]; 155 var tempIteQuad = quads[oldIndex]; 156 157 //update the index of other swapped item 158 this._descendants[newIndex].setAtlasIndex(oldIndex); 159 160 this._descendants[oldIndex] = this._descendants[newIndex]; 161 quads[oldIndex] = quads[newIndex]; 162 this._descendants[newIndex] = tempItem; 163 quads[newIndex] = tempIteQuad; 164 }, 165 166 // IMPORTANT XXX IMPORTNAT: 167 // These 2 methods can't be part of cc.TMXLayer since they call [super add...], and cc.SpriteSheet#add SHALL not be called 168 169 /** 170 * <p> 171 * Adds a quad into the texture atlas but it won't be added into the children array.<br/> 172 * This method should be called only when you are dealing with very big AtlasSrite and when most of the cc.Sprite won't be updated.<br/> 173 * For example: a tile map (cc.TMXMap) or a label with lots of characters (BitmapFontAtlas)<br/> 174 * </p> 175 * @param {cc.Sprite} sprite 176 * @param {Number} index 177 */ 178 addQuadFromSprite:function (sprite, index) { 179 cc.Assert(sprite != null, "SpriteBatchNode.addQuadFromSprite():Argument must be non-nil"); 180 cc.Assert((sprite instanceof cc.Sprite), "cc.SpriteBatchNode only supports cc.Sprites as children"); 181 182 /*while(index >= this._textureAtlas.getCapacity() || this._textureAtlas.getCapacity() == this._textureAtlas.getTotalQuads()){ 183 this.increaseAtlasCapacity(); 184 }*/ 185 //todo fixed 186 // 187 // update the quad directly. Don't add the sprite to the scene graph 188 // 189 sprite.setBatchNode(this); 190 sprite.setAtlasIndex(index); 191 192 this._textureAtlas.insertQuad(sprite.getQuad(), index); 193 194 // XXX: updateTransform will update the textureAtlas too using updateQuad. 195 // XXX: so, it should be AFTER the insertQuad 196 sprite.setDirty(true); 197 sprite.updateTransform(); 198 199 if (cc.renderContextType == cc.CANVAS) { 200 this._children = cc.ArrayAppendObjectToIndex(this._children, sprite, index); 201 } 202 }, 203 204 /** 205 * <p> 206 * This is the opposite of "addQuadFromSprite.<br/> 207 * It add the sprite to the children and descendants array, but it doesn't update add it to the texture atlas<br/> 208 * </p> 209 * @param {cc.Node} child 210 * @param {Number} z zOrder 211 * @param {Number} aTag 212 * @return {cc.SpriteBatchNode} 213 */ 214 addSpriteWithoutQuad:function (child, z, aTag) { 215 cc.Assert(child != null, "SpriteBatchNode.addQuadFromSprite():Argument must be non-nil"); 216 cc.Assert((child instanceof cc.Sprite), "cc.SpriteBatchNode only supports cc.Sprites as children"); 217 218 // quad index is Z 219 child.setAtlasIndex(z); 220 221 // XXX: optimize with a binary search 222 var i = 0; 223 224 if (this._descendants && this._descendants.length > 0) { 225 var obj = null; 226 for (var index = 0; index < this._descendants.length; index++) { 227 obj = this._descendants[index]; 228 if (obj && (obj.getAtlasIndex() >= z)) { 229 ++i; 230 } 231 } 232 } 233 this._descendants = cc.ArrayAppendObjectToIndex(this._descendants, child, i); 234 235 // IMPORTANT: Call super, and not self. Avoid adding it to the texture atlas array 236 this.addChild(child, z, aTag); 237 238 //#issue 1262 don't use lazy sorting, tiles are added as quads not as sprites, so sprites need to be added in order 239 this.reorderBatch(false); 240 241 return this; 242 }, 243 244 // property 245 /** 246 * Return TextureAtlas of cc.SpriteBatchNode 247 * @return {cc.TextureAtlas} 248 */ 249 getTextureAtlas:function () { 250 return this._textureAtlas; 251 }, 252 253 /** 254 * TextureAtlas of cc.SpriteBatchNode setter 255 * @param {cc.TextureAtlas} textureAtlas 256 */ 257 setTextureAtlas:function (textureAtlas) { 258 if (textureAtlas != this._textureAtlas) { 259 this._textureAtlas = textureAtlas; 260 } 261 }, 262 263 /** 264 * Return Descendants of cc.SpriteBatchNode 265 * @return {Array} 266 */ 267 getDescendants:function () { 268 return this._descendants; 269 }, 270 271 /** 272 * <p> 273 * initializes a CCSpriteBatchNode with a texture2d and capacity of children.<br/> 274 * The capacity will be increased in 33% in runtime if it run out of space. 275 * </p> 276 * @param {cc.Texture2D} tex 277 * @param {Number} capacity 278 * @return {Boolean} 279 */ 280 initWithTexture:function (tex, capacity) { 281 this._children = []; 282 this._descendants = []; 283 284 this._blendFunc.src = cc.BLEND_SRC; 285 this._blendFunc.dst = cc.BLEND_DST; 286 this._textureAtlas = new cc.TextureAtlas(); 287 capacity = capacity || cc.DEFAULT_SPRITE_BATCH_CAPACITY; 288 289 this._textureAtlas.initWithTexture(tex, capacity); 290 if (cc.renderContextType == cc.CANVAS) { 291 this._originalTexture = tex; 292 } 293 if (cc.renderContextType == cc.WEBGL) { 294 this._updateBlendFunc(); 295 //this.setShaderProgram(cc.ShaderCache.getInstance().programForKey(cc.Shader_PositionTextureColor)) ; 296 } 297 return true; 298 }, 299 300 /** 301 * set this node is dirty ,need redraw 302 */ 303 setNodeDirty:function () { 304 this._setNodeDirtyForCache(); 305 this._transformDirty = this._inverseDirty = true; 306 if (cc.NODE_TRANSFORM_USING_AFFINE_MATRIX) { 307 this._transformGLDirty = true; 308 } 309 }, 310 311 _setNodeDirtyForCache:function () { 312 this._cacheDirty = true; 313 }, 314 315 /** 316 * <p> 317 * initializes a cc.SpriteBatchNode with a file image (.png, .jpeg, .pvr, etc) and a capacity of children.<br/> 318 * The capacity will be increased in 33% in runtime if it run out of space.<br/> 319 * The file will be loaded using the TextureMgr. 320 * </p> 321 * @param {String} fileImage 322 * @param {Number} capacity 323 * @return {Boolean} 324 */ 325 init:function (fileImage, capacity) { 326 var texture2D = cc.TextureCache.getInstance().textureForKey(fileImage); 327 if (!texture2D) 328 texture2D = cc.TextureCache.getInstance().addImage(fileImage); 329 return this.initWithTexture(texture2D, capacity); 330 }, 331 332 /** 333 * increase Atlas Capacity 334 */ 335 increaseAtlasCapacity:function () { 336 // if we're going beyond the current TextureAtlas's capacity, 337 // all the previously initialized sprites will need to redo their texture coords 338 // this is likely computationally expensive 339 var quantity = (this._textureAtlas.getCapacity() + 1) * 4 / 3; 340 341 cc.log("cocos2d: CCSpriteBatchNode: resizing TextureAtlas capacity from " + this._textureAtlas.getCapacity() + " to [" + quantity + "]."); 342 343 if (!this._textureAtlas.resizeCapacity(quantity)) { 344 // serious problems 345 cc.log("cocos2d: WARNING: Not enough memory to resize the atlas"); 346 cc.Assert(false, "Not enough memory to resize the atla"); 347 } 348 }, 349 350 /** 351 * removes a child given a certain index. It will also cleanup the running actions depending on the cleanup parameter. 352 * @warning Removing a child from a CCSpriteBatchNode is very slow 353 * @param {Number} index 354 * @param {Boolean} doCleanup 355 */ 356 removeChildAtIndex:function (index, doCleanup) { 357 this.removeChild(this._children[index], doCleanup); 358 }, 359 360 /** 361 * add child helper 362 * @param {cc.Sprite} sprite 363 * @param {Number} index 364 */ 365 insertChild:function (sprite, index) { 366 sprite.setBatchNode(this); 367 sprite.setAtlasIndex(index); 368 sprite.setDirty(true); 369 370 if (this._textureAtlas.getTotalQuads() == this._textureAtlas.getCapacity()) { 371 this.increaseAtlasCapacity(); 372 } 373 374 this._textureAtlas.insertQuad(sprite.getQuad(), index); 375 376 this._descendants = cc.ArrayAppendObjectToIndex(this._descendants, sprite, index); 377 378 // update indices 379 var i = index + 1; 380 if (this._descendants && this._descendants.length > 0) { 381 for (; i < this._descendants.length; i++) { 382 this._descendants[i].setAtlasIndex(this._descendants[i].getAtlasIndex() + 1); 383 } 384 } 385 386 // add children recursively 387 var children = sprite.getChildren(); 388 if (children && children.length > 0) { 389 for (i = 0; i < children.length; i++) { 390 if (children[i]) { 391 var getIndex = this.atlasIndexForChild(children[i], children[i].getZOrder()); 392 this.insertChild(children[i], getIndex); 393 } 394 } 395 } 396 }, 397 398 /** 399 * addChild helper, faster than insertChild 400 * @param {cc.Sprite} sprite 401 */ 402 appendChild:function (sprite) { 403 this._reorderChildDirty = true; 404 sprite.setBatchNode(this); 405 sprite.setDirty(true); 406 407 if (this._textureAtlas.getTotalQuads() == this._textureAtlas.getCapacity()) { 408 this.increaseAtlasCapacity(); 409 } 410 411 cc.ArrayAppendObject(this._descendants, sprite); 412 413 var index = this._descendants.length - 1; 414 415 sprite.setAtlasIndex(index); 416 417 this._textureAtlas.insertQuad(sprite.getQuad(), index); 418 419 // add children recursively 420 var children = sprite.getChildren(); 421 for (var i = 0; i < children.length; i++) { 422 this.appendChild(children[i]); 423 } 424 }, 425 426 /** 427 * remove sprite from TextureAtlas 428 * @param {cc.Sprite} sprite 429 */ 430 removeSpriteFromAtlas:function (sprite) { 431 // remove from TextureAtlas 432 this._textureAtlas.removeQuadAtIndex(sprite.getAtlasIndex()); 433 434 // Cleanup sprite. It might be reused (issue #569) 435 sprite.setBatchNode(null); 436 437 var index = cc.ArrayGetIndexOfObject(this._descendants, sprite); 438 if (index != -1) { 439 cc.ArrayRemoveObjectAtIndex(this._descendants, index); 440 441 // update all sprites beyond this one 442 var len = this._descendants.length; 443 for (; index < len; ++index) { 444 var s = this._descendants[index]; 445 s.setAtlasIndex(s.getAtlasIndex() - 1); 446 } 447 } 448 449 // remove children recursively 450 var children = sprite.getChildren(); 451 if (children && children.length > 0) { 452 for (var i = 0; i < children.length; i++) { 453 if (children[i]) { 454 this.removeSpriteFromAtlas(children[i]); 455 } 456 } 457 } 458 }, 459 460 /** 461 * rebuild index in order for child 462 * @param {cc.Sprite} pobParent 463 * @param {Number} index 464 * @return {Number} 465 */ 466 rebuildIndexInOrder:function (pobParent, index) { 467 var children = pobParent.getChildren(); 468 469 if (children && children.length > 0) { 470 for (var i = 0; i < children.length; i++) { 471 var obj = children[i]; 472 if (obj && (obj.getZOrder() < 0)) { 473 index = this.rebuildIndexInOrder(obj, index); 474 } 475 } 476 } 477 478 // ignore self (batch node) 479 if (!pobParent.isEqual(this)) { 480 pobParent.setAtlasIndex(index); 481 index++; 482 } 483 484 if (children && children.length > 0) { 485 for (i = 0; i < children.length; i++) { 486 obj = children[i]; 487 if (obj && (obj.getZOrder() >= 0)) { 488 index = this.rebuildIndexInOrder(obj, index); 489 } 490 } 491 } 492 493 return index; 494 }, 495 496 /** 497 * get highest atlas index in child 498 * @param {cc.Sprite} sprite 499 * @return {Number} 500 */ 501 highestAtlasIndexInChild:function (sprite) { 502 var children = sprite.getChildren(); 503 504 if (!children || children.length == 0) { 505 return sprite.getAtlasIndex(); 506 } else { 507 return this.highestAtlasIndexInChild(children.pop()); 508 } 509 }, 510 511 /** 512 * get lowest atlas index in child 513 * @param {cc.Sprite} sprite 514 * @return {Number} 515 */ 516 lowestAtlasIndexInChild:function (sprite) { 517 var children = sprite.getChildren(); 518 519 if (!children || children.length == 0) { 520 return sprite.getAtlasIndex(); 521 } else { 522 return this.lowestAtlasIndexInChild(children.pop()); 523 } 524 }, 525 526 /** 527 * get atlas index for child 528 * @param {cc.Sprite} sprite 529 * @param {Number} nZ 530 * @return {Number} 531 */ 532 atlasIndexForChild:function (sprite, nZ) { 533 var brothers = sprite.getParent().getChildren(); 534 var childIndex = cc.ArrayGetIndexOfObject(brothers, sprite); 535 536 // ignore parent Z if parent is spriteSheet 537 var ignoreParent = sprite.getParent() == this; 538 var previous = null; 539 if (childIndex > 0 && childIndex < cc.UINT_MAX) { 540 previous = brothers[childIndex - 1]; 541 } 542 543 // first child of the sprite sheet 544 if (ignoreParent) { 545 if (childIndex == 0) { 546 return 0; 547 } 548 return this.highestAtlasIndexInChild(previous) + 1; 549 } 550 551 // parent is a CCSprite, so, it must be taken into account 552 // first child of an CCSprite ? 553 if (childIndex == 0) { 554 var p = sprite.getParent(); 555 556 // less than parent and brothers 557 if (nZ < 0) { 558 return p.getAtlasIndex(); 559 } else { 560 return p.getAtlasIndex() + 1; 561 } 562 } else { 563 // previous & sprite belong to the same branch 564 if ((previous.getZOrder() < 0 && nZ < 0) || (previous.getZOrder() >= 0 && nZ >= 0)) { 565 return this.highestAtlasIndexInChild(previous) + 1; 566 } 567 568 // else (previous < 0 and sprite >= 0 ) 569 var p = sprite.getParent(); 570 return p.getAtlasIndex() + 1; 571 } 572 573 // Should not happen. Error calculating Z on SpriteSheet 574 cc.Assert(0, "CCSpriteBatchNode.atlasIndexForChild():should not run here"); 575 return 0; 576 }, 577 578 /** 579 * Sprites use this to start sortChildren, don't call this manually 580 * @param {Boolean} reorder 581 */ 582 reorderBatch:function (reorder) { 583 this._reorderChildDirty = reorder; 584 }, 585 586 // CCTextureProtocol 587 /** 588 * Return texture of cc.SpriteBatchNode 589 * @return {cc.Texture2D} 590 */ 591 getTexture:function () { 592 return this._textureAtlas.getTexture(); 593 }, 594 595 /** 596 * texture of cc.SpriteBatchNode setter 597 * @param {cc.Texture2D} texture 598 */ 599 setTexture:function (texture) { 600 this._textureAtlas.setTexture(texture); 601 for (var i = 0; i < this._children.length; i++) { 602 this._children[i].setTexture(texture); 603 } 604 //this._updateBlendFunc(); 605 }, 606 607 /** 608 * set the source blending function for the texture 609 * @param {Number} src 610 * @param {Number} dst 611 */ 612 setBlendFunc:function (src, dst) { 613 if(arguments.length == 1) 614 this._blendFunc = src; 615 else 616 this._blendFunc = {src:src, dst:dst}; 617 }, 618 619 /** 620 * returns the blending function used for the texture 621 * @return {cc.BlendFunc} 622 */ 623 getBlendFunc:function () { 624 return this._blendFunc; 625 }, 626 627 /** 628 * don't call visit on it's children ( override visit of cc.Node ) 629 * @override 630 * @param {CanvasContext} ctx 631 */ 632 visit:function (ctx) { 633 if (cc.renderContextType == cc.CANVAS) { 634 var context = ctx || cc.renderContext; 635 // quick return if not visible 636 if (!this._visible) { 637 return; 638 } 639 context.save(); 640 this.transform(ctx); 641 var i; 642 if (this._isUseCache) { 643 if (this._cacheDirty) { 644 //add dirty region 645 this._renderTexture.clear(); 646 this._renderTexture.context.save(); 647 this._renderTexture.context.translate(this._anchorPointInPoints.x, -(this._anchorPointInPoints.y )); 648 if (this._children) { 649 this.sortAllChildren(); 650 for (i = 0; i < this._children.length; i++) { 651 if (this._children[i]) { 652 this._children[i].visit(this._renderTexture.context); 653 } 654 } 655 } 656 this._renderTexture.context.restore(); 657 this._cacheDirty = false; 658 } 659 // draw RenderTexture 660 this.draw(ctx); 661 } else { 662 if (this._children) { 663 this.sortAllChildren(); 664 for (i = 0; i < this._children.length; i++) { 665 if (this._children[i]) { 666 this._children[i].visit(context); 667 } 668 } 669 } 670 } 671 context.restore(); 672 } else { 673 //TODO 674 //cc.PROFILER_START_CATEGORY(kCCProfilerCategoryBatchSprite, "CCSpriteBatchNode - visit"); 675 676 // CAREFUL: 677 // This visit is almost identical to CocosNode#visit 678 // with the exception that it doesn't call visit on it's children 679 // 680 // The alternative is to have a void CCSprite#visit, but 681 // although this is less mantainable, is faster 682 // 683 if (!this._visible) { 684 return; 685 } 686 687 //kmGLPushMatrix(); 688 689 if (this._grid && this._grid.isActive()) { 690 this._grid.beforeDraw(); 691 this.transformAncestors(); 692 } 693 694 this.sortAllChildren(); 695 this.transform(); 696 697 this.draw(); 698 699 if (this._grid && this._grid.isActive()) { 700 this._grid.afterDraw(this); 701 } 702 703 //kmGLPopMatrix(); 704 this.setOrderOfArrival(0); 705 706 //cc.PROFILER_STOP_CATEGORY(kCCProfilerCategoryBatchSprite, "CCSpriteBatchNode - visit"); 707 } 708 }, 709 710 /** 711 * add child to cc.SpriteBatchNode (override addChild of cc.Node) 712 * @override 713 * @param {cc.Sprite} child 714 * @param {Number} zOrder 715 * @param {Number} tag 716 */ 717 addChild:function (child, zOrder, tag) { 718 switch (arguments.length) { 719 case 1: 720 this._super(child); 721 break; 722 case 2: 723 this._super(child, zOrder); 724 break; 725 case 3: 726 cc.Assert(child != null, "SpriteBatchNode.addChild():child should not be null"); 727 cc.Assert((child instanceof cc.Sprite), "cc.SpriteBatchNode only supports cc.Sprites as children"); 728 729 // check CCSprite is using the same texture id 730 if (cc.renderContextType != cc.CANVAS) { 731 cc.Assert(child.getTexture().getName() == this._textureAtlas.getTexture().getName(), 732 "SpriteBatchNode.addChild():check cc.Sprite is using the same texture id"); 733 } 734 this._super(child, zOrder, tag); 735 this.appendChild(child); 736 break; 737 case 4: 738 if (arguments[3]) { 739 this._super(child, zOrder, tag); 740 } 741 break; 742 default: 743 throw "Argument must be non-nil "; 744 break; 745 } 746 747 //this._addDirtyRegionToDirector(this.getBoundingBoxToWorld()); 748 this.setNodeDirty(); 749 }, 750 751 /** 752 * (override reorderChild of cc.Node) 753 * @override 754 * @param {cc.Sprite} child 755 * @param {Number} zOrder 756 */ 757 reorderChild:function (child, zOrder) { 758 cc.Assert(child != null, "SpriteBatchNode.addChild():the child should not be null"); 759 cc.Assert(this._children.indexOf(child) > -1, "SpriteBatchNode.addChild():Child doesn't belong to Sprite"); 760 761 if (zOrder == child.getZOrder()) { 762 return; 763 } 764 765 //save dirty region when before change 766 //this._addDirtyRegionToDirector(this.getBoundingBoxToWorld()); 767 768 //set the z-order and sort later 769 this._super(child, zOrder); 770 771 //save dirty region when after changed 772 //this._addDirtyRegionToDirector(this.getBoundingBoxToWorld()); 773 this.setNodeDirty(); 774 }, 775 776 /** 777 * remove child from cc.SpriteBatchNode (override removeChild of cc.Node) 778 * @param {cc.Sprite} child 779 * @param cleanup 780 */ 781 removeChild:function (child, cleanup) { 782 // explicit null handling 783 if (child == null) { 784 return; 785 } 786 cc.Assert(this._children.indexOf(child) > -1, "SpriteBatchNode.addChild():sprite batch node should contain the child"); 787 788 // cleanup before removing 789 this.removeSpriteFromAtlas(child); 790 791 this._super(child, cleanup); 792 }, 793 794 /** 795 * <p>Removes all children from the container and do a cleanup all running actions depending on the cleanup parameter. <br/> 796 * (override removeAllChildren of cc.Node)</p> 797 * @param {Boolean} cleanup 798 */ 799 removeAllChildren:function (cleanup) { 800 // Invalidate atlas index. issue #569 801 // useSelfRender should be performed on all descendants. issue #1216 802 var i; 803 if (this._descendants && this._descendants.length > 0) { 804 for (i = 0; i < this._descendants.length; i++) { 805 if (this._descendants[i]) { 806 this._descendants[i].setBatchNode(null); 807 } 808 } 809 } 810 811 this._super(cleanup); 812 this._descendants = []; 813 this._textureAtlas.removeAllQuads(); 814 }, 815 816 sortAllChildren:function () { 817 if (this._reorderChildDirty) { 818 var i = 0, j = 0, length = this._children.length; 819 //insertion sort 820 for (i = 1; i < length; i++) { 821 var tempItem = this._children[i]; 822 j = i - 1; 823 824 //continue moving element downwards while zOrder is smaller or when zOrder is the same but orderOfArrival is smaller 825 while (j >= 0 && (tempItem.getZOrder() < this._children[j].getZOrder() || 826 (tempItem.getZOrder() == this._children[j].getZOrder() && tempItem.getOrderOfArrival() < this._children[j].getOrderOfArrival()))) { 827 this._children[j + 1] = this._children[j]; 828 j--; 829 } 830 this._children[j + 1] = tempItem; 831 } 832 833 //sorted now check all children 834 if (this._children.length > 0) { 835 //first sort all children recursively based on zOrder 836 this._arrayMakeObjectsPerformSelector(this._children, cc.Node.StateCallbackType.sortAllChildren); 837 838 var index = 0; 839 //fast dispatch, give every child a new atlasIndex based on their relative zOrder (keep parent -> child relations intact) 840 // and at the same time reorder descedants and the quads to the right index 841 if (cc.renderContextType == cc.WEBGL) { 842 for (i = 0; i < this._children.length; i++) { 843 index = this._updateAtlasIndex(this._children[i], index); 844 } 845 } 846 } 847 848 this._reorderChildDirty = false; 849 } 850 }, 851 852 /** 853 * draw cc.SpriteBatchNode (override draw of cc.Node) 854 * @param {CanvasContext} ctx 855 */ 856 draw:function (ctx) { 857 //cc.PROFILER_START("cc.SpriteBatchNode - draw"); 858 this._super(); 859 860 if (cc.renderContextType == cc.CANVAS) { 861 var context = ctx || cc.renderContext; 862 //context.globalAlpha = this._opacity / 255; 863 var pos = cc.p(0 | ( -this._anchorPointInPoints.x), 0 | ( -this._anchorPointInPoints.y)); 864 if (this._renderTexture) { 865 //direct draw image by canvas drawImage 866 context.drawImage(this._renderTexture.getCanvas(), pos.x, -(pos.y + this._renderTexture.getCanvas().height)); 867 } 868 } else { 869 // Optimization: Fast Dispatch 870 if (this._textureAtlas.getTotalQuads() == 0) { 871 return; 872 } 873 874 //cc.NODE_DRAW_SETUP(); 875 876 this._arrayMakeObjectsPerformSelector(this._children, cc.Node.StateCallbackType.updateTransform); 877 878 //ccGLBlendFunc( m_blendFunc.src, m_blendFunc.dst ); 879 880 this._textureAtlas.drawQuads(); 881 882 //cc.PROFILER_STOP("CCSpriteBatchNode - draw"); 883 } 884 } 885 }); 886 887 /** 888 * <p> 889 * creates a CCSpriteBatchNode with a file image (.png, .jpeg, .pvr, etc) with a default capacity of 29 children.<br/> 890 * The capacity will be increased in 33% in runtime if it run out of space.<br/> 891 * The file will be loaded using the TextureMgr.<br/> 892 * </p> 893 * @param {String} fileImage 894 * @param {Number} capacity 895 * @return {cc.SpriteBatchNode} 896 * @example 897 * //create a SpriteBatchNode 898 * var parent2 = cc.SpriteBatchNode.create("res/animations/grossini.png", 50); 899 */ 900 cc.SpriteBatchNode.create = function (fileImage, capacity) { 901 if (!capacity) { 902 capacity = cc.DEFAULT_SPRITE_BATCH_CAPACITY; 903 } 904 905 var batchNode = new cc.SpriteBatchNode(); 906 batchNode.init(fileImage, capacity); 907 908 return batchNode; 909 }; 910 911 /** 912 * <p> 913 * creates a CCSpriteBatchNode with a texture2d and a default capacity of 29 children.<br/> 914 * The capacity will be increased in 33% in runtime if it run out of space.<br/> 915 * </p> 916 * @param {cc.Texture2D} texture 917 * @param {Number} capacity 918 * @return {cc.SpriteBatchNode} 919 */ 920 cc.SpriteBatchNode.createWithTexture = function (texture, capacity) { 921 if (!capacity) { 922 capacity = cc.DEFAULT_SPRITE_BATCH_CAPACITY; 923 } 924 925 var batchNode = new cc.SpriteBatchNode(); 926 batchNode.initWithTexture(texture, capacity); 927 928 return batchNode; 929 }; 930