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