It feels like there's quite a lot of code involved in order to manually build up an animation using the libGDX framework. In my specific case, I am creating a number of animations for a portrait view of a character. The character will do things like talk, blink, and laugh. There are a handful of different characters to worry about.
I would like to get some feedback on my approach. I'm hoping to simplify things as much as I can, but this is the best that I have come up with so far.
First, a texture atlas is created from a file. Then, the types from an enum are used to create a map of the types to the frames. I've removed all but one of the types just for brevity, but there is one for every single frame of animation.
PortraitType.java
public enum PortraitType {
GOBLIN_TALK01("goblinTalkRight01", 106),
GOBLIN_TALK02("goblinTalkRight02", 107),
GOBLIN_TALK03("goblinTalkRight03", 108),
GOBLIN_TALK04("goblinTalkRight04", 109),
GOBLIN_TALK05("goblinTalkRight05", 110),
GOBLIN_TALK06("goblinTalkRight06", 111),
GOBLIN_TALK07("goblinTalkRight07", 112),
GOBLIN_TALK08("goblinTalkRight08", 113),
GOBLIN_TALK09("goblinTalkRight09", 114),
GOBLIN_TALK10("goblinTalkRight10", 115),
GOBLIN_TALK11("goblinTalkRight11", 116);
public final String fileName;
public final int id;
private PortraitType(String fileName, int id) {
this.fileName = fileName;
this.id = id;
}
}
LibGDXGame.java
private Map<PortraitType, TextureRegion> loadPortraitTextures() {
Map<PortraitType, TextureRegion> textures = new HashMap<PortraitType, TextureRegion>();
TextureAtlas atlas = new TextureAtlas("rampartedPortraits01.atlas");
for (PortraitType type : PortraitType.values()) {
AtlasRegion region = atlas.findRegion(type.fileName);
TextureRegion textureRegion = region;
textures.put(type, textureRegion);
}
return textures;
}
After the map of all the frames is created, another map is created for each character that maps the type of animation to the animation itself.
PortraitAnimationType.java
public enum PortraitAnimationType {
NONE,
BLINK,
TALK,
TALK_BLINK,
LAUGH,
DEFEAT;
}
LibGDXGame.java
private Map<PortraitAnimationType, Animation> loadGoblinAnimations() {
Map<PortraitAnimationType, Animation> animations = new HashMap<PortraitAnimationType, Animation>();
animations.put(PortraitAnimationType.NONE, AnimationLoader.goblinNoneAnimation(this.portraitTextures));
animations.put(PortraitAnimationType.TALK, AnimationLoader.goblinTalkAnimation(this.portraitTextures));
animations.put(PortraitAnimationType.BLINK, AnimationLoader.goblinBlinkAnimation(this.portraitTextures));
animations.put(PortraitAnimationType.LAUGH, AnimationLoader.goblinLaughAnimation(this.portraitTextures));
animations.put(PortraitAnimationType.TALK_BLINK, AnimationLoader.goblinTalkBlinkAnimation(this.portraitTextures));
animations.put(PortraitAnimationType.DEFEAT, AnimationLoader.goblinDefeatAnimation(this.portraitTextures));
return animations;
}
There is a separate class that is only used for creating the actual animations. I realize that it is possible to create animations automatically using an atlas, however there are some limitations to that approach. By doing it manually like this, I can influence exactly what frames will be played, and can do things like add a pause to the animation by repeating a frame.
AnimationLoader.java
public static Animation goblinTalkAnimation(Map<PortraitType, TextureRegion> textures) {
TextureRegion[] regions = new TextureRegion[11];
regions[0] = textures.get(PortraitType.GOBLIN_TALK01);
regions[1] = textures.get(PortraitType.GOBLIN_TALK02);
regions[2] = textures.get(PortraitType.GOBLIN_TALK03);
regions[3] = textures.get(PortraitType.GOBLIN_TALK04);
regions[4] = textures.get(PortraitType.GOBLIN_TALK05);
regions[5] = textures.get(PortraitType.GOBLIN_TALK06);
regions[6] = textures.get(PortraitType.GOBLIN_TALK07);
regions[7] = textures.get(PortraitType.GOBLIN_TALK08);
regions[8] = textures.get(PortraitType.GOBLIN_TALK09);
regions[9] = textures.get(PortraitType.GOBLIN_TALK10);
regions[10] = textures.get(PortraitType.GOBLIN_TALK11);
return new Animation(1/8f, regions);
}
public static Animation goblinNoneAnimation(Map<PortraitType, TextureRegion> textures) {
TextureRegion[] noneRegions = new TextureRegion[6];
noneRegions[0] = textures.get(PortraitType.GOBLIN_TALK01);
noneRegions[1] = textures.get(PortraitType.GOBLIN_TALK01);
noneRegions[2] = textures.get(PortraitType.GOBLIN_TALK01);
noneRegions[3] = textures.get(PortraitType.GOBLIN_TALK01);
noneRegions[4] = textures.get(PortraitType.GOBLIN_TALK01);
noneRegions[5] = textures.get(PortraitType.GOBLIN_TALK01);
return new Animation(1/6f, noneRegions);
}
Finally, I've written a somewhat simple class that extends Image
that handles playing the animations. As long as this image is part of a stage, it will automatically play the animations without needing to be manually updated each frame. I also programmatically handle whether the face of the character is facing left or right.
AnimatedPortrait.java
public class AnimatedPortrait extends Image {
private float stateTime = 0;
private PortraitAnimationType currentAnimation;
private final Map<PortraitAnimationType, Animation> animations;
private boolean paused = false;
private boolean isRightSide;
public AnimatedPortrait(Map<PortraitAnimationType, Animation> animations, boolean isRightSide) {
super(animations.get(PortraitAnimationType.NONE).getKeyFrame(0));
this.animations = animations;
this.isRightSide = isRightSide;
this.currentAnimation = PortraitAnimationType.NONE;
}
public PortraitAnimationType getAnimation() {
return this.currentAnimation;
}
public void setAnimation(PortraitAnimationType type) {
this.stateTime = 0;
this.currentAnimation = type;
}
private void cycleAnimations(float delta) {
if (this.animations.get(currentAnimation).isAnimationFinished(this.stateTime)) {
int current = currentAnimation.ordinal();
current += 1;
if (current >= PortraitAnimationType.values().length) {
current = 0;
}
this.stateTime = 0;
this.currentAnimation = PortraitAnimationType.values()[current];
}
}
@Override
public void act(float delta) {
super.act(delta);
if (this.paused) {
return;
}
TextureRegion region = this.animations.get(this.currentAnimation).getKeyFrame(this.stateTime += delta, true);
if (this.isRightSide &&
!region.isFlipX()) {
region.flip(true, false);
}
((TextureRegionDrawable)getDrawable()).setRegion(region);
this.cycleAnimations(delta);
}
}
Here is a sample gif of the animation cycle for two characters:
And you can play an early demo of the game here:
PortraitType
type and use aGoblinTalkAnimation extends Animation
that just contains aList
ofAnimationFrame
s \$\endgroup\$