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 /** 29 * <p> 30 * A cc.SpriteBatchNode can reference one and only one texture (one image file, one texture atlas).<br/> 31 * Only the cc.Sprites that are contained in that texture can be added to the cc.SpriteBatchNode.<br/> 32 * All cc.Sprites added to a cc.SpriteBatchNode are drawn in one WebGL draw call. <br/> 33 * If the cc.Sprites are not added to a cc.SpriteBatchNode then an WebGL draw call will be needed for each one, which is less efficient. <br/> 34 * <br/> 35 * Limitations:<br/> 36 * - The only object that is accepted as child (or grandchild, grand-grandchild, etc...) is cc.Sprite or any subclass of cc.Sprite. <br/> 37 * eg: particles, labels and layer can't be added to a cc.SpriteBatchNode. <br/> 38 * - Either all its children are Aliased or Antialiased. It can't be a mix. <br/> 39 * This is because "alias" is a property of the texture, and all the sprites share the same texture. </br> 40 * </p> 41 * @class 42 * @extends cc.Node 43 * 44 * @param {String|cc.Texture2D} fileImage 45 * @param {Number} capacity 46 * @example 47 * 48 * // 1. create a SpriteBatchNode with image path 49 * var spriteBatchNode = new cc.SpriteBatchNode("res/animations/grossini.png", 50); 50 * 51 * // 2. create a SpriteBatchNode with texture 52 * var texture = cc.textureCache.addImage("res/animations/grossini.png"); 53 * var spriteBatchNode = new cc.SpriteBatchNode(texture,50); 54 * 55 * @property {cc.TextureAtlas} textureAtlas - The texture atlas 56 * @property {Array} descendants - <@readonly> Descendants of sprite batch node 57 */ 58 cc.SpriteBatchNode = cc.Node.extend(/** @lends cc.SpriteBatchNode# */{ 59 _blendFunc: null, 60 // all descendants: chlidren, gran children, etc... 61 _descendants: null, 62 _className: "SpriteBatchNode", 63 64 ctor: function (fileImage, capacity) { 65 cc.Node.prototype.ctor.call(this); 66 this._descendants = []; 67 this._blendFunc = new cc.BlendFunc(cc.BLEND_SRC, cc.BLEND_DST); 68 69 var texture2D; 70 capacity = capacity || cc.SpriteBatchNode.DEFAULT_CAPACITY; 71 if (cc.isString(fileImage)) { 72 texture2D = cc.textureCache.getTextureForKey(fileImage); 73 if (!texture2D) 74 texture2D = cc.textureCache.addImage(fileImage); 75 }else if (fileImage instanceof cc.Texture2D) 76 texture2D = fileImage; 77 78 texture2D && this.initWithTexture(texture2D, capacity); 79 }, 80 81 /** 82 * <p> 83 * This is the opposite of "addQuadFromSprite.<br/> 84 * It add the sprite to the children and descendants array, but it doesn't update add it to the texture atlas<br/> 85 * </p> 86 * @param {cc.Sprite} child 87 * @param {Number} z zOrder 88 * @param {Number} aTag 89 * @return {cc.SpriteBatchNode} 90 */ 91 addSpriteWithoutQuad: function (child, z, aTag) { 92 cc.assert(child, cc._LogInfos.SpriteBatchNode_addSpriteWithoutQuad_2); 93 94 if (!(child instanceof cc.Sprite)) { 95 cc.log(cc._LogInfos.SpriteBatchNode_addSpriteWithoutQuad); 96 return null; 97 } 98 99 // quad index is Z 100 child.atlasIndex = z; 101 102 // XXX: optimize with a binary search 103 var i = 0, len, locDescendants = this._descendants; 104 if (locDescendants && locDescendants.length > 0) { 105 for (i = 0, len = locDescendants.length; i < len; i++) { 106 var obj = locDescendants[i]; 107 if (obj && (obj.atlasIndex >= z)) 108 break; 109 } 110 } 111 locDescendants.splice(i, 0, child); 112 113 // IMPORTANT: Call super, and not self. Avoid adding it to the texture atlas array 114 cc.Node.prototype.addChild.call(this, child, z, aTag); 115 116 //#issue 1262 don't use lazy sorting, tiles are added as quads not as sprites, so sprites need to be added in order 117 this.reorderBatch(false); 118 return this; 119 }, 120 121 // property 122 /** 123 * Return TextureAtlas of cc.SpriteBatchNode 124 * @return {cc.TextureAtlas} 125 */ 126 getTextureAtlas: function () { 127 return this._renderCmd.getTextureAtlas(); 128 }, 129 130 /** 131 * TextureAtlas of cc.SpriteBatchNode setter 132 * @param {cc.TextureAtlas} textureAtlas 133 */ 134 setTextureAtlas: function (textureAtlas) { 135 this._renderCmd.getTextureAtlas(textureAtlas); 136 }, 137 138 /** 139 * Return Descendants of cc.SpriteBatchNode 140 * @return {Array} 141 */ 142 getDescendants: function () { 143 return this._descendants; 144 }, 145 146 /** 147 * <p> 148 * Initializes a cc.SpriteBatchNode with a file image (.png, .jpeg, .pvr, etc) and a capacity of children.<br/> 149 * The capacity will be increased in 33% in runtime if it run out of space.<br/> 150 * The file will be loaded using the TextureMgr.<br/> 151 * Please pass parameters to constructor to initialize the sprite batch node, do not call this function yourself. 152 * </p> 153 * @param {String} fileImage 154 * @param {Number} capacity 155 * @return {Boolean} 156 */ 157 initWithFile: function (fileImage, capacity) { 158 var texture2D = cc.textureCache.getTextureForKey(fileImage); 159 if (!texture2D) 160 texture2D = cc.textureCache.addImage(fileImage); 161 return this.initWithTexture(texture2D, capacity); 162 }, 163 164 _setNodeDirtyForCache: function () { 165 if(this._renderCmd && this._renderCmd._setNodeDirtyForCache) 166 this._renderCmd._setNodeDirtyForCache(); 167 }, 168 169 /** 170 * <p> 171 * initializes a cc.SpriteBatchNode with a file image (.png, .jpeg, .pvr, etc) and a capacity of children.<br/> 172 * The capacity will be increased in 33% in runtime if it run out of space.<br/> 173 * The file will be loaded using the TextureMgr.<br/> 174 * Please pass parameters to constructor to initialize the sprite batch node, do not call this function yourself. 175 * </p> 176 * @param {String} fileImage 177 * @param {Number} capacity 178 * @return {Boolean} 179 */ 180 init: function (fileImage, capacity) { 181 var texture2D = cc.textureCache.getTextureForKey(fileImage); 182 if (!texture2D) 183 texture2D = cc.textureCache.addImage(fileImage); 184 return this.initWithTexture(texture2D, capacity); 185 }, 186 187 /** 188 * Increase Atlas Capacity 189 */ 190 increaseAtlasCapacity: function () { 191 this._renderCmd.increaseAtlasCapacity(); 192 }, 193 194 /** 195 * Removes a child given a certain index. It will also cleanup the running actions depending on the cleanup parameter. 196 * @warning Removing a child from a cc.SpriteBatchNode is very slow 197 * @param {Number} index 198 * @param {Boolean} doCleanup 199 */ 200 removeChildAtIndex: function (index, doCleanup) { 201 this.removeChild(this._children[index], doCleanup); 202 }, 203 204 /** 205 * Rebuild index in order for child 206 * @param {cc.Sprite} pobParent 207 * @param {Number} index 208 * @return {Number} 209 */ 210 rebuildIndexInOrder: function (pobParent, index) { 211 var children = pobParent.children; 212 if (children && children.length > 0) { 213 for (var i = 0; i < children.length; i++) { 214 var obj = children[i]; 215 if (obj && (obj.zIndex < 0)) 216 index = this.rebuildIndexInOrder(obj, index); 217 } 218 } 219 // ignore self (batch node) 220 if (!pobParent === this) { 221 pobParent.atlasIndex = index; 222 index++; 223 } 224 if (children && children.length > 0) { 225 for (i = 0; i < children.length; i++) { 226 obj = children[i]; 227 if (obj && (obj.zIndex >= 0)) 228 index = this.rebuildIndexInOrder(obj, index); 229 } 230 } 231 return index; 232 }, 233 234 /** 235 * Returns highest atlas index in child 236 * @param {cc.Sprite} sprite 237 * @return {Number} 238 */ 239 highestAtlasIndexInChild: function (sprite) { 240 var children = sprite.children; 241 242 if (!children || children.length === 0) 243 return sprite.atlasIndex; 244 else 245 return this.highestAtlasIndexInChild(children[children.length - 1]); 246 }, 247 248 /** 249 * Returns lowest atlas index in child 250 * @param {cc.Sprite} sprite 251 * @return {Number} 252 */ 253 lowestAtlasIndexInChild: function (sprite) { 254 var children = sprite.children; 255 if (!children || children.length === 0) 256 return sprite.atlasIndex; 257 else 258 return this.lowestAtlasIndexInChild(children[children.length - 1]); 259 }, 260 261 /** 262 * Returns atlas index for child 263 * @param {cc.Sprite} sprite 264 * @param {Number} nZ 265 * @return {Number} 266 */ 267 atlasIndexForChild: function (sprite, nZ) { 268 var selParent = sprite.parent; 269 var brothers = selParent.children; 270 var childIndex = brothers.indexOf(sprite); 271 272 // ignore parent Z if parent is spriteSheet 273 var ignoreParent = selParent === this; 274 var previous = null; 275 if (childIndex > 0 && childIndex < cc.UINT_MAX) 276 previous = brothers[childIndex - 1]; 277 278 // first child of the sprite sheet 279 if (ignoreParent) { 280 if (childIndex === 0) 281 return 0; 282 return this.highestAtlasIndexInChild(previous) + 1; 283 } 284 285 // parent is a cc.Sprite, so, it must be taken into account 286 // first child of an cc.Sprite ? 287 if (childIndex === 0) { 288 // less than parent and brothers 289 if (nZ < 0) 290 return selParent.atlasIndex; 291 else 292 return selParent.atlasIndex + 1; 293 } else { 294 // previous & sprite belong to the same branch 295 if ((previous.zIndex < 0 && nZ < 0) || (previous.zIndex >= 0 && nZ >= 0)) 296 return this.highestAtlasIndexInChild(previous) + 1; 297 298 // else (previous < 0 and sprite >= 0 ) 299 return selParent.atlasIndex + 1; 300 } 301 }, 302 303 /** 304 * Sprites use this to start sortChildren, don't call this manually 305 * @param {Boolean} reorder 306 */ 307 reorderBatch: function (reorder) { 308 this._reorderChildDirty = reorder; 309 }, 310 311 /** 312 * Sets the source and destination blending function for the texture 313 * @param {Number | cc.BlendFunc} src 314 * @param {Number} dst 315 */ 316 setBlendFunc: function (src, dst) { 317 if (dst === undefined) 318 this._blendFunc = src; 319 else 320 this._blendFunc = {src: src, dst: dst}; 321 }, 322 323 /** 324 * Returns the blending function used for the texture 325 * @return {cc.BlendFunc} 326 */ 327 getBlendFunc: function () { 328 return new cc.BlendFunc(this._blendFunc.src,this._blendFunc.dst); 329 }, 330 331 /** 332 * Reorder children (override reorderChild of cc.Node) 333 * @override 334 * @param {cc.Sprite} child 335 * @param {Number} zOrder 336 */ 337 reorderChild: function (child, zOrder) { 338 cc.assert(child, cc._LogInfos.SpriteBatchNode_reorderChild_2); 339 if (this._children.indexOf(child) === -1) { 340 cc.log(cc._LogInfos.SpriteBatchNode_reorderChild); 341 return; 342 } 343 if (zOrder === child.zIndex) 344 return; 345 346 //set the z-order and sort later 347 cc.Node.prototype.reorderChild.call(this, child, zOrder); 348 //this.setNodeDirty(); 349 }, 350 351 /** 352 * Removes a child from cc.SpriteBatchNode (override removeChild of cc.Node) 353 * @param {cc.Sprite} child 354 * @param {Boolean} cleanup 355 */ 356 removeChild: function (child, cleanup) { 357 // explicit null handling 358 if (child == null) 359 return; 360 if (this._children.indexOf(child) === -1) { 361 cc.log(cc._LogInfos.SpriteBatchNode_removeChild); 362 return; 363 } 364 365 // cleanup before removing 366 this.removeSpriteFromAtlas(child); 367 cc.Node.prototype.removeChild.call(this, child, cleanup); 368 }, 369 370 /** 371 * <p> 372 * Updates a quad at a certain index into the texture atlas. The CCSprite won't be added into the children array. <br/> 373 * 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/> 374 * For example: a tile map (cc.TMXMap) or a label with lots of characters (BitmapFontAtlas)<br/> 375 * </p> 376 * @function 377 * @param {cc.Sprite} sprite 378 * @param {Number} index 379 */ 380 updateQuadFromSprite: function (sprite, index) { 381 cc.assert(sprite, cc._LogInfos.CCSpriteBatchNode_updateQuadFromSprite_2); 382 if (!(sprite instanceof cc.Sprite)) { 383 cc.log(cc._LogInfos.CCSpriteBatchNode_updateQuadFromSprite); 384 return; 385 } 386 this._renderCmd.checkAtlasCapacity(); 387 388 // 389 // update the quad directly. Don't add the sprite to the scene graph 390 // 391 sprite.batchNode = this; 392 sprite.atlasIndex = index; 393 sprite.dirty = true; 394 // UpdateTransform updates the textureAtlas quad 395 sprite.updateTransform(); 396 }, 397 398 /** 399 * <p> 400 * Inserts a quad at a certain index into the texture atlas. The cc.Sprite won't be added into the children array. <br/> 401 * This method should be called only when you are dealing with very big AtlasSprite and when most of the cc.Sprite won't be updated. <br/> 402 * For example: a tile map (cc.TMXMap) or a label with lots of characters (cc.LabelBMFont) 403 * </p> 404 * @function 405 * @param {cc.Sprite} sprite 406 * @param {Number} index 407 */ 408 insertQuadFromSprite: function (sprite, index) { 409 cc.assert(sprite, cc._LogInfos.CCSpriteBatchNode_insertQuadFromSprite_2); 410 if (!(sprite instanceof cc.Sprite)) { 411 cc.log(cc._LogInfos.CCSpriteBatchNode_insertQuadFromSprite); 412 return; 413 } 414 this._renderCmd.insertQuad(sprite, index); 415 416 // 417 // update the quad directly. Don't add the sprite to the scene graph 418 // 419 sprite.batchNode = this; 420 sprite.atlasIndex = index; 421 422 // XXX: updateTransform will update the textureAtlas too, using updateQuad. 423 // XXX: so, it should be AFTER the insertQuad 424 sprite.dirty = true; 425 sprite.updateTransform(); 426 this._renderCmd.cutting(sprite, index); 427 }, 428 429 /** 430 * <p> 431 * Initializes a cc.SpriteBatchNode with a texture2d and capacity of children.<br/> 432 * The capacity will be increased in 33% in runtime if it run out of space.<br/> 433 * Please pass parameters to constructor to initialize the sprite batch node, do not call this function yourself. 434 * </p> 435 * @function 436 * @param {cc.Texture2D} tex 437 * @param {Number} [capacity] 438 * @return {Boolean} 439 */ 440 initWithTexture: function (tex, capacity) { 441 this._children.length = 0; 442 this._descendants.length = 0; 443 444 capacity = capacity || cc.SpriteBatchNode.DEFAULT_CAPACITY; 445 this._renderCmd.initWithTexture(tex, capacity); 446 return true; 447 }, 448 449 /** 450 * Insert a child 451 * @param {cc.Sprite} sprite The child sprite 452 * @param {Number} index The insert index 453 */ 454 insertChild: function (sprite, index) { 455 //TODO WebGL only ? 456 sprite.batchNode = this; 457 sprite.atlasIndex = index; 458 sprite.dirty = true; 459 460 this._renderCmd.insertQuad(sprite, index); 461 this._descendants.splice(index, 0, sprite); 462 463 // update indices 464 var i = index + 1, locDescendant = this._descendants; 465 if (locDescendant && locDescendant.length > 0) { 466 for (; i < locDescendant.length; i++) 467 locDescendant[i].atlasIndex++; 468 } 469 470 // add children recursively 471 var locChildren = sprite.children, child, l; 472 if (locChildren) { 473 for (i = 0, l = locChildren.length || 0; i < l; i++) { 474 child = locChildren[i]; 475 if (child) { 476 var getIndex = this.atlasIndexForChild(child, child.zIndex); 477 this.insertChild(child, getIndex); 478 } 479 } 480 } 481 }, 482 483 /** 484 * Add child at the end, faster than insert child 485 * @function 486 * @param {cc.Sprite} sprite 487 */ 488 appendChild: function (sprite) { 489 this._reorderChildDirty = true; 490 sprite.batchNode = this; 491 sprite.dirty = true; 492 493 this._descendants.push(sprite); 494 var index = this._descendants.length - 1; 495 496 sprite.atlasIndex = index; 497 this._renderCmd.insertQuad(sprite, index); 498 499 // add children recursively 500 var children = sprite.children; 501 for (var i = 0, l = children.length || 0; i < l; i++) 502 this.appendChild(children[i]); 503 }, 504 505 /** 506 * Removes sprite from TextureAtlas 507 * @function 508 * @param {cc.Sprite} sprite 509 */ 510 removeSpriteFromAtlas: function (sprite) { 511 this._renderCmd.removeQuadAtIndex(sprite.atlasIndex); 512 513 // Cleanup sprite. It might be reused (issue #569) 514 sprite.batchNode = null; 515 var locDescendants = this._descendants; 516 var index = locDescendants.indexOf(sprite); 517 if (index !== -1) { 518 locDescendants.splice(index, 1); 519 520 // update all sprites beyond this one 521 var len = locDescendants.length; 522 for (; index < len; ++index) { 523 var s = locDescendants[index]; 524 s.atlasIndex--; 525 } 526 } 527 528 // remove children recursively 529 var children = sprite.children; 530 if (children) { 531 for (var i = 0, l = children.length || 0; i < l; i++) 532 children[i] && this.removeSpriteFromAtlas(children[i]); 533 } 534 }, 535 // CCTextureProtocol 536 /** 537 * Returns texture of the sprite batch node 538 * @function 539 * @return {cc.Texture2D} 540 */ 541 getTexture: function () { 542 return this._renderCmd.getTexture(); 543 }, 544 545 /** 546 * Sets the texture of the sprite batch node. 547 * @function 548 * @param {cc.Texture2D} texture 549 */ 550 setTexture: function(texture){ 551 this._renderCmd.setTexture(texture); 552 }, 553 554 /** 555 * Add child to the sprite batch node (override addChild of cc.Node) 556 * @function 557 * @override 558 * @param {cc.Sprite} child 559 * @param {Number} [zOrder] 560 * @param {Number} [tag] 561 */ 562 addChild: function (child, zOrder, tag) { 563 cc.assert(child != null, cc._LogInfos.CCSpriteBatchNode_addChild_3); 564 565 if(!this._renderCmd.isValidChild(child)) 566 return; 567 568 zOrder = (zOrder == null) ? child.zIndex : zOrder; 569 tag = (tag == null) ? child.tag : tag; 570 cc.Node.prototype.addChild.call(this, child, zOrder, tag); 571 this.appendChild(child); 572 //this.setNodeDirty(); 573 }, 574 575 /** 576 * Removes all children from the container and do a cleanup all running actions depending on the cleanup parameter. <br/> 577 * (override removeAllChildren of cc.Node) 578 * @function 579 * @param {Boolean} cleanup 580 */ 581 removeAllChildren: function (cleanup) { 582 // Invalidate atlas index. issue #569 583 // useSelfRender should be performed on all descendants. issue #1216 584 var locDescendants = this._descendants; 585 if (locDescendants && locDescendants.length > 0) { 586 for (var i = 0, len = locDescendants.length; i < len; i++) { 587 if (locDescendants[i]) 588 locDescendants[i].batchNode = null; 589 } 590 } 591 cc.Node.prototype.removeAllChildren.call(this, cleanup); 592 this._descendants.length = 0; 593 this._renderCmd.removeAllQuads(); 594 }, 595 596 /** 597 * Sort all children nodes (override draw of cc.Node) 598 */ 599 sortAllChildren: function () { 600 if (this._reorderChildDirty) { 601 var childrenArr = this._children; 602 var i, j = 0, length = childrenArr.length, tempChild; 603 //insertion sort 604 for (i = 1; i < length; i++) { 605 var tempItem = childrenArr[i]; 606 j = i - 1; 607 tempChild = childrenArr[j]; 608 609 //continue moving element downwards while zOrder is smaller or when zOrder is the same but mutatedIndex is smaller 610 while (j >= 0 && ( tempItem._localZOrder < tempChild._localZOrder || 611 ( tempItem._localZOrder === tempChild._localZOrder && tempItem.arrivalOrder < tempChild.arrivalOrder ))) { 612 childrenArr[j + 1] = tempChild; 613 j = j - 1; 614 tempChild = childrenArr[j]; 615 } 616 childrenArr[j + 1] = tempItem; 617 } 618 619 //sorted now check all children 620 if (childrenArr.length > 0) { 621 //first sort all children recursively based on zOrder 622 this._arrayMakeObjectsPerformSelector(childrenArr, cc.Node._stateCallbackType.sortAllChildren); 623 this._renderCmd.updateChildrenAtlasIndex(childrenArr); 624 } 625 this._reorderChildDirty = false; 626 } 627 }, 628 629 _createRenderCmd: function(){ 630 if(cc._renderType === cc._RENDER_TYPE_CANVAS) 631 return new cc.SpriteBatchNode.CanvasRenderCmd(this); 632 else 633 return new cc.SpriteBatchNode.WebGLRenderCmd(this); 634 } 635 }); 636 637 var _p = cc.SpriteBatchNode.prototype; 638 639 // Override properties 640 cc.defineGetterSetter(_p, "texture", _p.getTexture, _p.setTexture); 641 cc.defineGetterSetter(_p, "textureAtlas", _p.getTextureAtlas, _p.setTextureAtlas); 642 643 // Extended properties 644 /** @expose */ 645 _p.descendants; 646 cc.defineGetterSetter(_p, "descendants", _p.getDescendants); 647 648 649 /** 650 * @constant 651 * @type Number 652 */ 653 cc.SpriteBatchNode.DEFAULT_CAPACITY = 29; 654 655 /** 656 * <p> 657 * creates a cc.SpriteBatchNodeCanvas with a file image (.png, .jpg etc) with a default capacity of 29 children.<br/> 658 * The capacity will be increased in 33% in runtime if it run out of space.<br/> 659 * The file will be loaded using the TextureMgr.<br/> 660 * </p> 661 * @deprecated since v3.0, please use new construction instead 662 * @see cc.SpriteBatchNode 663 * @param {String|cc.Texture2D} fileImage 664 * @param {Number} capacity 665 * @return {cc.SpriteBatchNode} 666 */ 667 cc.SpriteBatchNode.create = function (fileImage, capacity) { 668 return new cc.SpriteBatchNode(fileImage, capacity); 669 }; 670 671 /** 672 * @deprecated since v3.0, please use new construction instead 673 * @see cc.SpriteBatchNode 674 * @function 675 */ 676 cc.SpriteBatchNode.createWithTexture = cc.SpriteBatchNode.create;