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  * @param {ccs.Armature} [armature] The armature
104  *
105  * @property {ccs.AnimationData}    animationData       - Animation data
106  * @property {Object}               userObject          - User custom object
107  * @property {Boolean}              ignoreFrameEvent    - Indicate whether the frame event is ignored
108  * @property {Number}               speedScale          - Animation play speed scale
109  * @property {Number}               animationScale      - Animation play speed scale
110  */
111 ccs.ArmatureAnimation = ccs.ProcessBase.extend(/** @lends ccs.ArmatureAnimation# */{
112     _animationData: null,
113     _movementData: null,
114     _armature: null,
115     _movementID: "",
116     _toIndex: 0,
117     _tweenList: null,
118     _speedScale: 1,
119     _ignoreFrameEvent: false,
120     _frameEventQueue: null,
121     _movementEventQueue: null,
122     _movementList: null,
123     _onMovementList: false,
124     _movementListLoop: false,
125     _movementIndex: 0,
126     _movementListDurationTo: -1,
127 
128     _movementEventCallFunc: null,
129     _frameEventCallFunc: null,
130     _movementEventTarget: null,
131     _frameEventTarget:null,
132     _movementEventListener: null,
133     _frameEventListener: null,
134 
135     ctor: function (armature) {
136         ccs.ProcessBase.prototype.ctor.call(this);
137 
138         this._tweenList = [];
139         this._movementList = [];
140         this._frameEventQueue = [];
141         this._movementEventQueue = [];
142         this._armature = null;
143 
144         armature && ccs.ArmatureAnimation.prototype.init.call(this, armature);
145     },
146 
147     /**
148      * Initializes with an armature object
149      * @param {ccs.Armature} armature
150      * @return {Boolean}
151      */
152     init: function (armature) {
153         this._armature = armature;
154         this._tweenList.length = 0;
155         return true;
156     },
157 
158     /**
159      * Pauses armature animation.
160      */
161     pause: function () {
162         var locTweenList = this._tweenList;
163         for (var i = 0; i < locTweenList.length; i++)
164             locTweenList[i].pause();
165         ccs.ProcessBase.prototype.pause.call(this);
166     },
167 
168     /**
169      * Resumes armature animation.
170      */
171     resume: function () {
172         var locTweenList = this._tweenList;
173         for (var i = 0; i < locTweenList.length; i++)
174             locTweenList[i].resume();
175         ccs.ProcessBase.prototype.resume.call(this);
176     },
177 
178     /**
179      * Stops armature animation.
180      */
181     stop: function () {
182         var locTweenList = this._tweenList;
183         for (var i = 0; i < locTweenList.length; i++)
184             locTweenList[i].stop();
185         locTweenList.length = 0;
186         ccs.ProcessBase.prototype.stop.call(this);
187     },
188 
189     /**
190      * Sets animation play speed scale.
191      * @deprecated since v3.0, please use setSpeedScale instead.
192      * @param {Number} animationScale
193      */
194     setAnimationScale: function (animationScale) {
195         this.setSpeedScale(animationScale);
196     },
197 
198     /**
199      * Returns animation play speed scale.
200      * @deprecated since v3.0, please use getSpeedScale instead.
201      * @returns {Number}
202      */
203     getAnimationScale: function () {
204         return this.getSpeedScale();
205     },
206 
207     /**
208      * Sets animation play speed scale.
209      * @param {Number} speedScale
210      */
211     setSpeedScale: function (speedScale) {
212         if (speedScale === this._speedScale)
213             return;
214         this._speedScale = speedScale;
215         this._processScale = !this._movementData ? this._speedScale : this._speedScale * this._movementData.scale;
216         var dict = this._armature.getBoneDic();
217         for (var key in dict) {
218             var bone = dict[key];
219             bone.getTween().setProcessScale(this._processScale);
220             if (bone.getChildArmature())
221                 bone.getChildArmature().getAnimation().setSpeedScale(this._processScale);
222         }
223     },
224 
225     /**
226      * Returns animation play speed scale.
227      * @returns {Number}
228      */
229     getSpeedScale: function () {
230         return this._speedScale;
231     },
232 
233     /**
234      * play animation by animation name.
235      * @param {String} animationName The animation name you want to play
236      * @param {Number} [durationTo=-1]
237      *         the frames between two animation changing-over.It's meaning is changing to this animation need how many frames
238      *         -1 : use the value from CCMovementData get from flash design panel
239      * @param {Number} [loop=-1]
240      *          Whether the animation is loop.
241      *         loop < 0 : use the value from CCMovementData get from flash design panel
242      *         loop = 0 : this animation is not loop
243      *         loop > 0 : this animation is loop
244      * @example
245      * // example
246      * armature.getAnimation().play("run",-1,1);//loop play
247      * armature.getAnimation().play("run",-1,0);//not loop play
248      */
249     play: function (animationName, durationTo, loop) {
250         cc.assert(this._animationData, "this.animationData can not be null");
251 
252         this._movementData = this._animationData.getMovement(animationName);
253         cc.assert(this._movementData, "this._movementData can not be null");
254 
255         durationTo = (durationTo === undefined) ? -1 : durationTo;
256         loop = (loop === undefined) ? -1 : loop;
257 
258         //! Get key frame count
259         this._rawDuration = this._movementData.duration;
260         this._movementID = animationName;
261         this._processScale = this._speedScale * this._movementData.scale;
262 
263         //! Further processing parameters
264         durationTo = (durationTo === -1) ? this._movementData.durationTo : durationTo;
265         var durationTween = this._movementData.durationTween === 0 ? this._rawDuration : this._movementData.durationTween;
266 
267         var tweenEasing = this._movementData.tweenEasing;
268         //loop = (!loop || loop < 0) ? this._movementData.loop : loop;
269         loop = (loop < 0) ? this._movementData.loop : loop;
270         this._onMovementList = false;
271 
272         ccs.ProcessBase.prototype.play.call(this, durationTo, durationTween, loop, tweenEasing);
273 
274         if (this._rawDuration === 0)
275             this._loopType = ccs.ANIMATION_TYPE_SINGLE_FRAME;
276         else {
277             this._loopType = loop ? ccs.ANIMATION_TYPE_TO_LOOP_FRONT : ccs.ANIMATION_TYPE_NO_LOOP;
278             this._durationTween = durationTween;
279         }
280 
281         this._tweenList.length = 0;
282 
283         var movementBoneData, map = this._armature.getBoneDic();
284         for(var element in map) {
285             var bone = map[element];
286             movementBoneData = this._movementData.movBoneDataDic[bone.getName()];
287 
288             var tween = bone.getTween();
289             if(movementBoneData && movementBoneData.frameList.length > 0) {
290                 this._tweenList.push(tween);
291                 movementBoneData.duration = this._movementData.duration;
292                 tween.play(movementBoneData, durationTo, durationTween, loop, tweenEasing);
293                 tween.setProcessScale(this._processScale);
294 
295                 if (bone.getChildArmature())
296                     bone.getChildArmature().getAnimation().setSpeedScale(this._processScale);
297             } else {
298                 if(!bone.isIgnoreMovementBoneData()){
299                     //! this bone is not include in this movement, so hide it
300                     bone.getDisplayManager().changeDisplayWithIndex(-1, false);
301                     tween.stop();
302                 }
303             }
304         }
305         this._armature.update(0);
306     },
307 
308     /**
309      * Plays animation with index, the other param is the same to play.
310      * @param {Number} animationIndex
311      * @param {Number} durationTo
312      * @param {Number} durationTween
313      * @param {Number} loop
314      * @param {Number} [tweenEasing]
315      * @deprecated since v3.0, please use playWithIndex instead.
316      */
317     playByIndex: function (animationIndex, durationTo, durationTween, loop, tweenEasing) {
318         cc.log("playByIndex is deprecated. Use playWithIndex instead.");
319         this.playWithIndex(animationIndex, durationTo, loop);
320     },
321 
322     /**
323      * Plays animation with index, the other param is the same to play.
324      * @param {Number|Array} animationIndex
325      * @param {Number} durationTo
326      * @param {Number} loop
327      */
328     playWithIndex: function (animationIndex, durationTo, loop) {
329         var movName = this._animationData.movementNames;
330         cc.assert((animationIndex > -1) && (animationIndex < movName.length));
331 
332         var animationName = movName[animationIndex];
333         this.play(animationName, durationTo, loop);
334     },
335 
336     /**
337      * Plays animation with names
338      * @param {Array} movementNames
339      * @param {Number} durationTo
340      * @param {Boolean} loop
341      */
342     playWithNames: function (movementNames, durationTo, loop) {
343         durationTo = (durationTo === undefined) ? -1 : durationTo;
344         loop = (loop === undefined) ? true : loop;
345 
346         this._movementListLoop = loop;
347         this._movementListDurationTo = durationTo;
348         this._onMovementList = true;
349         this._movementIndex = 0;
350         if(movementNames instanceof Array)
351             this._movementList = movementNames;
352         else
353             this._movementList.length = 0;
354         this.updateMovementList();
355     },
356 
357     /**
358      * Plays animation by indexes
359      * @param {Array} movementIndexes
360      * @param {Number} durationTo
361      * @param {Boolean} loop
362      */
363     playWithIndexes: function (movementIndexes, durationTo, loop) {
364         durationTo = (durationTo === undefined) ? -1 : durationTo;
365         loop = (loop === undefined) ? true : loop;
366 
367         this._movementList.length = 0;
368         this._movementListLoop = loop;
369         this._movementListDurationTo = durationTo;
370         this._onMovementList = true;
371         this._movementIndex = 0;
372 
373         var movName = this._animationData.movementNames;
374 
375         for (var i = 0; i < movementIndexes.length; i++) {
376             var name = movName[movementIndexes[i]];
377             this._movementList.push(name);
378         }
379 
380         this.updateMovementList();
381     },
382 
383     /**
384      * <p>
385      * Goes to specified frame and plays current movement.                                  <br/>
386      * You need first switch to the movement you want to play, then call this function.     <br/>
387      *                                                                                      <br/>
388      * example : playByIndex(0);                                                            <br/>
389      *           gotoAndPlay(0);                                                            <br/>
390      *           playByIndex(1);                                                            <br/>
391      *           gotoAndPlay(0);                                                            <br/>
392      *           gotoAndPlay(15);                                                           <br/>
393      * </p>
394      * @param {Number} frameIndex
395      */
396     gotoAndPlay: function (frameIndex) {
397         if (!this._movementData || frameIndex < 0 || frameIndex >= this._movementData.duration) {
398             cc.log("Please ensure you have played a movement, and the frameIndex is in the range.");
399             return;
400         }
401 
402         var ignoreFrameEvent = this._ignoreFrameEvent;
403         this._ignoreFrameEvent = true;
404         this._isPlaying = true;
405         this._isComplete = this._isPause = false;
406 
407         ccs.ProcessBase.prototype.gotoFrame.call(this, frameIndex);
408         this._currentPercent = this._curFrameIndex / (this._movementData.duration - 1);
409         this._currentFrame = this._nextFrameIndex * this._currentPercent;
410 
411         var locTweenList = this._tweenList;
412         for (var i = 0; i < locTweenList.length; i++)
413             locTweenList[i].gotoAndPlay(frameIndex);
414         this._armature.update(0);
415         this._ignoreFrameEvent = ignoreFrameEvent;
416     },
417 
418     /**
419      * Goes to specified frame and pauses current movement.
420      * @param {Number} frameIndex
421      */
422     gotoAndPause: function (frameIndex) {
423         this.gotoAndPlay(frameIndex);
424         this.pause();
425     },
426 
427     /**
428      * Returns the length of armature's movements
429      * @return {Number}
430      */
431     getMovementCount: function () {
432         return this._animationData.getMovementCount();
433     },
434 
435     /**
436      * Updates the state of ccs.Tween list, calls frame event's callback and calls movement event's callback.
437      * @param {Number} dt
438      */
439     update: function (dt) {
440         ccs.ProcessBase.prototype.update.call(this, dt);
441 
442         var locTweenList = this._tweenList;
443         for (var i = 0; i < locTweenList.length; i++)
444             locTweenList[i].update(dt);
445 
446         var frameEvents = this._frameEventQueue, event;
447         while (frameEvents.length > 0) {
448             event = frameEvents.shift();
449             this._ignoreFrameEvent = true;
450             if(this._frameEventCallFunc)
451                 this._frameEventCallFunc.call(this._frameEventTarget, event.bone, event.frameEventName, event.originFrameIndex, event.currentFrameIndex);
452             if(this._frameEventListener)
453                 this._frameEventListener(event.bone, event.frameEventName, event.originFrameIndex, event.currentFrameIndex);
454             this._ignoreFrameEvent = false;
455         }
456 
457         var movementEvents = this._movementEventQueue;
458         while (movementEvents.length > 0) {
459             event = movementEvents.shift();
460             if(this._movementEventCallFunc)
461                 this._movementEventCallFunc.call(this._movementEventTarget, event.armature, event.movementType, event.movementID);
462             if (this._movementEventListener)
463                 this._movementEventListener(event.armature, event.movementType, event.movementID);
464         }
465     },
466 
467     /**
468      * Updates will call this handler, you can handle your logic here
469      */
470     updateHandler: function () {      //TODO set it to protected in v3.1
471         var locCurrentPercent = this._currentPercent;
472         if (locCurrentPercent >= 1) {
473             switch (this._loopType) {
474                 case ccs.ANIMATION_TYPE_NO_LOOP:
475                     this._loopType = ccs.ANIMATION_TYPE_MAX;
476                     this._currentFrame = (locCurrentPercent - 1) * this._nextFrameIndex;
477                     locCurrentPercent = this._currentFrame / this._durationTween;
478                     if (locCurrentPercent < 1.0) {
479                         this._nextFrameIndex = this._durationTween;
480                         this.movementEvent(this._armature, ccs.MovementEventType.start, this._movementID);
481                         break;
482                     }
483                     break;
484                 case ccs.ANIMATION_TYPE_MAX:
485                 case ccs.ANIMATION_TYPE_SINGLE_FRAME:
486                     locCurrentPercent = 1;
487                     this._isComplete = true;
488                     this._isPlaying = false;
489 
490                     this.movementEvent(this._armature, ccs.MovementEventType.complete, this._movementID);
491 
492                     this.updateMovementList();
493                     break;
494                 case ccs.ANIMATION_TYPE_TO_LOOP_FRONT:
495                     this._loopType = ccs.ANIMATION_TYPE_LOOP_FRONT;
496                     locCurrentPercent = ccs.fmodf(locCurrentPercent, 1);
497                     this._currentFrame = this._nextFrameIndex === 0 ? 0 : ccs.fmodf(this._currentFrame, this._nextFrameIndex);
498                     this._nextFrameIndex = this._durationTween > 0 ? this._durationTween : 1;
499                     this.movementEvent(this, ccs.MovementEventType.start, this._movementID);
500                     break;
501                 default:
502                     //locCurrentPercent = ccs.fmodf(locCurrentPercent, 1);
503                     this._currentFrame = ccs.fmodf(this._currentFrame, this._nextFrameIndex);
504                     this._toIndex = 0;
505                     this.movementEvent(this._armature, ccs.MovementEventType.loopComplete, this._movementID);
506                     break;
507             }
508             this._currentPercent = locCurrentPercent;
509         }
510     },
511 
512     /**
513      * Returns the Id of current movement
514      * @returns {String}
515      */
516     getCurrentMovementID: function () {
517         if (this._isComplete)
518             return "";
519         return this._movementID;
520     },
521 
522     /**
523      * Sets movement event callback to animation.
524      * @param {function} callFunc
525      * @param {Object} target
526      */
527     setMovementEventCallFunc: function (callFunc, target) {
528         if(arguments.length === 1){
529             this._movementEventListener = callFunc;
530         }else if(arguments.length === 2){
531             this._movementEventTarget = target;
532             this._movementEventCallFunc = callFunc;
533         }
534     },
535 
536     /**
537      * Sets frame event callback to animation.
538      * @param {function} callFunc
539      * @param {Object} target
540      */
541     setFrameEventCallFunc: function (callFunc, target) {
542         if(arguments.length === 1){
543             this._frameEventListener = callFunc;
544         }else if(arguments.length === 2){
545             this._frameEventTarget = target;
546             this._frameEventCallFunc = callFunc;
547         }
548     },
549 
550     /**
551      * Sets user object to animation.
552      * @param {Object} userObject
553      */
554     setUserObject: function (userObject) {
555         this._userObject = userObject;
556     },
557 
558     /**
559      * Emits a frame event
560      * @param {ccs.Bone} bone
561      * @param {String} frameEventName
562      * @param {Number} originFrameIndex
563      * @param {Number} currentFrameIndex
564      */
565     frameEvent: function (bone, frameEventName, originFrameIndex, currentFrameIndex) {
566         if ((this._frameEventTarget && this._frameEventCallFunc) || this._frameEventListener) {
567             var frameEvent = new ccs.FrameEvent();
568             frameEvent.bone = bone;
569             frameEvent.frameEventName = frameEventName;
570             frameEvent.originFrameIndex = originFrameIndex;
571             frameEvent.currentFrameIndex = currentFrameIndex;
572             this._frameEventQueue.push(frameEvent);
573         }
574     },
575 
576     /**
577      * Emits a movement event
578      * @param {ccs.Armature} armature
579      * @param {Number} movementType
580      * @param {String} movementID
581      */
582     movementEvent: function (armature, movementType, movementID) {
583         if ((this._movementEventTarget && this._movementEventCallFunc) || this._movementEventListener) {
584             var event = new ccs.MovementEvent();
585             event.armature = armature;
586             event.movementType = movementType;
587             event.movementID = movementID;
588             this._movementEventQueue.push(event);
589         }
590     },
591 
592     /**
593      * Updates movement list.
594      */
595     updateMovementList: function () {
596         if (this._onMovementList) {
597             var movementObj, locMovementList = this._movementList;
598             if (this._movementListLoop) {
599                 movementObj = locMovementList[this._movementIndex];
600                 this.play(movementObj, movementObj.durationTo, 0);
601                 this._movementIndex++;
602                 if (this._movementIndex >= locMovementList.length)
603                     this._movementIndex = 0;
604             } else {
605                 if (this._movementIndex < locMovementList.length) {
606                     movementObj = locMovementList[this._movementIndex];
607                     this.play(movementObj, movementObj.durationTo, 0);
608                     this._movementIndex++;
609                 } else
610                     this._onMovementList = false;
611             }
612             this._onMovementList = true;
613         }
614     },
615 
616     /**
617      * Sets animation data to animation.
618      * @param {ccs.AnimationData} data
619      */
620     setAnimationData: function (data) {
621         if(this._animationData !== data)
622             this._animationData = data;
623     },
624 
625     /**
626      * Returns animation data of animation.
627      * @return {ccs.AnimationData}
628      */
629     getAnimationData: function () {
630         return this._animationData;
631     },
632 
633     /**
634      * Returns the user object of animation.
635      * @return {Object}
636      */
637     getUserObject: function () {
638         return this._userObject;
639     },
640 
641     /**
642      * Determines if the frame event is ignored
643      * @returns {boolean}
644      */
645     isIgnoreFrameEvent: function () {
646         return this._ignoreFrameEvent;
647     }
648 });
649 
650 var _p = ccs.ArmatureAnimation.prototype;
651 
652 // Extended properties
653 /** @expose */
654 _p.speedScale;
655 cc.defineGetterSetter(_p, "speedScale", _p.getSpeedScale, _p.setSpeedScale);
656 /** @expose */
657 _p.animationScale;
658 cc.defineGetterSetter(_p, "animationScale", _p.getAnimationScale, _p.setAnimationScale);
659 
660 _p = null;
661 
662 /**
663  * Allocates and initializes a ArmatureAnimation.
664  * @return {ccs.ArmatureAnimation}
665  * @deprecated since v3.1, please use new construction instead
666  */
667 ccs.ArmatureAnimation.create = function (armature) {
668     return new ccs.ArmatureAnimation(armature);
669 };
670