1 /****************************************************************************
  2  Copyright (c) 2013-2014 Chukong Technologies Inc.
  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 /**
 27  * ActionTimelineData
 28  * @name ccs.ActionTimelineData
 29  * @extend ccs.Class
 30  * @class
 31  *
 32  */
 33 ccs.ActionTimelineData = ccs.Class.extend({
 34 
 35     _actionTag: 0,
 36 
 37     ctor: function(actionTag){
 38         this._init(actionTag);
 39     },
 40 
 41     _init: function(actionTag){
 42         this._actionTag = actionTag;
 43         return true;
 44     },
 45 
 46     /**
 47      * Set the action tag.
 48      * @param {number} actionTag
 49      */
 50     setActionTag: function(actionTag){
 51         this._actionTag = actionTag;
 52     },
 53 
 54     /**
 55      * Gets the action tag.
 56      */
 57     getActionTag: function(){
 58         return this._actionTag;
 59     }
 60 
 61 });
 62 
 63 ccs.AnimationInfo = function(name, start, end){
 64     this.name = name;
 65     this.startIndex = start;
 66     this.endIndex = end;
 67 };
 68 
 69 ccs.ComExtensionData = ccs.Component.extend({
 70 
 71     _customProperty: null,
 72     _timelineData: null,
 73     _name: "ComExtensionData",
 74 
 75     ctor: function(){
 76         this._customProperty = "";
 77         this._timelineData = new ccs.ActionTimelineData(0);
 78         return true;
 79     },
 80 
 81     setActionTag: function(actionTag){
 82         this._timelineData.setActionTag(actionTag);
 83     },
 84 
 85     getActionTag: function(){
 86         return this._timelineData.getActionTag();
 87     },
 88 
 89     setCustomProperty: function(customProperty){
 90         this._customProperty = customProperty;
 91     },
 92 
 93     getCustomProperty: function(){
 94         return this._customProperty;
 95     }
 96 
 97 });
 98 
 99 ccs.ComExtensionData.create = function(){
100     return new ccs.ComExtensionData();
101 };
102 
103 /**
104  * Create new ActionTimelineData.
105  *
106  * @deprecated v3.0, please use new ccs.ActionTimelineData() instead.
107  *
108  * @name ccs.ActionTimelineData.create
109  * @function
110  * @param actionTag
111  * @returns {ccs.ActionTimelineData}
112  */
113 ccs.ActionTimelineData.create = function(actionTag){
114     return new ccs.ActionTimelineData(actionTag);
115 };
116 
117 
118 /**
119  * ActionTimeline
120  * @class
121  * @extend cc.Action
122  *
123  * @property gotoFrameAndPlay
124  * @property gotoFrameAndPause
125  */
126 ccs.ActionTimeline = cc.Action.extend({
127 
128     _timelineMap: null,
129     _timelineList: null,
130     _duration: 0,
131     _time: null,
132     _timeSpeed: 1,
133     _frameInternal: 1/60,
134     _playing: false,
135     _currentFrame: 0,
136     _startFrame: 0,
137     _endFrame: 0,
138     _loop: null,
139     _frameEventListener: null,
140     _animationInfos: null,
141     _lastFrameListener: null,
142 
143     ctor: function(){
144         cc.Action.prototype.ctor.call(this);
145         this._timelineMap = {};
146         this._timelineList = [];
147         this._animationInfos = {};
148         this.init();
149     },
150 
151     _gotoFrame: function(frameIndex){
152         var size = this._timelineList.length;
153         for(var i = 0; i < size; i++)
154         {
155             this._timelineList[i]._gotoFrame(frameIndex);
156         }
157     },
158 
159     _stepToFrame: function(frameIndex){
160         var size = this._timelineList.length;
161         for(var i = 0; i < size; i++){
162             this._timelineList[i]._stepToFrame(frameIndex);
163         }
164     },
165 
166     //emit frame event, call it when enter a frame
167     _emitFrameEvent: function(frame){
168         if(this._frameEventListener){
169             this._frameEventListener(frame);
170         }
171     },
172 
173     init: function(){
174         return true;
175     },
176 
177     /**
178      * Goto the specified frame index, and start playing from this index.
179      * @param startIndex The animation will play from this index.
180      * @param [endIndex=] The animation will end at this index.
181      * @param [currentFrameIndex=] set current frame index.
182      * @param [loop=] Whether or not the animation need loop.
183      */
184     gotoFrameAndPlay: function(startIndex, endIndex, currentFrameIndex, loop){
185         //Consolidation parameters
186         var i = 0,
187             argLen = arguments.length;
188         var num = [],
189             bool;
190         for(i; i<argLen; i++){
191             if(typeof arguments[i] === "boolean"){
192                 bool = arguments[i];
193             }else{
194                 num.push(arguments[i]);
195             }
196         }
197         startIndex = num[0];
198         endIndex = num[1] !== undefined ? num[1] : this._duration;
199         currentFrameIndex = num[2] || startIndex;
200         loop = bool!=null ? bool : true;
201 
202         this._startFrame = startIndex;
203         this._endFrame = endIndex;
204         this._currentFrame = currentFrameIndex;
205         this._loop = loop;
206         this._time = this._currentFrame * this._frameInternal;
207 
208         this.resume();
209         this._gotoFrame(this._currentFrame);
210     },
211 
212     /**
213      * Goto the specified frame index, and pause at this index.
214      * @param startIndex The animation will pause at this index.
215      */
216     gotoFrameAndPause: function(startIndex){
217         this._startFrame = this._currentFrame = startIndex;
218         this._time       = this._currentFrame * this._frameInternal;
219 
220         this.pause();
221         this._gotoFrame(this._currentFrame);
222     },
223 
224     /**
225      * Pause the animation.
226      */
227     pause: function(){
228         this._playing = false;
229     },
230 
231     /**
232      * Resume the animation.
233      */
234     resume: function(){
235         this._playing = true;
236     },
237 
238     /**
239      * Whether or not Action is playing.
240      */
241     isPlaying: function(){
242         return this._playing;
243     },
244 
245     /**
246      * Set the animation speed, this will speed up or slow down the speed.
247      * @param {number} speed
248      */
249     setTimeSpeed: function(speed){
250         this._timeSpeed = speed;
251     },
252 
253     /**
254      * Get current animation speed.
255      * @returns {number}
256      */
257     getTimeSpeed: function(){
258         return this._timeSpeed;
259     },
260 
261     /**
262      * duration of the whole action
263      * @param {number} duration
264      */
265     setDuration: function(duration){
266         this._duration = duration;
267     },
268 
269     /**
270      * Get current animation duration.
271      * @returns {number}
272      */
273     getDuration: function(){
274         return this._duration;
275     },
276 
277     /**
278      * Start frame index of this action
279      * @returns {number}
280      */
281     getStartFrame: function(){
282         return this._startFrame;
283     },
284 
285     /**
286      * End frame of this action.
287      * When action play to this frame, if action is not loop, then it will stop,
288      * or it will play from start frame again.
289      * @returns {number}
290      */
291     getEndFrame: function(){
292         return this._endFrame;
293     },
294 
295     /**
296      * Set current frame index, this will cause action plays to this frame.
297      */
298     setCurrentFrame: function(frameIndex){
299         if (frameIndex >= this._startFrame && frameIndex <= this._endFrame){
300             this._currentFrame = frameIndex;
301             this._time = this._currentFrame * this._frameInternal;
302         }else{
303             cc.log("frame index is not between start frame and end frame");
304         }
305 
306     },
307 
308     /**
309      * Get current frame.
310      * @returns {number}
311      */
312     getCurrentFrame: function(){
313         return this._currentFrame;
314     },
315 
316     /**
317      * add Timeline to ActionTimeline
318      * @param {ccs.Timeline} timeline
319      */
320     addTimeline: function(timeline){
321         var tag = timeline.getActionTag();
322         if (!this._timelineMap[tag]) {
323             this._timelineMap[tag] = [];
324         }
325 
326         if (this._timelineMap[tag].indexOf(timeline) === -1) {
327             this._timelineList.push(timeline);
328             this._timelineMap[tag].push(timeline);
329             timeline.setActionTimeline(this);
330         }
331 
332     },
333 
334     /**
335      * remove Timeline to ActionTimeline
336      * @param {ccs.Timeline} timeline
337      */
338     removeTimeline: function(timeline){
339         var tag = timeline.getActionTag();
340         if (this._timelineMap[tag]) {
341             if(this._timelineMap[tag].some(function(item){
342                 if(item === timeline)
343                     return true;
344             })) {
345                 cc.arrayRemoveObject(this._timelineMap[tag], timeline);
346                 cc.arrayRemoveObject(this._timelineList, timeline);
347                 timeline.setActionTimeline(null);
348             }
349         }
350     },
351 
352     /**
353      * Gets the timeline list
354      * @returns {array | null}
355      */
356     getTimelines: function(){
357         return this._timelineList;
358     },
359 
360     /**
361      * Set the Frame event
362      * @param {function} listener
363      */
364     setFrameEventCallFunc: function(listener){
365         this._frameEventListener = listener;
366     },
367 
368     /**
369      * remove event
370      */
371     clearFrameEventCallFunc: function(){
372         this._frameEventListener = null;
373     },
374 
375     /**
376      * Clone this timeline
377      * @returns {ccs.ActionTimeline}
378      */
379     clone: function(){
380         var newAction = new ccs.ActionTimeline();
381         newAction.setDuration(this._duration);
382         newAction.setTimeSpeed(this._timeSpeed);
383 
384         for (var a in this._timelineMap){
385             var timelines = this._timelineMap[a];
386             for(var b in timelines)
387             {
388                 var timeline = timelines[b];
389                 var newTimeline = timeline.clone();
390                 newAction.addTimeline(newTimeline);
391             }
392         }
393 
394         return newAction;
395 
396     },
397 
398     /**
399      * Reverse is not defined;
400      * @returns {null}
401      */
402     reverse: function(){
403         return null;
404     },
405 
406     /**
407      * Stepping of this time line.
408      * @param {number} delta
409      */
410     step: function(delta){
411         if (!this._playing || this._timelineMap.length === 0 || this._duration === 0)
412         {
413             return;
414         }
415 
416         this._time += delta * this._timeSpeed;
417         var endoffset = this._time - this._endFrame * this._frameInternal;
418 
419         if(endoffset < this._frameInternal){
420             this._currentFrame = Math.floor(this._time / this._frameInternal);
421             this._stepToFrame(this._currentFrame);
422             if(endoffset >= 0 && this._lastFrameListener)
423                 this._lastFrameListener();
424         }else{
425             this._playing = this._loop;
426             if(!this._playing){
427                 this._time = this._endFrame * this._frameInternal;
428                 if (this._currentFrame != this._endFrame){
429                     this._currentFrame = this._endFrame;
430                     this._stepToFrame(this._currentFrame);
431                     if(this._lastFrameListener)
432                         this._lastFrameListener();
433                 }
434             }else
435                 this.gotoFrameAndPlay(this._startFrame, this._endFrame, this._loop);
436         }
437 
438     },
439 
440     _foreachNodeDescendant: function(parent, callback){
441         callback(parent);
442 
443         var children = parent.getChildren();
444         for (var i=0; i<children.length; i++)
445         {
446             var child = children[i];
447             this._foreachNodeDescendant(child, callback);
448         }
449     },
450 
451     /**
452      * start with node.
453      * @param {cc.Node} target
454      */
455     startWithTarget: function(target){
456         cc.Action.prototype.startWithTarget.call(this, target);
457 
458         var self = this;
459         var callback = function(child){
460             var data = child.getComponent("ComExtensionData");
461 
462             if(data) {
463                 var actionTag = data.getActionTag();
464                 if(self._timelineMap[actionTag]) {
465                     var timelines = self._timelineMap[actionTag];
466                     for (var i=0; i<timelines.length; i++) {
467                         var timeline = timelines[i];
468                         timeline.setNode(child);
469                     }
470                 }
471             }
472         };
473 
474         this._foreachNodeDescendant(target, callback);
475     },
476 
477     /**
478      * Whether or not complete
479      * @returns {boolean}
480      */
481     isDone: function(){
482         return false;
483     },
484 
485     /**
486      * @param {String} name
487      * @param {Boolean} loop
488      */
489     play: function(name, loop){
490         var info = this._animationInfos[name];
491         if (!info)
492             return cc.log("Can't find animation info for %s", name);
493 
494         this.gotoFrameAndPlay(info.startIndex, info.endIndex, loop);
495     },
496 
497     /**
498      * Add animationInfo
499      * @param {Object} info
500      */
501     addAnimationInfo: function(info){
502         this._animationInfos[info.name] = info;
503     },
504 
505     /**
506      * Remove animationInfo
507      * @param {String} name
508      */
509     removeAnimationInfo: function(name){
510         delete this._animationInfos[name];
511     },
512 
513     isAnimationInfoExists: function(name){
514         return this._animationInfos[name];
515     },
516 
517     getAnimationInfo: function(name){
518         return this._animationInfos[name];
519     },
520 
521     setLastFrameCallFunc: function(listener){
522         this._lastFrameListener = listener;
523     },
524 
525     clearLastFrameCallFunc: function(){
526         this._lastFrameListener = null;
527     }
528 });
529 
530 /**
531  * create new ActionTimeline
532  *
533  * @deprecated v3.0, please use new ccs.ActionTimeline() instead.
534  *
535  * @name ccs.ActionTimeline.create
536  * @function
537  * @returns {ccs.ActionTimeline}
538  */
539 ccs.ActionTimeline.create = function(){
540     return new ccs.ActionTimeline();
541 };
542