1 /**
  2  * Hilo
  3  * Copyright 2015 alibaba.com
  4  * Licensed under the MIT License
  5  */
  6 
  7 /**
  8  * @language=zh
  9  * <iframe src='../../../examples/Sprite.html?noHeader' width = '550' height = '400' scrolling='no'></iframe>
 10  * <br/>
 11  * @class 动画精灵类。
 12  * @augments View
 13  * @module hilo/view/Sprite
 14  * @requires hilo/core/Hilo
 15  * @requires hilo/core/Class
 16  * @requires hilo/view/View
 17  * @requires hilo/view/Drawable
 18  * @param properties 创建对象的属性参数。可包含此类所有可写属性。此外还包括:
 19  * <ul>
 20  * <li><b>frames</b> - 精灵动画的帧数据对象。</li>
 21  * </ul>
 22  * @property {number} currentFrame 当前播放帧的索引。从0开始。只读属性。
 23  * @property {boolean} paused 判断精灵是否暂停。默认为false。
 24  * @property {boolean} loop 判断精灵是否可以循环播放。默认为true。
 25  * @property {boolean} timeBased 指定精灵动画是否是以时间为基准。默认为false,即以帧为基准。
 26  * @property {number} interval 精灵动画的帧间隔。如果timeBased为true,则单位为毫秒,否则为帧数。
 27  */
 28 var Sprite = Class.create(/** @lends Sprite.prototype */{
 29     Extends: View,
 30     constructor: function(properties){
 31         properties = properties || {};
 32         this.id = this.id || properties.id || Hilo.getUid("Sprite");
 33         Sprite.superclass.constructor.call(this, properties);
 34 
 35         this._frames = [];
 36         this._frameNames = {};
 37         this.drawable = new Drawable();
 38         if(properties.frames) this.addFrame(properties.frames);
 39     },
 40 
 41     _frames: null, //所有帧的集合 Collection of all frames
 42     _frameNames: null, //带名字name的帧的集合 Collection of frames that with name
 43     _frameElapsed: 0, //当前帧持续的时间或帧数 Elapsed time of current frame.
 44     _firstRender: true, //标记是否是第一次渲染 Is the first render.
 45 
 46     paused: false,
 47     loop: true,
 48     timeBased: false,
 49     interval: 1,
 50     currentFrame: 0, //当前帧的索引 Index of current frame
 51 
 52     /**
 53      * @language=zh
 54      * 返回精灵动画的总帧数。
 55      * @returns {Uint} 精灵动画的总帧数。
 56      */
 57     getNumFrames: function(){
 58         return this._frames ? this._frames.length : 0;
 59     },
 60 
 61     /**
 62      * @language=zh
 63      * 往精灵动画序列中增加帧。
 64      * @param {Object} frame 要增加的精灵动画帧数据。
 65      * @param {Int} startIndex 开始增加帧的索引位置。若不设置,默认为在末尾添加。
 66      * @returns {Sprite} Sprite对象本身。
 67      */
 68     addFrame: function(frame, startIndex){
 69         var start = startIndex != null ? startIndex : this._frames.length;
 70         if(frame instanceof Array){
 71             for(var i = 0, len = frame.length; i < len; i++){
 72                 this.setFrame(frame[i], start + i);
 73             }
 74         }else{
 75             this.setFrame(frame, start);
 76         }
 77         return this;
 78     },
 79 
 80     /**
 81      * @language=zh
 82      * 设置精灵动画序列指定索引位置的帧。
 83      * @param {Object} frame 要设置的精灵动画帧数据。
 84      * @param {Int} index 要设置的索引位置。
 85      * @returns {Sprite} Sprite对象本身。
 86      */
 87     setFrame: function(frame, index){
 88         var frames = this._frames,
 89             total = frames.length;
 90         index = index < 0 ? 0 : index > total ? total : index;
 91         frames[index] = frame;
 92         if(frame.name) this._frameNames[frame.name] = frame;
 93         if(index == 0 && !this.width || !this.height){
 94             this.width = frame.rect[2];
 95             this.height = frame.rect[3];
 96         }
 97         return this;
 98     },
 99 
100     /**
101      * @language=zh
102      * 获取精灵动画序列中指定的帧。
103      * @param {Object} indexOrName 要获取的帧的索引位置或别名。
104      * @returns {Object} 精灵帧对象。
105      */
106     getFrame: function(indexOrName){
107         if(typeof indexOrName === 'number'){
108             var frames = this._frames;
109             if(indexOrName < 0 || indexOrName >= frames.length) return null;
110             return frames[indexOrName];
111         }
112         return this._frameNames[indexOrName];
113     },
114 
115     /**
116      * @language=zh
117      * 获取精灵动画序列中指定帧的索引位置。
118      * @param {Object} frameValue 要获取的帧的索引位置或别名。
119      * @returns {Object} 精灵帧对象。
120      */
121     getFrameIndex: function(frameValue){
122         var frames = this._frames,
123             total = frames.length,
124             index = -1;
125         if(typeof frameValue === 'number'){
126             index = frameValue;
127         }else{
128             var frame = typeof frameValue === 'string' ? this._frameNames[frameValue] : frameValue;
129             if(frame){
130                 for(var i = 0; i < total; i++){
131                     if(frame === frames[i]){
132                         index = i;
133                         break;
134                     }
135                 }
136             }
137         }
138         return index;
139     },
140 
141     /**
142      * @language=zh
143      * 播放精灵动画。
144      * @returns {Sprite} Sprite对象本身。
145      */
146     play: function(){
147         this.paused = false;
148         return this;
149     },
150 
151     /**
152      * @language=zh
153      * 暂停播放精灵动画。
154      * @returns {Sprite} Sprite对象本身。
155      */
156     stop: function(){
157         this.paused = true;
158         return this;
159     },
160 
161     /**
162      * @language=zh
163      * 跳转精灵动画到指定的帧。
164      * @param {Object} indexOrName 要跳转的帧的索引位置或别名。
165      * @param {Boolean} pause 指示跳转后是否暂停播放。
166      * @returns {Sprite} Sprite对象本身。
167      */
168     goto: function(indexOrName, pause){
169         var total = this._frames.length,
170             index = this.getFrameIndex(indexOrName);
171 
172         this.currentFrame = index < 0 ? 0 : index >= total ? total - 1 : index;
173         this.paused = pause;
174         this._firstRender = true;
175         return this;
176     },
177 
178     /**
179      * @language=zh
180      * 渲染方法。
181      * @private
182      */
183     _render: function(renderer, delta){
184         var lastFrameIndex = this.currentFrame, frameIndex;
185 
186         if(this._firstRender){
187             frameIndex = lastFrameIndex;
188             this._firstRender = false;
189         }else{
190             frameIndex = this._nextFrame(delta);
191         }
192 
193         if(frameIndex != lastFrameIndex){
194             this.currentFrame = frameIndex;
195             var callback = this._frames[frameIndex].callback;
196             callback && callback.call(this);
197         }
198 
199         //NOTE: it will be deprecated, don't use it.
200         if(this.onEnterFrame) this.onEnterFrame(frameIndex);
201 
202         this.drawable.init(this._frames[frameIndex]);
203         Sprite.superclass._render.call(this, renderer, delta);
204     },
205 
206     /**
207      * @language=zh
208      * @private
209      */
210     _nextFrame: function(delta){
211         var frames = this._frames,
212             total = frames.length,
213             frameIndex = this.currentFrame,
214             frame = frames[frameIndex],
215             duration = frame.duration || this.interval,
216             elapsed = this._frameElapsed;
217 
218         //calculate the current frame elapsed frames/time
219         var value = (frameIndex == 0 && !this.drawable) ? 0 : elapsed + (this.timeBased ? delta : 1);
220         elapsed = this._frameElapsed = value < duration ? value : 0;
221 
222         if(frame.stop || !this.loop && frameIndex >= total - 1){
223             this.stop();
224         }
225 
226         if(!this.paused && elapsed == 0){
227             if(frame.next != null){
228                 //jump to the specified frame
229                 frameIndex = this.getFrameIndex(frame.next);
230             }else if(frameIndex >= total - 1){
231                 //at the end of the frames, go back to first frame
232                 frameIndex = 0;
233             }else if(this.drawable){
234                 //normal go forward to next frame
235                 frameIndex++;
236             }
237         }
238 
239         return frameIndex;
240     },
241 
242     /**
243      * @language=zh
244      * 设置指定帧的回调函数。即每当播放头进入指定帧时调用callback函数。若callback为空,则会删除回调函数。
245      * @param {Int|String} frame 要指定的帧的索引位置或别名。
246      * @param {Function} callback 指定回调函数。
247      * @returns {Sprite} 精灵本身。
248      */
249     setFrameCallback: function(frame, callback){
250         frame = this.getFrame(frame);
251         if(frame) frame.callback = callback;
252         return this;
253     },
254 
255     /**
256      * @language=zh
257      * 精灵动画的播放头进入新帧时的回调方法。默认值为null。此方法已废弃,请使用addFrameCallback方法。
258      * @type Function
259      * @deprecated
260      */
261     onEnterFrame: null
262 
263 });
264