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