1 /****************************************************************************
  2  Copyright (c) 2011-2012 cocos2d-x.org
  3  Copyright (c) 2013-2014 Chukong Technologies Inc.
  4 
  5  http://www.cocos2d-x.org
  6 
  7  Permission is hereby granted, free of charge, to any person obtaining a copy
  8  of this software and associated documentation files (the "Software"), to deal
  9  in the Software without restriction, including without limitation the rights
 10  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 11  copies of the Software, and to permit persons to whom the Software is
 12  furnished to do so, subject to the following conditions:
 13 
 14  The above copyright notice and this permission notice shall be included in
 15  all copies or substantial portions of the Software.
 16 
 17  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 18  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 19  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 20  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 21  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 22  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 23  THE SOFTWARE.
 24  ****************************************************************************/
 25 
 26 /**
 27  * movement event type enum
 28  * @constant
 29  * @type {Object}
 30  */
 31 ccs.MovementEventType = {
 32     start: 0,
 33     complete: 1,
 34     loopComplete: 2
 35 };
 36 
 37 /**
 38  * The animation event class, it has the callback, target and arguments.
 39  * @deprecated since v3.0.
 40  * @class
 41  * @extends ccs.Class
 42  */
 43 ccs.AnimationEvent = ccs.Class.extend(/** @lends ccs.AnimationEvent# */{
 44     _arguments: null,
 45     _callFunc: null,
 46     _selectorTarget: null,
 47 
 48     /**
 49      * Constructor of ccs.AnimationEvent
 50      * @param {function} callFunc
 51      * @param {object} target
 52      * @param {object} [data]
 53      */
 54     ctor: function (callFunc,target, data) {
 55         this._data = data;
 56         this._callFunc = callFunc;
 57         this._selectorTarget = target;
 58     },
 59     call: function () {
 60         if (this._callFunc)
 61             this._callFunc.apply(this._selectorTarget, this._arguments);
 62     },
 63     setArguments: function (args) {
 64         this._arguments = args;
 65     }
 66 });
 67 
 68 /**
 69  * The movement event class for Armature.
 70  * @constructor
 71  *
 72  * @property {ccs.Armature}             armature        - The armature reference of movement event.
 73  * @property {Number}                   movementType    - The type of movement.
 74  * @property {String}                   movementID      - The ID of movement.
 75  */
 76 ccs.MovementEvent = function () {
 77     this.armature = null;
 78     this.movementType = ccs.MovementEventType.start;
 79     this.movementID = "";
 80 };
 81 
 82 /**
 83  * The frame event class for Armature.
 84  * @constructor
 85  *
 86  * @property {ccs.Bone}             bone                - The bone reference of frame event.
 87  * @property {String}               frameEventName      - The name of frame event.
 88  * @property {Number}               originFrameIndex    - The index of origin frame.
 89  * @property {Number}               currentFrameIndex   - The index of current frame.
 90  */
 91 ccs.FrameEvent = function () {
 92     this.bone = null;
 93     this.frameEventName = "";
 94     this.originFrameIndex = 0;
 95     this.currentFrameIndex = 0;
 96 };
 97 
 98 /**
 99  * The Animation class for Armature, it plays armature animation, and controls speed scale and manages animation frame.
100  * @class
101  * @extends ccs.ProcessBase
102  *
103  * @property {ccs.AnimationData}    animationData       - Animation data
104  * @property {Object}               userObject          - User custom object
105  * @property {Boolean}              ignoreFrameEvent    - Indicate whether the frame event is ignored
106  * @property {Number}               speedScale          - Animation play speed scale
107  * @property {Number}               animationScale      - Animation play speed scale
108  */
109 ccs.ArmatureAnimation = ccs.ProcessBase.extend(/** @lends ccs.ArmatureAnimation# */{
110     _animationData: null,
111     _movementData: null,
112     _armature: null,
113     _movementID: "",
114     _toIndex: 0,
115     _tweenList: null,
116     _speedScale: 1,
117     _ignoreFrameEvent: false,
118     _frameEventQueue: null,
119     _movementEventQueue: null,
120     _movementList: null,
121     _onMovementList: false,
122     _movementListLoop: false,
123     _movementIndex: 0,
124     _movementListDurationTo: -1,
125 
126     _movementEventCallFunc: null,
127     _frameEventCallFunc: null,
128     _movementEventTarget: null,
129     _frameEventTarget:null,
130     _movementEventListener: null,
131     _frameEventListener: null,
132 
133     /**
134      * The Construction of ccs.ArmatureAnimation
135      */
136     ctor: function () {
137         ccs.ProcessBase.prototype.ctor.call(this);
138 
139         this._tweenList = [];
140         this._movementList = [];
141         this._frameEventQueue = [];
142         this._movementEventQueue = [];
143     },
144 
145     /**
146      * Initializes with an armature object
147      * @param {ccs.Armature} armature
148      * @return {Boolean}
149      */
150     init: function (armature) {
151         this._armature = armature;
152         this._tweenList.length = 0;
153         return true;
154     },
155 
156     /**
157      * Pauses armature animation.
158      */
159     pause: function () {
160         var locTweenList = this._tweenList;
161         for (var i = 0; i < locTweenList.length; i++)
162             locTweenList[i].pause();
163         ccs.ProcessBase.prototype.pause.call(this);
164     },
165 
166     /**
167      * Resumes armature animation.
168      */
169     resume: function () {
170         var locTweenList = this._tweenList;
171         for (var i = 0; i < locTweenList.length; i++)
172             locTweenList[i].resume();
173         ccs.ProcessBase.prototype.resume.call(this);
174     },
175 
176     /**
177      * Stops armature animation.
178      */
179     stop: function () {
180         var locTweenList = this._tweenList;
181         for (var i = 0; i < locTweenList.length; i++)
182             locTweenList[i].stop();
183         locTweenList.length = 0;
184         ccs.ProcessBase.prototype.stop.call(this);
185     },
186 
187     /**
188      * Sets animation play speed scale.
189      * @deprecated since v3.0, please use setSpeedScale instead.
190      * @param {Number} animationScale
191      */
192     setAnimationScale: function (animationScale) {
193         this.setSpeedScale(animationScale);
194     },
195 
196     /**
197      * Returns animation play speed scale.
198      * @deprecated since v3.0, please use getSpeedScale instead.
199      * @returns {Number}
200      */
201     getAnimationScale: function () {
202         return this.getSpeedScale();
203     },
204 
205     /**
206      * Sets animation play speed scale.
207      * @param {Number} speedScale
208      */
209     setSpeedScale: function (speedScale) {
210         if (speedScale == this._speedScale)
211             return;
212         this._speedScale = speedScale;
213         this._processScale = !this._movementData ? this._speedScale : this._speedScale * this._movementData.scale;
214         var dict = this._armature.getBoneDic();
215         for (var key in dict) {
216             var bone = dict[key];
217             bone.getTween().setProcessScale(this._processScale);
218             if (bone.getChildArmature())
219                 bone.getChildArmature().getAnimation().setSpeedScale(this._processScale);
220         }
221     },
222 
223     /**
224      * Returns animation play speed scale.
225      * @returns {Number}
226      */
227     getSpeedScale: function () {
228         return this._speedScale;
229     },
230 
231     /**
232      * play animation by animation name.
233      * @param {String} animationName The animation name you want to play
234      * @param {Number} [durationTo=-1]
235      *         he frames between two animation changing-over.It's meaning is changing to this animation need how many frames
236      *         -1 : use the value from CCMovementData get from flash design panel
237      * @param {Number} [loop=-1]
238      *          Whether the animation is loop.
239      *         loop < 0 : use the value from CCMovementData get from flash design panel
240      *         loop = 0 : this animation is not loop
241      *         loop > 0 : this animation is loop
242      * @example
243      * // example
244      * armature.getAnimation().play("run",-1,1);//loop play
245      * armature.getAnimation().play("run",-1,0);//not loop play
246      */
247     play: function (animationName, durationTo, loop) {
248         cc.assert(this._animationData, "this.animationData can not be null");
249 
250         this._movementData = this._animationData.getMovement(animationName);
251         cc.assert(this._movementData, "this._movementData can not be null");
252 
253         durationTo = (durationTo === undefined) ? -1 : durationTo;
254         loop = (loop === undefined) ? -1 : loop;
255 
256         //! Get key frame count
257         this._rawDuration = this._movementData.duration;
258         this._movementID = animationName;
259         this._processScale = this._speedScale * this._movementData.scale;
260 
261         //! Further processing parameters
262         durationTo = (durationTo == -1) ? this._movementData.durationTo : durationTo;
263         var durationTween = this._movementData.durationTween == 0 ? this._rawDuration : this._movementData.durationTween;
264 
265         var tweenEasing = this._movementData.tweenEasing;
266         //loop = (!loop || loop < 0) ? this._movementData.loop : loop;
267         loop = (loop < 0) ? this._movementData.loop : loop;
268         this._onMovementList = false;
269 
270         ccs.ProcessBase.prototype.play.call(this, durationTo, durationTween, loop, tweenEasing);
271 
272         if (this._rawDuration == 0)
273             this._loopType = ccs.ANIMATION_TYPE_SINGLE_FRAME;
274         else {
275             this._loopType = loop ? ccs.ANIMATION_TYPE_TO_LOOP_FRONT : ccs.ANIMATION_TYPE_NO_LOOP;
276             this._durationTween = durationTween;
277         }
278 
279         var movementBoneData;
280         this._tweenList = [];
281 
282         var map = this._armature.getBoneDic();
283         for(var element in map) {
284             var bone = map[element];
285             movementBoneData = this._movementData.movBoneDataDic[bone.getName()];
286 
287             var tween = bone.getTween();
288             if(movementBoneData && movementBoneData.frameList.length > 0) {
289                 this._tweenList.push(tween);
290                 movementBoneData.duration = this._movementData.duration;
291                 tween.play(movementBoneData, durationTo, durationTween, loop, tweenEasing);
292                 tween.setProcessScale(this._processScale);
293 
294                 if (bone.getChildArmature())
295                     bone.getChildArmature().getAnimation().setSpeedScale(this._processScale);
296             } else {
297                 if(!bone.isIgnoreMovementBoneData()){
298                     //! this bone is not include in this movement, so hide it
299                     bone.getDisplayManager().changeDisplayWithIndex(-1, false);
300                     tween.stop();
301                 }
302             }
303         }
304         this._armature.update(0);
305     },
306 
307     /**
308      * Plays animation with index, the other param is the same to play.
309      * @param {Number} animationIndex
310      * @param {Number} durationTo
311      * @param {Number} durationTween
312      * @param {Number} loop
313      * @param {Number} [tweenEasing]
314      * @deprecated since v3.0, please use playWithIndex instead.
315      */
316     playByIndex: function (animationIndex, durationTo, durationTween, loop, tweenEasing) {
317         cc.log("playByIndex is deprecated. Use playWithIndex instead.");
318         this.playWithIndex(animationIndex, durationTo, loop);
319     },
320 
321     /**
322      * Plays animation with index, the other param is the same to play.
323      * @param {Number|Array} animationIndex
324      * @param {Number} durationTo
325      * @param {Number} loop
326      */
327     playWithIndex: function (animationIndex, durationTo, loop) {
328         var movName = this._animationData.movementNames;
329         cc.assert((animationIndex > -1) && (animationIndex < movName.length));
330 
331         var animationName = movName[animationIndex];
332         this.play(animationName, durationTo, loop);
333     },
334 
335     /**
336      * Plays animation with names
337      * @param {Array} movementNames
338      * @param {Number} durationTo
339      * @param {Boolean} loop
340      */
341     playWithNames: function (movementNames, durationTo, loop) {
342         durationTo = (durationTo === undefined) ? -1 : durationTo;
343         loop = (loop === undefined) ? true : loop;
344 
345         this._movementListLoop = loop;
346         this._movementListDurationTo = durationTo;
347         this._onMovementList = true;
348         this._movementIndex = 0;
349         if(movementNames instanceof Array)
350             this._movementList = movementNames;
351         else
352             this._movementList.length = 0;
353         this.updateMovementList();
354     },
355 
356     /**
357      * Plays animation by indexes
358      * @param {Array} movementIndexes
359      * @param {Number} durationTo
360      * @param {Boolean} loop
361      */
362     playWithIndexes: function (movementIndexes, durationTo, loop) {
363         durationTo = (durationTo === undefined) ? -1 : durationTo;
364         loop = (loop === undefined) ? true : loop;
365 
366         this._movementList.length = 0;
367         this._movementListLoop = loop;
368         this._movementListDurationTo = durationTo;
369         this._onMovementList = true;
370         this._movementIndex = 0;
371 
372         var movName = this._animationData.movementNames;
373 
374         for (var i = 0; i < movementIndexes.length; i++) {
375             var name = movName[movementIndexes[i]];
376             this._movementList.push(name);
377         }
378 
379         this.updateMovementList();
380     },
381 
382     /**
383      * <p>
384      * Goes to specified frame and plays current movement.                                  <br/>
385      * You need first switch to the movement you want to play, then call this function.     <br/>
386      *                                                                                      <br/>
387      * example : playByIndex(0);                                                            <br/>
388      *           gotoAndPlay(0);                                                            <br/>
389      *           playByIndex(1);                                                            <br/>
390      *           gotoAndPlay(0);                                                            <br/>
391      *           gotoAndPlay(15);                                                           <br/>
392      * </p>
393      * @param {Number} frameIndex
394      */
395     gotoAndPlay: function (frameIndex) {
396         if (!this._movementData || frameIndex < 0 || frameIndex >= this._movementData.duration) {
397             cc.log("Please ensure you have played a movement, and the frameIndex is in the range.");
398             return;
399         }
400 
401         var ignoreFrameEvent = this._ignoreFrameEvent;
402         this._ignoreFrameEvent = true;
403         this._isPlaying = true;
404         this._isComplete = this._isPause = false;
405 
406         ccs.ProcessBase.prototype.gotoFrame.call(this, frameIndex);
407         this._currentPercent = this._curFrameIndex / (this._movementData.duration - 1);
408         this._currentFrame = this._nextFrameIndex * this._currentPercent;
409 
410         var locTweenList = this._tweenList;
411         for (var i = 0; i < locTweenList.length; i++)
412             locTweenList[i].gotoAndPlay(frameIndex);
413         this._armature.update(0);
414         this._ignoreFrameEvent = ignoreFrameEvent;
415     },
416 
417     /**
418      * Goes to specified frame and pauses current movement.
419      * @param {Number} frameIndex
420      */
421     gotoAndPause: function (frameIndex) {
422         this.gotoAndPlay(frameIndex);
423         this.pause();
424     },
425 
426     /**
427      * Returns the length of armature's movements
428      * @return {Number}
429      */
430     getMovementCount: function () {
431         return this._animationData.getMovementCount();
432     },
433 
434     /**
435      * Updates the state of ccs.Tween list, calls frame event's callback and calls movement event's callback.
436      * @param {Number} dt
437      */
438     update: function (dt) {
439         ccs.ProcessBase.prototype.update.call(this, dt);
440 
441         var locTweenList = this._tweenList;
442         for (var i = 0; i < locTweenList.length; i++)
443             locTweenList[i].update(dt);
444 
445         var frameEvents = this._frameEventQueue, event;
446         while (frameEvents.length > 0) {
447             event = frameEvents.shift();
448             this._ignoreFrameEvent = true;
449             if(this._frameEventCallFunc)
450                 this._frameEventCallFunc.call(this._frameEventTarget, event.bone, event.frameEventName, event.originFrameIndex, event.currentFrameIndex);
451             if(this._frameEventListener)
452                 this._frameEventListener(event.bone, event.frameEventName, event.originFrameIndex, event.currentFrameIndex);
453             this._ignoreFrameEvent = false;
454         }
455 
456         var movementEvents = this._movementEventQueue;
457         while (movementEvents.length > 0) {
458             event = movementEvents.shift();
459             if(this._movementEventCallFunc)
460                 this._movementEventCallFunc.call(this._movementEventTarget, event.armature, event.movementType, event.movementID);
461             if (this._movementEventListener)
462                 this._movementEventListener(event.armature, event.movementType, event.movementID);
463         }
464     },
465 
466     /**
467      * Updates will call this handler, you can handle your logic here
468      */
469     updateHandler: function () {      //TODO set it to protected in v3.1
470         var locCurrentPercent = this._currentPercent;
471         if (locCurrentPercent >= 1) {
472             switch (this._loopType) {
473                 case ccs.ANIMATION_TYPE_NO_LOOP:
474                     this._loopType = ccs.ANIMATION_TYPE_MAX;
475                     this._currentFrame = (locCurrentPercent - 1) * this._nextFrameIndex;
476                     locCurrentPercent = this._currentFrame / this._durationTween;
477                     if (locCurrentPercent < 1.0) {
478                         this._nextFrameIndex = this._durationTween;
479                         this.movementEvent(this._armature, ccs.MovementEventType.start, this._movementID);
480                         break;
481                     }
482                     break;
483                 case ccs.ANIMATION_TYPE_MAX:
484                 case ccs.ANIMATION_TYPE_SINGLE_FRAME:
485                     locCurrentPercent = 1;
486                     this._isComplete = true;
487                     this._isPlaying = false;
488 
489                     this.movementEvent(this._armature, ccs.MovementEventType.complete, this._movementID);
490 
491                     this.updateMovementList();
492                     break;
493                 case ccs.ANIMATION_TYPE_TO_LOOP_FRONT:
494                     this._loopType = ccs.ANIMATION_TYPE_LOOP_FRONT;
495                     locCurrentPercent = ccs.fmodf(locCurrentPercent, 1);
496                     this._currentFrame = this._nextFrameIndex == 0 ? 0 : ccs.fmodf(this._currentFrame, this._nextFrameIndex);
497                     this._nextFrameIndex = this._durationTween > 0 ? this._durationTween : 1;
498                     this.movementEvent(this, ccs.MovementEventType.start, this._movementID);
499                     break;
500                 default:
501                     //locCurrentPercent = ccs.fmodf(locCurrentPercent, 1);
502                     this._currentFrame = ccs.fmodf(this._currentFrame, this._nextFrameIndex);
503                     this._toIndex = 0;
504                     this.movementEvent(this._armature, ccs.MovementEventType.loopComplete, this._movementID);
505                     break;
506             }
507             this._currentPercent = locCurrentPercent;
508         }
509     },
510 
511     /**
512      * Returns the Id of current movement
513      * @returns {String}
514      */
515     getCurrentMovementID: function () {
516         if (this._isComplete)
517             return "";
518         return this._movementID;
519     },
520 
521     /**
522      * Sets movement event callback to animation.
523      * @param {function} callFunc
524      * @param {Object} target
525      */
526     setMovementEventCallFunc: function (callFunc, target) {
527         if(arguments.length == 1){
528             this._frameEventListener = target;
529         }else if(arguments.length == 2){
530             this._movementEventTarget = target;
531             this._movementEventCallFunc = callFunc;
532         }
533     },
534 
535     /**
536      * Sets frame event callback to animation.
537      * @param {function} callFunc
538      * @param {Object} target
539      */
540     setFrameEventCallFunc: function (callFunc, target) {
541         if(arguments.length == 1){
542             this._frameEventListener = target;
543         }else if(arguments.length == 2){
544             this._frameEventTarget = target;
545             this._frameEventCallFunc = callFunc;
546         }
547     },
548 
549     /**
550      * Sets user object to animation.
551      * @param {Object} userObject
552      */
553     setUserObject: function (userObject) {
554         this._userObject = userObject;
555     },
556 
557     /**
558      * Emits a frame event
559      * @param {ccs.Bone} bone
560      * @param {String} frameEventName
561      * @param {Number} originFrameIndex
562      * @param {Number} currentFrameIndex
563      */
564     frameEvent: function (bone, frameEventName, originFrameIndex, currentFrameIndex) {
565         if ((this._frameEventTarget && this._frameEventCallFunc) || this._frameEventListener) {
566             var frameEvent = new ccs.FrameEvent();
567             frameEvent.bone = bone;
568             frameEvent.frameEventName = frameEventName;
569             frameEvent.originFrameIndex = originFrameIndex;
570             frameEvent.currentFrameIndex = currentFrameIndex;
571             this._frameEventQueue.push(frameEvent);
572         }
573     },
574 
575     /**
576      * Emits a movement event
577      * @param {ccs.Armature} armature
578      * @param {Number} movementType
579      * @param {String} movementID
580      */
581     movementEvent: function (armature, movementType, movementID) {
582         if ((this._movementEventTarget && this._movementEventCallFunc) || this._movementEventListener) {
583             var event = new ccs.MovementEvent();
584             event.armature = armature;
585             event.movementType = movementType;
586             event.movementID = movementID;
587             this._movementEventQueue.push(event);
588         }
589     },
590 
591     /**
592      * Updates movement list.
593      */
594     updateMovementList: function () {
595         if (this._onMovementList) {
596             var movementObj, locMovementList = this._movementList;
597             if (this._movementListLoop) {
598                 movementObj = locMovementList[this._movementIndex];
599                 this.play(movementObj, movementObj.durationTo, 0);
600                 this._movementIndex++;
601                 if (this._movementIndex >= locMovementList.length)
602                     this._movementIndex = 0;
603             } else {
604                 if (this._movementIndex < locMovementList.length) {
605                     movementObj = locMovementList[this._movementIndex];
606                     this.play(movementObj, movementObj.durationTo, 0);
607                     this._movementIndex++;
608                 } else
609                     this._onMovementList = false;
610             }
611             this._onMovementList = true;
612         }
613     },
614 
615     /**
616      * Sets animation data to animation.
617      * @param {ccs.AnimationData} data
618      */
619     setAnimationData: function (data) {
620         if(this._animationData != data)
621             this._animationData = data;
622     },
623 
624     /**
625      * Returns animation data of animation.
626      * @return {ccs.AnimationData}
627      */
628     getAnimationData: function () {
629         return this._animationData;
630     },
631 
632     /**
633      * Returns the user object of animation.
634      * @return {Object}
635      */
636     getUserObject: function () {
637         return this._userObject;
638     },
639 
640     /**
641      * Determines if the frame event is ignored
642      * @returns {boolean}
643      */
644     isIgnoreFrameEvent: function () {
645         return this._ignoreFrameEvent;
646     }
647 });
648 
649 var _p = ccs.ArmatureAnimation.prototype;
650 
651 // Extended properties
652 /** @expose */
653 _p.speedScale;
654 cc.defineGetterSetter(_p, "speedScale", _p.getSpeedScale, _p.setSpeedScale);
655 /** @expose */
656 _p.animationScale;
657 cc.defineGetterSetter(_p, "animationScale", _p.getAnimationScale, _p.setAnimationScale);
658 
659 _p = null;
660 
661 /**
662  * Allocates and initializes a ArmatureAnimation.
663  * @return {ccs.ArmatureAnimation}
664  * @example
665  * // example
666  * var animation = ccs.ArmatureAnimation.create();
667  */
668 ccs.ArmatureAnimation.create = function (armature) {    //TODO it will be deprecated in v3.1
669     var animation = new ccs.ArmatureAnimation();
670     if (animation && animation.init(armature))
671         return animation;
672     return null;
673 };