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().setProcessScale(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         this._onMovementList = false;
229 
230         ccs.ProcessBase.prototype.play.call(this, durationTo, durationTween, loop, tweenEasing);
231 
232         if (this._rawDuration == 0)
233             this._loopType = ccs.ANIMATION_TYPE_SINGLE_FRAME;
234         else {
235             this._loopType = loop ? ccs.ANIMATION_TYPE_TO_LOOP_FRONT : ccs.ANIMATION_TYPE_NO_LOOP;
236             this._durationTween = durationTween;
237         }
238 
239         var movementBoneData;
240         this._tweenList = [];
241 
242         var map = this._armature.getBoneDic();
243         for(var element in map) {
244             var bone = map[element];
245             movementBoneData = this._movementData.movBoneDataDic[bone.getName()];
246 
247             var tween = bone.getTween();
248             if(movementBoneData && movementBoneData.frameList.length > 0) {
249                 this._tweenList.push(tween);
250                 movementBoneData.duration = this._movementData.duration;
251                 tween.play(movementBoneData, durationTo, durationTween, loop, tweenEasing);
252                 tween.setProcessScale(this._processScale);
253 
254                 if (bone.getChildArmature())
255                     bone.getChildArmature().getAnimation().setProcessScale(this._processScale);
256             } else {
257                 if(!bone.isIgnoreMovementBoneData()){
258                     //! this bone is not include in this movement, so hide it
259                     bone.getDisplayManager().changeDisplayWithIndex(-1, false);
260                     tween.stop();
261                 }
262             }
263         }
264         this._armature.update(0);
265     },
266 
267     /**
268      * Play animation with index, the o ther param is the same to play.
269      * @param {Number} animationIndex
270      * @param {Number} durationTo
271      * @param {Number} durationTween
272      * @param {Number} loop
273      * @param {Number} tweenEasing
274      */
275     playByIndex: function (animationIndex, durationTo, durationTween, loop, tweenEasing) {
276         cc.log("playByIndex is deprecated. Use playWithIndex instead.");
277         this.playWithIndex(animationIndex, durationTo, loop);
278     },
279 
280     /**
281      * Play animation with index, the other param is the same to play.
282      * @param {Number||Array} animationIndex
283      * @param {Number} durationTo
284      * @param {Number} loop
285      */
286     playWithIndex: function (animationIndex, durationTo, loop) {
287         var movName = this._animationData.movementNames;
288         cc.assert((animationIndex > -1) && (animationIndex < movName.length));
289 
290         var animationName = movName[animationIndex];
291         this.play(animationName, durationTo, loop);
292     },
293 
294     /**
295      * play with names
296      * @param {Array} movementNames
297      * @param {Number} durationTo
298      * @param {Boolean} loop
299      */
300     playWithNames: function (movementNames, durationTo, loop) {
301         durationTo = (durationTo === undefined) ? -1 : durationTo;
302         loop = (loop === undefined) ? true : loop;
303 
304         this._movementListLoop = loop;
305         this._movementListDurationTo = durationTo;
306         this._onMovementList = true;
307         this._movementIndex = 0;
308         if(movementNames instanceof Array)
309             this._movementList = movementNames;
310         else
311             this._movementList.length = 0;
312 
313         this.updateMovementList();
314     },
315 
316     /**
317      *  play by indexes
318      * @param movementIndexes
319      * @param {Number} durationTo
320      * @param {Boolean} loop
321      */
322     playWithIndexes: function (movementIndexes, durationTo, loop) {
323         durationTo = (durationTo === undefined) ? -1 : durationTo;
324         loop = (loop === undefined) ? true : loop;
325 
326         this._movementList.length = 0;
327         this._movementListLoop = loop;
328         this._movementListDurationTo = durationTo;
329         this._onMovementList = true;
330         this._movementIndex = 0;
331 
332         var movName = this._animationData.movementNames;
333 
334         for (var i = 0; i < movementIndexes.length; i++) {
335             var name = movName[movementIndexes[i]];
336             this._movementList.push(name);
337         }
338 
339         this.updateMovementList();
340     },
341 
342     /**
343      * Go to specified frame and play current movement.
344      * You need first switch to the movement you want to play, then call this function.
345      *
346      * example : playByIndex(0);
347      *           gotoAndPlay(0);
348      *           playByIndex(1);
349      *           gotoAndPlay(0);
350      *           gotoAndPlay(15);
351      * @param {Number} frameIndex
352      */
353     gotoAndPlay: function (frameIndex) {
354         if (!this._movementData || frameIndex < 0 || frameIndex >= this._movementData.duration) {
355             cc.log("Please ensure you have played a movement, and the frameIndex is in the range.");
356             return;
357         }
358 
359         var ignoreFrameEvent = this._ignoreFrameEvent;
360         this._ignoreFrameEvent = true;
361         this._isPlaying = true;
362         this._isComplete = this._isPause = false;
363 
364         ccs.ProcessBase.prototype.gotoFrame.call(this, frameIndex);
365         this._currentPercent = this._curFrameIndex / (this._movementData.duration - 1);
366         this._currentFrame = this._nextFrameIndex * this._currentPercent;
367 
368         var locTweenList = this._tweenList;
369         for (var i = 0; i < locTweenList.length; i++) {
370             locTweenList[i].gotoAndPlay(frameIndex);
371         }
372         this._armature.update(0);
373         this._ignoreFrameEvent = ignoreFrameEvent;
374     },
375 
376     /**
377      * Go to specified frame and pause current movement.
378      * @param {Number} frameIndex
379      */
380     gotoAndPause: function (frameIndex) {
381         this.gotoAndPlay(frameIndex);
382         this.pause();
383     },
384 
385     /**
386      * get movement count
387      * @return {Number}
388      */
389     getMovementCount: function () {
390         return this._animationData.getMovementCount();
391     },
392 
393     update: function (dt) {
394         ccs.ProcessBase.prototype.update.call(this, dt);
395 
396         var locTweenList = this._tweenList;
397         for (var i = 0; i < locTweenList.length; i++)
398             locTweenList[i].update(dt);
399 
400         var frameEvents = this._frameEventQueue, event;
401         while (frameEvents.length > 0) {
402             event = frameEvents.shift();
403             this._ignoreFrameEvent = true;
404             if(this._frameEventCallFunc)
405                 this._frameEventCallFunc.call(this._frameEventTarget, event.bone, event.frameEventName, event.originFrameIndex, event.currentFrameIndex);
406             if(this._frameEventListener)
407                 this._frameEventListener(event.bone, event.frameEventName, event.originFrameIndex, event.currentFrameIndex);
408             this._ignoreFrameEvent = false;
409         }
410 
411         var movementEvents = this._movementEventQueue;
412         while (movementEvents.length > 0) {
413             event = movementEvents.shift();
414             if(this._movementEventCallFunc)
415                 this._movementEventCallFunc.call(this._movementEventTarget, event.armature, event.movementType, event.movementID);
416             if (this._movementEventListener)
417                 this._movementEventListener(event.armature, event.movementType, event.movementID);
418         }
419     },
420 
421     /**
422      * update will call this handler, you can handle your logic here
423      */
424     updateHandler: function () {
425         var locCurrentPercent = this._currentPercent;
426         if (locCurrentPercent >= 1) {
427             switch (this._loopType) {
428                 case ccs.ANIMATION_TYPE_NO_LOOP:
429                     this._loopType = ccs.ANIMATION_TYPE_MAX;
430                     this._currentFrame = (locCurrentPercent - 1) * this._nextFrameIndex;
431                     locCurrentPercent = this._currentFrame / this._durationTween;
432                     if (locCurrentPercent < 1.0) {
433                         this._nextFrameIndex = this._durationTween;
434                         this.movementEvent(this._armature, ccs.MovementEventType.start, this._movementID);
435                         break;
436                     }
437                     break;
438                 case ccs.ANIMATION_TYPE_MAX:
439                 case ccs.ANIMATION_TYPE_SINGLE_FRAME:
440                     locCurrentPercent = 1;
441                     this._isComplete = true;
442                     this._isPlaying = false;
443 
444                     this.movementEvent(this._armature, ccs.MovementEventType.complete, this._movementID);
445 
446                     this.updateMovementList();
447                     break;
448                 case ccs.ANIMATION_TYPE_TO_LOOP_FRONT:
449                     this._loopType = ccs.ANIMATION_TYPE_LOOP_FRONT;
450                     locCurrentPercent = ccs.fmodf(locCurrentPercent, 1);
451                     this._currentFrame = this._nextFrameIndex == 0 ? 0 : ccs.fmodf(this._currentFrame, this._nextFrameIndex);
452                     this._nextFrameIndex = this._durationTween > 0 ? this._durationTween : 1;
453                     this.movementEvent(this, ccs.MovementEventType.start, this._movementID);
454                     break;
455                 default:
456                     //locCurrentPercent = ccs.fmodf(locCurrentPercent, 1);
457                     this._currentFrame = ccs.fmodf(this._currentFrame, this._nextFrameIndex);
458                     this._toIndex = 0;
459                     this.movementEvent(this._armature, ccs.MovementEventType.loopComplete, this._movementID);
460                     break;
461             }
462             this._currentPercent = locCurrentPercent;
463         }
464     },
465 
466     /**
467      * Get current movementID
468      * @returns {String}
469      */
470     getCurrentMovementID: function () {
471         if (this._isComplete)
472             return "";
473         return this._movementID;
474     },
475 
476     /**
477      * connect a event
478      * @param {function} callFunc
479      * @param {Object} target
480      */
481     setMovementEventCallFunc: function (callFunc, target) {
482         if(arguments.length == 1){
483             this._frameEventListener = target;
484         }else if(arguments.length == 2){
485             this._movementEventTarget = target;
486             this._movementEventCallFunc = callFunc;
487         }
488     },
489 
490     /**
491      * connect a event
492      * @param {function} callFunc
493      * @param {Object} target
494      */
495     setFrameEventCallFunc: function (callFunc, target) {
496         if(arguments.length == 1){
497             this._frameEventListener = target;
498         }else if(arguments.length == 2){
499             this._frameEventTarget = target;
500             this._frameEventCallFunc = callFunc;
501         }
502     },
503 
504     /**
505      * userObject setter
506      * @param {Object} userObject
507      */
508     setUserObject: function (userObject) {
509         this._userObject = userObject;
510     },
511 
512     /**
513      * @param {ccs.Bone} bone
514      * @param {String} frameEventName
515      * @param {Number} originFrameIndex
516      * @param {Number} currentFrameIndex
517      */
518     frameEvent: function (bone, frameEventName, originFrameIndex, currentFrameIndex) {
519         if ((this._frameEventTarget && this._frameEventCallFunc) || this._frameEventListener) {
520             var frameEvent = new ccs.FrameEvent();
521             frameEvent.bone = bone;
522             frameEvent.frameEventName = frameEventName;
523             frameEvent.originFrameIndex = originFrameIndex;
524             frameEvent.currentFrameIndex = currentFrameIndex;
525 
526             this._frameEventQueue.push(frameEvent);
527         }
528     },
529 
530     movementEvent: function (armature, movementType, movementID) {
531         if ((this._movementEventTarget && this._movementEventCallFunc) || this._movementEventListener) {
532             var event = new ccs.MovementEvent();
533             event.armature = armature;
534             event.movementType = movementType;
535             event.movementID = movementID;
536             this._movementEventQueue.push(event);
537         }
538     },
539 
540     updateMovementList: function () {
541         if (this._onMovementList) {
542             var movementObj, locMovementList = this._movementList;
543             if (this._movementListLoop) {
544                 movementObj = locMovementList[this._movementIndex];
545                 this.play(movementObj, movementObj.durationTo, 0);
546                 this._movementIndex++;
547                 if (this._movementIndex >= locMovementList.length)
548                     this._movementIndex = 0;
549             } else {
550                 if (this._movementIndex < locMovementList.length) {
551                     movementObj = locMovementList[this._movementIndex];
552                     this.play(movementObj, movementObj.durationTo, 0);
553                     this._movementIndex++;
554                 } else
555                     this._onMovementList = false;
556             }
557             this._onMovementList = true;
558         }
559     },
560 
561     /**
562      * animationData setter
563      * @param {ccs.AnimationData} data
564      */
565     setAnimationData: function (data) {
566         if(this._animationData != data)
567             this._animationData = data;
568     },
569 
570     /**
571      * animationData getter
572      * @return {ccs.AnimationData}
573      */
574     getAnimationData: function () {
575         return this._animationData;
576     },
577 
578     /**
579      * userObject getter
580      * @return {Object}
581      */
582     getUserObject: function () {
583         return this._userObject;
584     },
585 
586     /**
587      * Determines if the frame event is ignored
588      * @returns {boolean}
589      */
590     isIgnoreFrameEvent: function () {
591         return this._ignoreFrameEvent;
592     }
593 });
594 
595 var _p = ccs.ArmatureAnimation.prototype;
596 
597 // Extended properties
598 /** @expose */
599 _p.speedScale;
600 cc.defineGetterSetter(_p, "speedScale", _p.getSpeedScale, _p.setSpeedScale);
601 /** @expose */
602 _p.animationScale;
603 cc.defineGetterSetter(_p, "animationScale", _p.getAnimationScale, _p.setAnimationScale);
604 
605 _p = null;
606 
607 /**
608  * allocates and initializes a ArmatureAnimation.
609  * @constructs
610  * @return {ccs.ArmatureAnimation}
611  * @example
612  * // example
613  * var animation = ccs.ArmatureAnimation.create();
614  */
615 ccs.ArmatureAnimation.create = function (armature) {
616     var animation = new ccs.ArmatureAnimation();
617     if (animation && animation.init(armature)) {
618         return animation;
619     }
620     return null;
621 };