1 /**
  2  * The MIT License (MIT)
  3  *
  4  * Copyright (c) 2016 DeNA Co., Ltd.
  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 THE
 22  * SOFTWARE.
 23  */
 24 
 25 /// <reference path="base.js"/>
 26 /// <reference path="display_object.js"/>
 27 /// <reference path="sprite_sheet.js"/>
 28 /// <reference path="bounding_box.js"/>
 29 /// <reference path="tick_event.js"/>
 30 
 31 /**
 32  * A class that displays a frame or sequence of frames (i.e. an animation) in a
 33  * createjs.SpriteSheet object.
 34  * @param {createjs.SpriteSheet} spriteSheet
 35  * @param {(string|number)=} opt_frameOrAnimation
 36  * @extends {createjs.DisplayObject}
 37  * @constructor
 38  */
 39 createjs.Sprite = function(spriteSheet, opt_frameOrAnimation) {
 40   createjs.DisplayObject.call(this);
 41 
 42   if (spriteSheet != null) {
 43     /**
 44      * The SpriteSheet instance exported to applications.
 45      * @type {createjs.SpriteSheet}
 46      */
 47     this['spriteSheet'] = spriteSheet;
 48 
 49     /**
 50      * The SpriteSheet instance being played.
 51      * @type {createjs.SpriteSheet}
 52      * @private
 53      */
 54     this.spriteSheet_ = spriteSheet;
 55 
 56     /**
 57      * The number of frames to advance.
 58      * @type {number}
 59      * @private
 60      */
 61     this.framerate_ = spriteSheet.framerate;
 62 
 63     if (opt_frameOrAnimation != null) {
 64       this.gotoAndPlay(opt_frameOrAnimation || 0);
 65     }
 66   }
 67 };
 68 createjs.inherits('Sprite', createjs.Sprite, createjs.DisplayObject);
 69 
 70 /**
 71  * The name of the animation being played.
 72  * @type {string}
 73  * @private
 74  */
 75 createjs.Sprite.prototype.currentAnimation = '';
 76 
 77 /**
 78  * The frame index within the animation being played.
 79  * @type {number}
 80  * @private
 81  */
 82 createjs.Sprite.prototype.currentAnimationFrame = 0;
 83 
 84 /**
 85  * The last time when this sprite has been updated.
 86  * @type {number}
 87  * @private
 88  */
 89 createjs.Sprite.prototype.lastTime_ = 0;
 90 
 91 /**
 92  * @type {createjs.SpriteSheet.Animation}
 93  * @private
 94  */
 95 createjs.Sprite.prototype.animation_ = null;
 96 
 97 /**
 98  * Updates the bounding box of this sprite and sets its dirty flag when this
 99  * sprite has its frame updated.
100  * @param {number} currentFrame
101  * @private
102  */
103 createjs.Sprite.prototype.updateShape_ = function(currentFrame) {
104   /// <param type="number" name="currentFrame"/>
105   if (currentFrame != this.getCurrentFrame()) {
106     var frame = this.spriteSheet_.getFrame(this.getCurrentFrame());
107     if (!frame) {
108       return;
109     }
110     var minX = -frame.regX;
111     var minY = -frame.regY;
112     var maxX = minX + frame.rect.width;
113     var maxY = minY + frame.rect.height;
114     this.setBoundingBox(minX, minY, maxX, maxY);
115     this.setDirty(createjs.DisplayObject.DIRTY_SHAPE);
116   }
117 };
118 
119 /**
120  * Normalizes the current animation frame, advancing animations and dispatching
121  * callbacks as appropriate.
122  * @private
123  */
124 createjs.Sprite.prototype.normalizeAnimation_ = function() {
125   var TYPE = 'animationend';
126   var hasListener = this.hasListener(TYPE);
127   var animation = this.animation_;
128   var frame = createjs.floor(this.currentAnimationFrame);
129   var length = animation.getFrameLength();
130   while (frame >= length) {
131     var next = animation.getNext();
132     if (hasListener) {
133       var event = new createjs.AnimationEvent(
134           TYPE, false, false, animation.getName(), next);
135       this.dispatchRawEvent(event);
136     }
137     if (!next) {
138       this.setIsPaused(true);
139       frame = length - 1;
140       break;
141     }
142     animation = this.spriteSheet_.getAnimation(next);
143     frame -= length;
144     length = animation.getFrameLength();
145   }
146   this.currentAnimationFrame = frame;
147   this.setCurrentFrame(animation.getFrame(frame));
148   this.animation_ = animation;
149 };
150 
151 /**
152  * Normalizes the current frame, advancing animations and dispatching callbacks
153  * as appropriate.
154  * @private
155  */
156 createjs.Sprite.prototype.normalizeFrame_ = function() {
157   var TYPE = 'animationend';
158   var hasListener = this.hasListener(TYPE);
159   var frame = createjs.floor(this.getCurrentFrame());
160   var length = this.spriteSheet_.getFrameLength();
161   while (frame >= length) {
162     if (hasListener) {
163       var event = new createjs.AnimationEvent(TYPE, false, false, '', '');
164       this.dispatchRawEvent(event);
165     }
166     frame -= length;
167   }
168   this.setCurrentFrame(frame);
169 };
170 
171 /**
172  * Moves the play offset to the specified frame number or the beginning of the
173  * specified animation.
174  * @param {boolean} paused
175  * @param {string|number} frameOrAnimation
176  * @private
177  */
178 createjs.Sprite.prototype.goto_ = function(paused, frameOrAnimation) {
179   this.setIsPaused(paused);
180   var currentFrame = this.getCurrentFrame();
181   if (createjs.isString(frameOrAnimation)) {
182     var name = createjs.getString(frameOrAnimation);
183     if (this.currentAnimation != name) {
184       var animation = this.spriteSheet_.getAnimation(name);
185       createjs.assert(!!animation);
186       this.animation_ = animation;
187       this.currentAnimation = name;
188       this.setCurrentFrame(animation.getFrame(0));
189     }
190     this.currentAnimationFrame = 0;
191   } else {
192     var frame = createjs.getNumber(frameOrAnimation);
193     this.currentAnimationFrame = 0;
194     this.currentAnimation = '';
195     this.animation_ = null;
196     this.setCurrentFrame(frame);
197     this.normalizeFrame_();
198   }
199   this.updateShape_(currentFrame);
200 };
201 
202 /**
203  * Starts playing (unpause) the current animation. The Sprite will be paused if
204  * either the Sprite.stop() method or the Sprite.gotoAndStop() method is called.
205  * Single frame animations will remain unchanged.
206  * @const
207  */
208 createjs.Sprite.prototype.play = function() {
209   this.setIsPaused(false);
210   this.setDirty(createjs.DisplayObject.DIRTY_ALL);
211 };
212 
213 /**
214  * Stops playing a running animation. The Sprite will be playing if the
215  * Sprite.gotoAndPlay() method is called. Note that calling the
216  * Sprite.gotoAndPlay() method or the Sprite.play() method will resume playback.
217  * @const
218  */
219 createjs.Sprite.prototype.stop = function() {
220   this.setIsPaused(true);
221 };
222 
223 /**
224  * Sets paused to false and plays the specified animation name, named frame, or
225  * frame number.
226  * @param {string|number} value
227  * @const
228  */
229 createjs.Sprite.prototype.gotoAndPlay = function(value) {
230   this.goto_(false, value);
231   this.setDirty(createjs.DisplayObject.DIRTY_ALL);
232 };
233 
234 /**
235  * Sets paused to true and seeks to the specified animation name, named frame,
236  * or frame number.
237  * @param {string|number} value
238  * @const
239  */
240 createjs.Sprite.prototype.gotoAndStop = function(value) {
241   this.goto_(true, value);
242 };
243 
244 /**
245  * Advances the play position. This occurs automatically each tick by default.
246  * @param {number=} opt_time
247  * @const
248  */
249 createjs.Sprite.prototype.advance = function(opt_time) {
250   var time = 1;
251   if (opt_time && this.framerate_) {
252     time = opt_time * this.framerate_ / 1000;
253   }
254   var currentFrame = this.getCurrentFrame();
255   if (this.animation_) {
256     this.currentAnimationFrame += time * this.animation_.getSpeed();
257     this.normalizeAnimation_();
258   } else {
259     this.setCurrentFrame(currentFrame + time);
260     this.normalizeFrame_();
261   }
262   this.updateShape_(currentFrame);
263 };
264   
265 /** @override */
266 createjs.Sprite.prototype.isVisible = function() {
267   if (!createjs.Sprite.superClass_.isVisible.call(this)) {
268     return false;
269   }
270   return this.spriteSheet_.isComplete();
271 };
272 
273 /** @override */
274 createjs.Sprite.prototype.removeAllChildren = function(opt_destroy) {
275   this['spriteSheet'] = null;
276   this.spriteSheet_ = null;
277 };
278 
279 /** @override */
280 createjs.Sprite.prototype.layout =
281     function(renderer, parent, dirty, time, draw) {
282   /// <param type="createjs.Renderer" name="renderer"/>
283   /// <param type="createjs.DisplayObject" name="parent"/>
284   /// <param type="number" name="dirty"/>
285   /// <param type="number" name="time"/>
286   /// <param type="number" name="draw"/>
287   if (!this.spriteSheet_.isComplete()) {
288     return 0;
289   }
290   if (!this.isPaused()) {
291     var delta = time - this.lastTime_;
292     if (delta > 0) {
293       this.advance(time);
294     }
295     this.lastTime_ = time;
296   }
297   return createjs.Sprite.superClass_.layout.call(
298       this, renderer, parent, dirty, time, draw);
299 };
300 
301 /** @override */
302 createjs.Sprite.prototype.paintObject = function(renderer) {
303   var frame =
304       this.spriteSheet_.getFrame(createjs.floor(this.getCurrentFrame()));
305   createjs.assert(!!frame);
306   var rect = frame.rect;
307   renderer.drawPartial(
308       frame.image,
309       rect.x, rect.y, rect.width, rect.height,
310       -frame.regX, -frame.regY, rect.width, rect.height);
311 };
312 
313 // Export the createjs.Sprite object to the global namespace.
314 createjs.exportObject('createjs.Sprite', createjs.Sprite, {
315   // createjs.Sprite methods
316   'play': createjs.Sprite.prototype.play,
317   'stop': createjs.Sprite.prototype.stop,
318   'gotoAndPlay': createjs.Sprite.prototype.gotoAndPlay,
319   'gotoAndStop': createjs.Sprite.prototype.gotoAndStop,
320   'advance': createjs.Sprite.prototype.advance
321 
322   // createjs.DisplayObject methods
323 
324   // createjs.EventDispatcher methods
325 
326   // createjs.Object methods.
327 });
328