While libgdx's UI system provides basic animation capabilities, game developers often need more flexible solutions. Universal Tween Engine is a pure Java animation library that can animate any object representable as float values. It integrates seamlessly with libgdx, Android, Swing, and other Java-based frameworks.
Project homepage: http://code.google.com/p/java-universal-tween-engine/
Core Integration Pattern
The essential step is implementing the TweenAccessor interface, which defines getValues and setValues methods. Register the accessor with the engine, queue animation definitions to the manager, and call update each frame.
Refer to the official Wiki for detailed usage: http://code.google.com/p/java-universal-tween-engine/wiki/GetStarted
Implementing Animations in libgdx Stage
All examples below target the Stage-based UI system.
Rather than creating separate accessors for each actor type, a single generic ActorAccessor handles all scene2d actors. The implementation supports three operation types: modifying X position only, modifying Y position only, and modifying both coordinates simultaneously.
public class ActorAccessor implements TweenAccessor<Actor> {
public static final int POSITION_X = 1;
public static final int POSITION_Y = 2;
public static final int POSITION_XY = 3;
@Override
public int getValues(Actor target, int tweenType, float[] values) {
switch (tweenType) {
case POSITION_X:
values[0] = target.getX();
return 1;
case POSITION_Y:
values[0] = target.getY();
return 1;
case POSITION_XY:
values[0] = target.getX();
values[1] = target.getY();
return 2;
default:
return -1;
}
}
@Override
public void setValues(Actor target, int tweenType, float[] values) {
switch (tweenType) {
case POSITION_X:
target.setX(values[0]);
break;
case POSITION_Y:
target.setY(values[0]);
break;
case POSITION_XY:
target.setPosition(values[0], values[1]);
break;
}
}
}
The getValues method must return the exact count of values being modified.
Simple Movement Example
The following demonstrates an interactive icon that moves to where the user taps. First, initialize the animation manager:
private TweenManager animationManager = new TweenManager();
Register the accessor for Image objects:
Tween.registerAccessor(Image.class, new ActorAccessor());
Handle touch input by converting screen coordinates to stage coordinates, then animate the target position with a bounce easing:
@Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
Vector3 stageCoords = new Vector3(screenX, screenY, 0);
stage.getCamera().unproject(stageCoords);
Tween.to(icon, ActorAccessor.POSITION_XY, 1.0f)
.ease(Bounce.OUT)
.target(stageCoords.x, stageCoords.y)
.start(animationManager);
return false;
}
Note that Stage coordinates differ from default Input coordinates, requiring the unproject conversion.
The Tween.to() call specifies the target actor, the accessor type, and duration in seconds. The target() method defines destination coordinates, while ease(Bounce.OUT) applies a bouncing animation curve. Available easing functions are documented at http://robertpenner.com/easing/easing_demo.html.
Update the animation manager each render cycle:
@Override
public void render() {
animationManager.update(Gdx.graphics.getDeltaTime());
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
}
Extended Animation Capabilities
Beyond basic movement, the accessor can control opacity, scale, rotation, and color. Supporting multiple animation types enables complex transitions:
public class ActorAccessor implements TweenAccessor<Actor> {
public static final int POS_XY = 1;
public static final int CENTER_POSITION = 2;
public static final int SCALE_XY = 3;
public static final int ROTATION = 4;
public static final int OPACITY = 5;
public static final int COLOR_RGB = 6;
@Override
public int getValues(Actor target, int tweenType, float[] values) {
switch (tweenType) {
case POS_XY:
values[0] = target.getX();
values[1] = target.getY();
return 2;
case CENTER_POSITION:
values[0] = target.getX() + target.getWidth() / 2;
values[1] = target.getY() + target.getHeight() / 2;
return 2;
case SCALE_XY:
values[0] = target.getScaleX();
values[1] = target.getScaleY();
return 2;
case ROTATION:
values[0] = target.getRotation();
return 1;
case OPACITY:
values[0] = target.getColor().a;
return 1;
case COLOR_RGB:
values[0] = target.getColor().r;
values[1] = target.getColor().g;
values[2] = target.getColor().b;
return 3;
default:
return -1;
}
}
@Override
public void setValues(Actor target, int tweenType, float[] values) {
switch (tweenType) {
case POS_XY:
target.setPosition(values[0], values[1]);
break;
case CENTER_POSITION:
target.setPosition(values[0] - target.getWidth() / 2,
values[1] - target.getHeight() / 2);
break;
case SCALE_XY:
target.setScale(values[0], values[1]);
break;
case ROTATION:
target.setRotation(values[0]);
break;
case OPACITY:
Color c = target.getColor();
c.set(c.r, c.g, c.b, values[0]);
break;
case COLOR_RGB:
c = target.getColor();
c.set(values[0], values[1], values[2], c.a);
break;
}
}
}
Since libgdx's Actor color field is immutable, extract, modify, and reassign it through the getter.
Sequencing Animations with Timeline
The Timeline feature handles sequential and parallel animation combinations. Sequential animations execute one after another:
Timeline.createSequence()
.beginSequence()
.push(Tween.to(element, ActorAccessor.POS_XY, 1.0f)
.target(100, 100))
.push(Tween.to(element, ActorAccessor.POS_XY, 1.0f)
.target(200, 20))
.endSequence()
.start(animationManager);
This moves the element to (100, 100), then continues to (200, 20).
Parallel animations run simultaneously:
Timeline.createParallel()
.beginParallel()
.push(Tween.to(element, ActorAccessor.CENTER_POSITION, 1.0f)
.target(stageCoords.x, stageCoords.y))
.push(Tween.to(element, ActorAccessor.ROTATION, 1.0f)
.target(360))
.push(Tween.to(element, ActorAccessor.SCALE_XY, 1.0f)
.target(1.5f, 1.5f))
.endParallel()
.start(animationManager);
This animates the element moving, rotating 360 degrees, and scaling up to 1.5x simultaneous.