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