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 }