      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