1 /****************************************************************************
  2  Copyright (c) 2011-2012 cocos2d-x.org
  3  Copyright (c) 2013-2014 Chukong Technologies Inc.
  4  Copyright (c) 2014 Shengxiang Chen (Nero Chan)
  5 
  6  http://www.cocos2d-x.org
  7 
  8  Permission is hereby granted, free of charge, to any person obtaining a copy
  9  of this software and associated documentation files (the "Software"), to deal
 10  in the Software without restriction, including without limitation the rights
 11  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 12  copies of the Software, and to permit persons to whom the Software is
 13  furnished to do so, subject to the following conditions:
 14 
 15  The above copyright notice and this permission notice shall be included in
 16  all copies or substantial portions of the Software.
 17 
 18  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 19  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 20  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 21  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 22  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 23  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 24  THE SOFTWARE.
 25  ****************************************************************************/
 26 
 27 /**
 28  * @ignore
 29  */
 30 sp._atlasPage_createTexture_webGL = function (self, path) {
 31     var texture = cc.textureCache.addImage(path);
 32     self.rendererObject = new cc.TextureAtlas(texture, 128);
 33     self.width = texture.getPixelsWide();
 34     self.height = texture.getPixelsHigh();
 35 };
 36 
 37 sp._atlasPage_createTexture_canvas = function(self, path) {
 38     self._texture = cc.textureCache.addImage(path);
 39 };
 40 
 41 sp._atlasPage_disposeTexture = function (self) {
 42     self.rendererObject.release();
 43 };
 44 
 45 sp._atlasLoader = {
 46     spAtlasFile:null,
 47     setAtlasFile:function(spAtlasFile){
 48         this.spAtlasFile = spAtlasFile;
 49     },
 50     load:function(page, line, spAtlas){
 51         var texturePath = cc.path.join(cc.path.dirname(this.spAtlasFile), line);
 52         if (cc._renderType === cc.game.RENDER_TYPE_WEBGL)
 53             sp._atlasPage_createTexture_webGL(page,texturePath);
 54         else
 55             sp._atlasPage_createTexture_canvas(page,texturePath);
 56     },
 57     unload:function(obj){
 58     }
 59 };
 60 
 61 /**
 62  * The event type of spine skeleton animation. It contains event types: START(0), END(1), COMPLETE(2), EVENT(3).
 63  * @constant
 64  * @type {{START: number, END: number, COMPLETE: number, EVENT: number}}
 65  */
 66 sp.ANIMATION_EVENT_TYPE = {
 67     START: 0,
 68     END: 1,
 69     COMPLETE: 2,
 70     EVENT: 3
 71 };
 72 
 73 sp.TrackEntryListeners = function(startListener, endListener, completeListener, eventListener){
 74     this.startListener = startListener || null;
 75     this.endListener = endListener || null;
 76     this.completeListener = completeListener || null;
 77     this.eventListener = eventListener || null;
 78 };
 79 
 80 sp.TrackEntryListeners.getListeners = function(entry){
 81     if(!entry.rendererObject){
 82         entry.rendererObject = new sp.TrackEntryListeners();
 83         entry.listener = sp.trackEntryCallback;
 84     }
 85     return entry.rendererObject;
 86 };
 87 
 88 sp.trackEntryCallback = function(state, trackIndex, type, event, loopCount) {
 89     state.rendererObject.onTrackEntryEvent(trackIndex, type, event, loopCount);
 90 };
 91 
 92 /**
 93  * The skeleton animation of spine. It updates animation's state and skeleton's world transform.
 94  * @class
 95  * @extends sp.Skeleton
 96  * @example
 97  * var spineBoy = new sp.SkeletonAnimation('res/skeletons/spineboy.json', 'res/skeletons/spineboy.atlas');
 98  * this.addChild(spineBoy, 4);
 99  */
100 sp.SkeletonAnimation = sp.Skeleton.extend(/** @lends sp.SkeletonAnimation# */{
101     _state: null,
102     _target: null,
103     _callback: null,
104 
105     _ownsAnimationStateData: false,
106     _startListener: null,
107     _endListener: null,
108     _completeListener: null,
109     _eventListener: null,
110 
111     /**
112      * Initializes a sp.SkeletonAnimation. please do not call this function by yourself, you should pass the parameters to constructor to initialize it.
113      * @override
114      */
115     init: function () {
116         sp.Skeleton.prototype.init.call(this);
117         this._ownsAnimationStateData = true;
118         this.setAnimationStateData(new spine.AnimationStateData(this._skeleton.data));
119     },
120 
121     /**
122      * Sets animation state data to sp.SkeletonAnimation.
123      * @param {spine.AnimationStateData} stateData
124      */
125     setAnimationStateData: function (stateData) {
126         var state = new spine.AnimationState(stateData);
127         state.rendererObject = this;
128         state.onStart = this._onAnimationStateStart.bind(this);
129         state.onComplete = this._onAnimationStateComplete.bind(this);
130         state.onEnd = this._onAnimationStateEnd.bind(this);
131         state.onEvent = this._onAnimationStateEvent.bind(this);
132         this._state = state;
133     },
134 
135     /**
136      * Mix applies all keyframe values, interpolated for the specified time and mixed with the current values.  <br/>
137      * @param {String} fromAnimation
138      * @param {String} toAnimation
139      * @param {Number} duration
140      */
141     setMix: function (fromAnimation, toAnimation, duration) {
142         this._state.data.setMixByName(fromAnimation, toAnimation, duration);
143     },
144 
145     /**
146      * Sets event listener of sp.SkeletonAnimation.
147      * @param {Object} target
148      * @param {Function} callback
149      */
150     setAnimationListener: function (target, callback) {
151         this._target = target;
152         this._callback = callback;
153     },
154 
155     /**
156      * Set the current animation. Any queued animations are cleared.
157      * @param {Number} trackIndex
158      * @param {String} name
159      * @param {Boolean} loop
160      * @returns {spine.TrackEntry|null}
161      */
162     setAnimation: function (trackIndex, name, loop) {
163         var animation = this._skeleton.data.findAnimation(name);
164         if (!animation) {
165             cc.log("Spine: Animation not found: " + name);
166             return null;
167         }
168         return this._state.setAnimation(trackIndex, animation, loop);
169     },
170 
171     /**
172      * Adds an animation to be played delay seconds after the current or last queued animation.
173      * @param {Number} trackIndex
174      * @param {String} name
175      * @param {Boolean} loop
176      * @param {Number} [delay=0]
177      * @returns {spine.TrackEntry|null}
178      */
179     addAnimation: function (trackIndex, name, loop, delay) {
180         delay = delay == null ? 0 : delay;
181         var animation = this._skeleton.data.findAnimation(name);
182         if (!animation) {
183             cc.log("Spine: Animation not found:" + name);
184             return null;
185         }
186         return this._state.addAnimation(trackIndex, animation, loop, delay);
187     },
188 
189     /**
190      * Returns track entry by trackIndex.
191      * @param trackIndex
192      * @returns {spine.TrackEntry|null}
193      */
194     getCurrent: function (trackIndex) {
195         return this._state.getCurrent(trackIndex);
196     },
197 
198     /**
199      * Clears all tracks of animation state.
200      */
201     clearTracks: function () {
202         this._state.clearTracks();
203     },
204 
205     /**
206      * Clears track of animation state by trackIndex.
207      * @param {Number} trackIndex
208      */
209     clearTrack: function (trackIndex) {
210         this._state.clearTrack(trackIndex);
211     },
212 
213     /**
214      * Update will be called automatically every frame if "scheduleUpdate" is called when the node is "live".
215      * It updates animation's state and skeleton's world transform.
216      * @param {Number} dt Delta time since last update
217      * @override
218      */
219     update: function (dt) {
220         this._super(dt);
221         dt *= this._timeScale;
222         this._state.update(dt);
223         this._state.apply(this._skeleton);
224         this._skeleton.updateWorldTransform();
225         this._renderCmd._updateChild();
226     },
227 
228     /**
229      * Set the start event listener.
230      * @param {function} listener
231      */
232     setStartListener: function(listener){
233         this._startListener = listener;
234     },
235 
236     /**
237      * Set the end event listener.
238      * @param {function} listener
239      */
240     setEndListener: function(listener) {
241         this._endListener = listener;
242     },
243 
244     setCompleteListener: function(listener) {
245         this._completeListener = listener;
246     },
247 
248     setEventListener: function(listener){
249         this._eventListener = listener;
250     },
251 
252     setTrackStartListener: function(entry, listener){
253         sp.TrackEntryListeners.getListeners(entry).startListener = listener;
254     },
255 
256     setTrackEndListener: function(entry, listener){
257         sp.TrackEntryListeners.getListeners(entry).endListener = listener;
258     },
259 
260     setTrackCompleteListener: function(entry, listener){
261         sp.TrackEntryListeners.getListeners(entry).completeListener = listener;
262     },
263 
264     setTrackEventListener: function(entry, listener){
265         sp.TrackEntryListeners.getListeners(entry).eventListener = listener;
266     },
267 
268     onTrackEntryEvent: function(traceIndex, type, event, loopCount){
269         var entry = this._state.getCurrent(traceIndex);
270         if(!entry.rendererObject)
271             return;
272         var listeners = entry.rendererObject;
273         switch (type){
274             case sp.ANIMATION_EVENT_TYPE.START:
275                 if(listeners.startListener)
276                     listeners.startListener(traceIndex);
277                 break;
278             case sp.ANIMATION_EVENT_TYPE.END:
279                 if(listeners.endListener)
280                     listeners.endListener(traceIndex);
281                 break;
282             case sp.ANIMATION_EVENT_TYPE.COMPLETE:
283                 if(listeners.completeListener)
284                     listeners.completeListener(traceIndex, loopCount);
285                 break;
286             case sp.ANIMATION_EVENT_TYPE.EVENT:
287                 if(listeners.eventListener)
288                     listeners.eventListener(traceIndex, event);
289                 break;
290         }
291     },
292 
293     onAnimationStateEvent: function(trackIndex, type, event, loopCount) {
294         switch(type){
295             case sp.ANIMATION_EVENT_TYPE.START:
296                 if(this._startListener)
297                     this._startListener(trackIndex);
298                 break;
299             case sp.ANIMATION_EVENT_TYPE.END:
300                 if(this._endListener)
301                     this._endListener(trackIndex);
302                 break;
303             case sp.ANIMATION_EVENT_TYPE.COMPLETE:
304                 if(this._completeListener)
305                     this._completeListener(trackIndex, loopCount);
306                 break;
307             case sp.ANIMATION_EVENT_TYPE.EVENT:
308                 if(this._eventListener)
309                     this._eventListener(trackIndex, event);
310                 break;
311         }
312     },
313 
314     getState: function(){
315         return this._state;
316     },
317 
318     _onAnimationStateStart: function (trackIndex) {
319         this._animationStateCallback(trackIndex, sp.ANIMATION_EVENT_TYPE.START, null, 0);
320     },
321     _onAnimationStateEnd: function (trackIndex) {
322         this._animationStateCallback(trackIndex, sp.ANIMATION_EVENT_TYPE.END, null, 0);
323     },
324     _onAnimationStateComplete: function (trackIndex, count) {
325         this._animationStateCallback(trackIndex, sp.ANIMATION_EVENT_TYPE.COMPLETE, null, count);
326     },
327     _onAnimationStateEvent: function (trackIndex, event) {
328         this._animationStateCallback(trackIndex, sp.ANIMATION_EVENT_TYPE.EVENT, event, 0);
329     },
330     _animationStateCallback: function (trackIndex, type, event, loopCount) {
331         this.onAnimationStateEvent(trackIndex, type, event, loopCount);
332         if (this._target && this._callback) {
333             this._callback.call(this._target, this, trackIndex, type, event, loopCount)
334         }
335     }
336 });
337 
338 /**
339  * Creates a skeleton animation object.
340  * @deprecated since v3.0, please use new sp.SkeletonAnimation(skeletonDataFile, atlasFile, scale) instead.
341  * @param {spine.SkeletonData|String} skeletonDataFile
342  * @param {String|spine.Atlas|spine.SkeletonData} atlasFile atlas filename or atlas data or owns SkeletonData
343  * @param {Number} [scale] scale can be specified on the JSON or binary loader which will scale the bone positions, image sizes, and animation translations.
344  * @returns {sp.Skeleton}
345  */
346 sp.SkeletonAnimation.create = function (skeletonDataFile, atlasFile/* or atlas*/, scale) {
347     return new sp.SkeletonAnimation(skeletonDataFile, atlasFile, scale);
348 };