1 /* $Id$ */ 2 /*************************************************************************** 3 * (C) Copyright 2003-2010 - Stendhal * 4 *************************************************************************** 5 *************************************************************************** 6 * * 7 * This program is free software; you can redistribute it and/or modify * 8 * it under the terms of the GNU General Public License as published by * 9 * the Free Software Foundation; either version 2 of the License, or * 10 * (at your option) any later version. * 11 * * 12 ***************************************************************************/ 13 package games.stendhal.client.sprite; 14 15 16 import java.awt.Graphics; 17 import java.util.Arrays; 18 19 import org.apache.log4j.Logger; 20 21 /** 22 * This is a sprite that transparently animates itself when drawn. 23 */ 24 public class AnimatedSprite implements Sprite { 25 private static Logger logger = Logger.getLogger(AnimatedSprite.class); 26 27 /** 28 * The identifier reference. 29 */ 30 private Object reference; 31 32 /** 33 * Whether the sprite is currently animating. 34 */ 35 private boolean animating; 36 37 /** 38 * The amount of time passed in the cycle. 39 */ 40 private int cycleTime; 41 42 /** 43 * The [minimum] frame durations. 44 */ 45 protected int[] delays; 46 47 /** 48 * The total duration of a cycle. 49 */ 50 private int duration; 51 52 /** 53 * The current frame index. 54 */ 55 private int index; 56 57 /** 58 * The frame sprites. 59 */ 60 protected Sprite[] frames; 61 62 /** 63 * The sprite height. 64 */ 65 private int height; 66 67 /** 68 * The time of the last update. 69 */ 70 protected long lastUpdate; 71 72 /** 73 * Whether to loop after last frame. 74 */ 75 protected boolean loop; 76 77 /** 78 * The current sprite. 79 */ 80 protected Sprite sprite; 81 82 /** 83 * The sprite width. 84 */ 85 private int width; 86 87 /** 88 * Create an animated sprite from a set of frame sprites. 89 * 90 * <strong>NOTE: The array of frames passed is not copied, and must not be 91 * modified while this instance exists (unless you are sure you know what 92 * you are doing).</strong> 93 * 94 * @param frames 95 * The frames to animate. 96 * @param delay 97 * The minimum delay between frames (in ms). 98 * 99 * @throws IllegalArgumentException 100 * If less than one frame is given, or the delay is < 0. 101 */ AnimatedSprite(final Sprite[] frames, final int delay)102 AnimatedSprite(final Sprite[] frames, final int delay) { 103 this(frames, delay, true); 104 } 105 106 /** 107 * Create an animated sprite from a set of frame sprites. 108 * 109 * <strong>NOTE: The array of frames passed is not copied, and must not be 110 * modified while this instance exists (unless you are sure you know what 111 * you are doing).</strong> 112 * 113 * @param frames 114 * The frames to animate. 115 * @param delay 116 * The minimum delay between frames (in ms). 117 * @param animating 118 * If animation is enabled. 119 * 120 * @throws IllegalArgumentException 121 * If less than one frame is given, or the delay is < 0. 122 */ AnimatedSprite(final Sprite[] frames, final int delay, final boolean animating)123 public AnimatedSprite(final Sprite[] frames, final int delay, 124 final boolean animating) { 125 this(frames, delay, animating, null); 126 } 127 128 /** 129 * Create an animated sprite from a set of frame sprites. 130 * 131 * <strong>NOTE: The array of frames passed is not copied, and must not be 132 * modified while this instance exists (unless you are sure you know what 133 * you are doing).</strong> 134 * 135 * @param frames 136 * The frames to animate. 137 * @param delay 138 * The minimum delay between frames (in ms). 139 * @param animating 140 * If animation is enabled. 141 * @param reference 142 * The sprite identifier reference. 143 * 144 * @throws IllegalArgumentException 145 * If less than one frame is given, or the delay is < 0. 146 */ AnimatedSprite(final Sprite[] frames, final int delay, final boolean animating, final Object reference)147 private AnimatedSprite(final Sprite[] frames, final int delay, 148 final boolean animating, final Object reference) { 149 this(frames, createDelays(delay, frames.length), animating, reference); 150 } 151 152 /** 153 * Create an animated sprite from a set of frame sprites. 154 * 155 * <strong>NOTE: The array of frames/delays passed is not copied, and must 156 * not be modified while this instance exists (unless you are sure you know 157 * what you are doing).</strong> 158 * 159 * @param frames 160 * The frames to animate. 161 * @param delays 162 * The minimum duration for each frame (in ms). 163 * @param animating 164 * If animation is enabled. 165 * @param reference 166 * The sprite identifier reference. 167 * 168 * @throws IllegalArgumentException 169 * If less than one frame is given, or the delay is < 0. 170 */ AnimatedSprite(final Sprite[] frames, final int[] delays, final boolean animating, final Object reference)171 AnimatedSprite(final Sprite[] frames, final int[] delays, 172 final boolean animating, final Object reference) { 173 if (frames.length == 0) { 174 logger.warn("AnimatedSprite needs at least one frame"); 175 } 176 177 if (delays.length != frames.length) { 178 throw new IllegalArgumentException( 179 "Mismatch between number of frame sprites and delays"); 180 } 181 182 /* 183 * Validate delay values. Calculate total cycle duration. 184 */ 185 duration = 0; 186 187 for (int i = 0; i < delays.length; i++) { 188 if (delays[i] < 0) { 189 throw new IllegalArgumentException("Delay < 0"); 190 } 191 192 duration += delays[i]; 193 } 194 195 this.frames = frames; 196 this.delays = delays; 197 this.animating = animating; 198 this.reference = reference; 199 200 loop = true; 201 202 height = 0; 203 width = 0; 204 205 for (final Sprite frame : frames) { 206 height = Math.max(height, frame.getHeight()); 207 width = Math.max(width, frame.getWidth()); 208 } 209 210 index = 0; 211 if (frames.length > 0) { 212 sprite = frames[0]; 213 } else { 214 sprite = null; 215 } 216 217 cycleTime = 0; 218 219 long now = System.currentTimeMillis(); 220 if (loop) { 221 /* 222 * Make all looped animations look like they were started at time 0. 223 * Keeps the map sprites nicely in sync. 224 */ 225 lastUpdate = now - (now % duration); 226 } else { 227 lastUpdate = now; 228 } 229 } 230 231 // 232 // AnimatedSprite 233 // 234 235 /** 236 * Utility method to convert a single delay to an array of delays having 237 * that value. 238 * 239 * @param delay 240 * The delay value. 241 * @param count 242 * The size of the array to create. 243 * 244 * @return An array of delays. 245 */ createDelays(final int delay, final int count)246 private static int[] createDelays(final int delay, final int count) { 247 final int[] delays = new int[count]; 248 Arrays.fill(delays, delay); 249 250 return delays; 251 } 252 253 /** 254 * Reset the animation back to specified frame, and reset the next frame 255 * time. 256 * 257 * @param index the default index 258 */ reset(int index)259 public void reset(int index) { 260 setIndex(index); 261 } 262 263 /** 264 * Set the frame index to a specific value. 265 * 266 * @param index 267 * The index to use. 268 * 269 * @throws ArrayIndexOutOfBoundsException 270 * If the index is less than 0 or greater than or equal to the 271 * number of frames. 272 */ setIndex(final int index)273 private void setIndex(final int index) { 274 if ((index < 0) || (index >= frames.length)) { 275 throw new ArrayIndexOutOfBoundsException("Invalid index: " + index); 276 } 277 278 this.index = index; 279 sprite = frames[index]; 280 281 /* 282 * Calculate the time into this frame 283 */ 284 cycleTime = 0; 285 286 for (int i = 0; i < index; i++) { 287 cycleTime += delays[i]; 288 } 289 290 lastUpdate = System.currentTimeMillis(); 291 } 292 293 /** 294 * Start the sprite animating. 295 * 296 * @see #stop() 297 */ start()298 public void start() { 299 animating = true; 300 } 301 302 /** 303 * Stop the sprite animating. This does not change the current frame. 304 * 305 * @see #start() 306 */ stop()307 public void stop() { 308 animating = false; 309 } 310 311 /** 312 * Update the current frame sprite. 313 */ update()314 private void update() { 315 final long now = System.currentTimeMillis(); 316 update((int) (now - lastUpdate)); 317 lastUpdate = now; 318 } 319 320 // 321 // Sprite 322 // 323 /** 324 * Create a sub-region of this sprite. <strong>NOTE: This does not use 325 * caching.</strong> 326 * 327 * @param x 328 * The starting X coordinate. 329 * @param y 330 * The starting Y coordinate. 331 * @param width 332 * The region width. 333 * @param height 334 * The region height. 335 * @param ref 336 * The sprite reference. 337 * 338 * @return A new sprite. 339 */ 340 @Override createRegion(final int x, final int y, final int width, final int height, final Object ref)341 public Sprite createRegion(final int x, final int y, final int width, 342 final int height, final Object ref) { 343 return new TileSprite(this, x, y, width, height, ref); 344 } 345 346 /** 347 * Draw the sprite onto the graphics context provided. 348 * 349 * @param g 350 * The graphics context on which to draw the sprite 351 * @param x 352 * The x location at which to draw the sprite 353 * @param y 354 * The y location at which to draw the sprite 355 */ 356 @Override draw(final Graphics g, final int x, final int y)357 public void draw(final Graphics g, final int x, final int y) { 358 update(); 359 360 if (sprite != null) { 361 sprite.draw(g, x, y); 362 } 363 } 364 365 /** 366 * Draws the image. 367 * 368 * @param g 369 * the graphics context where to draw to 370 * @param destx 371 * destination x 372 * @param desty 373 * destination y 374 * @param x 375 * the source x 376 * @param y 377 * the source y 378 * @param w 379 * the width 380 * @param h 381 * the height 382 */ 383 @Override draw(final Graphics g, final int destx, final int desty, final int x, final int y, final int w, final int h)384 public void draw(final Graphics g, final int destx, final int desty, 385 final int x, final int y, final int w, final int h) { 386 update(); 387 388 if (sprite != null) { 389 sprite.draw(g, destx, desty, x, y, w, h); 390 } 391 } 392 393 /** 394 * Get the height of the drawn sprite. 395 * 396 * @return The height in pixels of this sprite. 397 */ 398 @Override getHeight()399 public int getHeight() { 400 return height; 401 } 402 403 /** 404 * Get the sprite reference. This identifier is an externally opaque object 405 * that implements equals() and hashCode() to uniquely/repeatably reference 406 * a keyed sprite. 407 * 408 * @return The reference identifier, or <code>null</code> if not 409 * referencable. 410 */ 411 @Override getReference()412 public Object getReference() { 413 return reference; 414 } 415 416 /** 417 * Get the width of the drawn sprite. 418 * 419 * @return The width in pixels of this sprite. 420 */ 421 @Override getWidth()422 public int getWidth() { 423 return width; 424 } 425 426 /** 427 * Update the current frame sprite. 428 * <em>Idealy this would be called from a central time manager, 429 * instead of draw() like now.</em> 430 * 431 * @param delta 432 * The time since last update (in ms). 433 */ update(final int delta)434 private void update(final int delta) { 435 if (animating) { 436 cycleTime += delta; 437 if (loop) { 438 // let the non-looping sprites overflow, so that they get 439 // properly stopped 440 cycleTime %= duration; 441 } 442 443 while (cycleTime >= delays[index]) { 444 cycleTime -= delays[index]; 445 446 if (++index == frames.length) { 447 index = 0; 448 449 if (!loop) { 450 sprite = null; 451 animating = false; 452 return; 453 } 454 } 455 } 456 457 sprite = frames[index]; 458 } 459 } 460 } 461