Strip animation class

I’ve been reading through a book by David Brackeen recently, this is a modified version of his Animation class that I edited. Feel free to improve further ^^
You would still have to type out that monster Graphics2D.drawImage(5 000 000 arguments); statement when drawing the image though, but it removes a lot of the strip animation code from your rendering loop.

To draw the image you would do something like this

protected Animation mainChar = new Animation("/images/spritesheet.png", 50, 100, 1, 3, 100, 0, 2);

g2d.drawImage(mainChar.getImage(), mCharPosX, mCharPosY,
				mCharPosX+mainChar.getFrameWidth(),
				mCharPosY+mainChar.getFrameHeight(),
				mainChar.getFrameX(),
				mainChar.getFrameY(),
				mainChar.getFrameX()+mainChar.getFrameWidth(),
				mainChar.getFrameY()+mainChar.getFrameHeight(),
				null);

/**
 * @param String stripname - the name of the strip image
 * @param Integer framewidth - the width of an individual frame in pixels
 * @param Integer frameheight - the height of an individual frame in pixels
 * @param Integer rows - the number of rows the strip has
 * @param Integer columns - the number of columns the strip has
 * @param Long frametime - how long each frame will be shown
 * @param Integer startpos - This is which frame in the strip image the animation will start from
 * @param Integer endpos - You guessed it! The frame on the strip image the animation will end at and loop back to the startpos
 * 
 * @version 0.005
 *

 *Date created: 2011-06-13
 *

 *Date updated: 2011-06-14
 */
public class Animation {

	private int frameWidth, frameHeight;
	private int rows, cols;
	private int totFrames;
	private int currFrameIndex;
	protected int frameX, frameY;
	private long animTime;
	private long totalDuration;
	private long frameTime;
	private int startPos;
	private int endPos;
	protected boolean moving;
	private int updateCount;
	
	protected Image stripImage;
	
	
	//Creates a new empty Animation
	public Animation(String stripname, int framewidth, int frameheight, int row, int col, long frametime, int startpos, int endpos){
		frameWidth = framewidth;
		frameHeight = frameheight;
		rows = row;
		cols = col;
		totFrames = cols*rows;
		totalDuration = 0;
		frameTime = frametime;
		stripImage = loadImage(stripname);
		startPos = startpos;
		endPos = endpos;
		start();
	}
	
	private Image loadImage(String fileName){
		return new ImageIcon(getClass().getResource(fileName)).getImage();
	}
	
	/**
	 * Starts the animation over from the beginning
	 */
	public synchronized void start(){
		animTime = 0;
		currFrameIndex = startPos;
	}
	
	/**
	 * Updates this animation's current image(frame), if necessary
	 */
	public synchronized void update(long elapsedTime){
		updateCount++;
		
		if(totFrames > 1){
			animTime += elapsedTime;
			totalDuration += frameTime;
			
			if(updateCount >= frameTime/20){
				if(currFrameIndex >= endPos+1){
					currFrameIndex = startPos;
				}
			
				frameX = (currFrameIndex % cols) * frameWidth;
				frameY = (currFrameIndex / cols) * frameHeight;
			
				if(moving){
					currFrameIndex++;
				}
				updateCount = 0;
			}
		}
	}
	
	//Gets the image lol
	public synchronized int getIndex(){
		if(totFrames == 0){
			return 0;
		}else{
			return currFrameIndex;
		}
	}
	
	public Image getImage(){
		return stripImage;
	}
	
	public void setMoving(boolean b){ moving = b; }
	
	public synchronized int getFrameX(){ return frameX; }
	public synchronized int getFrameY(){ return frameY; }
	public synchronized int getFrameWidth(){ return frameWidth; }
	public synchronized int getFrameHeight(){return frameHeight; }
	
}

Hope this will help some of you ^^

Edit: Whoops, forgot to update the params

The update method is not reliable. Lots of thoughts:

  1. How does the updateCount relate to the frameTime? It makes no sense since the elapsed time could be anything. Instead you should update based on the animTime.
  2. totalDuration is pointless, I don’t see it used anywhere except just adding the frameTime, which is also not correct since the frameTime is not the time that has passed.
  3. You are not getting frameY correctly, it should be “frameY = (currFrameIndex % rows) * frameHeight;”
  4. The getIndex() method only needs 1 line: “return currFrameIndex;” since currFrameIndex is already 0 if the totFrames = 0.
  5. If you don’t want to have that massive drawImage call, your getImage method should only return the frame that is shown. You do this by creating a new BufferedImage and using that massive drawImage method there. All the user would have to do is call “g.drawImage(anim.getImage(),x,y,null);”

Thank you for the feedback ra4king, I will try to answer your questions the best I can ^^

  1. Each time the update method is called from the class that calls it, it adds one to updateCount. The FrameTime is how long you want each frame to display, I added the if(updateCount >= frameTime/20) to stop the animation from updating index once every 20 ms. The elapsedTime and animTime variables are leftovers from the original Animation class used in david brackeens book, I should actually remove them since they serves no purpose.

  2. Also a leftover from the original class, I have been sloppy removing em it seems.

  3. I believe the (currFrameIndex / cols) * frameHeight is indeed correct since it returns the correct frames for any animation I run. When I first learned strip animation it was by the formulas shown in this class. Might be multiple ways to get the coordinates in the strip though.
    /*Edit: I tried with frameY = (currFrameIndex % rows) * frameHeight, it couldn’t get the y position and kept looping row 1 on the strip image. */

  4. Thank you for pointing that out, I will change it in my class right away ^^

  5. I was thinking about building a draw method in the class and tuck that massive drawImage method there, but your idea sounds like a better solution. Quick question: Would there be any major performance difference if a use BufferedImages instead of regular Images? I have only used BufferedImages for backbuffring, I have never stored image files in them.

  1. Oh, your update method depends on a fixed game loop running at 50FPS. However, since game loops may not be reliable and the elapsed time could vary which would cause a non-fluid animation, it is best if you keep the elapsedTime and animTime variables and remove updateCount variable altogether:

public void update(long deltaTime) {
	if(totFrames <= 1)
		return;
	
	animTime = (animTime+deltaTime)%(totFrames*frameTime);
	
	currentFrameIndex = animTime/frameTime;
}

You could also remove the frameX and frameY variables, since the getImage() method will take care of the slicing of the whole image.
Take a look at the getImage() method at bullet #5.

  1. Aha, I just looked at the math again and you are correct, I was wrong. You get the column from the remainder and the row number from the division.

  2. You can’t instantiate an object of type Image. BufferedImage is the only class which you may instantiate directly. However it is more recommended to get a compatible image from GraphicsConfiguration:


GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
GraphicsConfiguration gc = gd.getDefaultConfiguration();

//There are two different createCompatibleImage methods:
BufferedImage compatibleImage = gc.createCompatibleImage(width,height);
//and
BufferedImage compatibleImage = gc.createCompatibleImage(width,height,transparency);

Make sure to look at the javadocs: http://ra4king.is-a-geek.net/javadocs/java/awt/GraphicsConfiguration.html

And now for an example of how your getImage() should look:


public BufferedImage getImage() {
	BufferedImage image = GraphicsEnvironment.getLocalGraphicsEnvironment()
											 .getDefaultScreenDevice()
											 .getDefaultConfiguration()
											 .createCompatibleImage(frameWidth,frameHeight,stripImage.getTransparency());
	
	int frameX = (currentFrameIndex % cols) * frameWidth;
	int frameY = (currentFrameIndex / cols) * frameHeight;
	
	Graphics g = image.getGraphics();
	g.drawImage(stripImage,0,0,frameWidth,frameHeight,frameX,frameY,frameX+frameWidth,frameY+frameHeight,null);
	g.dispose();
	
	return image;
}

I would recommend moving the loading of your image outside the animation class. Have the Animation class take an Image object. That way you can reuse the image object on different animations. It will save memory. This is my version of the Animation class from the same book.


public class LoopedAnimationStrip implements Animation {
   private boolean animating;
   private List<AnimatedFrame> frames = new ArrayList<AnimatedFrame>();
   private int totalFrames = 0;
   private int currentFrameIndex = 0;
   // total time of animation.
   private long totalDuration = 0;
   private long sequenceTime = 0;
   private int height;
   private int width;
   private int xOffset = 0;
   private int yOffset = 0;
   private String name;

   public LoopedAnimationStrip(Image image, long duration) {
      totalDuration = duration;
      totalFrames = 1;
      frames.add(new AnimatedFrame(image, duration));
      height = image.getHeight(null);
      width = image.getWidth(null);
      reset();
   }

   public synchronized void addFrame(Image image, long duration) {
      totalDuration += duration;
      totalFrames++;
      frames.add(new AnimatedFrame(image, totalDuration));
      if (height == 0 && width == 0) {
         height = image.getHeight(null);
         width = image.getWidth(null);
      }
   }

   public synchronized void reset() {
      currentFrameIndex = 0;
      sequenceTime = 0;
   }

   public synchronized void update(long elapseTime) {
      // Add the elapseTime to the sequence time and get the total sequence time.
      sequenceTime += elapseTime;
      // If the sequence time is greater than the total duration time,
      // reset the loop to the begining.
      if (sequenceTime >= totalDuration) {
         // get the "left over" time from the total time.
         sequenceTime = sequenceTime % totalDuration;
         currentFrameIndex = 0;
      }
      // if we have exceeded the frame's end time, advance to the next frame.
      while (sequenceTime > getAnimatedFrame(currentFrameIndex).getEndTime()) {
         currentFrameIndex++;
      }
   }

   public synchronized Image getImage() {
      return getAnimatedFrame(currentFrameIndex).getImage();
   }
   public void draw(int x, int y) {
      int xL = x + xOffset;
      int yL = y + yOffset;
      GameGFX.gfx().drawImage(getAnimatedFrame(currentFrameIndex).getImage(), xL, yL, null);
   }

   public Image getFrame(int i) {
      return ((AnimatedFrame) frames.get(i)).getImage();
   }

   private AnimatedFrame getAnimatedFrame(int i) {
      return (AnimatedFrame) frames.get(i);
   }

   public int getHeight() {
      return height;
   }

   public int getWidth() {
      return width;
   }

   public void setOffsets(int x, int y) {
      xOffset = x;
      yOffset = y;
   }

   public boolean isAnimating() {
      return true;
   }

   public void setName(String name) {
      this.name = name;
   }
}


I use an interface so I can have different implementation, such as an Animation that goes through the sequence once (for deaths and such). Also, I store my current graphics in a static object which is created from the main loop when I update the buffer.

My Animation class is quite similar with an addFrame method that takes an image and duration. However, your draw method is weird…you get your Graphics object through a singleton? That is a big no no. It is best if you pass the Graphics object to the draw method.

@ra4king
It’s a static function, not necessarily a singleton, I don’t see the relevant code to say one way or the other. I agree it looks a lot uglier than having the graphics object passed in tho.

@aazimon
You declared the getImage() function but didn’t use it in your draw, this function will help make the draw more readable.

There is no reason to typecast your frames.get calls to an AnimatedFrame because they already are AnimatedFrame’s.

Ah yes my mistake, it is a static method not a singleton, I didn’t look very closely. ;D

The draw method allows for an image to be larger then the given cell. Say the image is a tree, I set the base of the tree at a specific cell, and the top of the tree can extend outside the tree. Since it is at the specific cell, there is not collision detection for the branches at the top of the tree. The values passed in are the cell location which comes from the sprite, and the internal values are adjustments for the image itself.

Not the graphics object is not a singleton. It is taken from the canvas. I set it in the static element. I got the idea reading through the LWJGL. It just a convenience, so I don’t have to pass the Graphics around everywhere.


   private void gameRender() {
      if (GameGFX.dbImage == null) {
         GameGFX.dbImage = createImage(this.getWidth(), this.getHeight());
         if (GameGFX.dbImage == null) {
            throw new NullPointerException("Double buffer image is null.");
         }
      }
      // clear the background
      GameGFX.gfx().setColor(Color.white);
      GameGFX.gfx().fillRect(0, 0, this.getWidth(), this.getHeight());

      // draw game elements
      gameMap.draw();

      if (gameOver) {
         gameOverMessge();
      }
   }

The getImage method is old code, and I don’t use it anymore. Sorry I didn’t remove it.

@ra4king: Thanks for the suggestions, I will play around a bit with your code there and try to improve my animation class with it ^^ Also, lol@ “http://ra4king.is-a-geek.net”, I wish I had made a domain with such a name XD

@aazimon: When I created my animation class I tried to focus on the object logic I guess you could call it. You know all those examples in books:
“A dog object has a weight, so we’ll give it a weight.
private int weight;”

That’s the reason I stored the Image inside the animation class, however, your idea sounds better, and nicer to the memory, so I will probably change the Animation class a bit ^^

Thanks for the feedback everyone! ^^

I just meant turn

GameGFX.gfx().drawImage(getAnimatedFrame(currentFrameIndex).getImage(), xL, yL, null);

into

GameGFX.gfx().drawImage(getImage(), xL, yL, null);

I would advise keeping the getImage function, it will create a nice function to overwrite if you ever want to expand on your animations later, like supporting layered animations for example.

@aazimon
LWJGL and Java2D are two completely different systems. In LWJGL, you use static methods to access OpenGL functions because you only have 1 OpenGL “context” so to say. It doesn’t make sense to create multiple OpenGL objects.
However in Java2D, each Graphics object is unique. Setting the Graphics object in a static method not only is quite ugly and non-OO, it may produce synchronization issues where the Graphics object used is the wrong one.

@zoto
I see what you’re saying. Why didn’t I think of that.