best 2d engine recommendation for simple animation (frames with layers collage)

hi,

i am learning about 2d animation and i want to know which 2d engine is currently more suitable for what i need: a simple animation consisting of 160 frames (images).

a subset of those 160 frames are variable, about 40, they have to be constructed with combination of layers depending on configurable conditions (which can change dynamically but not with much frequency).

so, my questions is, for performing the following tasks in as high-speed as possible:

  • loading layers (images)
  • collaging them to create 160 frames (images) - processing layers, merging them as fast as possible to create each frame
  • writing frames on screen on an adjustable frames-per-second basis

what’s the best 2d engine recommendation to achieve this simple animation?

thank you very much!

You don’t need an engine to do that, it’s pretty simple. And if your images are not gigantic (which they shouldn’t be if there are 160 of them) ImageIO will work just fine to load them.

You can use my Animation and AnimationSet class for the animations if you want. It’s pretty basic but it sounds like it will work for you. It functions like a hash map, so you just specify the name of the animation you want and it will play. Similarly you give each animation an integer priority so it will automatically prioritize animations as you would want. So like someone’s standing pose would be your default animation with a priority of 0, and then walk would have a priority of 1 and could therefore replace it, and kick would be 2, etc.


//A Vector2 is simply two floats.
//The Model.TARGET_DELTA has to do with my adaptive framerate. So basically if the delta was at
//0.4 and the target delta was 0.2, it would update the animations twice as quickly.
//The ImageManager just loads and holds images/sprites.

/**
 * An Animaton contains an array of Textures that can be used for an Animation.
 * @author Eli Delventhal
 */
public class Animation
{
	/** The sprites that will be drawn. */
	private String[] sprites;
	
	/** The offsets at which to draw each sprite, automatically determined. */
	private Vector2[] offsets;
	
	/** The length of each frame in ticks. */
	private int frameLength;
	
	/** Whether or not this Animation loops. */
	private boolean loops;
	
	/** The priority of this Animation. */
	private int priority;
	
	/** How many ticks have gone by this frame. */
	private float ticksThisFrame;
	
	/** The frame number we're currently in. */
	private int frameNumber;
	
	/** Whether or not this Animation is finished. */
	private boolean isFinished;
	
	public Animation(String sprite, int prior)
	{
		this(new String[]{sprite},Integer.MAX_VALUE,true,prior);
	}
	
	/**
	 * Creates an Animation from an array of string references.
	 * @param images	An array of Strings to use as keys for the ImageManager.
	 * @param delay		The delay in ticks to have between frame changes.
	 * @param loop		Whether or not this Animation loops.
	 * @param prior		The priority of this Animation.
	 */
	public Animation(String[] images, int delay, boolean loop, int prior)
	{
		sprites = images;
		frameLength = delay;
		ticksThisFrame = 0;
		frameNumber = 0;
		isFinished = false;
		loops = loop;
		priority = prior;
		offsets = new Vector2[sprites.length];
	}
	
	/**
	 * Draws the current frame of this Animation at the specified position.
	 * Also uses the specified scale.
	 * In addition, advances the animation if appropriate.
	 */
	public void draw(float x, float y, Vector2 scale, float rotation, boolean flipped)
	{
		Sprite sprite = ImageManager.getSprite(sprites[frameNumber]);
		Vector2 offset = offsets[frameNumber];
		
		int width = (int) (sprite.getWidth()*scale.x);
		int height = (int) (sprite.getHeight()*scale.y);
		
		//Draw the current frame at the specified position and size.
		if (flipped && offset != null)
			sprite.draw((int)((x-offset.x)*scale.x),(int)((y-offset.y)*scale.y),width,height,rotation,flipped);
		else
			sprite.draw((int)(x*scale.x),(int)((y-offset.y)*scale.y),width,height,rotation,flipped);
	}
	
	/**
	 * Draws the current frame of this Animation at the specified position.
	 * In addition, advances the animation if appropriate.
	 */
	public void draw(float x, float y, float width, float height, float rotation, boolean flipped)
	{
		Sprite sprite = ImageManager.getSprite(sprites[frameNumber]);
		Vector2 offset = offsets[frameNumber];
		
		//Draw the current frame at the specified position and size.
		if (flipped && offset != null)
			sprite.draw((int)(x-offset.x),(int)(y-offset.y),(int)width,(int)height,rotation,flipped);
		else
			sprite.draw((int)x,(int)(y-offset.y),(int)width,(int)height,rotation,flipped);
	}
	
	public void advanceAnimation(float delta)
	{
		//Advance the frame if appropriate.
		ticksThisFrame += delta / Model.TARGET_DELTA;
		if (ticksThisFrame >= frameLength)
		{
			frameNumber++;
			ticksThisFrame = 0;
		}
		if (frameNumber >= sprites.length)
		{
			if (loops)
				frameNumber = 0;
			else
				frameNumber = sprites.length-1;
			isFinished = true;
		}
	}
	
	public void restart()
	{
		frameNumber = 0;
		isFinished = false;
		ticksThisFrame = 0;
	}
	
	/**
	 * Whether or not this animation has reached and finished its last frame.
	 * @return	True / false if it's finished.
	 */
	public boolean isFinished()
	{
		return isFinished;
	}
	
	public int getPriority()
	{
		return priority;
	}
	
	public boolean loops()
	{
		return loops;
	}
	
	public int getFrameNumber()
	{
		return frameNumber;
	}
	
	public void preloadAll()
	{
		for (int i = 0; i < sprites.length; i++)
			ImageManager.preloadSprite(sprites[i]);
	}
	
	/**
	 * Will calculate image offsets that should be used when it is flipped in order to make
	 * the animation look fluid. This must be called by whoever has the AnimationSet after
	 * preloadAll() (or the dependent images have been preloaded), and is therefore not
	 * automatically called.
	 */
	public void findOffsets()
	{
		//Find the smallest sprite (in both width and height).
		int minWidth = ImageManager.getSprite(sprites[0]).getWidth();
		int minHeight = ImageManager.getSprite(sprites[0]).getHeight();
		for (int i = 1; i < sprites.length; i++)
		{
			int wi = ImageManager.getSprite(sprites[i]).getWidth();
			int hi = ImageManager.getSprite(sprites[i]).getHeight();
			if (wi < minWidth)
				minWidth = wi;
			if (hi < minHeight)
				minHeight = hi;
		}
		
		//Now that we've got the smallest sprite in the animation,
		//adjust the offsets of the bigger sprites to match the difference.
		for (int i = 0; i < sprites.length; i++)
		{
			int wi = ImageManager.getSprite(sprites[i]).getWidth();
			int hi = ImageManager.getSprite(sprites[i]).getHeight();
			offsets[i] = new Vector2(wi-minWidth,hi-minHeight);
		}
	}
	
	public void manuallySetOffset(int position, Vector2 offset)
	{
		if (position >= 0 && position < offsets.length)
			offsets[position] = offset;
	}
	
	public float getTime()
	{
		return frameNumber * frameLength + ticksThisFrame;
	}
	
	public int getLength()
	{
		return frameLength * sprites.length;
	}
}

Cont’d.


import java.util.HashMap;
import java.util.Iterator;

//Entity is simply what I use to represent each thing that exists in the game. They all have an AnimationSet
//and owner logic is checked elsewhere.
/**
 * An AnimationSet contains a map of animations that can be referred to by string names.
 * @author Eli Delventhal
 *
 */
public class AnimationSet
{
	private Entity owner;
	private HashMap<String,Animation> animations;
	private String defaultAnimation;
	private Animation currentAnimation;
	private String currentAnimationName;
	
	public AnimationSet(Animation defaultAnim, String defaultAnimName)
	{
		animations = new HashMap<String,Animation>();
		animations.put(defaultAnimName, defaultAnim);
		defaultAnimation = defaultAnimName;
		currentAnimation = animations.get(defaultAnimation);
		currentAnimationName = defaultAnimation;
	}
	
	/**
	 * Draws this animation at the specified coords and width and stuff.
	 */
	public void draw(float x, float y, float width, float height, float rotation, boolean flipped)
	{
		currentAnimation.draw(x, y, width, height, rotation, flipped);
	}
	
	/**
	 * Sets an animation to the new passed one. It will only set
	 * if its priority works or the old one is done.
	 * @param animation	The animation to set.
	 */
	public boolean setAnimation(String animation)
	{
		boolean wasSet = false;
		Animation newAnimation = animations.get(animation);
		if (newAnimation != null
			&& (newAnimation != currentAnimation || currentAnimation.isFinished())
			&& (newAnimation.getPriority() >= currentAnimation.getPriority()
					|| (currentAnimation.isFinished() && !currentAnimation.loops())))
		{
			if (owner != null)
				owner.notifyAnimationFinished(currentAnimationName,currentAnimation.isFinished());
			currentAnimation = newAnimation;
			currentAnimation.restart();
			currentAnimationName = animation;
			wasSet = true;
		}
		
		if (newAnimation == currentAnimation)
			wasSet = true;
		
		return wasSet;
	}
	
	/**
	 * Sets the new animation no matter what.
	 * @param animation	The new animation.
	 */
	public void forciblySetAnimation(String animation)
	{
		Animation newAnimation = animations.get(animation);
		if (newAnimation != null)
		{
			currentAnimation = newAnimation;
			currentAnimationName = animation;
		}
	}
	
	public Animation getCurrentAnimation()
	{
		return currentAnimation;
	}
	
	public String getCurrentAnimationName()
	{
		return currentAnimationName;
	}
	
	public Animation getAnimation(String name)
	{
		return animations.get(name);
	}
	
	public void addAnimation(Animation anim, String name)
	{
		animations.put(name, anim);
	}
	
	public void preloadAll()
	{
		for (Iterator<Animation> i = animations.values().iterator(); i.hasNext();)
			i.next().preloadAll();
	}
	
	public void findOffsets()
	{
		for (Iterator<Animation> i = animations.values().iterator(); i.hasNext();)
			i.next().findOffsets();
	}
	
	public void setOwner(Entity e)
	{
		owner = e;
	}
	
	public float getAnimationTime()
	{
		return currentAnimation.getTime();
	}
	
	public int getAnimationLength()
	{
		return currentAnimation.getLength();
	}
	
	public void advanceCurrentAnimation(float delta)
	{
		currentAnimation.advanceAnimation(delta);
	}
}


//Example initialization:
AnimationSet set = new AnimationSet(new Animation(sheet+ANIM_WALK_0,0),"Stand");
		set.addAnimation(new Animation(sheet+ANIM_HIT_2, 500), "Death");
		
		set.addAnimation(new Animation(sheet+ANIM_HIT_2, 100), "Knocked");
		set.addAnimation(new Animation(sheet+ANIM_JUMP_0, 20), "Jump");
		
		String[] walkSprites = new String[]
		{
			sheet+ANIM_WALK_0,
			sheet+ANIM_WALK_1,
			sheet+ANIM_WALK_2
		};
		set.addAnimation(new Animation(walkSprites, 10, false, 1), "Walk");
		
		String[] punchSprites = new String[]
		{
			sheet+ANIM_PUNCH_0,
			sheet+ANIM_PUNCH_1,
			sheet+ANIM_PUNCH_2
		};
		set.addAnimation(new Animation(punchSprites, 6, false, 10), "Punch");
//etc.


//Example use, from Entity, who has a Vector2 position and a Vector2 size.
public void draw(Vector2 scale)
{
	animations.getCurrentAnimation().draw(position.x, position.y, scale, rotation, facingLeft);
	animations.advanceCurrentAnimation(Model.TARGET_DELTA);
}

Hope that helps, let me know if you have any questions.

hi, Demonpants,

thank you very much for your replies. i will try your proposal.

i have already done simpler animations before, using swing-timer and printing bufferedimage’s on a jframe from an array, the index of which i was increasing on each timer cycle. this scenary worked fine, but to get a good fps i had to set timer cycle to 0 seconds. that is, at the most faster as possible. this is a basic animation scenary in java-swing that was good then, but now i have much more processing to do, the images are more numerous and some of them (subset) have to be re-collaged sometimes.

taking a quick look at your code now, i have a little question: should i use it in an escenary like described, with swing-timer and so on? i would appreciate to see what’s your scenary proposal.

thank you!

That was using LWJGL and Display.sync(), but you can really use any timer method.

The reason you pass a float delta and there’s a target delta, however, is so the animation will catch up or slow down if the framrate gets abnormal. This also allows you to do fun stuff like add in slow motion and whatnot.

And if you’re using the Swing timer, you would indeed probably need to have it at a very low level in order to get a nice looking animation. I wouldn’t recommend using it… instead maybe use System.nanoTime() and Thread.yield, which is probably the best method if you’re not using LWJGL.

ok, i’m gonna learn lwjgl then, cause i never used it directly before, only indirectly by means of jme on other projects.

any suggestion about where to start with it; some good, clear and quick tutorial about lwjgl i can find here? i guess i would find that in lwjgl site, but if here there are good resources, that would be great.

thank you, once again, Demonpants.

btw, the answer to my question: “best 2d engine recommendation”, from your side i guess it might be: “use lwjgl api” right?

Check out Kevin Glass’s Space Invaders tutorial.

http://www.cokeandcode.com/spaceinvaderstutorial

Also, LWJGL is indeed what I use for 2D games, although it’s not necessary to do so. However it’s a good thing to learn if you’ve got the time for it.

hi, Demonpants,

yes, i was exactly checking that example yesterday! :slight_smile: thanks for confirming it to me.

i downloaded the source code for that game sample, but i had a problem: could not resolve the dependencies with net.java.games.jogl.*.

i downloaded api from jogl, but that package net.java.games.jogl doesn’t seem to exist anymore…

so i coulnt yet run the game from source.

any idea about this issue?

thank you, once again.

Get the source in the last section of that tutorial, where it is designed for LWJGL. You noticed that there were a few sections, right, starting with Java2D and going on up to LWJGL?

In fact, just go to lwjgl.org and download their latest build, and in the source I believe you’ll find the space invaders example all set up for you already.

Actually if you want to make your life a lot easier get hold of Slick which is a very handy library of useful 2D stuff for doing games.

Cas :slight_smile:

hi, cas,

thanks for your recommendation.

my goal is:

  • 2d engine
  • best performance
  • best manageability
  • best mantainability
  • best extensibility
  • most standard

i guess lwjgl may be a good candidate for this.

what’s your opinion?

thank you

Cas is right. Slick is literally just LWJGL with another level of detail added on. And it’s specifically meant for 2D.

ok, Demonpants, i will check it out :wink: thanks