JavaFX: Pivot and JavaFX

I have been trying out both Pivot and JavaFX as means to write a user interface for my proposed tool to route OSC messages to Midi (see previous post).

Rather than jump straight in with something which needs lots of features I decided to try something simple. My mavenize tool needs a front end so I thought that would be a good candidate. Mavenize takes a source and target directory and produces a ‘mavenized’ version of the source project in the target folder. It will create the appropriate folders and move all the files into the appropriate place. It is the dogs-bollocks and I have used it a lot to save me lots of boring graft. Stop being a mug and just download the command line version from here and better still there is a version with a proper UI coming in the next post.

Since I was once a big Flex user I decided to look at the two SDK’s available for Java which use a ‘flex’-like approach. These are Apache Pivot and JavaFX that is now part of the standard Java 7 SDK. Both of these have the UI layout and components defined in an XML file and the logic for the UI in the code. Also, these SDK’s embody the concept of a scene-graph for assembling the interface components which at least promises to deliver something more elegant than the somewhat old-skool collection of composite objects which make up Swing.

pivot1

I put a basic Pivot version together fairly quickly. I am not entirely sure why they need to have their own version of the collection classes, I am not going to speculate as I am sure there is a good reason. This put me off for a start. Does the world need another set of collection classes beyond those tried and trusted from the JDK? This is a desktop app and they were always going to have to use the JDK. As you can see from the screenshot below there was also a custom file chooser dialog. Somewhat frustrating.

pivot2

Also, as I was trying to write a desktop application I was a bit confused by the data model that the table view used. It turned out to be a list of maps which was fine but I couldn’t find a single example of how to use this in a programmatic way outside the bxml examples. I figured it out the end but was left with a sense that this is glaring gap in their otherwise exhaustive set of examples. If you try to use a framework and suddenly find yourself rushing towards an edge case that is a bad sign.

I am not going to spend much time on my Pivot version of the application. The framework is usable but pales into insignificance against JavaFX in terms of ease of use.

So, then I decided to take a look at JavaFX. I have to say I am pleasantly surprised. The scene graph and concept of data binding is very close to Flex. So much so that I think I might be a bit miffed if I was Adobe. There is a lot of (ahem) influence from Flex in the feature set. The scene builder tool is probably one of the nicest UI builder tools I have ever used with lots of cleverness to help you layout your user interface. Here is screenshot of the finished tool.

javafx1

There is a whole bunch of JavaFX goodness here where I have solved the issue of updating the table view from a background service and have a nice custom cell with selectable images. I put the last one in because I knew I would need it for my proposed OSC router application.

I am going to outline what I did in the next post.

LibGDX:Audio – Tonome OSC version

Ah sweet synthesized music! Here is a screenshot of my Tonome LibGDX application sending OSC Bundle messages to a instance of pure data. The pure data instance is broadcasting the OSC Bundle contents as Midi note out messages to a bank of Crystal synths running inside Podium. Each VST synth instance has been assigned a different Midi channel where each channel is associated with a different panel on the Tonome application. Neat!

Tonome_osc

This has been my project for the last couple of weeks.

I am going to tidy up the code somewhat and publish this next. I’ll expand a bit on the implementation as well. There is all sorts of stuff in the code like my own java OSC implementation, netty, pure data and google Guice would you believe.

LIBGDX – Demos Update to 0.9.6

I have finally updated all the LibGDX demos to the latest release version of LibGDX as of today (0.9.6).

Here are the links

Tonome – A tonematrix style sample player here.

Box2D with Scene2D – Bumpers demo here.

Box2D with Scene2D – Simple fixture demo here.

Game Test – Simple shoot-em-up with settings panel and slide transitions here.

The update was fairly painless with a few things to note:

Some of the demos have a maven pom file.Technically these are purely for illustration as they don’t pull the libGDX files from any repository just locally. I use them to generate the project files for the maven style layout (resources directory etc).

Note

These demos have all been updated, mavenized and are now up on Github. There has been a ton of fixes and improvements.

JAVA: MAVENIZE – Update

Whilst this component hasn’t exactly set the internets on fire can I give another shout out for my ‘mavenize‘ tool. This is a command line tool for ‘mavenizing’ java projects. I have found myself using this a fair bit when I am looking to bring older components into a existing maven java project and it can save a fair bit of faffing around.

Using it is as simple as:

java -jar mavenize-1.0.2.jar -iC:\Development\java\project\original -oC:\Development\java\project\mavenized

I have made some minor recent fixes. If you are interested in looking for a good example of the use of the org.apache.commons.io.DirectoryWalker class then you should take a look at the source code.

LibGDX – Audio – Tonome

Tonome is an matrix style sample player in the same vein as Tonematrix and Monome. This version is the result of a lot of attempts to get something which is usable. I think this is close but not quite there yet but I’m going to release the code as I’m tired of playing around with it and want to leave it for a while.

Source code here.

You can get a flavour of what’s involved from these screenshots below.

Tonome2

Features include:

Tonome2

I couldn’t decided on how to present the settings for volume and BPM so I have placed them into a fairly boring setting view for the moment. There should be some kind of slide in panel which has these on the main interface but I’ll put that off for the moment. You can exit the touch panels by ESC on PC and the ‘back’ key on the phone.

Tonome4

The assigned samples are fixed for the moment as this is still technically a demo/prototype. You can see in the code where they are inserted if you want to change them.

I have only tried this in the emulator for the moment as my Nexus phone won’t talk to my PC anymore 🙁 The android project is bundled with the source. The emulator gives me a laughable 4fps which is unusable so there may be an issue with running it on a real phone. If it works can someone tell me.

Note Choice

I studied tonematrix to see what notes the application used as they have been chosen specifically to go together. These are A3,B3,D3,E3,G3,A4,B4,D4..and so on.

Sample Choice

Tonome3

I spent ages trawling freesound to try and get some decent samples that sounded good together. I can’t say I have succeeded. This stuff is hard. I went through a ton of them before selecting the final choice. I’m still unhappy with how they sound. A future plan is to forego samples altogether and instead have OSC support but in the meantime the samples demonstrate the intention of how to build up a composition  from the different panels. There are two panels with ‘lead’ type sounds, one bass panel and one drum machine panel with some nice samples from a TR909.

There is a subtlety in the sample choice which I should point out as you are bound to notice it. The samples on the second (right) panel appear to play together in polyphony okay. There is always going to be a slight delay in the launching of each sample but if your lucky it shouldn’t be noticeable. The samples on the first panel, on the other hand, don’t seem to sit so well together once you start to bunch them up on the same bar. For the first 4 notes on my PC it plays fine but thereafter increasing the number of simultaneous notes makes a scratching sound appear. I am convinced this is something to do with the sample itself as the other samples don’t exhibit this behaviour. If I get a chance I will try and fix this somehow. You can always insert your own samples. If you get some decent ones and want to share then drop me a line.

OSC Support

I think there the core of a nice control panel for audio here. It would require proper message support for OSC messages but should be doable. I haven’t managed to find a suitable OSC  library (with the appropriate license) yet but I will keep looking.

Re-pitching Audio Samples

I tried to use SoundTouch to re-pitch a selected audio sample but could only get it to work on the desktop version as I put together a kind of hacky solution.

This actually worked really well but only for the Desktop since Android uses SoundPool to manage audio samples. You can find the code in a class called “AudioHelper”. As I say it never made the final cut but might prove useful if you are targeting a desktop application only.

Source code here. As usual Apache 2.0 license.

 

Update #1

I am in the process of implementing OSC support. When I get this working the plan is to show how to hook the panel up to MIDI. I’ll probably drop the sample playing aspect if this as well as make the UI look a bit nicer.

LibGDX : Scene2D – Box2d – Bumpers Demo

This demo encapsulates some of the Scene2D and Box2D integration I have been playing around with. Specifically collision detection and selection by touch/mouse.

Below is a screenshot with random numbered elements bouncing around inside the view. The walls are supposed to act like pinball bumpers so that when an element hits them they give the element a ‘kick’. The implementation for this is pretty experimental but seems to work well enough for illustration.

Box2D Bumpers

I have implemented a layer which lets you ‘grab’ an element body. When picked up  a selection animation is run around the selected element (the zooming white square). The selection mechanism is actually completely generalised in the sense that it broadcasts events which another layer responds to for the animation. Nice and loosely coupled.

Box2D Bumpers

When an element hits the wall it will bounce and make a clonk sound but when hits another element it will run a flashing animation at the points where the bodies touch and play a boing sound. This illustrates I am detecting what sort of collision is taking place. The layer which has the bouncing elements also has a pool of contact listeners which it can dish out to elements in the view. They are released from the pool when an element is removed (by clicking on it).

Box2D Bumpers

There is some neat stuff here (at least I think so).

These features will hopefully find their way into an actual application  at some point. I have a whole load of bits and pieces which are slowly coalescing into something.

I haven’t created the Android wrapper for this. It’s trivial to put this together and it saves me using my webspace which I am starting to fill at an alarming rate.

Demo code here.

There is a test application bundled as well which uses the LibGDX debug renderer.

This uses LibGDX 0.9.3.

All the code is licensed as Apache 2.0.

LibGDX: Scene2D with Box2D

Here is another LibGDX demo. This one uses Box2D to build a simple model of a box with a gear cog spinning in the centre. If you click on the screen a ball is released which will drop into the view and bounce off the gear and walls. I am no expert in Box2D but I think you may find this useful if you are trying to combine it and LibGDX Scene2D elements.

Game Test

This demo is heavily indebted to the ideas from the demo provided for the Box2D editor which I used to create the polygon model for the central cog.

Few things of note to point out:

Here is a screenshot of the debug rendered ‘world’.

Game Test

 

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

Demo code here.

LibGDX: Scene2D demo with Scene Transitions

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.

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 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.

LibGDX: Scene2d Demo with Java Universal Tween Engine

So I have modified my original demo somewhat to implement the Asteroid and ship pulse animations to use the Java Universal Tween Engine. I have also fixed some stuff and added some new features.

I put the star field behind the menu layer.

Game Test

I used Hiero to convert an existing free font to bring it into the application. BTW you can declare multiple fonts in the uiskin.json file i.e.

com.badlogic.gdx.graphics.g2d.BitmapFont:
{ default-font: { file: digital-7_60.fnt },
large-font: { file: digital-7_70.fnt }
},

I had to generate a second json file to hold a different sized default font because you can’t specify the font name for TextButton and I wanted a bigger size to match a phone touch-screen real-estate.

Taking a nod from the Tween engine I have created a proper callback action. This will take a count (<0 for infinite) and duration and repeatedly execute a call-back method. I could not use the completion handler in LibGDX actions as it resets the handler reference every time it get's called.

I have created two new action types TimelineAction and TweenAction. These encapsulate the running and management of the tweens. They are associated with an Actor and will animate it until complete. They manage the release of the tween back into the pool
(more about this further on).

The movement and rotation of the asteroids is implemented using the Timeline feature from the tween engine. Note the COMPLETE event callback to send an event which will trigger the removal of the element from the view.

int durationMove = (int) ((Math.random() * MAX_DURATION + MIN_DURATION) * 1000);
int rotate = (int) (Math.random() * 1440)-720;

Timeline timeline = Timeline.createParallel()
		.beginParallel()
		.push(Tween.to(this, ActorAccessor.POSITION_XY, durationMove).target(x, 0 - this.height).ease(Linear.INOUT))
		.beginSequence()
		.push(Tween.to(this, GroupAccessor.ROTATION, 0).target(0))
		.push(Tween.to(this, GroupAccessor.ROTATION, durationMove).target(rotate))
		.end()
		.end()
		.addCallback(EventType.COMPLETE, this)
		.start();

TimelineAction timelineAction = TimelineAction.$(timeline);

action(timelineAction);

Something of interest to note here. I want the rotation to last the length of the screen traversal and I want it to start from zero. I could set the “rotation” value of the sprite explicitly at the start of each run or I can do a non-Tween which will initially rotate to zero. This is what I have done here. There doesn’t seem to be a way to set the initial value in the Timeline/Tween setup itself any other way although I am open to suggestions.

The pulse is a straight Tween. Again, note the COMPLETE event callback to send an event which will trigger the removal of the element from the view.

Tween tween = Tween.to(this, ActorAccessor.POSITION_XY, duration)
                    .target(x, height)
                    .ease(Quad.OUT)
                    .addCallback(EventType.COMPLETE, this)
                    .start();
tweenAction = TweenAction.$(tween);

action(tweenAction);

I have extended the Actor and Group classes to manage the clean up of actions which are prematurely removed from the view. What I mean by this is – imagine a pulse is moving up the screen, animated by TweenAction. In normal operation the animation would complete, the “done” flag would get set and the “act” method would call the “finish” method on the associated actions which will put the respective actions back in the pool. If the pulse hits an Asteroid on the way up we should probably set the “done” flag on the PulseSprite and let it clean up itself but because of the way I am pooling these items I am having to override the “remove” method and force the “finish” method on all associated actions myself. This is a work in progress and I am still having a think about the best approach here. It works for now and you should have no leaking objects.

Demo code here.

LibGDX: Example of Scene2D application with Event Handlers.

Note: This demo has been updated considerably since this original post. The common elements have been factored out into their own library and the application mavenised to provide an easier build process. You can find all the details here.

This is the original post so some of the details have changed…..

I have written a simple demo using LibGDX and some of the ideas I had kicking around from a previous demo. I am quite pleased how this has turned out. I’m going to join the chorus of praise for LibGDX and say it is a lot easier to use than I thought it would be. I was putting off ditching my own framework and adopting it but it was actually fun to use and has an impressive amount of  knowledge behind it. The ability to develop on the desktop and then run it on Android is a killer feature.

This demo is a kind of mixture of framework items and source artefacts to implement a noddy shoot-em-up. The point is to show off how to assemble something which uses mostly the Scene2d classes.

Here is the application running on the desktop.

Game Test

So what is interesting about this demo?

This is a bit of an experiment to see if I could take what I had and make it scale up a bit more. The key concept is as follows:

The demo code is here.

Demo in detail



Some of the source code below is abbreviated, use the source download rather than cutting and pasting if you are interested in the source.


The Frame animation Sprite implements an Actor which takes spritesheet and implements straightforward animation. You cannot run actions like rotate on this as Actor does not have this attribute. You can make it move though. This lifts some of the example code from the LibGDX samples and reworks it into an Actor.

public class FrameSprite extends Actor
{
	private TextureRegion[] frames;

	private Animation animation;

	private TextureRegion currentFrame;

	private float stateTime;
	private boolean looping;

	public FrameSprite(TextureRegion texture, int rows, int cols, float frameDuration, boolean looping)
	{
		this.looping = looping;

		int tileWidth = texture.getRegionWidth() / cols;
		int tileHeight = texture.getRegionHeight() / rows;
		TextureRegion[][] tmp = texture.split(tileWidth, tileHeight);
		frames = new TextureRegion[cols * rows];

		int tileWidth = texture.getRegionWidth() / cols;
		int tileHeight = texture.getRegionHeight() / rows;
		TextureRegion[][] tmp = texture.split(tileWidth, tileHeight);
		frames = new TextureRegion[cols * rows];

		int index = 0;
		for (int i = 0; i < rows; i++)
		{
			for (int j = 0; j < cols; j++)
			{
				frames[index++] = tmp[i][j];
			}
		}

		width = tileWidth;
		height = tileHeight;

		animation = new Animation(frameDuration, frames);
		stateTime = 0f;

	}

	/**
	 * Reset animation.
	 *
	 * You can use this to ensure the animation plays from the start again. It's
	 * handy if you have one-shot animations like explosions but you are using
	 * re-usable Sprites. You must reset the animation to ensure the animation
	 * plays back again.
	 */
	public void resetAnimation()
	{
		stateTime = 0;
	}

	/**
	 * Check to see if animation finished.
	 *
	 * @param stateTime
	 *
	 * @return True if finished.
	 */
	public boolean isAnimationFinished()
	{
		return animation.isAnimationFinished(stateTime);
	}

}

The Animated Sprite class composes the FrameSprite to allow actions such as rotate and scale to work upon it.

public class AnimatedSprite extends Group
{
	private FrameSprite frameSprite;

	/**
	 * Create sprite.
	 *
	 * @param texture
	 *            The animation texture.
	 * @param rows
	 *            The animation texture rows.
	 * @param cols
	 *            The animation texture rows.
	 * @param frameDuration
	 *            The animation frame duration.
	 */
	public AnimatedSprite(TextureRegion textureRegion, int rows, int cols, float frameDuration)
	{
		frameSprite = new FrameSprite(textureRegion, rows, cols, frameDuration, true);

		this.width = frameSprite.width;
		this.height = frameSprite.height;

		addActor(frameSprite);
	}

	@Override
	public Actor hit(float x, float y)
	{
	    return super.hit(x, y);
	}
}

A scene is a Stage whichs maps to the size of the view and implements an InputMultiplexer to which input events are routed. When the Director makes a scene active it sets the chosen Scene multiplexer as the destination for all input events. Note also “entry” and “exit” scene methods. These get called when a scene is activated and removed respectively.

public class Scene extends Stage implements Node
{
	private static final int DEFAULT_LAYER_CAPACITY = 10;

	/**
	 * Associated input multiplexer.
	 */
	private InputMultiplexer inputMultiplexer;

	/**
	 * Stage elements as nodes. We need this so we can call enter and exit on
	 * actors in order to manage registration and de-registration of event
	 * handlers.
	 */
	private Array nodes;

	public Scene()
	{
		super(Director.instance().getWidth(), Director.instance().getHeight(), Director.instance().isStretch());

		inputMultiplexer = new InputMultiplexer(this);

		nodes = new Array(DEFAULT_LAYER_CAPACITY);
	}

	/**
	 * Get input multiplexer.
	 *
	 * @return The input multiplexer.
	 */
	public InputMultiplexer getInputMultiplexer()
	{
		return inputMultiplexer;
	}

	/**
	 * Add scene layer ensuring it adopts the same size as the owning scene.
	 *
	 * Note layer in nodes list.
	 *
	 * @param group
	 */
	public void addLayer(Layer layer)
	{
		layer.width = this.width;
		layer.height = this.height;

		nodes.add(layer);

		super.addActor(layer);
	}

	/**
	 * Handle pre-display tasks.
	 *
	 */
	@Override
	public void enter()
	{
		int size = nodes.size;
		for (int i = 0; i < size; i++)
		{
			nodes.get(i).enter();
		}
	}

	/**
	 * Handle post-display tasks.
	 *
	 */
	@Override
	public void exit()
	{
		int size = nodes.size;
		for (int i = 0; i < size; i++)
		{
			nodes.get(i).exit();
		}
	}

}

The Director maintains a note of the chosen size. It handles setting the current scene, running the render “tick”, updating the event mechanism and updating current actions associated with the active scene. It also handles recalculating the scaling offsets for touch/mouse events if you stretch the size of the screen.

public class Director
{
	private static final boolean DEFAULT_STRETCH = true;

	private static Director instance = null;

	private ActorEventSource eventSource;

	private int width;
	private int height;
	private boolean stretch;

	private Scene scene;

	private float scaleFactorX;
	private float scaleFactorY;

	/**
	 * Access singleton instance
	 *
	 * @return instance of class
	 */
	public synchronized static Director instance()
	{
		if (instance == null)
		{
			instance = new Director();
		}

		return instance;
	}

	/**
	 * Create reference to command pipeline.
	 *
	 */
	public Director()
	{
		scene = null;

		stretch = DEFAULT_STRETCH;

		// Latch onto event source.
		eventSource = ActorEventSource.instance();

		// These are scale factors for adjusting touch events to the actual size
		// of the view-port.
		scaleFactorX = 1;
		scaleFactorY = 1;
	}

	/**
	 * Update main loop.
	 *
	 */
	public void update()
	{
		// Update events.
		eventSource.update();

		// Update View
		Gdx.gl.glClearColor(0, 0, 0, 1);
		Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

		if (scene != null)
		{
			scene.act(Gdx.graphics.getDeltaTime());

			scene.draw();
		}
		else
		{
			Gdx.app.log("WTF!", "No scene");
		}
	}

	/**
	 * Set the current scene.
	 *
	 * @param scene
	 */
	public synchronized void setScene(Scene scene)
	{
		// If already active scene...
		if (this.scene != null)
		{
			// Exit stage left..
			this.scene.exit();
		}

		this.scene = scene;

		if (this.scene != null)
		{
			// Enter stage right..
			this.scene.enter();

			// NOTE: Route input events to the scene.
			Gdx.input.setInputProcessor(scene.getInputMultiplexer());
		}

	}

	/**
	 * Adjust the scale factors for touch/mouse events to match the size of the
	 * stage.
	 *
	 * @param width
	 *            The new width.
	 * @param height
	 *            The new height.
	 */
	public void recalcScaleFactors(int width, int height)
	{
		scaleFactorX = (float) this.width / width;
		scaleFactorY = (float) this.height / height;
	}
}

A Layer is a holder which implements InputProcessor and the default “enter” and “exit” handlers.

A scene can hold multiple layers which may or may not receive input. Here is the main GameScene.

public class GameScene extends Scene
{
	private Layer gameLayer;
	private Layer shipLayer;
	private Layer pulseLayer;
	private Layer asteroidLayer;
	private Layer explosionLayer;
	private Layer statsLayer;

	/**
	 * Main game scene.
	 *
	 */
	public GameScene()
	{
		// ---------------------------------------------------------------
		// Control layer
		// ---------------------------------------------------------------
		gameLayer = new GameLayer(this.width, this.height);

		getInputMultiplexer().addProcessor(gameLayer);

		addLayer(gameLayer);

		// ---------------------------------------------------------------
		// Pulse layer.
		// ---------------------------------------------------------------
		pulseLayer = new PulseLayer(this.width, this.height);

		addLayer(pulseLayer);

		// ---------------------------------------------------------------
		// Asteroid Layer
		// ---------------------------------------------------------------
		asteroidLayer = new AsteroidLayer(this.width, this.height);

		addLayer(asteroidLayer);

		explosionLayer = new ExplosionLayer(this.width, this.height);

		addLayer(explosionLayer);

		// ---------------------------------------------------------------
		// Ship layer
		// ---------------------------------------------------------------
		shipLayer = new ShipLayer(this.width, this.height);

		getInputMultiplexer().addProcessor(shipLayer);

		addLayer(shipLayer);

		// ---------------------------------------------------------------
		// Statistics layer
		// ---------------------------------------------------------------
		statsLayer = new StatsLayer(this.width, this.height);

		addLayer(statsLayer);
	}

	public Layer getShipLayer()
	{
		return shipLayer;
	}

	public Group getPulseLayer()
	{
		return pulseLayer;
	}

	public Group getAsteroidLayer()
	{
		return asteroidLayer;
	}

}

Implemented layers register themselves with the event mechanism when visible and de-register when they are no longer within an active scene. This is to avoid routing events to elements which do not need them and also means you can (technically) generate new instances without having to get weird behaviour where events are getting sucked up elsewhere. NOTE: I learnt the hard way to keep this stuff as simple as possible from my previous attempt at this demo. I was routing everything through source and observers including input events and it was a nightmare to debug.

Events

Lets look into the event handling more closely.

The  AsteroidLayer has a delayed callback which when triggered launches an Asteroid from the top of the screen by generating a random position and running the associated actions for the sprite.
/**
 * Launch a sprite from pool (if one available).
 *
 */
private void handleStartAsteroid()
{
	// Get free sprite from pool.
	AsteroidSprite sprite = pool.obtain();

	// Set running.
	sprite.run();

	// Add to view.
	addActor(sprite);
}

Once the asteroid is “running” it too has a periodic call back which checks to see if it has collided with anything and if so send an event to indicate which kind of collision.

Listening for collision events are the layers:

Lets take a look at an event handler for the AsteroidLayer:

/**
 * Handle events.
 *
 */
@Override
public boolean handleEvent(ActorEvent event)
{
	boolean handled = false;

	switch (event.getId())
	{
		case GameActorEvents.EVENT_START_ASTEROID:
			handleStartAsteroid();
			handled = true;
			break;
		case GameActorEvents.EVENT_COLLISION_ASTEROID_PULSE:
			handlePulseCollision(event.getActor());
			handled = true;
			break;
		case GameActorEvents.EVENT_END_ASTEROID:
			handleEndAsteroid(event.getActor());
			handled = true;
			break;
		default:
			break;
	}

	return handled;
}

/**
 * Launch a sprite from pool (if one available).
 *
 */
private void handleStartAsteroid()
{
	// Get free sprite from pool.
	AsteroidSprite sprite = pool.obtain();

	// Set running.
	sprite.run();

	// Add to view.
	addActor(sprite);
}

/**
 * Handle pulse hitting asteroid.
 *
 * @param actor
 */
private void handlePulseCollision(Actor actor)
{
	// Run explosion sprite
	this.director.sendEvent(GameActorEvents.EVENT_START_ASTEROID_EXPLOSION, actor);

	handleEndAsteroid(actor);

	// Update score.
	GameStats.instance().incScore();
}

/**
 * Handle controller events.
 *
 * @param event
 *            The actor event.
 *
 * @return True if handled.
 */
private void handleEndAsteroid(Actor source)
{
	// Free this from pool so it can be re-used.
	pool.free((AsteroidSprite) source);

	// Remove from view.
	removeActor(source);
}

You can see that as events are routed to the layer it responds accordingly. If the handler returns true the event will be removed from the event list and will not be passed on to any any other handlers.

Actions

Actions are used to run the sprite movements. As an example lets examine the PulseSprite, which is a simple animation, it has to move from the ship position to the top of the screen.


/**
 * Run actions for actor.
 *
 * @param x
 * @param y
 *
 * @return The main actions object.
 */
public void run(float x, float y)
{
	// ---------------------------------------------------------------
	// BECAUSE THE 'ACTION' METHOD DOES NOT CLEAR THE EXISTING LIST IT ADDS
	// TO IT YOU MUST CLEAR ACTIONS ASSOCIATED WITH ACTOR. THIS IS
	// BECAUSE WE ARE RECYCLING SPRITES.
	// ---------------------------------------------------------------
	clearActions();

	// Set initial position
	this.x = x;
	this.y = y;

	// Calculate the duration to cover distance at pixels-per-sec.
	float height = Director.instance().getHeight();
	float distance = height - y;
	float duration = PIXELS_PER_SEC_FACTOR * distance;

	// Move from source to top of screen.
	MoveTo moveTo = MoveTo.$(x, height, duration);
	moveTo.setCompletionListener(this);

	// Run
	action(moveTo);
}

/**
 * Handles pulse action complete.
 *
 */
@Override
public void completed(Action action)
{
        // Send notification that pulse has completed.
	Director.instance().sendEvent(GameActorEvents.EVENT_END_PULSE, this);
}

Note, we assign a completion handler to the move actions. If the pulse reaches the top of the screen without hitting anything then the completion handler will get triggered and it will send a event from the sprite to the PulseLayer which will remove it from the view, killing any further action execution as of course actions are associated with their Actor.

Because the PulseSprite is pooled we have to clear the associated action list when it is reused to clear out any unfinished or complete actions.

The demo code is here. This uses 0.9.3 snapshot from the Google Code subversion repository.

I am currently rewriting this again to bring in the Universal Tween Engine. That will be the next post.

Important

Only the “Menu”, “About” and “Game” view are implemented. To navigate out of a view PRESS THE ESC KEY. In Android you can press the back key.

Update #1

This project has been moved to gitHub. There have been a lot of improvements to the codebase including mavenizing it and separating out my netthreads-libgdx extensions library which grew out of this and subsequent demos.

← Previous PageNext Page →