Constructing Games

Game States and Navigation


Learning Objectives

  • You know of finite state machines and how they can be used to represent the flow between screens in a game.
  • You know of possibilities for structuring application flow between screens.

When we think of games as applications, they typically have a starting point, a point during which the concrete game is engaged with, and an end point. This flow can be represented as a finite state machine, where the user progresses through the game.

Flow between screens

A game that consists of a start screen, a game screen, and a result screen can be represented as a finite state machine shown in Figure 1. Typically, games created at least for mobile devices do not have an exit button, as the operating system typically provides a way to exit the application.

Fig 1. — A finite state machine representing the flow in a system with a start screen, a game screen, and a result screen.

Thinking of the above flow, such a flow could be created with Flutter and GetX as follows. In the following example, we have a simple game where the user is asked a question, and the user can answer the question. After answering the question, the user is shown a result screen, and from there, the user can start the game again.

Run the program to see the output

When we look at the code in the above application, there is plenty of repetition due to styles. To make the code more readable, we can create our own buttons and scaffolds. The following code shows how to create a GameButton and a GameScaffold that can be used in the application.

class GameButton extends StatelessWidget {
  final String text;
  final Function onPressed;
  const GameButton({required this.text, required this.onPressed});

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () => onPressed(),
      child: Text(
        text,
        style: TextStyle(fontSize: 24),
      ),
    );
  }
}

class GameScaffold extends StatelessWidget {
  final List<Widget> content;
  const GameScaffold({required this.content});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: content,
        ),
      ),
    );
  }
}

With such utility classes, the code for the screens can become more readable, and it is easier to e.g. be consistent with the styling.

State within screens

Screens may also include a state, as shown in Figure 2. In the example, the game screen shows a question and has a way to keep track of whether there are any more whether there are any more questions left after the user provides an answer.

Fig 2. — A finite state machine representing the flow in a system with a start screen, a game screen, and a result screen. The game screen has a separate finite state machine that represents movements between states within the game screen.

Again with Flutter and GetX, such a flow could be implemented as follows. In the example below, the questions are in an array of maps, hardcoded to the application.

Run the program to see the output

Although the above example has hard-coded questions, the questions could be fetched from an API, a database, or a file.

Level selection

Games may also feature functionality for selecting a level, as shown in Figure 3. In the example, the user can select a level, and after completing the level, the user can select another level.

Fig 3. — A finite state machine representing the flow in a system with a start screen, a level selection screen, a game screen, and a result screen.

The level selection could also somehow be restricted, allowing the user to access only the next level that they have not yet completed. In the following example, the user can select a level, and after completing the level, the user can select another level. The user can also see which levels have been completed.

Run the program to see the output

Loading Exercise...