1 /** 2 * The Render Engine 3 * Sprite 4 * 5 * @fileoverview A class for working with sprites. 6 * 7 * @author: Brett Fattori (brettf@renderengine.com) 8 * @author: $Author: bfattori $ 9 * @version: $Revision: 1556 $ 10 * 11 * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com) 12 * 13 * Permission is hereby granted, free of charge, to any person obtaining a copy 14 * of this software and associated documentation files (the "Software"), to deal 15 * in the Software without restriction, including without limitation the rights 16 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 * copies of the Software, and to permit persons to whom the Software is 18 * furnished to do so, subject to the following conditions: 19 * 20 * The above copyright notice and this permission notice shall be included in 21 * all copies or substantial portions of the Software. 22 * 23 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 * THE SOFTWARE. 30 * 31 */ 32 33 // The class this file defines and its required classes 34 R.Engine.define({ 35 "class":"R.resources.types.Sprite", 36 "requires":[ 37 "R.engine.PooledObject", 38 "R.math.Rectangle2D" 39 ] 40 }); 41 42 /** 43 * @class A 2D sprite object. Sprites are either a single frame, or an animation composed of 44 * multiple frames run at a specified frame speed. Animations can be run once, loop 45 * continuously, or toggle back and forth through the frames. It is possible to start 46 * and stop animations, and also modify the speed at which each frame is played. 47 * <p/> 48 * In addition to the normal controls for an animation, a developer can also respond 49 * to events triggered on the sprite. Linking to the events is done through 50 * {@link R.engine.BaseObject#addEvent}. The following are the events, and their 51 * descriptions: 52 * <ul> 53 * <li><tt>finished</tt> - A "run once" animation has played and completed</li> 54 * <li><tt>loopRestarted</tt> - A looping animation has begun a new cycle</li> 55 * <li><tt>toggled</tt> - A toggle animation has changed animation direction</li> 56 * </ul> 57 * 58 * @constructor 59 * @param name {String} The name of the sprite within the resource 60 * @param spriteObj {Object} Passed in by a {@link SpriteLoader}. An array which defines the 61 * sprite frame, and parameters. 62 * @param spriteResource {Object} The sprite resource loaded by the {@link SpriteLoader} 63 * @extends R.engine.BaseObject 64 */ 65 R.resources.types.Sprite = function () { 66 return R.engine.BaseObject.extend(/** @scope R.resources.types.Sprite.prototype */{ 67 68 // The type of sprite: Single or Animation 69 type:-1, 70 71 // Animation mode: loop or toggle 72 mode:-1, 73 74 // Animation frame count 75 count:-1, 76 77 // Animation speed 78 speed:-1, 79 80 // The rect which defines the sprite frame 81 frame:null, 82 83 // The image map that contains the sprite(s) 84 image:null, 85 86 // The bounding box for the sprite 87 bbox:null, 88 89 lastTime:null, 90 sync:false, 91 finished:false, 92 toggleDir:null, 93 frameNum:0, 94 playing:false, 95 resource:null, 96 loader:null, 97 98 /** @private */ 99 constructor:function (name, spriteObj, spriteResource, fileVersion, spriteLoader) { 100 this.base(name); 101 this.finished = false; 102 this.frameNum = 0; 103 this.playing = true; 104 105 if (fileVersion == 1) { 106 this.type = (spriteObj["a"] ? R.resources.types.Sprite.TYPE_ANIMATION : R.resources.types.Sprite.TYPE_SINGLE); 107 } else if (fileVersion == 2) { 108 this.type = (spriteObj.length == 4 ? R.resources.types.Sprite.TYPE_SINGLE : R.resources.types.Sprite.TYPE_ANIMATION); 109 } 110 111 var s; 112 if (fileVersion == 1) { 113 s = (this.type == R.resources.types.Sprite.TYPE_ANIMATION ? spriteObj["a"] : spriteObj["f"]); 114 } else if (fileVersion == 2) { 115 s = spriteObj; 116 } 117 118 if (this.type == R.resources.types.Sprite.TYPE_ANIMATION) { 119 switch (s[R.resources.types.Sprite.INDEX_TYPE]) { 120 case "loop" : 121 this.mode = R.resources.types.Sprite.MODE_LOOP; 122 break; 123 case "toggle" : 124 this.mode = R.resources.types.Sprite.MODE_TOGGLE; 125 break; 126 case "once" : 127 this.mode = R.resources.types.Sprite.MODE_ONCE; 128 break; 129 } 130 if (s.length - 1 == R.resources.types.Sprite.INDEX_SYNC) { 131 this.sync = true; 132 this.lastTime = null; 133 this.toggleDir = -1; // Trust me 134 } else { 135 this.sync = false; 136 } 137 this.count = s[R.resources.types.Sprite.INDEX_COUNT]; 138 this.speed = s[R.resources.types.Sprite.INDEX_SPEED]; 139 } else { 140 this.count = 1; 141 this.speed = -1; 142 } 143 144 this.resource = spriteResource; 145 this.loader = spriteLoader; 146 this.image = spriteResource.image; 147 this.frame = R.math.Rectangle2D.create(s[R.resources.types.Sprite.INDEX_LEFT], s[R.resources.types.Sprite.INDEX_TOP], s[R.resources.types.Sprite.INDEX_WIDTH], s[R.resources.types.Sprite.INDEX_HEIGHT]); 148 this.bbox = R.math.Rectangle2D.create(0, 0, s[R.resources.types.Sprite.INDEX_WIDTH], s[R.resources.types.Sprite.INDEX_HEIGHT]); 149 }, 150 151 /** 152 * Destroy the sprite instance 153 */ 154 destroy:function () { 155 this.bbox.destroy(); 156 this.frame.destroy(); 157 this.base(); 158 }, 159 160 /** 161 * Release the sprite back into the pool for reuse 162 */ 163 release:function () { 164 this.base(); 165 this.mode = -1; 166 this.type = -1; 167 this.count = -1; 168 this.speed = -1; 169 this.frame = null; 170 this.image = null; 171 this.bbox = null; 172 this.resource = null; 173 this.loader = null; 174 }, 175 176 /** 177 * Get the resource this sprite originated from 178 * @return {Object} 179 */ 180 getSpriteResource:function () { 181 return this.resource; 182 }, 183 184 /** 185 * Get the sprite loader this sprite originated from 186 * @return {R.resources.loaders.SpriteLoader} 187 */ 188 getSpriteLoader:function () { 189 return this.loader; 190 }, 191 192 /** 193 * Returns <tt>true</tt> if the sprite is an animation. 194 * @return {Boolean} <tt>true</tt> if the sprite is an animation 195 */ 196 isAnimation:function () { 197 return (this.type == R.resources.types.Sprite.TYPE_ANIMATION); 198 }, 199 200 /** 201 * Returns <tt>true</tt> if the sprite is an animation and loops. 202 * @return {Boolean} <tt>true</tt> if the sprite is an animation and loops 203 */ 204 isLoop:function () { 205 return (this.isAnimation() && this.mode == R.resources.types.Sprite.MODE_LOOP); 206 }, 207 208 /** 209 * Returns <tt>true</tt> if the sprite is an animation and toggles. 210 * @return {Boolean} <tt>true</tt> if the sprite is an animation and toggles 211 */ 212 isToggle:function () { 213 return (this.isAnimation() && this.mode == R.resources.types.Sprite.MODE_TOGGLE); 214 }, 215 216 /** 217 * Returns <tt>true</tt> if the sprite is an animation and plays once. 218 * @return {Boolean} <tt>true</tt> if the sprite is an animation and plays once 219 */ 220 isOnce:function () { 221 return (this.isAnimation() && this.mode == R.resources.types.Sprite.MODE_ONCE); 222 }, 223 224 /** 225 * Get the number of frames in the sprite. 226 * @return {Number} 227 */ 228 getFrameCount:function () { 229 return this.count; 230 }, 231 232 /** 233 * Get the frame speed of the animation in milliseconds, or -1 if it's a single frame. 234 * @return {Number} 235 */ 236 getFrameSpeed:function () { 237 return this.speed; 238 }, 239 240 /** 241 * For animated sprites, play the animation if it is stopped. 242 */ 243 play:function () { 244 this.playing = true; 245 }, 246 247 /** 248 * For animated sprites, stop the animation if it is playing. 249 */ 250 stop:function () { 251 this.playing = false; 252 }, 253 254 /** 255 * For animated sprites, reset the animation to frame zero. 256 */ 257 reset:function () { 258 this.frameNum = 0; 259 }, 260 261 /** 262 * For animated sprites, go to a particular frame number. 263 * @param frameNum {Number} The frame number to jump to 264 */ 265 gotoFrame:function (frameNum) { 266 this.frameNum = (frameNum < 0 ? 0 : (frameNum >= this.count ? this.count - 1 : frameNum)); 267 }, 268 269 /** 270 * Get the bounding box for the sprite. 271 * @return {R.math.Rectangle2D} The bounding box which contains the entire sprite 272 */ 273 getBoundingBox:function () { 274 return this.bbox; 275 }, 276 277 /** 278 * Gets the frame of the sprite. The frame is the rectangle defining what 279 * portion of the image map the sprite frame occupies, given the specified time. 280 * 281 * @param time {Number} Current world time 282 * @param dt {Number} The delta between the world time and the last time the world was updated 283 * in milliseconds. 284 * @return {R.math.Rectangle2D} A rectangle which defines the frame of the sprite in 285 * the source image map. 286 */ 287 getFrame:function (time, dt) { 288 if (!this.isAnimation()) { 289 return R.math.Rectangle2D.create(this.frame); 290 } else { 291 var f = R.math.Rectangle2D.create(this.frame); 292 var fn = this.calcFrameNumber(time, dt); 293 return f.offset(f.w * fn, 0); 294 } 295 }, 296 297 /** 298 * Calculate the frame number for the type of animation. 299 * @param time {Number} The current world time 300 * @param dt {Number} The delta between the world time and the last time the world was updated 301 * in milliseconds. 302 * @private 303 */ 304 calcFrameNumber:function (time, dt) { 305 if (!this.playing) { 306 return this.frameNum; 307 } 308 309 if (this.sync) { 310 // Synchronized animations 311 312 if (this.lastTime === null) { 313 // Note the time when the first frame is requested and just return frame zero 314 this.lastTime = time; 315 return 0; 316 } 317 318 // How much time has elapsed since the last frame update? 319 if (dt > this.speed) { 320 // Engine is lagging, skip to correct frame 321 this.frameNum += (Math.floor(dt / this.speed) * this.toggleDir); 322 } else { 323 this.frameNum += (time - this.lastTime > this.speed ? this.toggleDir : 0); 324 } 325 326 // Modify the frame number for the animation mode 327 if (this.isOnce()) { 328 // Play animation once from beginning to end 329 if (this.frameNum >= this.count) { 330 this.frameNum = this.count - 1; 331 if (!this.finished) { 332 // Call event when finished 333 this.finished = true; 334 this.triggerEvent("finished"); 335 } 336 } 337 } else if (this.isLoop()) { 338 if (this.frameNum > this.count - 1) { 339 // Call event when loop restarts 340 this.frameNum = 0; 341 this.triggerEvent("loopRestarted"); 342 } 343 } else { 344 if (this.frameNum == this.count - 1 || this.frameNum == 0) { 345 // Call event when animation toggles 346 this.toggleDir *= -1; 347 this.frameNum += this.toggleDir; 348 this.triggerEvent("toggled"); 349 } 350 } 351 352 // Remember the last time a frame was requested 353 this.lastTime = time; 354 355 } else { 356 // Unsynchronized animations 357 var lastFrame = this.frameNum; 358 if (this.isLoop()) { 359 this.frameNum = Math.floor(time / this.speed) % this.count; 360 if (this.frameNum < lastFrame) { 361 this.triggerEvent("loopRestarted"); 362 } 363 } else if (this.isOnce() && !this.finished) { 364 this.frameNum = Math.floor(time / this.speed) % this.count; 365 if (this.frameNum < lastFrame) { 366 this.finished = true; 367 this.frameNum = this.count - 1; 368 this.triggerEvent("finished"); 369 } 370 } else if (this.isToggle()) { 371 this.frameNum = Math.floor(time / this.speed) % (this.count * 2); 372 if (this.frameNum > this.count - 1) { 373 this.frameNum = this.count - (this.frameNum - (this.count - 1)); 374 this.triggerEvent("toggled"); 375 } 376 } 377 } 378 379 return this.frameNum; 380 }, 381 382 /** 383 * Set the speed, in milliseconds, that an animation runs at. If the sprite is 384 * not an animation, this has no effect. 385 * 386 * @param speed {Number} The number of milliseconds per frame of an animation 387 */ 388 setSpeed:function (speed) { 389 if (speed >= 0) { 390 this.speed = speed; 391 } 392 }, 393 394 /** 395 * Get the number of milliseconds each frame is displayed for an animation 396 * @return {Number} The milliseconds per frame 397 */ 398 getSpeed:function () { 399 return this.speed; 400 }, 401 402 /** 403 * The source image loaded by the {@link SpriteLoader} when the sprite was 404 * created. 405 * @return {HTMLImage} The source image the sprite is contained within 406 */ 407 getSourceImage:function () { 408 return this.image; 409 }, 410 411 onSpriteLoopRestart:function () { 412 } 413 414 }, /** @scope R.resources.types.Sprite.prototype */{ 415 /** 416 * Gets the class name of this object. 417 * @return {String} The string "R.resources.types.Sprite" 418 */ 419 getClassName:function () { 420 return "R.resources.types.Sprite"; 421 }, 422 423 /** The sprite animation loops 424 * @type {Number} 425 */ 426 MODE_LOOP:0, 427 428 /** The sprite animation toggles - Plays from the first to the last frame 429 * then plays backwards to the first frame and repeats. 430 * @type {Number} 431 */ 432 MODE_TOGGLE:1, 433 434 /** The sprite animation plays once from the beginning then stops at the last frame 435 * @type {Number} 436 */ 437 MODE_ONCE:2, 438 439 /** The sprite is a single frame 440 * @type {Number} 441 */ 442 TYPE_SINGLE:0, 443 444 /** The sprite is an animation 445 * @type {Number} 446 */ 447 TYPE_ANIMATION:1, 448 449 /** The field in the sprite definition file for the left pixel of the sprite frame 450 * @private 451 */ 452 INDEX_LEFT:0, 453 454 /** The field in the sprite definition file for the top pixel of the sprite frame 455 * @private 456 */ 457 INDEX_TOP:1, 458 459 /** The field in the sprite definition file for the width of the sprite frame 460 * @private 461 */ 462 INDEX_WIDTH:2, 463 464 /** The field in the sprite definition file for the height of the sprite frame 465 * @private 466 */ 467 INDEX_HEIGHT:3, 468 469 /** The field in the sprite definition file for the count of frames in the sprite 470 * @private 471 */ 472 INDEX_COUNT:4, 473 474 /** The field in the sprite definition file for the speed in milliseconds that the sprite animates 475 * @private 476 */ 477 INDEX_SPEED:5, 478 479 /** The field in the sprite definition file for the type of sprite animation 480 * @private 481 */ 482 INDEX_TYPE:6, 483 484 /** 485 * Use synchronized frame starts and no skipping. 486 * @private 487 */ 488 INDEX_SYNC:7 489 490 }); 491 }; 492