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