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.game.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;