com.github.mathiewz.slick.Animation Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of modernized-slick Show documentation
Show all versions of modernized-slick Show documentation
The main purpose of this libraryis to modernize and maintain the slick2D library.
The newest version!
package com.github.mathiewz.slick;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.StringJoiner;
import java.util.stream.IntStream;
import org.lwjgl.Sys;
/**
* 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 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 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 SpriteSheet ss.
*
* @param ss
* The SpriteSheet 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 SpriteSheet
* @param y
* The y location of the frame on the spriteSheet
*/
public void addFrame(int duration, int x, int y) {
if (duration == 0) {
throw new SlickException("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) {
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.isEmpty()) {
return;
}
timeLeft = nextChange;
stopped = true;
}
/**
* Start the animation playing again
*/
public void start() {
if (!stopped || frames.isEmpty()) {
return;
}
stopped = false;
nextChange = timeLeft;
}
/**
* Restart the animation from the beginning
*/
public void restart() {
if (frames.isEmpty()) {
return;
}
stopped = false;
currentFrame = 0;
nextChange = (int) (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) {
throw new SlickException("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
*/
@Override
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
*/
@Override
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
*/
@Override
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
*/
@Override
public void draw(float x, float y, float width, float height, Color col) {
if (frames.isEmpty()) {
return;
}
autoUpdateRendering();
Frame frame = getCurrentFrame();
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.isEmpty()) {
return;
}
autoUpdateRendering();
Frame frame = getCurrentFrame();
spriteSheet.renderInUse(x, y, frame.x, frame.y);
}
private void autoUpdateRendering() {
if (autoUpdate) {
long now = getTime();
long delta = now - lastUpdate;
if (firstUpdate) {
delta = 0;
firstUpdate = false;
}
lastUpdate = now;
nextFrame(delta);
}
}
/**
* Get the width of the current frame
*
* @return The width of the current frame
*/
public int getWidth() {
return getCurrentImage().getWidth();
}
/**
* Get the height of the current frame
*
* @return The height of the current frame
*/
public int getHeight() {
return getCurrentImage().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.isEmpty()) {
return;
}
autoUpdateRendering();
Frame frame = getCurrentFrame();
frame.image.drawFlash(x, y, width, height, col);
}
/**
* 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) {
return frames.get(index).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 getCurrentImage() {
return getCurrentFrame().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 || frames.isEmpty()) {
return;
}
nextChange -= delta;
while (nextChange < 0 && !stopped) {
if (currentFrame == stopAt || currentFrame == frames.size() - 1 && !loop && !pingPong) {
stopped = true;
} else {
nextFrame();
}
}
}
private void nextFrame() {
currentFrame = (currentFrame + direction) % frames.size();
if (pingPong) {
currentFrame = Math.max(currentFrame, 0);
if (currentFrame == 0) {
direction = 1;
if (!loop) {
stopped = true;
return;
}
} else if (currentFrame >= frames.size() - 1) {
currentFrame = frames.size() - 1;
direction = -1;
}
}
int realDuration = (int) (getCurrentFrame().duration / speed);
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 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) {
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() {
return IntStream.range(0, frames.size())
.map(this::getDuration)
.toArray();
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringJoiner joiner = new StringJoiner(",");
frames.forEach(frame -> joiner.add(String.valueOf(frame.duration)));
return MessageFormat.format("[Animation ({0}) {1}]", frames.size(), joiner);
}
/**
* 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 */
private Image image;
/** The duration to display the image frame */
private int duration;
/** The x location of this frame on a SpriteSheet */
private int x = -1;
/** The y location of this frame on a SpriteSheet */
private 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 SpriteSheet
* @param y
* the y location of the frame on the SpriteSheet
*/
public Frame(int duration, int x, int y) {
image = spriteSheet.getSubImage(x, y);
this.duration = duration;
this.x = x;
this.y = y;
}
}
private Frame getCurrentFrame() {
return frames.get(currentFrame);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy