1 /**
  2 * BitmapSequence by Grant Skinner. Dec 5, 2010
  3 * Visit www.gskinner.com/blog for documentation, updates and more free code.
  4 *
  5 *
  6 * Copyright (c) 2010 Grant Skinner
  7 * 
  8 * Permission is hereby granted, free of charge, to any person
  9 * obtaining a copy of this software and associated documentation
 10 * files (the "Software"), to deal in the Software without
 11 * restriction, including without limitation the rights to use,
 12 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 13 * copies of the Software, and to permit persons to whom the
 14 * Software is furnished to do so, subject to the following
 15 * conditions:
 16 * 
 17 * The above copyright notice and this permission notice shall be
 18 * included in all copies or substantial portions of the Software.
 19 * 
 20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 21 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 22 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 23 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 24 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 25 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 27 * OTHER DEALINGS IN THE SOFTWARE.
 28 **/
 29 
 30 
 31 
 32 /**
 33 * Constructs a BitmapSequence object with the specified source image.
 34 * @param image The Image, Canvas, or Video to use as a sprite sheet.
 35 * @param frameWidth The width in pixels of each frame on the sprite sheet.
 36 * @param frameHeight The height in pixels of each frame on the sprite sheet.
 37 * @class Displays frames or sequences of frames from a sprite sheet image. A sprite sheet is a series of images (usually animation frames) combined into a single image on a regular grid. For example, an animation consisting of 8 100x100 images could be combined into a 400x200 sprite sheet (4 frames across by 2 high). You can display individual frames, play sequential frames as an animation, and even sequence animations together.<br/><br/>
 38 * The simplest way to use BitmapSequence is to specify the image, frameWidth, frameHeight. It will then play all of the frames in the animation and loop if the loop property is true. In this simple mode, you can also use the currentFrame property to move between frames manually, and you can set the totalFrames property if you have extraneous frames in your sprite sheet (for example, a 2x4 frame sprite sheet, with only 7 frames used).<br/><br/>
 39 * A more advanced usage of BitmapSequence is to set a frameData property, which provides named sequences and frames which can be played and sequenced together. See frameData for more information.
 40 * @augments DisplayObject
 41 **/
 42 function BitmapSequence(image,frameWidth,frameHeight) {
 43   this.init(image,frameWidth,frameHeight);
 44 this.prototype = new DisplayObject();
 45 
 46 // public properties:
 47 	/** Specifies a funciton to call whenever any sequence reaches its end. **/
 48 	this.callback = null;
 49 	/** The frame that will be drawn on the next tick. This can also be set, but it will not update the current sequence, so it may result in unexpected behaviour if you are using frameData. **/
 50 	this.currentFrame = -1;
 51 	/** Returns the currently playing sequence when using frameData. READ-ONLY. **/
 52 	this.currentSequence = null; // READ-ONLY
 53 	/** Returns the last frame of the currently playing sequence when using frameData. READ-ONLY. **/
 54 	this.currentEndFrame = null; // READ-ONLY
 55 	/** Returns the first frame of the currently playing sequence when using frameData. READ-ONLY. **/
 56 	this.currentStartFrame = null; // READ-ONLY
 57 	/** The width in pixels of each frame on the sprite sheet. **/
 58 	this.frameWidth;
 59 	/** The height in pixels of each frame on the sprite sheet. **/
 60 	this.frameHeight;
 61 	/** The Image, Canvas, or Video to use as a sprite sheet. **/
 62 	this.image;
 63 	/** Returns the name of the next sequence that will be played, or null if it will stop playing after the current sequence. READ-ONLY. **/
 64 	this.nextSequence = null;
 65 	/** Prevents the animation from advancing each tick automatically. For example, you could create a sprite sheet of icons, set paused to true, and display the appropriate icon by setting currentFrame. **/
 66 	this.paused = false;
 67 	/** Defines named frames and frame sequences. Frame data is specified as a generic object, where each property name will be used to define a new named frame or sequence. Named frames specify a frame number. Sequences are defined using an array of 2 or 3 values: the start frame, the end frame, and optionally the name of the next sequence to play. For example, examine the following frame data:<br/>{walk:[0,20], shoot:[21,25,"walk"], crouch:[26,30,false], stand:31}<br/>This will create 3 sequences and a named frame. The first sequence will be named "walk", and will loop frames 0 to 20 inclusive. The second sequence will be named "shoot", and will play frames 21 to 25 then play the walk sequence. The third sequence "crouch" will play frames 26 to 30 then pause on frame 30, due to false being passed as the next sequence. The named frame "stand" will display frame 31. **/
 68 	this.frameData = null;
 69 	/** The loop property is only used if no frameData is specified, and indicates whether all frames (as specified with totalFrames) should loop. If false, the animation will play to totalFrames, then pause. **/
 70 	this.loop = true; // exclusive of frameData
 71 	/** Specifies the total number of frames in the sprite sheet if no frameData is specified. This is useful for excluding extraneous frames (for example, if you have 7 frames in a 2x4 sprite sheet). The total frames will be calculated based on frame and image dimensions if totalFrames is 0. **/
 72 	this.totalFrames = 0; // exclusive of frameData
 73 	
 74 // constructor:
 75 	/** @private **/
 76 	this._init = this.init;
 77 	/** @private **/
 78 	this.init = function(image,frameWidth,frameHeight) {
 79 		this._init();
 80 		this.image = image;
 81 		this.frameWidth = frameWidth;
 82 		this.frameHeight = frameHeight;
 83 	}
 84 	
 85 // public methods:
 86 	this._draw = this.draw;
 87 	this.draw = function(ctx,ignoreCache) {
 88 		if (this.image == null || !this.image.complete || this.currentFrame < 0) { return false; }
 89 		if (!this._draw(ctx,ignoreCache)) { return false; }
 90 		
 91 		var cols = this.image.width/this.frameWidth|0;
 92 		var rows = this.image.height/this.frameHeight|0;
 93 		
 94 		if (this.currentEndFrame != null) {
 95 			if (this.currentFrame > this.currentEndFrame) {
 96 				if (this.nextSequence) {
 97 					this.goto(this.nextSequence);
 98 				} else {
 99 					this.paused = true;
100 					this.currentFrame = this.currentEndFrame;
101 				}
102 				if (this.callback) { this.callback(this); }
103 			}
104 		} else if (this.frameData) {
105 			// sequence data is set, but we haven't played a sequence yet:
106 			this.paused = true;
107 		} else {
108 			var ttlFrames = this.totalFrames || cols*rows;
109 			if (this.currentFrame >= ttlFrames) {
110 				if (this.loop) { this.currentFrame = -1; }
111 				else {
112 					this.currentFrame = totalFrames;
113 					this.paused = true;
114 				}
115 				if (this.callback) { this.callback(this); }
116 			}
117 		}
118 		if (this.currentFrame >= 0) {
119 			var col = this.currentFrame%cols;
120 			var row = this.currentFrame/cols|0;
121 			ctx.drawImage(this.image, this.frameWidth*col, this.frameHeight*row, this.frameWidth, this.frameHeight, 0, 0, this.frameWidth, this.frameHeight);
122 		}
123 	}
124 	
125 	/**
126 	* Advances the currentFrame if paused is not true. This is called automatically when the Stage ticks.
127 	**/
128 	this.tick = function() {
129 		if (this.paused) { return; }
130 		this.currentFrame++;
131 	}
132 	
133 	/**
134 	* Because the content of a Bitmap is already in a simple format, cache is unnecessary for BitmapSequence instances.
135 	**/
136 	this.cache = function() {}
137 	/**
138 	* Because the content of a Bitmap is already in a simple format, cache is unnecessary for BitmapSequence instances.
139 	**/
140 	this.uncache = function() {}
141 	
142 	/**
143 	* Sets paused to false and plays the specified sequence name, named frame, or frame number.
144 	**/
145 	this.gotoAndPlay = function(frameOrSequence) {
146 		this.paused = false;
147 		this.goto(frameOrSequence);
148 	}
149 	
150 	/**
151 	* Seeks to the specified sequence name, named frame, or frame number without modifying the paused property.
152 	**/
153 	this.goto = function(frameOrSequence) {
154 		if (isNaN(frameOrSequence)) {
155 			if (frameOrSequence == this.currentSequence) {
156 				this.currentFrame = this.currentStartFrame;
157 				return;
158 			}
159 			var data = this.frameData[frameOrSequence];
160 			if (data instanceof Array) {
161 				this.currentFrame = this.currentStartFrame = data[0];
162 				this.currentSequence = frameOrSequence;
163 				this.currentEndFrame = data[1];
164 				if (this.currentEndFrame == null) { this.currentEndFrame = this.currentFrame; }
165 				this.nextSequence = data[2];
166 				if (this.nextSequence == null) { this.nextSequence = this.currentSequence; }
167 				else if (this.nextSequence == false) { this.nextSequence = null; }
168 				return;
169 			} else {
170 				frameOrSequence = data;
171 			}
172 		}
173 		this.currentSequence = this.nextSequence = null;
174 		this.currentEndFrame = this.currentFrame = this.currentStartFrame = frameOrSequence;
175 		
176 	}
177 	
178 	/**
179 	* Sets paused to true and seeks to the specified sequence name, named frame, or frame number.
180 	**/
181 	this.gotoAndStop = function(frameOrSequence) {
182 		this.paused = true;
183 		this.goto(frameOrSequence);
184 	}
185 	
186 	this.clone = function() {
187 		var o = new BitmapSequence(this.image, this.frameWidth, this.frameHeight);
188 		this.cloneProps(o);
189 		return o;
190 	}
191 		
192 	this.toString = function() {
193 		return "[BitmapSequence (name="+  this.name +")]";
194 	}
195 	
196 // private methods:
197 	/** @private **/
198 	this._cloneProps = this.cloneProps;
199 	/** @private **/
200 	this.cloneProps = function(o) {
201 		this._cloneProps(o);
202 		o.callback = this.callback;
203 		o.currentFrame = this.currentFrame;
204 		o.currentStartFrame = this.currentStartFrame;
205 		o.currentEndFrame = this.currentEndFrame;
206 		o.currentSequence = this.currentSequence;
207 		o.loop = this.loop;
208 		o.nextSequence = this.nextSequence;
209 		o.paused = this.paused;
210 		o.frameData = this.frameData;
211 		o.totalFrames = this.totalFrames;
212 	}
213 }