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                     if (!bone.getChildArmature().getAnimation().isPlaying())
298                         bone.getChildArmature().getAnimation().playWithIndex(0);
299                 }
300             } else {
301                 if(!bone.isIgnoreMovementBoneData()){
302                     //! this bone is not include in this movement, so hide it
303                     bone.getDisplayManager().changeDisplayWithIndex(-1, false);
304                     tween.stop();
305                 }
306             }
307         }
308         this._armature.update(0);
309     },
310 
311     /**
312      * Plays animation with index, the other param is the same to play.
313      * @param {Number} animationIndex
314      * @param {Number} durationTo
315      * @param {Number} durationTween
316      * @param {Number} loop
317      * @param {Number} [tweenEasing]
318      * @deprecated since v3.0, please use playWithIndex instead.
319      */
320     playByIndex: function (animationIndex, durationTo, durationTween, loop, tweenEasing) {
321         cc.log("playByIndex is deprecated. Use playWithIndex instead.");
322         this.playWithIndex(animationIndex, durationTo, loop);
323     },
324 
325     /**
326      * Plays animation with index, the other param is the same to play.
327      * @param {Number|Array} animationIndex
328      * @param {Number} durationTo
329      * @param {Number} loop
330      */
331     playWithIndex: function (animationIndex, durationTo, loop) {
332         var movName = this._animationData.movementNames;
333         cc.assert((animationIndex > -1) && (animationIndex < movName.length));
334 
335         var animationName = movName[animationIndex];
336         this.play(animationName, durationTo, loop);
337     },
338 
339     /**
340      * Plays animation with names
341      * @param {Array} movementNames
342      * @param {Number} durationTo
343      * @param {Boolean} loop
344      */
345     playWithNames: function (movementNames, durationTo, loop) {
346         durationTo = (durationTo === undefined) ? -1 : durationTo;
347         loop = (loop === undefined) ? true : loop;
348 
349         this._movementListLoop = loop;
350         this._movementListDurationTo = durationTo;
351         this._onMovementList = true;
352         this._movementIndex = 0;
353         if(movementNames instanceof Array)
354             this._movementList = movementNames;
355         else
356             this._movementList.length = 0;
357         this.updateMovementList();
358     },
359 
360     /**
361      * Plays animation by indexes
362      * @param {Array} movementIndexes
363      * @param {Number} durationTo
364      * @param {Boolean} loop
365      */
366     playWithIndexes: function (movementIndexes, durationTo, loop) {
367         durationTo = (durationTo === undefined) ? -1 : durationTo;
368         loop = (loop === undefined) ? true : loop;
369 
370         this._movementList.length = 0;
371         this._movementListLoop = loop;
372         this._movementListDurationTo = durationTo;
373         this._onMovementList = true;
374         this._movementIndex = 0;
375 
376         var movName = this._animationData.movementNames;
377 
378         for (var i = 0; i < movementIndexes.length; i++) {
379             var name = movName[movementIndexes[i]];
380             this._movementList.push(name);
381         }
382 
383         this.updateMovementList();
384     },
385 
386     /**
387      * <p>
388      * Goes to specified frame and plays current movement.                                  <br/>
389      * You need first switch to the movement you want to play, then call this function.     <br/>
390      *                                                                                      <br/>
391      * example : playByIndex(0);                                                            <br/>
392      *           gotoAndPlay(0);                                                            <br/>
393      *           playByIndex(1);                                                            <br/>
394      *           gotoAndPlay(0);                                                            <br/>
395      *           gotoAndPlay(15);                                                           <br/>
396      * </p>
397      * @param {Number} frameIndex
398      */
399     gotoAndPlay: function (frameIndex) {
400         if (!this._movementData || frameIndex < 0 || frameIndex >= this._movementData.duration) {
401             cc.log("Please ensure you have played a movement, and the frameIndex is in the range.");
402             return;
403         }
404 
405         var ignoreFrameEvent = this._ignoreFrameEvent;
406         this._ignoreFrameEvent = true;
407         this._isPlaying = true;
408         this._isComplete = this._isPause = false;
409 
410         ccs.ProcessBase.prototype.gotoFrame.call(this, frameIndex);
411         this._currentPercent = this._curFrameIndex / (this._movementData.duration - 1);
412         this._currentFrame = this._nextFrameIndex * this._currentPercent;
413 
414         var locTweenList = this._tweenList;
415         for (var i = 0; i < locTweenList.length; i++)
416             locTweenList[i].gotoAndPlay(frameIndex);
417         this._armature.update(0);
418         this._ignoreFrameEvent = ignoreFrameEvent;
419     },
420 
421     /**
422      * Goes to specified frame and pauses current movement.
423      * @param {Number} frameIndex
424      */
425     gotoAndPause: function (frameIndex) {
426         this.gotoAndPlay(frameIndex);
427         this.pause();
428     },
429 
430     /**
431      * Returns the length of armature's movements
432      * @return {Number}
433      */
434     getMovementCount: function () {
435         return this._animationData.getMovementCount();
436     },
437 
438     /**
439      * Updates the state of ccs.Tween list, calls frame event's callback and calls movement event's callback.
440      * @param {Number} dt
441      */
442     update: function (dt) {
443         ccs.ProcessBase.prototype.update.call(this, dt);
444 
445         var locTweenList = this._tweenList;
446         for (var i = 0; i < locTweenList.length; i++)
447             locTweenList[i].update(dt);
448 
449         var frameEvents = this._frameEventQueue, event;
450         while (frameEvents.length > 0) {
451             event = frameEvents.shift();
452             this._ignoreFrameEvent = true;
453             if(this._frameEventCallFunc)
454                 this._frameEventCallFunc.call(this._frameEventTarget, event.bone, event.frameEventName, event.originFrameIndex, event.currentFrameIndex);
455             if(this._frameEventListener)
456                 this._frameEventListener(event.bone, event.frameEventName, event.originFrameIndex, event.currentFrameIndex);
457             this._ignoreFrameEvent = false;
458         }
459 
460         var movementEvents = this._movementEventQueue;
461         while (movementEvents.length > 0) {
462             event = movementEvents.shift();
463             if(this._movementEventCallFunc)
464                 this._movementEventCallFunc.call(this._movementEventTarget, event.armature, event.movementType, event.movementID);
465             if (this._movementEventListener)
466                 this._movementEventListener(event.armature, event.movementType, event.movementID);
467         }
468     },
469 
470     /**
471      * Updates will call this handler, you can handle your logic here
472      */
473     updateHandler: function () {      //TODO set it to protected in v3.1
474         var locCurrentPercent = this._currentPercent;
475         if (locCurrentPercent >= 1) {
476             switch (this._loopType) {
477                 case ccs.ANIMATION_TYPE_NO_LOOP:
478                     this._loopType = ccs.ANIMATION_TYPE_MAX;
479                     this._currentFrame = (locCurrentPercent - 1) * this._nextFrameIndex;
480                     locCurrentPercent = this._currentFrame / this._durationTween;
481                     if (locCurrentPercent < 1.0) {
482                         this._nextFrameIndex = this._durationTween;
483                         this.movementEvent(this._armature, ccs.MovementEventType.start, this._movementID);
484                         break;
485                     }
486                     break;
487                 case ccs.ANIMATION_TYPE_MAX:
488                 case ccs.ANIMATION_TYPE_SINGLE_FRAME:
489                     locCurrentPercent = 1;
490                     this._isComplete = true;
491                     this._isPlaying = false;
492 
493                     this.movementEvent(this._armature, ccs.MovementEventType.complete, this._movementID);
494 
495                     this.updateMovementList();
496                     break;
497                 case ccs.ANIMATION_TYPE_TO_LOOP_FRONT:
498                     this._loopType = ccs.ANIMATION_TYPE_LOOP_FRONT;
499                     locCurrentPercent = ccs.fmodf(locCurrentPercent, 1);
500                     this._currentFrame = this._nextFrameIndex === 0 ? 0 : ccs.fmodf(this._currentFrame, this._nextFrameIndex);
501                     this._nextFrameIndex = this._durationTween > 0 ? this._durationTween : 1;
502                     this.movementEvent(this, ccs.MovementEventType.start, this._movementID);
503                     break;
504                 default:
505                     //locCurrentPercent = ccs.fmodf(locCurrentPercent, 1);
506                     this._currentFrame = ccs.fmodf(this._currentFrame, this._nextFrameIndex);
507                     this._toIndex = 0;
508                     this.movementEvent(this._armature, ccs.MovementEventType.loopComplete, this._movementID);
509                     break;
510             }
511             this._currentPercent = locCurrentPercent;
512         }
513     },
514 
515     /**
516      * Returns the Id of current movement
517      * @returns {String}
518      */
519     getCurrentMovementID: function () {
520         if (this._isComplete)
521             return "";
522         return this._movementID;
523     },
524 
525     /**
526      * Sets movement event callback to animation.
527      * @param {function} callFunc
528      * @param {Object} target
529      */
530     setMovementEventCallFunc: function (callFunc, target) {
531         if(arguments.length === 1){
532             this._movementEventListener = callFunc;
533         }else if(arguments.length === 2){
534             this._movementEventTarget = target;
535             this._movementEventCallFunc = callFunc;
536         }
537     },
538 
539     /**
540      * Sets frame event callback to animation.
541      * @param {function} callFunc
542      * @param {Object} target
543      */
544     setFrameEventCallFunc: function (callFunc, target) {
545         if(arguments.length === 1){
546             this._frameEventListener = callFunc;
547         }else if(arguments.length === 2){
548             this._frameEventTarget = target;
549             this._frameEventCallFunc = callFunc;
550         }
551     },
552 
553     /**
554      * Sets user object to animation.
555      * @param {Object} userObject
556      */
557     setUserObject: function (userObject) {
558         this._userObject = userObject;
559     },
560 
561     /**
562      * Emits a frame event
563      * @param {ccs.Bone} bone
564      * @param {String} frameEventName
565      * @param {Number} originFrameIndex
566      * @param {Number} currentFrameIndex
567      */
568     frameEvent: function (bone, frameEventName, originFrameIndex, currentFrameIndex) {
569         if ((this._frameEventTarget && this._frameEventCallFunc) || this._frameEventListener) {
570             var frameEvent = new ccs.FrameEvent();
571             frameEvent.bone = bone;
572             frameEvent.frameEventName = frameEventName;
573             frameEvent.originFrameIndex = originFrameIndex;
574             frameEvent.currentFrameIndex = currentFrameIndex;
575             this._frameEventQueue.push(frameEvent);
576         }
577     },
578 
579     /**
580      * Emits a movement event
581      * @param {ccs.Armature} armature
582      * @param {Number} movementType
583      * @param {String} movementID
584      */
585     movementEvent: function (armature, movementType, movementID) {
586         if ((this._movementEventTarget && this._movementEventCallFunc) || this._movementEventListener) {
587             var event = new ccs.MovementEvent();
588             event.armature = armature;
589             event.movementType = movementType;
590             event.movementID = movementID;
591             this._movementEventQueue.push(event);
592         }
593     },
594 
595     /**
596      * Updates movement list.
597      */
598     updateMovementList: function () {
599         if (this._onMovementList) {
600             var movementObj, locMovementList = this._movementList;
601             if (this._movementListLoop) {
602                 movementObj = locMovementList[this._movementIndex];
603                 this.play(movementObj, movementObj.durationTo, 0);
604                 this._movementIndex++;
605                 if (this._movementIndex >= locMovementList.length)
606                     this._movementIndex = 0;
607             } else {
608                 if (this._movementIndex < locMovementList.length) {
609                     movementObj = locMovementList[this._movementIndex];
610                     this.play(movementObj, movementObj.durationTo, 0);
611                     this._movementIndex++;
612                 } else
613                     this._onMovementList = false;
614             }
615             this._onMovementList = true;
616         }
617     },
618 
619     /**
620      * Sets animation data to animation.
621      * @param {ccs.AnimationData} data
622      */
623     setAnimationData: function (data) {
624         if(this._animationData !== data)
625             this._animationData = data;
626     },
627 
628     /**
629      * Returns animation data of animation.
630      * @return {ccs.AnimationData}
631      */
632     getAnimationData: function () {
633         return this._animationData;
634     },
635 
636     /**
637      * Returns the user object of animation.
638      * @return {Object}
639      */
640     getUserObject: function () {
641         return this._userObject;
642     },
643 
644     /**
645      * Determines if the frame event is ignored
646      * @returns {boolean}
647      */
648     isIgnoreFrameEvent: function () {
649         return this._ignoreFrameEvent;
650     }
651 });
652 
653 var _p = ccs.ArmatureAnimation.prototype;
654 
655 // Extended properties
656 /** @expose */
657 _p.speedScale;
658 cc.defineGetterSetter(_p, "speedScale", _p.getSpeedScale, _p.setSpeedScale);
659 /** @expose */
660 _p.animationScale;
661 cc.defineGetterSetter(_p, "animationScale", _p.getAnimationScale, _p.setAnimationScale);
662 
663 _p = null;
664 
665 /**
666  * Allocates and initializes a ArmatureAnimation.
667  * @return {ccs.ArmatureAnimation}
668  * @deprecated since v3.1, please use new construction instead
669  */
670 ccs.ArmatureAnimation.create = function (armature) {
671     return new ccs.ArmatureAnimation(armature);
672 };
673