person coding at a computer
Enplug

This article describes how to create smooth transitions between Libgdx screens, using the Java Universal Tween Engine. This includes animations that show parts of both screens at the same time, e.g., sliding the new screen on top of the old one or scrolling in the new one while the old one scrolls out.

The technique takes snapshots of both the current and the next screen and animates them, so it works well as long as the next screen is not animating while the transition is in progress, which is usually the case.

The advantage of this method is that it doesn’t impose any particular implementation on the Screens. In our scenario we even use it to provide transitions between different games.

1. In your Game class, set a transition screen instead of setting the next screen directly.

[csharp]
public void setScreenWithTransition(Screen screen)
{
TransitionScreen transition = new TransitionScreen(currentScreen, screen, this);
setScreen(transition);
}
[/csharp]

2. The transition screen creates a snapshot of each screen by rendering it to the FrameBuffer and creating a sprite out of it.
This way we get a picture of each screen without displaying anything to the user yet.

[csharp]
FrameBuffer buffer = new FrameBuffer(Pixmap.Format.RGBA8888, (int)screenWidth, (int)screenHeight, false);

buffer.begin();
next.render(Gdx.graphics.getDeltaTime());
buffer.end();

nextScreenSprite = new Sprite(buffer.getColorBufferTexture());
nextScreenSprite.setPosition(screenWidth, 0);
[/csharp]

We’re using as an example an animation where the current screen serves as a static background while the new screen slides over it right to left.
Like this:
Rendering
For this particular animation, we set the original position of the nextScreenSprite to be just outside the right border of the visible area, and then use the Tween Engine to animate the movement.

[csharp]
Tween.to(nextScreenSprite, SpriteTween.POS_XY, 1.0f)
.target(0, 0)
.setCallback(backgroundAnimationTweenComplete)
.setCallbackTriggers(TweenCallback.COMPLETE)
.start(manager);
[/csharp]

When the tween completes the animation backgroundAnimationTweenComplete will be called and set the screen to the next one.

[csharp]
backgroundAnimationTweenComplete = new TweenCallback()
{
@Override
public void onEvent(int type, BaseTween<?> source)
{
game.setScreen(next);
}
};
[/csharp]

3. Then finally in render we draw both sprites on the screen

[csharp]
@Override
public void render(float delta)
{
manager.update(Gdx.graphics.getDeltaTime());

spriteBatch.begin();
currentScreenSprite.draw(spriteBatch);
nextScreenSprite.draw(spriteBatch);
spriteBatch.end();
}
[/csharp]

Here is the code for TransitionScreen. It could be optimized in different ways; creating a new screen each time might not be a good idea if your transitions happen frequently. You also will need to make sure that all graphics resources are disposed properly, but this version shows the idea.

[csharp]
public class TransitionScreen implements Screen
{
public final static float screenWidth = Gdx.graphics.getWidth();
public final static float screenHeight = Gdx.graphics.getHeight();

private Game game;
private Screen current;
private Screen next;

private FrameBuffer currentBuffer;
private FrameBuffer nextBuffer;

private SpriteBatch spriteBatch;
private TweenManager manager;
private TweenCallback backgroundAnimationTweenComplete;

private Sprite currentScreenSprite;
private Sprite nextScreenSprite;

public TransitionScreen(Screen current, Screen next, Game game)
{
this.current = current;
this.next = next;
this.game = game;
}

@Override
public void render(float delta)
{
manager.update(Gdx.graphics.getDeltaTime());

spriteBatch.begin();
currentScreenSprite.draw(_spriteBatch);
nextScreenSprite.draw(_spriteBatch);
spriteBatch.end();

}

@Override
public void show()
{
spriteBatch = new SpriteBatch();

manager = new TweenManager();
Tween.registerAccessor(Sprite.class, new SpriteTween());

backgroundAnimationTweenComplete = new TweenCallback()
{
@Override
public void onEvent(int type, BaseTween<?> source)
{
game.setScreen(next);
}
};

nextBuffer = new FrameBuffer(Pixmap.Format.RGBA8888, (int)screenWidth, (int)screenHeight, false);

nextBuffer .begin();
next.render(Gdx.graphics.getDeltaTime());
nextBuffer .end();

nextScreenSprite = new Sprite(nextBuffer .getColorBufferTexture());
nextScreenSprite.setPosition(screenWidth, 0);
nextScreenSprite.flip(false, true);

currentBuffer = new FrameBuffer(Pixmap.Format.RGBA8888, (int)screenWidth, (int)screenHeight, false);
currentBuffer .begin();
current.render(Gdx.graphics.getDeltaTime());
currentBuffer .end();

currentScreenSprite = new Sprite(currentBuffer.getColorBufferTexture());
currentScreenSprite.setPosition(0, 0);
currentScreenSprite.flip(false, true);
}

@Override
public void resume()
{
Tween.to(_nextScreenSprite, SpriteTween.POS_XY, 1.0f)
.target(0, 0)
.setCallback(backgroundAnimationTweenComplete)
.setCallbackTriggers(TweenCallback.COMPLETE)
.start(manager);
}
}
[/csharp]

Posted in: