1 /****************************************************************************
  2  Copyright (c) 2010-2012 cocos2d-x.org
  3 
  4  http://www.cocos2d-x.org
  5 
  6  Permission is hereby granted, free of charge, to any person obtaining a copy
  7  of this software and associated documentation files (the "Software"), to deal
  8  in the Software without restriction, including without limitation the rights
  9  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 10  copies of the Software, and to permit persons to whom the Software is
 11  furnished to do so, subject to the following conditions:
 12 
 13  The above copyright notice and this permission notice shall be included in
 14  all copies or substantial portions of the Software.
 15 
 16  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 17  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 18  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 19  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 20  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 21  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 22  THE SOFTWARE.
 23  ****************************************************************************/
 24 
 25 /**
 26  * movement event type
 27  * @type {Object}
 28  */
 29 ccs.MovementEventType = {
 30     start: 0,
 31     complete: 1,
 32     loopComplete: 2
 33 };
 34 /**
 35  * Base class for cc.MovementEvent objects.
 36  * @class
 37  * @extends ccs.Class
 38  */
 39 ccs.AnimationEvent = ccs.Class.extend({
 40     _arguments:null,
 41     _callFunc:null,
 42     _selectorTarget:null,
 43     ctor:function (target, callFunc, data) {
 44         this._data = data;
 45         this._callFunc = callFunc;
 46         this._selectorTarget = target;
 47     },
 48     call:function () {
 49         if (this._callFunc) {
 50             this._callFunc.apply(this._selectorTarget, this._arguments);
 51         }
 52     },
 53     setArguments:function (args) {
 54         this._arguments = args;
 55     }
 56 });
 57 /**
 58  * movement event
 59  * @constructor
 60  */
 61 ccs.MovementEvent = function () {
 62     this.armature = null;
 63     this.movementType = "";
 64     this.movementID = "";
 65 };
 66 /**
 67  * frame event
 68  * @constructor
 69  */
 70 ccs.FrameEvent = function () {
 71     this.bone = null;
 72     this.frameEventName = "";
 73     this.originFrameIndex = 0;
 74     this.currentFrameIndex = 0;
 75 };
 76 /**
 77  * Base class for ccs.ArmatureAnimation objects.
 78  * @class
 79  * @extends ccs.ProcessBase
 80  */
 81 ccs.ArmatureAnimation = ccs.ProcessBase.extend(/** @lends ccs.ArmatureAnimation# */{
 82     _animationData:null,
 83     _movementData:null,
 84     _armature:null,
 85     _movementID:"",
 86     _prevFrameIndex:0,
 87     _toIndex:0,
 88     _tweenList:null,
 89     _frameEvent:null,
 90     _movementEvent:null,
 91     _speedScale:1,
 92     _ignoreFrameEvent:false,
 93     _frameEventQueue:null,
 94     _movementEventQueue:null,
 95     _userObject:null,
 96     _movementList: null,
 97     _onMovementList: false,
 98     _movementListLoop: false,
 99     _movementIndex: 0,
100     ctor:function () {
101         ccs.ProcessBase.prototype.ctor.call(this);
102         this._animationData = null;
103         this._movementData = null;
104         this._movementID = "";
105         this._armature = null;
106         this._prevFrameIndex = 0;
107         this._toIndex = 0;
108         this._tweenList = [];
109         this._frameEvent = null;
110         this._movementEvent = null;
111         this._speedScale = 1;
112         this._ignoreFrameEvent = false;
113         this._frameEventQueue = [];
114         this._movementEventQueue = [];
115         this._userObject = null;
116         this._movementList = [];
117         this._onMovementList = false;
118         this._movementListLoop = false;
119         this._movementIndex = 0;
120     },
121 
122     /**
123      * init with a CCArmature
124      * @param {ccs.Armature} armature
125      * @return {Boolean}
126      */
127     init:function (armature) {
128         this._armature = armature;
129         this._tweenList = [];
130         return true;
131     },
132     pause:function () {
133         for (var i = 0; i < this._tweenList.length; i++) {
134             this._tweenList[i].pause();
135         }
136         ccs.ProcessBase.prototype.pause.call(this);
137     },
138     resume:function () {
139         for (var i = 0; i < this._tweenList.length; i++) {
140             this._tweenList[i].resume();
141         }
142         ccs.ProcessBase.prototype.resume.call(this);
143     },
144 
145     stop:function () {
146         for (var i = 0; i < this._tweenList.length; i++) {
147             this._tweenList[i].stop();
148         }
149         this._tweenList = [];
150         ccs.ProcessBase.prototype.stop.call(this);
151     },
152 
153     /**
154      * scale animation play speed
155      * @param {Number} speedScale
156      */
157     setSpeedScale:function (speedScale) {
158         if (speedScale == this._speedScale) {
159             return;
160         }
161         this._speedScale = speedScale;
162         this._processScale = !this._movementData ? this._speedScale : this._speedScale * this._movementData.scale;
163         var dict = this._armature.getBoneDic();
164         for (var key in dict) {
165             var bone = dict[key];
166             bone.getTween().setProcessScale(this._processScale);
167             if (bone.getChildArmature()) {
168                 bone.getChildArmature().getAnimation().setProcessScale(this._processScale);
169             }
170         }
171     },
172 
173     getSpeedScale:function(){
174         return this._speedScale;
175     },
176 
177     getAnimationScale:function(){
178         return this.getSpeedScale();
179     },
180     setAnimationScale:function(animationScale){
181         return this.setSpeedScale(animationScale);
182     },
183 
184     /**
185      * play animation by animation name.
186      * @param {String} animationName The animation name you want to play
187      * @param {Number} durationTo
188      *         he frames between two animation changing-over.It's meaning is changing to this animation need how many frames
189      *         -1 : use the value from CCMovementData get from flash design panel
190      * @param {Number} durationTween he
191      *         frame count you want to play in the game.if  _durationTween is 80, then the animation will played 80 frames in a loop
192      *         -1 : use the value from CCMovementData get from flash design panel
193      * @param {Number} loop
194      *          Whether the animation is loop.
195      *         loop < 0 : use the value from CCMovementData get from flash design panel
196      *         loop = 0 : this animation is not loop
197      *         loop > 0 : this animation is loop
198      * @param {Number} tweenEasing
199      *          CCTween easing is used for calculate easing effect
200      *         TWEEN_EASING_MAX : use the value from CCMovementData get from flash design panel
201      *         -1 : fade out
202      *         0  : line
203      *         1  : fade in
204      *         2  : fade in and out
205      * @example
206      * // example
207      * armature.getAnimation().play("run",-1,-1,1,-1);//loop play
208      * armature.getAnimation().play("run",-1,-1,0,-1);//not loop play
209      */
210     play:function (animationName, durationTo, durationTween, loop, tweenEasing) {
211         if (this._animationData == null) {
212             cc.log("this._animationData can not be null");
213             return;
214         }
215         this._movementData = this._animationData.getMovement(animationName);
216         if (this._movementData == null) {
217             cc.log("this._movementData can not be null");
218             return;
219         }
220         if (typeof durationTo == "undefined") {
221             durationTo = -1;
222         }
223 
224         if (typeof loop == "undefined") {
225             loop = -1;
226         }
227 
228         var locMovementData = this._movementData;
229         //Get key frame count
230         this._rawDuration = locMovementData.duration;
231         this._movementID = animationName;
232         this._processScale = this._speedScale * locMovementData.scale;
233         //Further processing parameters
234         durationTo = (durationTo == -1) ? locMovementData.durationTo : durationTo;
235         var durationTween = locMovementData.durationTween;
236         durationTween = (durationTween == 0) ? locMovementData.duration : durationTween;//todo
237         var tweenEasing = locMovementData.tweenEasing;
238 
239         if (loop < 0) {
240             loop = locMovementData.loop;
241         } else {
242             loop = Boolean(loop);
243         }
244 
245         this._onMovementList = false;
246         ccs.ProcessBase.prototype.play.call(this, durationTo, tweenEasing);
247 
248         if (this._rawDuration == 0) {
249             this._loopType = CC_ANIMATION_TYPE_SINGLE_FRAME;
250         }
251         else {
252             if (loop) {
253                 this._loopType = CC_ANIMATION_TYPE_TO_LOOP_FRONT;
254             }
255             else {
256                 this._loopType = CC_ANIMATION_TYPE_NO_LOOP;
257             }
258             this._durationTween = durationTween;
259         }
260 
261         this._tweenList = [];
262 
263         var movementBoneData;
264         var dict = this._armature.getBoneDic();
265         for (var key in dict) {
266             var bone = dict[key];
267             movementBoneData = locMovementData.getMovementBoneData(bone.getName());
268             var tween = bone.getTween();
269             if (movementBoneData && movementBoneData.frameList.length > 0) {
270                 this._tweenList.push(tween);
271                 movementBoneData.duration = locMovementData.duration;
272                 tween.play(movementBoneData, durationTo, durationTween, loop, tweenEasing);
273 
274                 tween.setProcessScale(this._processScale);
275                 if (bone.getChildArmature()) {
276                     bone.getChildArmature().getAnimation().setProcessScale(this._processScale);
277                 }
278             } else {
279                 if (!bone.getIgnoreMovementBoneData()) {
280                     bone.getDisplayManager().changeDisplayWithIndex(-1, false);
281                     tween.stop();
282                 }
283             }
284         }
285         this._armature.update(0);
286     },
287 
288     /**
289      * play with names
290      * @param {Array} movementNames
291      * @param {Number} durationTo
292      * @param {Boolean} loop
293      */
294     playWithNames: function (movementNames, durationTo, loop) {
295         this._movementList = [];
296         this._movementListLoop = loop;
297         this._onMovementList = true;
298         this._movementIndex = 0;
299 
300         for (var i = 0; i < movementNames.length; i++) {
301             this._movementList.push({name: movementNames[i], durationTo: durationTo});
302         }
303 
304         this.updateMovementList();
305     },
306 
307     updateMovementList: function () {
308         if (this._onMovementList) {
309             if (this._movementListLoop) {
310                 var movementObj = this._movementList[this._movementIndex];
311                 this.play(movementObj.name, movementObj.durationTo,-1,0);
312                 this._movementIndex++;
313                 if (this._movementIndex >= this._movementList.length) {
314                     this._movementIndex = 0;
315                 }
316             }
317             else {
318                 if (this._movementIndex < this._movementList.length) {
319                     var movementObj = this._movementList[this._movementIndex];
320                     this.play(movementObj.name, movementObj.durationTo,-1,0);
321                     this._movementIndex++;
322                 }
323                 else {
324                     this._onMovementList = false;
325                 }
326             }
327             this._onMovementList = true;
328         }
329     },
330     
331     /**
332      * Go to specified frame and play current movement.
333      * You need first switch to the movement you want to play, then call this function.
334      *
335      * example : playByIndex(0);
336      *           gotoAndPlay(0);
337      *           playByIndex(1);
338      *           gotoAndPlay(0);
339      *           gotoAndPlay(15);
340      * @param {Number} frameIndex
341      */
342     gotoAndPlay: function (frameIndex) {
343         if (!this._movementData || frameIndex < 0 || frameIndex >= this._movementData.duration) {
344             cc.log("Please ensure you have played a movement, and the frameIndex is in the range.");
345             return;
346         }
347 
348         var ignoreFrameEvent = this._ignoreFrameEvent;
349         this._ignoreFrameEvent = true;
350         this._isPlaying = true;
351         this._isComplete = this._isPause = false;
352 
353         ccs.ProcessBase.prototype.gotoFrame.call(this, frameIndex);
354         this._currentPercent = this._curFrameIndex / (this._movementData.duration - 1);
355         this._currentFrame = this._nextFrameIndex * this._currentPercent;
356 
357         for (var i = 0; i < this._tweenList.length; i++) {
358             var tween = this._tweenList[i];
359             tween.gotoAndPlay(frameIndex);
360         }
361         this._armature.update(0);
362         this._ignoreFrameEvent = ignoreFrameEvent;
363     },
364 
365     /**
366      * Go to specified frame and pause current movement.
367      * @param {Number} frameIndex
368      */
369     gotoAndPause: function (frameIndex) {
370         this.gotoAndPlay(frameIndex);
371         this.pause();
372     },
373 
374     /**
375      * Play animation with index, the other param is the same to play.
376      * @param {Number||Array} animationIndex
377      * @param {Number} durationTo
378      * @param {Number} durationTween
379      * @param {Number} loop
380      * @param {Number} tweenEasing
381      */
382     playWithIndex:function (animationIndex, durationTo, durationTween, loop, tweenEasing) {
383         if (typeof durationTo == "undefined") {
384             durationTo = -1;
385         }
386         if (typeof loop == "undefined") {
387             loop = -1;
388         }
389         var moveNames = this._animationData.movementNames;
390         if (animationIndex < -1 || animationIndex >= moveNames.length) {
391             return;
392         }
393         var animationName = moveNames[animationIndex];
394         this.play(animationName, durationTo,-1, loop, 0);
395     },
396 
397     /**
398      * Play animation with index, the o ther param is the same to play.
399      * @param {Number} animationIndex
400      * @param {Number} durationTo
401      * @param {Number} durationTween
402      * @param {Number} loop
403      * @param {Number} tweenEasing
404      */
405     playByIndex:function(animationIndex, durationTo, durationTween, loop, tweenEasing){
406         cc.log("playByIndex is deprecated. Use playWithIndex instead.");
407         this.playWithIndex(animationIndex, durationTo, durationTween, loop, tweenEasing);
408     },
409 
410     /**
411      *  play by indexes
412      * @param movementIndexes
413      * @param {Number} durationTo
414      * @param {Boolean} loop
415      */
416     playWithIndexes: function (movementIndexes, durationTo, loop) {
417         this._movementList = [];
418         this._movementListLoop = loop;
419         this._onMovementList = true;
420         this._movementIndex = 0;
421 
422         var movName = this._animationData.movementNames;
423 
424         for (var i = 0; i < movementIndexes.length; i++) {
425             var name = movName[movementIndexes[i]];
426             this._movementList.push({name: name, durationTo: durationTo});
427         }
428 
429         this.updateMovementList();
430     },
431 
432     /**
433      * get movement count
434      * @return {Number}
435      */
436     getMovementCount:function () {
437         return this._animationData.getMovementCount();
438     },
439 
440     update:function (dt) {
441         if(ccs.ProcessBase.prototype.update.call(this, dt)){
442             for (var i = 0; i < this._tweenList.length; i++) {
443                 this._tweenList[i].update(dt);
444             }
445         }
446 
447         var frameEvents = this._frameEventQueue;
448         while (frameEvents.length > 0) {
449             var frameEvent = frameEvents.shift();
450             this._ignoreFrameEvent = true;
451             this.callFrameEvent([frameEvent.bone, frameEvent.frameEventName, frameEvent.originFrameIndex, frameEvent.currentFrameIndex]);
452             this._ignoreFrameEvent = false;
453         }
454 
455         var movementEvents = this._movementEventQueue;
456         while (movementEvents.length > 0) {
457             var movEvent = movementEvents.shift();
458             this.callMovementEvent([movEvent.armature, movEvent.movementType, movEvent.movementID]);
459         }
460     },
461 
462     /**
463      * update will call this handler, you can handle your logic here
464      */
465     updateHandler:function () {
466         var locCurrentPercent = this._currentPercent;
467         if (locCurrentPercent >= 1) {
468             switch (this._loopType) {
469                 case CC_ANIMATION_TYPE_NO_LOOP:
470                     this._loopType = CC_ANIMATION_TYPE_MAX;
471                     this._currentFrame = (locCurrentPercent - 1) * this._nextFrameIndex;
472                     locCurrentPercent = this._currentFrame / this._durationTween;
473                     if (locCurrentPercent < 1.0) {
474                         this._nextFrameIndex = this._durationTween;
475                         this.movementEvent(this._armature, ccs.MovementEventType.start, this._movementID);
476                         break;
477                     }
478                 case CC_ANIMATION_TYPE_MAX:
479                 case CC_ANIMATION_TYPE_SINGLE_FRAME:
480                     locCurrentPercent = 1;
481                     this._isComplete = true;
482                     this._isPlaying = false;
483                     this.movementEvent(this._armature, ccs.MovementEventType.complete, this._movementID);
484                     this.updateMovementList();
485                     break;
486                 case CC_ANIMATION_TYPE_TO_LOOP_FRONT:
487                     this._loopType = CC_ANIMATION_TYPE_LOOP_FRONT;
488                     locCurrentPercent = ccs.fmodf(locCurrentPercent, 1);
489                     this._currentFrame = this._nextFrameIndex == 0 ? 0 : ccs.fmodf(this._currentFrame, this._nextFrameIndex);
490                     this._nextFrameIndex = this._durationTween > 0 ? this._durationTween : 1;
491                     this.movementEvent(this, ccs.MovementEventType.start, this._movementID);
492                     break;
493                 default:
494                     //locCurrentPercent = ccs.fmodf(locCurrentPercent, 1);
495                     this._currentFrame = ccs.fmodf(this._currentFrame, this._nextFrameIndex);
496                     this._toIndex = 0;
497                     this.movementEvent(this._armature, ccs.MovementEventType.loopComplete, this._movementID);
498                     break;
499             }
500             this._currentPercent = locCurrentPercent;
501         }
502     },
503 
504     /**
505      * Get current movementID
506      * @returns {String}
507      */
508     getCurrentMovementID: function () {
509         if (this._isComplete)
510             return "";
511         return this._movementID;
512     },
513 
514     /**
515      * connect a event
516      * @param {Object} target
517      * @param {function} callFunc
518      */
519     setMovementEventCallFunc:function (callFunc, target) {
520         this._movementEvent = new ccs.AnimationEvent(target, callFunc);
521     },
522 
523     /**
524      * call event
525      * @param {Array} args
526      */
527     callMovementEvent:function (args) {
528         if (this._movementEvent) {
529             this._movementEvent.setArguments(args);
530             this._movementEvent.call();
531         }
532     },
533 
534     /**
535      * connect a event
536      * @param {Object} target
537      * @param {function} callFunc
538      */
539     setFrameEventCallFunc:function (callFunc, target) {
540         this._frameEvent = new ccs.AnimationEvent(target, callFunc);
541     },
542 
543     /**
544      * call event
545      * @param {Array} args
546      */
547     callFrameEvent:function (args) {
548         if (this._frameEvent) {
549             this._frameEvent.setArguments(args);
550             this._frameEvent.call();
551         }
552     },
553 
554     movementEvent:function(armature, movementType,  movementID){
555         if (this._movementEvent)    {
556             var event = new ccs.MovementEvent();
557             event.armature = armature;
558             event.movementType = movementType;
559             event.movementID = movementID;
560             this._movementEventQueue.push(event);
561         }
562     },
563 
564     /**
565      * @param {ccs.Bone} bone
566      * @param {String} frameEventName
567      * @param {Number} originFrameIndex
568      * @param {Number} currentFrameIndex
569      */
570     frameEvent:function(bone, frameEventName,  originFrameIndex,  currentFrameIndex){
571         if (this._frameEvent)    {
572             var frameEvent = new ccs.FrameEvent();
573             frameEvent.bone = bone;
574             frameEvent.frameEventName = frameEventName;
575             frameEvent.originFrameIndex = originFrameIndex;
576             frameEvent.currentFrameIndex = currentFrameIndex;
577             this._frameEventQueue.push(frameEvent);
578         }
579     },
580     
581     /**
582      * animationData setter
583      * @param {ccs.AnimationData} aniData
584      */
585     setAnimationData:function (aniData) {
586         this._animationData = aniData;
587     },
588 
589     /**
590      * animationData getter
591      * @return {ccs.AnimationData}
592      */
593     getAnimationData:function () {
594         return this._animationData;
595     },
596     /**
597      * userObject setter
598      * @param {Object} userObject
599      */
600     setUserObject:function (userObject) {
601         this._userObject = userObject;
602     },
603 
604     /**
605      * userObject getter
606      * @return {Object}
607      */
608     getUserObject:function () {
609         return this._userObject;
610     },
611 
612     /**
613      * Determines if the frame event is ignore
614      * @returns {boolean}
615      */
616     isIgnoreFrameEvent:function(){
617         return this._ignoreFrameEvent;
618     },
619 
620     /**
621      * Sets whether the frame event is ignore
622      * @param {Boolean} bool
623      */
624     setIgnoreFrameEvent:function(bool){
625         this._ignoreFrameEvent = bool;
626     }
627 });
628 
629 /**
630  * allocates and initializes a ArmatureAnimation.
631  * @constructs
632  * @return {ccs.ArmatureAnimation}
633  * @example
634  * // example
635  * var animation = ccs.ArmatureAnimation.create();
636  */
637 ccs.ArmatureAnimation.create = function (armature) {
638     var animation = new ccs.ArmatureAnimation();
639     if (animation && animation.init(armature)) {
640         return animation;
641     }
642     return null;
643 };