Day 16: Scene

Today we finally implement scenes. Scenes are entities that correspond to a single state (screen) in a game. This can be a menu screen, the main game, or credits screen. A game made using Artenus has a single stage, on which scenes are switched to create the flow of the game. Having implemented the building blocks, there is not much work left for us to do today.

Implementing fundamentals

The last day we created sprite collections, which we saw how they are going to act as layers of our scene. So, we start our implementation by plugging them in. Create a class called Scene as a sibling to Stage, and add the following fields to it:

	protected Stage stage;
	private final SpriteCollection[] layers;

As our scene is part of a stage, we need to keep track of the parent for later. That’s what the first field is for. The second field contains an array of sprite collections. When we add a sprite to a scene, we do so by indirectly adding it to one of these layers. We could also use an ArrayList instead of an array. But since the cases where we want to push an extra layer in the middle of a scene are rare (and can almost always be replaced by an improvisation), we stick to the array.

Our constructor does nothing but filling in these fields:

public Scene(Stage parentStage, int layerCount) {
	stage = parentStage;
	layers = new SpriteCollection[layerCount];

	for(int i = 0; i < layers.length; i++)
		layers[i] = new SpriteCollection();
}

It should be self-explanatory, but the important thing to note is that the number of layers is predetermined for a scene, and once constructed, it can’t be changed. We can also add a convenience constructor, which uses the default value (3 here) for this number:

public Scene(Stage parentStage) {
	this(parentStage, 3);
}

For external classes to be able to access the parent stage of the scene, we add a getter for the corresponding field:

public final Stage getStage() {
	return stage;
}

Background color

Although we can always clear the stage in black before we begin to draw, we can be a bit more creative by picking a color we want, or slightly more creative to let each scene decide for its background color. So, we add this information as a field to Scene:

RGB bgColor = new RGB(0, 0, 0);

RGB is a type we use for colors in Artenus. We won’t go into details of this type. You may download the code for the class at the end of this post. We also introduce a getter and a setter for this field:

public final RGB getBackColor() {
	return bgColor;
}

public final void setBackColor(RGB color) {
	bgColor.r = color.r;
	bgColor.g = color.g;
	bgColor.b = color.b;
}

The setter function does not replace the RGB instance with the one passed to it. Instead, it copies the values into our bgColor field. Another convenience method we can add is to set the color directly using r, g, and b values. Thus a separate object won’t need to be created when we don’t need any. Using the following setter is more efficient in most cases:

public final void setBackColor(float r, float g, float b) {
	bgColor.r = r;
	bgColor.g = g;
	bgColor.b = b;
}

The background color is used by Stage when initiating a render.

Adding and removing sprites

We have already implemented these functions for sprite collections. So we are just going to use them here. The add function will be the simplest to implement:

public final void add(Sprite sprite, int level) {
	layers[level].add(sprite);
}

It simply adds the sprite to the given layer. The remove function is a bit more sophisticated. The expected practice in our framework is to add each sprite to only one layer in the scene, although this policy is not strictly enforced. However, we use this assumption when removing a sprite. We don’t mind which layer contains the sprite when we are to remove it. We just go through all layers:

public final void remove(Sprite sprite) {
	for(int i = 0; i < layers.length; i++)
		if(layers[i].remove(sprite))
			break;
}

As you can see, the function just takes a reference to the sprite, and not the layer index. It then tries to remove the sprite from each layer, and it stops when it is successfully removed.

Rendering and animation

Advancing sprite animations is also handled in individual layers. But the stage does not directly communicate with them. Instead it calls an advance function on the scene when it is time to update sprites for the next frame. The scene then just simply calls individual advance methods on its layers. So if we put it into code, it will look like this:

public void advance(float elapsedTime) {
	for(int i = 0; i < layers.length; i++)
		layers[i].advance(elapsedTime);
}

The story is the same for the render method. We should just remember to enforce the view matrix before letting the layers do their job.

final void render(GL10 gl) {
	gl.glMatrixMode(GL10.GL_MODELVIEW);

	for(int i = 0; i < layers.length; i++)
		layers[i].render(gl);
}

Green light for resources

In order for rendering to be done correctly, all image resources must be declared. However, this is completely handled by the stage and Scene need not worry about it. What the scene should be careful about is not to create any sprites before this happens. Because sprites are created based on resource identifiers and those identifiers are mapped to OpenGL textures through TextureManager. So before these mappings are defined, those identifiers are invalid as long as our framework is concerned.

To resolve this problem, we add a signalling mechanism so the stage can give the scene a green light on when sprites can be created. We add a method onLoaded to the scene, which is called by the stage whenever it is clear to create sprites. We also add a field that is set when the scene is loaded.

private boolean loaded = false;

The method itself can be derived from subclasses of Scene. The basic implementation does nothing but to set this field:

public void onLoaded() {
	loaded = true;
}

A class derived from Scene will always need to implement this function. It should use it to add its sprites. And for reasons you will see when we’ll implement Stage, the superclass method must always be called at the end.

We also add a getter to check whether the scene is already loaded. This is layer used mainly by Stage:

public final boolean isLoaded() {
	return loaded;
}

Next steps

The scene we implemented here is not yet complete. However, the remaining bits are tied with the stage. So the next step will most probably be implementing the stage, and we will add some more what’s left out of Scene in the process. You can download the files from today’s work here: