Simplify flow between game ”screens” using the scene system

Problem

You’re making a game that has a bunch of different screens. Even in just a small game, you’ve probably got menu screens, a high score screen, a “normal play” screen, maybe a shop screen, and more. There are four separate issues here, but it turns out that they all are related enough to have a common solution:

  1. How do you manage the loading and unloading of the many screens, assuming you can’t keep the data/graphics all loaded at once (and with full screen background images, it’s not usually a good idea).
  2. How do you switch between screens? What is the easiest way to organize all of these different sections of game logic?
  3. How do you switch between control schemes?
  4. For ease of testing, how can you jump into a particular screen, assuming that a bunch of stuff has to be loaded for the scene to run correctly?

Early ideas

At first I thought, “Let’s keep all the data and control logic in a single static class. We can divide up the game into states, one representing each different set of control logic, and do a switch statement to decide which data, game logic, etc. to use at any give time.”

The problem here was that it isn’t very scalable. It works fine for a very small number of simple screens/control schemes, but increasing the number or the complexity of the screens/control schemes and things get out of hand very quickly. Also, what constitutes a game state is pretty vague. Is it when the screen’s background has an obvious change? Is it each GUI? Are there only two states (just the play and menu)? Is it a new state when only the control logic changes, but nothing new has to be loaded?

This is basically how I started coding Cash Cow, and because I was answering all of those previous questions a different way each time, the control structure for the states grew more and more complex. Things were never consistent, which made adding and changes states very difficult. It also didn’t address issue number 4 at all, which made testing tedious because we had to navigate through menus to get to any particular part of the game. By the time I started coding Primate Panic, we had a much better idea.

Our Solution

I call it “The Scene System.” Essentially, you divide the game up into scenes, where a scene is (usually) defined by a change in the background image. This means that the main menu screen is a scene, the map screen is a scene, the normal gameplay is a scene, etc. Why divide them up this way? Because in our current platform, background images take a noticeable amount of time to load (at least enough to cause the cursor to flinch). The idea is that if you only load a new background between scenes when the entire screen is black, the user won’t notice any delay from loading.

This means that each scene should have a natural way to transition in and transition out, and the unloading/loading takes place after the transition out of one scene and before the transition in of the next. In Primate Panic, you see the end of a scene when the screen fades to black. This is when the next scene is being loaded, but on most computers it is fast enough that you don’t notice any load time.

There is also a class that manages the scenes, and tells the current scene to update its game logic and redraw everything each frame.

How Does It Work In Code?

In Java or Slag, you have you have a SceneManager class (which is a singleton for ease of use). This class contains a static reference to the only instance of Scene Manager, and a reference to the currently running scene. It needs this scene reference to update and redraw the current scene, as well as change it. Here is a quick Java example of the idea:


class SceneManager
{
  private static SceneManager instance;
  Scene currScene;

  public static SceneManager getInstance()
  {
    if (instance == null) instance = SceneManager()
    return instance
  }

  public void update() {currScene.update()}
   public void redraw() {currScene.redraw()}

  // A simplified way of loading and unloading scenes
  // Notice that the method is static so that anyone can call it.
  public static void changeScene(Scene newScene)
  {
    if (instance.currScene != null) instance.currScene.unload()
    instance.newScene.load()
    instance.currScene = newScene
  }
}

The Scene class is abstract and has four abstract methods that each scene must implement. Overriding these abstract methods is what keeps your scene code clean and avoids those nasty switches. Scenes can of course have plenty of other methods that you override as well, but these are the essentials.


abstract class Scene
{
  abstract void update(); // update logic for this scene
  abstract void redraw(); // redraw everything on screen
  abstract void load(); // load all of the data and graphics that this scene needs to function.
  abstract void unload(); // unload everything that the garbage collector won’t unload, itself, including graphics
}

Internally, our Scene implementations also have a transitionIn() and a transitionOut() procedure. This is where the screen fades happen in Primate Panic.

So how does the scene system solve each problem we mentioned earlier?


How do you manage the loading and unloading of the many screens, assuming you can’t keep the data/graphics all loaded at once (and with full screen background images, it’s not usually a good idea.)

Loading and unloading is done between scenes, when it is going to be unnoticeable by the user. Typically background changes happen at the same time that other large amount of data and graphics need to be loaded and unloaded, so the scene transition solves this problem naturally.

How do you switch between screens? What is the easiest way to organize all of these different sections of game logic?
SceneManager just runs the currScene and can switch between scenes. Because of polymorphism, your SceneManager does not have to be aware of the internals of each scene, just that each scene has those four important methods. To change the scene from anywhere else in your code (this happens a lot in your GUI code), just call SceneManager.changeScene(newScene).

How do you switch between control schemes?
Each Scene will handle its own set of controls, regardless of how this data reaches the scene. The scene might listen to the input data (like the keyboard and mouse) directly, or the data could be passed down by the SceneManager, as long as each scene implements the proper functions.

For ease of testing, how can you jump into a particular screen, assuming that a bunch of stuff has to be loaded for the scene to run correctly?
Each Scene’s load() method should load all of the data and graphics you need to run that particular scene. If you wanted to say, jump directly to the PlayScene, you would just instantiate this scene at the start of your game’s initialization and call SceneManager.changeScene() with the play scene as an argument. If you want to get really fancy, you could create a tiny file to read in during your game’s initialization that tells your game which scene to start with. That way you don’t have to rebuild your code just to start at a different scene.

But what happens when menus are changing but the background isn’t? Is each menu a new scene since the control is changing (at least a little bit)?
No, GUIs are contained within a scene and are handled separately. See “The GUI Stack” article for more details.