mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 20:31:51 +09:00
Former-commit-id: 1647fa32ef6894bd7db44f741f07c2f4dcdf9054 Former-commit-id: 0e5810dcfbe1fd59b13e7cabe9f1e93c5542da2d
725 lines
18 KiB
Java
725 lines
18 KiB
Java
package org.newdawn.slick;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import org.lwjgl.Sys;
|
|
import org.newdawn.slick.util.Log;
|
|
|
|
/**
|
|
* A utility to hold and render animations
|
|
*
|
|
* @author kevin
|
|
* @author DeX (speed updates)
|
|
*/
|
|
public class Animation implements Renderable {
|
|
/** The list of frames to render in this animation */
|
|
private ArrayList frames = new ArrayList();
|
|
/** The frame currently being displayed */
|
|
private int currentFrame = -1;
|
|
/** The time the next frame change should take place */
|
|
private long nextChange = 0;
|
|
/** True if the animation is stopped */
|
|
private boolean stopped = false;
|
|
/** The time left til the next frame */
|
|
private long timeLeft;
|
|
/** The current speed of the animation */
|
|
private float speed = 1.0f;
|
|
/** The frame to stop at */
|
|
private int stopAt = -2;
|
|
/** The last time the frame was automagically updated */
|
|
private long lastUpdate;
|
|
/** True if this is the first update */
|
|
private boolean firstUpdate = true;
|
|
/** True if we should auto update the animation - default true */
|
|
private boolean autoUpdate = true;
|
|
/** The direction the animation is running */
|
|
private int direction = 1;
|
|
/** True if the animation in ping ponging back and forth */
|
|
private boolean pingPong;
|
|
/** True if the animation should loop (default) */
|
|
private boolean loop = true;
|
|
/** The spriteSheet backing this animation */
|
|
private SpriteSheet spriteSheet = null;
|
|
|
|
/**
|
|
* Create an empty animation
|
|
*/
|
|
public Animation() {
|
|
this(true);
|
|
}
|
|
|
|
/**
|
|
* Create a new animation from a set of images
|
|
*
|
|
* @param frames The images for the animation frames
|
|
* @param duration The duration to show each frame
|
|
*/
|
|
public Animation(Image[] frames, int duration) {
|
|
this(frames, duration, true);
|
|
}
|
|
|
|
/**
|
|
* Create a new animation from a set of images
|
|
*
|
|
* @param frames The images for the animation frames
|
|
* @param durations The duration to show each frame
|
|
*/
|
|
public Animation(Image[] frames, int[] durations) {
|
|
this(frames, durations, true);
|
|
}
|
|
|
|
/**
|
|
* Create an empty animation
|
|
*
|
|
* @param autoUpdate True if this animation should automatically update. This means that the
|
|
* current frame will be caculated based on the time between renders
|
|
*/
|
|
public Animation(boolean autoUpdate) {
|
|
currentFrame = 0;
|
|
this.autoUpdate = autoUpdate;
|
|
}
|
|
|
|
/**
|
|
* Create a new animation from a set of images
|
|
*
|
|
* @param frames The images for the animation frames
|
|
* @param duration The duration to show each frame
|
|
* @param autoUpdate True if this animation should automatically update. This means that the
|
|
* current frame will be caculated based on the time between renders
|
|
*/
|
|
public Animation(Image[] frames, int duration, boolean autoUpdate) {
|
|
for (int i=0;i<frames.length;i++) {
|
|
addFrame(frames[i], duration);
|
|
}
|
|
currentFrame = 0;
|
|
this.autoUpdate = autoUpdate;
|
|
}
|
|
|
|
/**
|
|
* Create a new animation from a set of images
|
|
*
|
|
* @param frames The images for the animation frames
|
|
* @param durations The duration to show each frame
|
|
* @param autoUpdate True if this animation should automatically update. This means that the
|
|
* current frame will be caculated based on the time between renders
|
|
*/
|
|
public Animation(Image[] frames, int[] durations, boolean autoUpdate) {
|
|
this.autoUpdate = autoUpdate;
|
|
if (frames.length != durations.length) {
|
|
throw new RuntimeException("There must be one duration per frame");
|
|
}
|
|
|
|
for (int i=0;i<frames.length;i++) {
|
|
addFrame(frames[i], durations[i]);
|
|
}
|
|
currentFrame = 0;
|
|
}
|
|
|
|
/**
|
|
* Create a new animation based on the sprite from a sheet. It assumed that
|
|
* the sprites are organised on horizontal scan lines and that every sprite
|
|
* in the sheet should be used.
|
|
*
|
|
* @param frames The sprite sheet containing the frames
|
|
* @param duration The duration each frame should be displayed for
|
|
*/
|
|
public Animation(SpriteSheet frames, int duration) {
|
|
this(frames, 0,0,frames.getHorizontalCount()-1,frames.getVerticalCount()-1,true,duration,true);
|
|
}
|
|
|
|
/**
|
|
* Create a new animation based on a selection of sprites from a sheet
|
|
*
|
|
* @param frames The sprite sheet containing the frames
|
|
* @param x1 The x coordinate of the first sprite from the sheet to appear in the animation
|
|
* @param y1 The y coordinate of the first sprite from the sheet to appear in the animation
|
|
* @param x2 The x coordinate of the last sprite from the sheet to appear in the animation
|
|
* @param y2 The y coordinate of the last sprite from the sheet to appear in the animation
|
|
* @param horizontalScan True if the sprites are arranged in hoizontal scan lines. Otherwise
|
|
* vertical is assumed
|
|
* @param duration The duration each frame should be displayed for
|
|
* @param autoUpdate True if this animation should automatically update based on the render times
|
|
*/
|
|
public Animation(SpriteSheet frames, int x1, int y1, int x2, int y2, boolean horizontalScan, int duration, boolean autoUpdate) {
|
|
this.autoUpdate = autoUpdate;
|
|
|
|
if (!horizontalScan) {
|
|
for (int x=x1;x<=x2;x++) {
|
|
for (int y=y1;y<=y2;y++) {
|
|
addFrame(frames.getSprite(x, y), duration);
|
|
}
|
|
}
|
|
} else {
|
|
for (int y=y1;y<=y2;y++) {
|
|
for (int x=x1;x<=x2;x++) {
|
|
addFrame(frames.getSprite(x, y), duration);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a new Animation where each frame is a sub-image of <tt>SpriteSheet</tt> ss.
|
|
* @param ss The <tt>SpriteSheet</tt> backing this animation
|
|
* @param frames An array of coordinates of sub-image locations for each frame
|
|
* @param duration The duration each frame should be displayed for
|
|
*/
|
|
public Animation(SpriteSheet ss, int[] frames, int[] duration){
|
|
spriteSheet = ss;
|
|
int x = -1;
|
|
int y = -1;
|
|
|
|
for(int i = 0; i < frames.length/2; i++){
|
|
x = frames[i*2];
|
|
y = frames[i*2 + 1];
|
|
addFrame(duration[i], x, y);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add animation frame to the animation.
|
|
* @param duration The duration to display the frame for
|
|
* @param x The x location of the frame on the <tt>SpriteSheet</tt>
|
|
* @param y The y location of the frame on the <tt>spriteSheet</tt>
|
|
*/
|
|
public void addFrame(int duration, int x, int y){
|
|
if (duration == 0) {
|
|
Log.error("Invalid duration: "+duration);
|
|
throw new RuntimeException("Invalid duration: "+duration);
|
|
}
|
|
|
|
if (frames.isEmpty()) {
|
|
nextChange = (int) (duration / speed);
|
|
}
|
|
|
|
frames.add(new Frame(duration, x, y));
|
|
currentFrame = 0;
|
|
}
|
|
|
|
/**
|
|
* Indicate if this animation should automatically update based on the
|
|
* time between renders or if it should need updating via the update()
|
|
* method.
|
|
*
|
|
* @param auto True if this animation should automatically update
|
|
*/
|
|
public void setAutoUpdate(boolean auto) {
|
|
this.autoUpdate = auto;
|
|
}
|
|
|
|
/**
|
|
* Indicate if this animation should ping pong back and forth
|
|
*
|
|
* @param pingPong True if the animation should ping pong
|
|
*/
|
|
public void setPingPong(boolean pingPong) {
|
|
this.pingPong = pingPong;
|
|
}
|
|
|
|
/**
|
|
* Check if this animation has stopped (either explictly or because it's reached its target frame)
|
|
*
|
|
* @see #stopAt
|
|
* @return True if the animation has stopped
|
|
*/
|
|
public boolean isStopped() {
|
|
return stopped;
|
|
}
|
|
|
|
/**
|
|
* Adjust the overall speed of the animation.
|
|
*
|
|
* @param spd The speed to run the animation. Default: 1.0
|
|
*/
|
|
public void setSpeed(float spd) {
|
|
if (spd > 0) {
|
|
// Adjust nextChange
|
|
nextChange = (long) (nextChange * speed / spd);
|
|
|
|
speed = spd;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the current speed of the animation.
|
|
*
|
|
* @return The speed this animation is being played back at
|
|
*/
|
|
public float getSpeed() {
|
|
return speed;
|
|
}
|
|
|
|
|
|
/**
|
|
* Stop the animation
|
|
*/
|
|
public void stop() {
|
|
if (frames.size() == 0) {
|
|
return;
|
|
}
|
|
timeLeft = nextChange;
|
|
stopped = true;
|
|
}
|
|
|
|
/**
|
|
* Start the animation playing again
|
|
*/
|
|
public void start() {
|
|
if (!stopped) {
|
|
return;
|
|
}
|
|
if (frames.size() == 0) {
|
|
return;
|
|
}
|
|
stopped = false;
|
|
nextChange = timeLeft;
|
|
}
|
|
|
|
/**
|
|
* Restart the animation from the beginning
|
|
*/
|
|
public void restart() {
|
|
if (frames.size() == 0) {
|
|
return;
|
|
}
|
|
stopped = false;
|
|
currentFrame = 0;
|
|
nextChange = (int) (((Frame) frames.get(0)).duration / speed);
|
|
firstUpdate = true;
|
|
lastUpdate = 0;
|
|
}
|
|
|
|
/**
|
|
* Add animation frame to the animation
|
|
*
|
|
* @param frame The image to display for the frame
|
|
* @param duration The duration to display the frame for
|
|
*/
|
|
public void addFrame(Image frame, int duration) {
|
|
if (duration == 0) {
|
|
Log.error("Invalid duration: "+duration);
|
|
throw new RuntimeException("Invalid duration: "+duration);
|
|
}
|
|
|
|
if (frames.isEmpty()) {
|
|
nextChange = (int) (duration / speed);
|
|
}
|
|
|
|
frames.add(new Frame(frame, duration));
|
|
currentFrame = 0;
|
|
}
|
|
|
|
/**
|
|
* Draw the animation to the screen
|
|
*/
|
|
public void draw() {
|
|
draw(0,0);
|
|
}
|
|
|
|
/**
|
|
* Draw the animation at a specific location
|
|
*
|
|
* @param x The x position to draw the animation at
|
|
* @param y The y position to draw the animation at
|
|
*/
|
|
public void draw(float x,float y) {
|
|
draw(x,y,getWidth(),getHeight());
|
|
}
|
|
|
|
/**
|
|
* Draw the animation at a specific location
|
|
*
|
|
* @param x The x position to draw the animation at
|
|
* @param y The y position to draw the animation at
|
|
* @param filter The filter to apply
|
|
*/
|
|
public void draw(float x,float y, Color filter) {
|
|
draw(x,y,getWidth(),getHeight(), filter);
|
|
}
|
|
|
|
/**
|
|
* Draw the animation
|
|
*
|
|
* @param x The x position to draw the animation at
|
|
* @param y The y position to draw the animation at
|
|
* @param width The width to draw the animation at
|
|
* @param height The height to draw the animation at
|
|
*/
|
|
public void draw(float x,float y,float width,float height) {
|
|
draw(x,y,width,height,Color.white);
|
|
}
|
|
|
|
/**
|
|
* Draw the animation
|
|
*
|
|
* @param x The x position to draw the animation at
|
|
* @param y The y position to draw the animation at
|
|
* @param width The width to draw the animation at
|
|
* @param height The height to draw the animation at
|
|
* @param col The colour filter to use
|
|
*/
|
|
public void draw(float x,float y,float width,float height, Color col) {
|
|
if (frames.size() == 0) {
|
|
return;
|
|
}
|
|
|
|
if (autoUpdate) {
|
|
long now = getTime();
|
|
long delta = now - lastUpdate;
|
|
if (firstUpdate) {
|
|
delta = 0;
|
|
firstUpdate = false;
|
|
}
|
|
lastUpdate = now;
|
|
nextFrame(delta);
|
|
}
|
|
|
|
Frame frame = (Frame) frames.get(currentFrame);
|
|
frame.image.draw(x,y,width,height, col);
|
|
}
|
|
|
|
/**
|
|
* Render the appropriate frame when the spriteSheet backing this Animation is in use.
|
|
* @param x The x position to draw the animation at
|
|
* @param y The y position to draw the animation at
|
|
*/
|
|
public void renderInUse(int x, int y){
|
|
if (frames.size() == 0) {
|
|
return;
|
|
}
|
|
|
|
if (autoUpdate) {
|
|
long now = getTime();
|
|
long delta = now - lastUpdate;
|
|
if (firstUpdate) {
|
|
delta = 0;
|
|
firstUpdate = false;
|
|
}
|
|
lastUpdate = now;
|
|
nextFrame(delta);
|
|
}
|
|
|
|
Frame frame = (Frame) frames.get(currentFrame);
|
|
spriteSheet.renderInUse(x, y, frame.x, frame.y);
|
|
}
|
|
|
|
/**
|
|
* Get the width of the current frame
|
|
*
|
|
* @return The width of the current frame
|
|
*/
|
|
public int getWidth() {
|
|
return ((Frame) frames.get(currentFrame)).image.getWidth();
|
|
}
|
|
|
|
/**
|
|
* Get the height of the current frame
|
|
*
|
|
* @return The height of the current frame
|
|
*/
|
|
public int getHeight() {
|
|
return ((Frame) frames.get(currentFrame)).image.getHeight();
|
|
}
|
|
|
|
/**
|
|
* Draw the animation
|
|
*
|
|
* @param x The x position to draw the animation at
|
|
* @param y The y position to draw the animation at
|
|
* @param width The width to draw the animation at
|
|
* @param height The height to draw the animation at
|
|
*/
|
|
public void drawFlash(float x,float y,float width,float height) {
|
|
drawFlash(x,y,width,height, Color.white);
|
|
}
|
|
|
|
/**
|
|
* Draw the animation
|
|
*
|
|
* @param x The x position to draw the animation at
|
|
* @param y The y position to draw the animation at
|
|
* @param width The width to draw the animation at
|
|
* @param height The height to draw the animation at
|
|
* @param col The colour for the flash
|
|
*/
|
|
public void drawFlash(float x,float y,float width,float height, Color col) {
|
|
if (frames.size() == 0) {
|
|
return;
|
|
}
|
|
|
|
if (autoUpdate) {
|
|
long now = getTime();
|
|
long delta = now - lastUpdate;
|
|
if (firstUpdate) {
|
|
delta = 0;
|
|
firstUpdate = false;
|
|
}
|
|
lastUpdate = now;
|
|
nextFrame(delta);
|
|
}
|
|
|
|
Frame frame = (Frame) frames.get(currentFrame);
|
|
frame.image.drawFlash(x,y,width,height,col);
|
|
}
|
|
|
|
/**
|
|
* Update the animation cycle without draw the image, useful
|
|
* for keeping two animations in sync
|
|
*
|
|
* @deprecated
|
|
*/
|
|
public void updateNoDraw() {
|
|
if (autoUpdate) {
|
|
long now = getTime();
|
|
long delta = now - lastUpdate;
|
|
if (firstUpdate) {
|
|
delta = 0;
|
|
firstUpdate = false;
|
|
}
|
|
lastUpdate = now;
|
|
nextFrame(delta);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update the animation, note that this will have odd effects if auto update
|
|
* is also turned on
|
|
*
|
|
* @see #autoUpdate
|
|
* @param delta The amount of time thats passed since last update
|
|
*/
|
|
public void update(long delta) {
|
|
nextFrame(delta);
|
|
}
|
|
|
|
/**
|
|
* Get the index of the current frame
|
|
*
|
|
* @return The index of the current frame
|
|
*/
|
|
public int getFrame() {
|
|
return currentFrame;
|
|
}
|
|
|
|
/**
|
|
* Set the current frame to be rendered
|
|
*
|
|
* @param index The index of the frame to rendered
|
|
*/
|
|
public void setCurrentFrame(int index) {
|
|
currentFrame = index;
|
|
}
|
|
|
|
/**
|
|
* Get the image assocaited with a given frame index
|
|
*
|
|
* @param index The index of the frame image to retrieve
|
|
* @return The image of the specified animation frame
|
|
*/
|
|
public Image getImage(int index) {
|
|
Frame frame = (Frame) frames.get(index);
|
|
return frame.image;
|
|
}
|
|
|
|
/**
|
|
* Get the number of frames that are in the animation
|
|
*
|
|
* @return The number of frames that are in the animation
|
|
*/
|
|
public int getFrameCount() {
|
|
return frames.size();
|
|
}
|
|
|
|
/**
|
|
* Get the image associated with the current animation frame
|
|
*
|
|
* @return The image associated with the current animation frame
|
|
*/
|
|
public Image getCurrentFrame() {
|
|
Frame frame = (Frame) frames.get(currentFrame);
|
|
return frame.image;
|
|
}
|
|
|
|
/**
|
|
* Check if we need to move to the next frame
|
|
*
|
|
* @param delta The amount of time thats passed since last update
|
|
*/
|
|
private void nextFrame(long delta) {
|
|
if (stopped) {
|
|
return;
|
|
}
|
|
if (frames.size() == 0) {
|
|
return;
|
|
}
|
|
|
|
nextChange -= delta;
|
|
|
|
while (nextChange < 0 && (!stopped)) {
|
|
if (currentFrame == stopAt) {
|
|
stopped = true;
|
|
break;
|
|
}
|
|
if ((currentFrame == frames.size() - 1) && (!loop) && (!pingPong)) {
|
|
stopped = true;
|
|
break;
|
|
}
|
|
currentFrame = (currentFrame + direction) % frames.size();
|
|
|
|
if (pingPong) {
|
|
if (currentFrame <= 0) {
|
|
currentFrame = 0;
|
|
direction = 1;
|
|
if (!loop) {
|
|
stopped = true;
|
|
break;
|
|
}
|
|
}
|
|
else if (currentFrame >= frames.size()-1) {
|
|
currentFrame = frames.size()-1;
|
|
direction = -1;
|
|
}
|
|
}
|
|
int realDuration = (int) (((Frame) frames.get(currentFrame)).duration / speed);
|
|
nextChange = nextChange + realDuration;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Indicate if this animation should loop or stop at the last frame
|
|
*
|
|
* @param loop True if this animation should loop (true = default)
|
|
*/
|
|
public void setLooping(boolean loop) {
|
|
this.loop = loop;
|
|
}
|
|
|
|
/**
|
|
* Get the accurate system time
|
|
*
|
|
* @return The system time in milliseconds
|
|
*/
|
|
private long getTime() {
|
|
return (Sys.getTime() * 1000) / Sys.getTimerResolution();
|
|
}
|
|
|
|
/**
|
|
* Indicate the animation should stop when it reaches the specified
|
|
* frame index (note, not frame number but index in the animation
|
|
*
|
|
* @param frameIndex The index of the frame to stop at
|
|
*/
|
|
public void stopAt(int frameIndex) {
|
|
stopAt = frameIndex;
|
|
}
|
|
|
|
/**
|
|
* Get the duration of a particular frame
|
|
*
|
|
* @param index The index of the given frame
|
|
* @return The duration in (ms) of the given frame
|
|
*/
|
|
public int getDuration(int index) {
|
|
return ((Frame) frames.get(index)).duration;
|
|
}
|
|
|
|
/**
|
|
* Set the duration of the given frame
|
|
*
|
|
* @param index The index of the given frame
|
|
* @param duration The duration in (ms) for the given frame
|
|
*/
|
|
public void setDuration(int index, int duration) {
|
|
((Frame) frames.get(index)).duration = duration;
|
|
}
|
|
|
|
/**
|
|
* Get the durations of all the frames in this animation
|
|
*
|
|
* @return The durations of all the frames in this animation
|
|
*/
|
|
public int[] getDurations() {
|
|
int[] durations = new int[frames.size()];
|
|
for (int i=0;i<frames.size();i++) {
|
|
durations[i] = getDuration(i);
|
|
}
|
|
|
|
return durations;
|
|
}
|
|
|
|
|
|
/**
|
|
* @see java.lang.Object#toString()
|
|
*/
|
|
public String toString() {
|
|
String res = "[Animation ("+frames.size()+") ";
|
|
for (int i=0;i<frames.size();i++) {
|
|
Frame frame = (Frame) frames.get(i);
|
|
res += frame.duration+",";
|
|
}
|
|
|
|
res += "]";
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* Create a copy of this animation. Note that the frames
|
|
* are not duplicated but shared with the original
|
|
*
|
|
* @return A copy of this animation
|
|
*/
|
|
public Animation copy() {
|
|
Animation copy = new Animation();
|
|
|
|
copy.spriteSheet = spriteSheet;
|
|
copy.frames = frames;
|
|
copy.autoUpdate = autoUpdate;
|
|
copy.direction = direction;
|
|
copy.loop = loop;
|
|
copy.pingPong = pingPong;
|
|
copy.speed = speed;
|
|
|
|
return copy;
|
|
}
|
|
|
|
/**
|
|
* A single frame within the animation
|
|
*
|
|
* @author kevin
|
|
*/
|
|
private class Frame {
|
|
/** The image to display for this frame */
|
|
public Image image;
|
|
/** The duration to display the image fro */
|
|
public int duration;
|
|
/** The x location of this frame on a SpriteSheet*/
|
|
public int x = -1;
|
|
/** The y location of this frame on a SpriteSheet*/
|
|
public int y = -1;
|
|
|
|
/**
|
|
* Create a new animation frame
|
|
*
|
|
* @param image The image to display for the frame
|
|
* @param duration The duration in millisecond to display the image for
|
|
*/
|
|
public Frame(Image image, int duration) {
|
|
this.image = image;
|
|
this.duration = duration;
|
|
}
|
|
|
|
/**
|
|
* Creates a new animation frame with the frames image location on a sprite sheet
|
|
* @param duration The duration in millisecond to display the image for
|
|
* @param x the x location of the frame on the <tt>SpriteSheet</tt>
|
|
* @param y the y location of the frame on the <tt>SpriteSheet</tt>
|
|
*/
|
|
public Frame(int duration, int x, int y) {
|
|
this.image = spriteSheet.getSubImage(x, y);
|
|
this.duration = duration;
|
|
this.x = x;
|
|
this.y = y;
|
|
}
|
|
}
|
|
}
|