Anyone familiar with Cocos2d will recognise the concept of Scene transitions. These are classes which compose an incoming and outgoing scene and apply an effect to them to a transition from one to the other.

I have updated my simple demo to apply slide-in scene transitions using the magic of the Java Universal Tween Engine (although I guess I could have used the stock actions). They use the TimeLineAction class I built in the previous demo to run two seperate TimeLines on the incoming and outgoing scenes (stages). I am quite pleased how elegant the code has turned out to be for these. The “settings” and “about” screens have bounce applied to them as they ease-out which gives a nice effect.

Game Test

Game Test

Implementation

First we define the base class which composes the two scenes and runs the in and out actions on the contents of the composed scenes. We don’t have to manually clean up any actions because we will always let them run until they are “done”.

public class TransitionScene extends Scene implements TweenCallback
{
	private boolean complete;

	private float inX;
	private float inY;
	private float outX;
	private float outY;

	private Scene inScene;
	private Scene outScene;
	private Group inSceneRoot;
	private Group outSceneRoot;

	private int durationMillis;
	private TweenEquation easeEquation;

	/**
	 * Enter handler makes a note of scene contents position.
	 *
	 */
	@Override
	public void enter()
	{
		this.complete = false;

		inX = inSceneRoot.x;
		inY = inSceneRoot.y;

		outX = outSceneRoot.x;
		outY = outSceneRoot.y;
	}

	/**
	 * Exit handler resets scene contents positions.
	 *
	 */
	@Override
	public void exit()
	{
		this.complete = true;

		inSceneRoot.x = inX;
		inSceneRoot.y = inY;

		outSceneRoot.x = outX;
		outSceneRoot.y = outY;
	}

	/**
	 * Draw both scenes as we animated contents.
	 *
	 */
	@Override
	public void draw()
	{
		// Move
		inSceneRoot.act(Gdx.graphics.getDeltaTime());
		outSceneRoot.act(Gdx.graphics.getDeltaTime());

		// Draw
		if (!complete)
		{
			outScene.draw();
		}
		inScene.draw();
	}

	/**
	 * Default transition handlers
	 */
	@Override
	public void onEvent(EventType eventType, BaseTween source)
	{
		switch (eventType)
		{
		case COMPLETE:
			Director.instance().setScene(this.inScene);
			break;
		default:
			break;
		}
	}

	/**
	 * Transition complete.
	 *
	 * @return The transition complete handler.
	 */
	public boolean isComplete()
	{
		return complete;
	}
}

So an actual transition class like “MoveInRTransitionScene” (Move In From the Right) looks like this below.

public class MoveInRTransitionScene extends TransitionScene
{
	private static Pool _pool = new Pool()
	{
		@Override
		protected MoveInRTransitionScene newObject()
		{
			MoveInRTransitionScene transitionScene = new MoveInRTransitionScene();

			return transitionScene;
		}
	};

	/**
	 * Create transition.
	 *
	 * @param inScene
	 *            The incoming scene.
	 * @param outScene
	 *            The outgoing scene.
	 * @param durationMillis
	 *            The duration of transition.
	 * @param easeEquation
	 *            The easing type.
	 */
	public static MoveInRTransitionScene $(Scene inScene, Scene outScene, int durationMillis, TweenEquation easeEquation)
	{
		MoveInRTransitionScene transitionScene = _pool.obtain();
		transitionScene.setInScene(inScene);
		transitionScene.setInSceneRoot(inScene.getRoot());
		transitionScene.setOutScene(outScene);
		transitionScene.setOutSceneRoot(outScene.getRoot());
		transitionScene.setDurationMillis(durationMillis);
		transitionScene.setEaseEquation(easeEquation);

		return transitionScene;
	}

	/**
	 * On entry build easing TimeLines.
	 *
	 */
	@Override
	public void enter()
	{
		super.enter();

	    // In Scene TimeLine.
		Timeline inTimeline = Timeline.createSequence()
				.beginSequence()
					.push(Tween.to(getInSceneRoot(), GroupAccessor.POSITION_XY, 0).target(getInScene().width(), 0).ease(getEaseEquation()))
					.push(Tween.to(getInSceneRoot(), GroupAccessor.POSITION_XY, getDurationMillis()).target(0, 0).ease(getEaseEquation()))
				.end()
				.start();

	    // In Scene TimeLine Action.
		TimelineAction inTimelineAction = TimelineAction.$(inTimeline);
		getInSceneRoot().action(inTimelineAction);

	    // Out Scene TimeLine.
		Timeline outTimeline = Timeline.createSequence()
				.beginSequence()
					.push(Tween.to(getOutSceneRoot(), GroupAccessor.POSITION_XY, 0).target(0, 0).ease(getEaseEquation()))
					.push(Tween.to(getOutSceneRoot(), GroupAccessor.POSITION_XY, getDurationMillis()).target(-getOutScene().width(), 0).ease(getEaseEquation()))
				    .addCallback(EventType.COMPLETE, this)
				.end()
				.start();

	    // Out Scene TimeLine Action.
		TimelineAction outTimelineAction = TimelineAction.$(outTimeline);
		getOutSceneRoot().action(outTimelineAction);
	}

	/**
	 * On exit tidy up.
	 *
	 */
	@Override
	public void exit()
	{
		super.exit();

		_pool.free(this);
	}
}

Some stuff to note here.

  • I am applying the trick of initialising the TimeLine by applying a tween of duration zero.
  • I am using a pool and cleaning it up in the node enter and exit methods. These methods are very similar to their counterparts in Cocos2d .
  • It is actually possible to draw multiple stages in this fashion which was a bit of an experiment and somewhat of a pleasant surprise when I tried it.

Usage

To actually use the transition you give references to the incoming and outgoing scenes along with duration and easing type. The transitions are pooled so they can be reused.

private void transitionToSettingsScene()
{
	Scene inScene = getSettingsScene();
	Scene outScene = this.director.getScene();

	TransitionScene transitionScene = MoveInLTransitionScene.$(inScene, outScene, DURATION_SETTINGS_TRANSITION, Bounce.OUT);

	this.director.setScene(transitionScene);
}

I have tidied a great deal of the code up but there are still a few funnies hanging around.

  • The LED font looks terrible on the Android emulator. My Nexus phone won’t talk to my Windows 7 machine since I had to replace the USB cable (never lend your phone to anyone) so I have no idea what this looks like on an actual phone. BTW this font is not free for commercial use. I am going to find one which is and replace it.
  • The ship rotation sometimes gets confused. Needs investigated.

The demo now has all of the features that were in my original example using my own framework. I am going to concentrate on fixing the above issues next.

Demo code here. The code is licensed under the same license as LibGDX itself with Apache 2.0.

Update #2
There was an issue with the first version of the code. It was leaking objects. I have fixed this and made some improvements to the life-cycle of the sprites.

Update #3
In the process of looking at something else I realised I was creating multiple instances of the SpriteBatch object by having Scene subclass from Stage. I have fixed this and added disposal of the batch contents on cleanup from the controller.