Tag: sprite

Sprite Animation in Java

In this posting I will be describing the process by which we can program a simple sprite-based animation.  A sprite is simply a two-dimensional graphical image that is integrated into a larger scene.  They are a popular tool for populating scenes in video games as they are easily manipulated separately from the rest of the scene.  To animate it we need a series of image frames for each individual sprite.  These are often stored collectively in a single image called a spritesheet.  When we draw our sprite we only draw one frame at a time.  Then as time passes we just switch between our frames to give the illusion that our sprite is moving.  I will be writing the sample code for this example in Java, but it should not be too hard to adapt it to other languages.

The first thing we want to do is define a structure to store each frame of our animation.  The purpose of this is to put some separation between the constant definitions that are shared between animations of a single type and the constantly updating data of the individual animation objects.  In it we will store three things:  the image associated with this frame, the number of the frame indicating where in the list of frames it belongs, and the duration of time that the animation should spend on this one frame.

// AnimationFrame.java
import java.awt.image.BufferedImage;
public class AnimationFrame {
    public final BufferedImage image;
    public final int frameNumber;
    public final int duration;

    public AnimationFrame(BufferedImage image, int frameNumber, int duration) {
        this.image = image;
        this.frameNumber = frameNumber;
        this.duration = duration;
    }
}

Next we can create our Animation class.  In it we store a list frames in the order that they should be played, the name of the current animation, the index of the current frame, the amount of time that needs to elapse before we switch to the next frame and a boolean variable to indicate whether or not the animation is currently playing.  Optionally we can also include a boolean variable to allow the animation to loop back the the beginning once the final  frame has been shown.

Most of the work done by the Animation object is done within our update method which is meant to be called every frame before we draw our Sprite to the screen.  It takes as a parameter the amount of time that has elapsed since our last update.  We subtract this time from our timeToNext value and if the result is negative we know that it is time to switch to the next frame.  then we increment our timeToNext variable by the duration of the next frame.  The rest is just a matter of providing methods that allow the client to start, stop and pause the animation.  If you are a fan of the Observer Pattern you can even include a custom listener class object whose functions are called when certain state changes are made within the Animation class.  We could make such an interface in just a few lines like so:

// AnimationListener.java
public interface AnimationListener {
    public void onComplete(); // gets called when animation reaches end (unless looping)
    public void onStart();    // gets called when the animation's start method is called
    public void onPlay();     // gets called when the animation's play method is called
    public void onPause();    // gets called when the animation's pause method is called
    public void onStop();     // gets called when the animation's stop method is called
}

One final note to make is that if you wish your program to be multithreaded, it may be a good idea to define any function calls that alter your animation’s state using the synchronized keyword.

// AnimationClip.java
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

public class AnimationClip {
    private final List<AnimationFrame> frames;
    private final List<AnimationListener> listeners;
    private String name;
    private int timeToNextFrame;
    private int currentFrame;
    private boolean playing;
    private final boolean loop;

    /**
     * Class constructor
     * @param name name of the clip
     * @param frames ordered list of frames
     * @param loop boolean value determining whether the clip should repeat itself
     */
    public AnimationClip(String name, ArrayList<AnimationFrame> frames, boolean loop) {
        this.frames = frames;
        this.name = name;
        this.loop = loop;
        listeners = new ArrayList<>();
        playing = false;
        currentFrame = 0;
        timeToNextFrame = (frames.size() > 0)? frames.get(currentFrame).duration : 0;
    }

    /**
     * Gets the name of the clip
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * Gets whether the clip is playing
     * @return true if clip is currently playing
     */
    public boolean isPlaying() {
        return playing;
    }

    /**
     * Gets the current frame of the animation
     * @return the frame image or null if there are no frames
     */
    public synchronized BufferedImage getFrame() {
        return (frames.size() > 0)? frames.get(currentFrame).image : null;
    }

    /**
     * Starts the clip from the beginning
     */
    public synchronized void start() {
        playing = true;
        currentFrame = 0;
        timeToNextFrame = (frames.size() > 0)? frames.get(currentFrame).duration : 0;
        for (AnimationListener listener : listeners) listener.onStart();
    }

    /**
     * Starts the clip from the current frame
     */
    public synchronized void play() {
        playing = true;
        for (AnimationListener listener : listeners) listener.onPlay();
    }

    /**
     * Pauses the clip
     */
    public synchronized void pause() {
        playing = false;
        for (AnimationListener listener : listeners) listener.onPause();
    }

    /**
     * Stops the clip and sets the current frame back to the beginning
     */
    public synchronized void stop() {
        playing = false;
        currentFrame = 0;
        timeToNextFrame = (frames.size() > 0)? frames.get(currentFrame).duration : 0;
        for (AnimationListener listener : listeners) listener.onStop();
    }

    /**
     * Updates the animation based on the amount of time that has elapsed.
     * Note: Will only update if the animation is currently playing
     * @param elapsed
     */
    public synchronized void update(long elapsed) {
        if (!playing) return;
        timeToNextFrame -= elapsed;
        if (timeToNextFrame < 0) {
            int numFrames = frames.size();
            ++currentFrame;
            if (currentFrame >= numFrames) {
                if (loop) currentFrame = 0;
                else {
                    // end of the animation, stop it
                    currentFrame = numFrames - 1;
                    timeToNextFrame = 0;
                    playing = false;
                    for (AnimationListener listener : listeners)
                        listener.onComplete();
                    return;
                }
            }
            AnimationFrame current = frames.get(currentFrame);
            timeToNextFrame += current.duration;
        }
    }

    /**
     * Adds a listener to the list of listeners
     * @param listener the listener to add
     */
    public void addAnimationListener(AnimationListener listener) {
        listeners.add(listener);
    }
}