Page 1 of 1

Phobos - A JavaFX Games Engine: Part 2 - JavaFX Scene API and the FSM Rate Topic: -----

#1 pryogene  Icon User is offline

  • The Leafiest of the Leif's
  • member icon

Reputation: 42
  • View blog
  • Posts: 673
  • Joined: 30-June 09

Posted 01 June 2014 - 06:27 PM

Phobos - A JavaFX Games Engine

Part 2: The JavaFX Scene API and the Finite State Manager

Welcome back to my multi-part tutorial on building a Games Engine with the JavaFX framework. Last time we were here we discussed the topics of Threading & Software Design Patterns and how they mattered within the scope of the project. This time we're going to look at JavaFX, a rich client platform built on Java (but you already knew that). The advantage that JavaFX really offers us is the native hardware acceleration and a rich media interface, among other useful features such as the Scene API. We're also going to explore the beginnings of our Finite State Manager - and make a return to those dastardly Singletons!

As a footnote, this part of the series assumes familiarity with the HashMap, EventHandler and KeyEvent classes. If you aren’t familiar with these, I’d give them a Google and get up to speed.

Humble Beginnings - The JavaFX Scene Graph

JavaFX, released in 2008 (v1.0), is a rich client API primarily designed for use with data-driven applications and those that need to leverage the power of modern audio and graphics hardware (!). It's also an impressive UI toolkit, akin to the Windows Presentation Foundation which Microsoft released in 2006 (.NET 3.0), that offers an expanse of customisability through CSS styles and code. We're not really interested in the UI toolkit or the use of data; what we're interested in is the Scene API - a powerful layout engine based on a tree paradigm. In fact, we're interested in one teeeeeeeny portion of it: The Node class.

The Scene Graph is a series of Nodes. Each node can be one of three types:
  • The Root: The first node in the scene graph, which can not have a parent.
  • A Parent: Nodes with children, but not the root node. These are Groups, Regions and Controls.
  • A Child: Child nodes are specific subclasses, such as Rectangles and ImageViews – specialised nodes for performing very particular tasks.

Each node can exist pretty much anywhere within a tree within the scene graph, but what we're particularly concerned with is nodes attached to a scene - making them eligible for rendering.

A little bit of House Keeping

Before we move further, we’ll define our base Engine class. It will become quickly apparent why we do this, however we won’t explore it just yet. Copy the code below into a file named “Engine.java”
public final class Engine extends Application {
	
	private final static Object syncLock = new Object();
	private static Engine singleton;
	
	public final static int WIDTH = 800;
	public final static int HEIGHT = 450;
	
	private Engine() {
		super();
	    synchronized(syncLock){
	        if(singleton == null) throw new UnsupportedOperationException(
	                getClass()+" is singleton but constructor called more than once");
	        singleton = this;
	    }
	}
	
	@Override
	public void start(final Stage primaryStage) throws Exception {

	}
}


If you got here from our previous ventures, you’ll recognise a pattern here. That’s right, it’s a Singleton.* More importantly, we define a few things.
  • WIDTH, the Width of the Window in pixels. We default to 800px.
  • HEIGHT, the Height of the Window in pixels. We default to 450px.
  • A Constructor
  • An overridden method responsible for starting up the Application class.

Of these the more important pair, and the ones I’d like you to take not of, are WIDTH and HEIGHT. Let’s keep moving, try and keep up!

*Props to user CasiOo for the syncLock suggestion.

Setting the Scene

So far I’ve loosely discussed how JavaFX’ Scene Graph works. Let’s now look at implementing the basics of the Finite State Manager. We’ll get started out with the basic “scene” component, which we’re going to call “BaseScene”. The BaseScene class will inherit from JavaFX’ Scene class, a class responsible for a ‘view’ so to speak – depending on your individual background. The Scene is a node, second only to the Application node – the root node – and parent node to all of our graphics data. Here’s a pretty diagram to explain that a little better:
Posted Image
The Engine is the root node. At any given time, the engine can be managing a number of scenes (represented by the beautiful ‘scene’ box). Inside the most wonderful scene box are four scenes, one of which is the current child – that child is both a child and a parent and of type scene. Lastly, each scene can several children, for which I’ve used Images as an example – those are ImageView classes, for the uninitiated. Largely, that’s how the engine looks at a given snapshot in time. Okay, now for some code.
public abstract class BaseScene extends Scene {
	
	/**
	 * The group of items to be rendered on screen
	 */
	private Group nodes;

	/**
	 * Default Constructor
	 * @param root
	 * @param fill
	 */
	public BaseScene(Paint fill) {
		super(new Group(), Engine.WIDTH, Engine.HEIGHT, fill);
		// TODO Auto-generated constructor stub
		nodes = (Group) super.rootProperty().get();

	}	
}


To make this a little quicker, as we have a lot to cover, I’m going to explain these classes with bullet points – worth a shot, really. My joke was terrible and I feel terrible.
  • We import Group, Node and Scene from javafx.scene – the Group will handle our children, the Node is there for casting and our BaseScene will extend the Scene class.
  • BaseScene is abstract. We can’t create an instance of it, it just provides all of the basic services the Scenes will need to provide to the Engine.
  • BaseScene extends the Scene class, we’ll have to override a couple things later.
  • We declare a private Group – we’ll use this as a local reference to the Scene’s “root” node. This Group will store and manage our child nodes in the scene.
  • Lastly, a default constructor. We call the super class constructor, declaring that the root node should be a Group with nothing in it, the Scene should have the Width and Height variables we defined in the Engine class earlier and then we just pass in the fill from the constructor!

Let’s think about what basic functionality the Scene will need. It’s a game engine, so we’ll want to update the scene contents. We’ll also want to be able to respond to keyboard input and add children to the node group. Let’s throw a few functions and fields in.
/**
 * The keyboard reader assigned to this scene
 */
private Keyboard keys;

public Keyboard getKeys() {
	return keys;
}

/**
 * Gets and Processes input from the core Engine loop
 */
protected void takeInput() {
	keys.poll();
}

/**
 * Updates the BaseScene's contents
 */
public void update() {
	takeInput();
}

public void addItem(Node e) {
	nodes.getChildren().add(e);
}


As we’ve said above we want to take input (takeInput), update the scene (update) and add children to the group node (addItem). We also add in a Getter for a Keyboard variable named keys. But wait…

Keyboard? That’s not in the API!?

Right you are, we’re going to define it ourselves. The good thing about the JavaFX Scene class is that it behaves very similarly to a Swing JFrame, by which I mean we can set things such as KeyListeners etc. on them. What we’re going to do next is define our own style of Key Listener. We’ll listen to the scene to find out which Keys are currently up and down and use that information to decide what state the key is in. With that out of the way, let’s get down to coding!

First, a little enumeration:
public enum KeyState {
	UP,
	DOWN,
	HIT,
}


Our enumeration serves only as a container for the states, or KeyStates, that a key can be in. Those are UP, meaning not currently held; DOWN, meaning currently held; and HIT, meaning that the key was DOWN last frame and is now UP again. That last one might be flagging a question for some, the answer is that sometimes we only want to register the press once (think ESC key), whereas for others we want to see that it is held (I.e WASD).
public class Keyboard {
	public static final int KEY_COUNT = 255;
	
	private HashMap<KeyCode,KeyState> states;
	private HashMap<KeyCode,Boolean> flags;

        private KeyState getKeyState(KeyCode key) {
		return states.get(key);
        }

        private void setKeyFlag(KeyCode key, Boolean flag) {
		flags.put(key, flag);
        }

	public Keyboard(BaseScene scene) {
		
	}

	public boolean isKeyUp(KeyCode key) {
		return (getKeyState(key) == KeyState.UP);
	}
	
	public boolean isKeyDown(KeyCode key) {
		return (getKeyState(key) == KeyState.DOWN);
	}

	public boolean isKeyHit(KeyCode key) {
		return (getKeyState(key) == KeyState.HIT);
	}
}


Here we’ve got the basic outline of our Keyboard class. We define a couple of things:
  • A static final int KEY_COUNT, set to 255, defining the number of possible keys.
  • A HashMap to contain the KeyCode and its current KeyState.
  • A second HashMap containing KeyCodes and their flags – showing their state has changed.
  • A Getter and Setter for the current KeyState and Key Flag respectively.
  • 3 functions for pulling whether or not a particular key is current Up, Down or Hit.
  • Lastly, the Constructor, which takes a BaseScene as its argument (we’ll see why in a moment).

Moving forward, let’s take a look at the constructor
public Keyboard(BaseScene scene) {	
	states = new HashMap<KeyCode,KeyState>();
	flags = new HashMap<KeyCode,Boolean>(); 
	scene.setonkeypressed(new EventHandler<KeyEvent>() {
	    public void handle(KeyEvent event) {
	    	setKeyFlag(event.getCode(), true);
	        event.consume();
	    }
	});
	scene.setOnKeyReleased(new EventHandler<KeyEvent>() {
	    public void handle(KeyEvent event) {
		setKeyFlag(event.getCode(), false);
			event.consume();
		}
	}); 
}


First and foremost, we initialise the values of states and flags to their respective parameterised HashMaps. The following two blocks of code perform similar actions, so let’s look at the first block:
		
scene.setonkeypressed(new EventHandler<KeyEvent>() {
    public void handle(KeyEvent event) {
        setKeyFlag(event.getCode(), true);
            event.consume();
        }
});


We take the BaseScene we passed into the constructor and look to its setonkeypressed setter, which takes a parameterized EventHandler of type KeyEvent as its sole argument. We’re not going to need to change the event handler later, so we’ll lazy initialise it within the function.
public void handle(KeyEvent event) {
    setKeyFlag(event.getCode(), true);
    event.consume();
}


The function handle appears within EventHandler. Overriding the handle function allows us to define what should happen when the onkeypressed and onKeyReleased events fire. We want to set the Key Flags for our Keyboard class, so we write the line setKeyFlag(event.getKeyCode(), true); for onkeypressed and change our true to false for onKeyReleased. The actual state processing happens a little later in a function we’re going to write named poll.

So, moving swiftly onward!
public synchronized void poll() { 
	Boolean b;
	KeyState s;
	for (KeyCode k : flags.keySet()) {
		b = flags.get(k);
		s = states.get(k);
		if (b) {
			if (s == null || s == KeyState.UP) {
				states.put(k, KeyState.HIT);
			} else {
				states.put(k, KeyState.DOWN);
			}
		} else {
			states.put(k, KeyState.UP);
		}
	}
}


If you’re not registered to vote then this probably doesn’t apply to you. In the poll function, we take all of our Boolean flags and convert them to their new representations. The beauty of this function is that we don’t have to have any data in place where states are concerned. Starting at the beginning:
Boolean b;
KeyState s;


We define a pair of temp variables – one for the Boolean of a KeyCode and one for the associated KeyState.
for (KeyCode k : flags.keySet())

This style of loop is Java’s answer to the for each found in many other languages. We tell java that for every KeyCode key in the Boolean flags KeySet, we want to pull out the value in the associated ValueSet into a variable named k.
b = flags.get(k);
s = states.get(k);


Once we have the KeyCode in our k variable, we want to pull the values. We pull the Boolean flag into our b variable and the KeyState into the s variable. It’s worth noting here that when setting s we can end up with s being = to null. This is completely fine, in fact it’s useful information.
if (b) {
    if (s == null || s == KeyState.UP) {
	states.put(k, KeyState.HIT);
    } else {
	states.put(k, KeyState.DOWN);
    }
} else {
    states.put(k, KeyState.UP);
}


Why, you ask? Because if they key hasn’t been touched before then we know what to do with it! First and foremost, we want to know if they key has been flagged before: if it has we want to check which way to go:
if (s == null || s == KeyState.UP) {
	states.put(k, KeyState.HIT);
} else {
	states.put(k, KeyState.DOWN);
}


If we’ve never touched the key before, or it’s currently in its UP state, then we want to set its state to HIT. Otherwise, it’s being held down.*
} else {
	states.put(k, KeyState.UP);
}


If the key flag is false, then we know the key is up.

*A Key is HIT before it is classed as DOWN. We have to check two frames before confirming it is being held DOWN. If we didn’t, we could introduce some weird behaviour.

Every time the poll function is called, we update the states. Then, we could do something like
Keyboard.isKeyUp(KeyCode.A);

To check whether the A key is currently not pressed.

ANYWHO. That pretty much covers the keyboard input. That was fun! Let’s get back on the scene.

MEANWHILE, IN BASESCENE.JAVA!

Firstly we need to make a minor alteration but an important one nonetheless – inside our BaseScene constructor. Add the following line to the end:
Keys = new Keyboard(this);

By this point, I expect you to know what that’s doing so I won’t be explaining it.

Another of the functions that BaseScene will be responsible for is to draw the game screens to the screen. JavaFX offers us something called an ImageView. ImageView is a node designed to output an image to the screen so we’re going to use it as if it were a full-screen quad.
private ImageView output;

Inside the constructor, add the following code –
Output = new ImageView();
Renderer.setFunction(new IRenderAction() { 
	@Override
	Public void doRender(Graphics2D g) { draw(g); }
});
This.addItem(output);


And inside the update function add
Output.setImage(Renderer.render());

And lastly throw a -
Public abstract void draw(Graphics2D g);

- at the end of our class. In order of definition:
  • We define an ImageView called output which we will use to send our rendered scenes to the screen.
  • We construct the ImageView within the BaseScene constructor.
  • We add the ImageView to the BaseScene’s group node.
  • We set a function on the Renderer using an Interface to define the function anonymously.
  • We tell the engine to render the image to the ImageView during the update loop.

Renderer you say? What’s this new-fangled tech I’m hearing of all of a sudden?

Caught me again, I really need to improve my subtlety. Renderer is our class responsible for shifting images to the screen. First, let’s define the interface –
public interface IRenderAction {
	void doRender(Graphics2D g);
}


In here we are simply providing the anonymous function for use by the renderer. Anonymous functions mean that any given scene can be drawing itself at once – only the scene will know what’s happening. This simply decouples the rendering process. However, I’ve implemented this with an incredible lack of elegance and I encourage you to improve on it. Moving swiftly on, let’s start with a code listing for renderer.
public class Renderer {
	
	private static Renderer singleton;
	
	private BufferedImage clearbuffer;
	private BufferedImage backbuffer;
	private Graphics2D device;
	private IRenderAction function;
	
	
	public synchronized static void setFunction(IRenderAction function) {
		get().setRenderFunction(function);
	}
	
	private void setRenderFunction(IRenderAction function) {
		this.function = function;
	}
	
	private synchronized static Renderer get() {
		if (singleton == null) {
			singleton = new Renderer();
		}
		return singleton;
	}
	
	private Renderer() {
		super();
		createClearBuffer();
		createBackBuffer();
	    Engine.trace("Successfully initialised Renderer");
	}
	
	private void createBackBuffer() {
		backbuffer = new BufferedImage(Engine.WIDTH, Engine.HEIGHT, BufferedImage.TYPE_INT_ARGB);
		device = backbuffer.createGraphics();
	}
	
	private void createClearBuffer() {
		clearbuffer = new BufferedImage(Engine.WIDTH, Engine.HEIGHT, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = clearbuffer.createGraphics();
		g.setColor(Color.BLACK);
		g.fillRect(0, 0, Engine.WIDTH, Engine.HEIGHT);
		g.dispose();
	}
	
	private Image getBuffer() {
		ColorModel c = backbuffer.getColorModel();
		boolean a = c.isAlphaPremultiplied();
		WritableRaster r = backbuffer.copyData(null);
		return SwingFXUtils.toFXImage(new BufferedImage(c,r,a,null),null);
	}
	
	public synchronized static Image render() {
		get().beforeRender();
		get().doRender();
		return get().getBuffer();
	}
	
	private void beforeRender() {
		backbuffer.setData(clearbuffer.getRaster());
	}
	private void doRender() {
		function.doRender(device);
	}
	
	public synchronized static void dispose() {
		get().clearbuffer.flush();
		get().backbuffer.flush();
		get().device.dispose();
	}
}


Renderer is another singleton. *cue complaints*. So we’ll start by defining an instance variable. Continuing on we define two buffered images – one which we’ll put the rendered contents into and another which represents a clear screen. The last two fields are our Graphics2D instance to draw with and an IRenderAction for anonymous drawing.
Next down the list is the get function. Similar to the engine class, get returns the single instance of the renderer. I won’t labour this with another explanation of how or why this works – if you’re still unsure I encourage you to take a look at part 1!
private Renderer() {
	super();
	createClearBuffer();
	createBackBuffer();
	Engine.trace("Successfully initialised Renderer");
}


We also have a constructor – again it’s private so we can’t construct an instance elsewhere. Note the two functions createClearBuffer and createBackBuffer. We’ll explore the trace function a little later. Onward!
private void createBackBuffer() {
	backbuffer = new BufferedImage(Engine.WIDTH, Engine.HEIGHT, BufferedImage.TYPE_INT_ARGB);
	device = backbuffer.createGraphics();
}

private void createClearBuffer() {
	clearbuffer = new BufferedImage(Engine.WIDTH, Engine.HEIGHT, BufferedImage.TYPE_INT_ARGB);
	Graphics2D g = clearbuffer.createGraphics();
	g.setColor(Color.BLACK);
	g.fillRect(0, 0, Engine.WIDTH, Engine.HEIGHT);
	g.dispose();
}


Inside createBackBuffer we begin by instantiating the BufferedImage with the engine width and height variables as well as a defining the surface type as ARGB. Next we create an instance of Graphics2D from the backbuffer image – this is critical as this is how we’ll be drawing graphics, but it also defines where we draw them to. Inside createClearBuffer we again instantiate the BufferedImage – same variables. This time round, however, we want to set the content of the image and leave it at that so we create a new Graphics2D object, set the colour to black, fill a rectangle as big as the screen and then get rid of the Graphics2D object. Straight forward enough.
private void beforeRender() {
	backbuffer.setData(clearbuffer.getRaster());
}

private void doRender() {
	function.doRender(device);
}


Next up, before and do Render. The former gets the contents of the clearBuffer and shoves it into the backbuffer image. The latter is clever – the anonymous function we define gets called by function.doRender. The renderer will never know which scene it is actually rendering which makes it incredibly versatile.
private Image getBuffer() {
	ColorModel c = backbuffer.getColorModel();
	boolean a = c.isAlphaPremultiplied();
	WritableRaster r = backbuffer.copyData(null);
	return SwingFXUtils.toFXImage(new BufferedImage(c,r,a,null),null);
}	


GetBuffer is an interesting function as it concerns the conversion of data suitable for one framework to data suitable for another. It’s incredibly inefficient and not something that should be even remotely necessary. Let’s get on with it anyway. Firstly, we need to pull some information from the BufferedImage, namely its ColorModel, a value telling us whether the alpha channel is premultiplied and a copy of its actual data – the raster. The Swing library has a handy set of utility functions, one of which is the toFXImage function. We can use this function to then return the correct type of image.
 
public synchronized static Image render() {
	get().beforeRender();
	get().doRender();
	return get().getBuffer();
}


Extending on from here we have the render function – this simply makes calls to the beforeRender, doRender and getBuffer functions – performing a full render loop. Notably, this function is synchronized static. This is because, if you recall, our scenes need to output something to their ImageViews – to do this, a simple call to Render is made. Neato!
The last two functions are setRenderFunction – which does what it says on the tin – and setFunction, a thread safe version of setRenderFunction. Note how setRenderFunction is private so that it can’t be accessed anywhere else. You may have noticed, in fact, that all of the functions which are not static are private – because we can’t instantiate the class we don’t want them exposed.

Ending on Beginnings – Back to the Engine
public final class Engine extends Application {
	
	private final static Object syncLock = new Object();
	private static Engine singleton;
	
	public final static int WIDTH = 800;
	public final static int HEIGHT = 450;
	
	private Engine() {
	    super();
	    synchronized(syncLock){
	        if(singleton != null) throw new UnsupportedOperationException(
	                getClass()+" is singleton but constructor called more than once");
	        singleton = this;
	    }
	}
	
	@Override
	public void start(final Stage primaryStage) throws Exception {

	}	
}


When we started we defined the basic portions of the engine class. Now we’re going to improve on this and add in some actual functionality. Let’s start with a few variables and helper functions.
private int fps = 0;
private boolean shouldTrackFrames = true;
private Stage staging;
private List<BaseScene> scenes new ArrayList<BaseScene>();

public Stage stage() {
	return staging;
}
 	
public synchronized static final void addScene(BaseScene scene) {
	singleton.scenes.add(scene);
	int sceneNum = singleton.scenes.size() - 1;
	singleton.stage().setScene(singleton.scenes.get(sceneNum));
}

public synchronized static final void removeScene() {
	int sceneNum = singleton.scenes.size() - 2;
	singleton.stage().setScene(singleton.scenes.get(sceneNum));
	sceneNum += 2;
	if (sceneNum == 1) {
		System.exit(0);
	}
	singleton.scenes.remove(sceneNum-1);
}

public synchronized static final void setScene(Scene scene) {
	singleton.stage().setScene(scene);
}

public synchronized static final void kill() {
	singleton.quit();
}

public Object clone() throws CloneNotSupportedException {
	throw new CloneNotSupportedException();
}

public static void Launch(String[] args) {
	launch(args);
}

private void setupFrameTracker() {
        Timer fpsTime = new Timer();
        fpsTime.scheduleAtFixedRate(new TimerTask() {
		@Override
		public void run() {
			staging.setTitle("Phobos XRT: Singleton Experiment [FPS: " + fps + "]");
			fps = 0;
		}
	}, 1000, 1000);
}

public void quit() {
	Engine.trace("deinitialising...");
	System.exit(0);
} 
	
public static void trace(final String msg) {
	System.out.print("XRT: ");
	System.out.println(msg);
}
		
public static final Timeline create(final KeyFrame frame) {
	return TimelineBuilder.create().cycleCount(Animation.INDEFINITE).keyFrames(frame).build();
}


You must be thinking “woaaaaah that’s a lot of code”. You’d be right. That’s pretty much the entirety of the engine class. We’ll run through this bit by bit, starting with code that explain itself.
public void quit() {
	Engine.trace("deinitialising...");
	System.exit(0);
} 
	
public static void trace(final String msg) {
	System.out.print("XRT: ");
	System.out.println(msg);
}


Quit outputs a message and then calls exit(0) on the program, 0 being “terminated correctly”. Trace, a function we saw earlier, outputs a message prefixed with XRT to the standard output – I used this for debugging portions of the engine as I could add a prefix inside the message to show where it came from.
public synchronized static final void setScene(Scene scene) {
	singleton.stage().setScene(scene);
}

public synchronized static final void kill() {
	singleton.quit();
}
	
public Object clone() throws CloneNotSupportedException {
	throw new CloneNotSupportedException();
}
	
public static void Launch(String[] args) {
	launch(args);
}


These are, again, relatively self-explanatory. In setScene, we set the stage’s scene value. In kill, we make a call to quit (dat function overhead). In clone, we give the user a grilling because they shouldn’t be attempting to clone a singleton. Lastly, Launch makes a call to the Application classes launch function – this starts off the engine and its subsystems.
private void setupFrameTracker() {
        Timer fpsTime = new Timer();
        fpsTime.scheduleAtFixedRate(new TimerTask() {
		@Override
		public void run() {
			staging.setTitle("Phobos XRT: Singleton Experiment [FPS: " + fps + "]");
			fps = 0;
		}
	}, 1000, 1000);
}


setupFrameTracker Simply creates an fps counter. We do this by defining a Timer called fpsTime. We tell that timer to schedule at task at a fixed rate (sheduleAtFixedRate) and pass it a new TimerTask since we won’t need to reuse it. The TimerTask gives us another useful anonymous function, run, in which we will tell the engine to set the title of the window to display the fps. We will also instruct it to reset the fps counter for the next run. Lastly, we tell the TimerTask that it should run once a second, with its first run starting a second from now!
public synchronized static final void addScene(BaseScene scene) {
	singleton.scenes.add(scene);
	int sceneNum = singleton.scenes.size() - 1;
	singleton.stage().setScene(singleton.scenes.get(sceneNum));
}

public synchronized static final void removeScene() {
	int sceneNum = singleton.scenes.size() - 2;
	singleton.stage().setScene(singleton.scenes.get(sceneNum));
	sceneNum += 2;
	if (sceneNum == 1) {
		System.exit(0);
	}
	singleton.scenes.remove(sceneNum-1);
}


addScene And removeScene are a little bit more complicated. Starting with addScene – we first add the BaseScene ‘scene’ onto our list. After that, we calculate it’s position (which will ALWAYS be the end of the list) using size – 1. Then we make a call to setScene to finish off the job by setting the current scene on the stage.

Remove scene, on the other hand, requires a little bit more in the way of logistics. Firstly, we must calculate the index of the screen before the current screen – we do this with size – 2. Next, we check to see if the number we have calculated is one or less. In the event it is there will be no scene to replace this with and so we should exit – once again we terminate with exit code 0. If there’s another scene to be worked with we should set it on the stage – its index will be the value of sceneNum – and then remove the top-most scene.

It’s important to understand that, whilst awkward, this process must be carried out carefully else the JVM will start throwing errors at us like a monkey throws faeces.
public static final Timeline create(final KeyFrame frame) {
	return TimelineBuilder.create().cycleCount(Animation.INDEFINITE).keyFrames(frame).build();
}


The create function creates a timeline that will run indefinitely, playing our single update frame each cycle.

This brings us to our endgame. The Start function. This is, quite possibly, the most important part of the engine class – it gets everything going and throws us into glorious 2D fantastimazing stuff. I really didn’t have something for that…
@Override
public void start(final Stage primaryStage) throws Exception {
	//singleton = this;
	// TODO Auto-generated method stub
	//1) perform renderer initialisation routines
	staging = primaryStage;
	stage().setTitle("Phobos XRT: Singleton Experiment");
	final Duration oneFrameAmt = Duration.millis(1000/30);
        KeyFrame oneFrame = new KeyFrame(oneFrameAmt, 
            new EventHandler<ActionEvent>() {     
                @Override
                public void handle(javafx.event.ActionEvent event) {
                	//update scene stuff
                	if(scenes.size() > 0) {
                		scenes.get(scenes.size()-1).update();
        
                	}
                	fps++;
                }
        }); // oneFrame
        create(oneFrame).play();
        if (shouldTrackFrames) setupFrameTracker();

	staging.show();
}


The very first thing we must do is set the value of staging to the value passed in the Start function – primaryStage. We also set the title but that’s not really of any huge importance. We define a duration variable equal a 30th (1000/30) of a second – this limits the engine to running at a maximum of 30 frames a second but feel free to experiment with different timing values.

The next portion we’ll dissect. We start with a Keyframe –
KeyFrame oneFrame = new KeyFrame(oneFrameAmt...


When defining the frame we set its duration to that of oneFrameAmt which we previously defined as a 30th of a second (or whatever playful number you choose to use, you devilishly adventurous programmer you).
new EventHandler<ActionEvent>

The KeyFrame takes an EventHandler too. The event in question is what happens when an action is performed – in this case, the action is that every 30th of a second the engine will attempt to run the handler function defined – if we’re not processing something else still, that is.
  
@Override
public void handle(javafx.event.ActionEvent event) {
    //update scene stuff
    if(scenes.size() > 0) {
        scenes.get(scenes.size()-1).update();  
    }
    fps++;
}


It gets easier, though. Inside the handle function we only want to do 2 things, really. Firstly, we need to ensure that we don’t try to update anything that doesn’t exist so we check that the size of scenes is at least 1 by asking if scenes.size() > 0. If it is, we run an update on the topmost screen. Either way, at the end of this function we increment the fps counter – this will top out at 30fps if we’re not updating any scenes.
          
create(oneFrame).play();

if (shouldTrackFrames) setupFrameTracker();

staging.show();


Last, but not least, we create the timeline for our frame and play it! That sets the ball in motion and really gives the engine the kick it needs to start updating (it’s funny because… never mind). All we do after this is decide whether to actually run the fps counter and then show the stage.

And that’s that.

Wasn’t hard, was it?

At this point we’ve covered the basic components our engine is going to need to run. We implemented the basic scene class and a renderer to handle the graphics output. We implemented the base engine class to handle the scenes and the application life cycle. We looked at singletons again, making notes of some pros and cons.

Next time, we’ll implement a menu system and some menus as well as a really basic “game” the engine. It’s gonna be fun on a bun! See you then!

Full Code Listing.
In addition to providing a lengthy listing, you can have a neatly packaged jar. I'm just that lovely. The code contained within the Jar is a full code listing for that of this tutorial. If you run it, you should be presented with a black 800x450 window.

Attached File  Phobos_Part2_Code.zip (21.49K)
Number of downloads: 86

Is This A Good Question/Topic? 1
  • +

Replies To: Phobos - A JavaFX Games Engine: Part 2 - JavaFX Scene API and the FSM

#2 pryogene  Icon User is offline

  • The Leafiest of the Leif's
  • member icon

Reputation: 42
  • View blog
  • Posts: 673
  • Joined: 30-June 09

Posted 07 June 2014 - 06:04 PM

As a note to readers, there is an error in the source code archive.

In Engine.java, the second line of the constructor reads if (singleton != null)
It SHOULD read if (singleton == null)

My apologies for the oversight.

This post has been edited by pryogene: 07 June 2014 - 06:04 PM

Was This Post Helpful? 0
  • +
  • -

Page 1 of 1