A simple example of data-driven game design in "Above Your Clearance"
In my latest game, I experimented with making my game objects data-driven.
At its core, the concept of data-driven design is to avoid hard-coding the game objects and game logic into the game code. You can implement this to various degrees. For example, instead of defining your different game objects as different Java classes, you can instead define a generic game object structure. You can then define properties and types that you can then apply to these generic objects. In this way, you can "assemble" game objects dynamically, using these properties and types. And your game engine then uses these to handle the game logic. This is the approach used in Entity Component Systems for instance. You could even implement your game-specific logic in a scripting language, which you could then maintain using external tools.
There are several advantages to such an approach. As your game objects are now data instead of code, the game can import them from external data files. You can then alter these files, for instance using an external editor. This means that designers (or modders!) can tweak the game logic, without requiring any code changes. You also get a clear distinction between the game code and the game logic.
The structure of the game
"Above Your Clearance" is a short adventure-game, which follows the format of classic "choose your own path" adventures. This means that the game has a very simple structure. The game presents you with a situation, and then you get a number of choices. Each choice will potentially change the state of the game world, and then move you to a new situation. I designed this structure as series of nodes, connected by choices, which can be simplified into the following diagram:
Game concepts
I also introduced two resources to the game, credits (money) and clones (lives). If the player run out of either, he loses the game. Additionally, I also included the concept of "plot flags", in order to track the choices made by the player. This means that the game logic boils down to the following concepts:
- The game tracks which node is currently the active node. The active node is the node which is currently being displayed to the player.
- A node has a description, which the player can read. It also has a list of choices, which the player chooses from, in order to change to the next node.
- A choice has a description, describing the choice to the player. It also has a destination, which is the node which will become active if the player chooses this choice. In addition, a choice has a requirement, which checks e.g. a specific plot flag. If it doesn't pass, the choice will not be visible to the player. Finally, a choice can trigger one or more effects. An effect is a specific change to the game world, for instance setting a plot flag, or changing the amount of credits possessed by the player.
- A requirement is a combination of the type of resource and a value. The game logic uses these to check the fulfillment of the requirement.
- An effect is a combination of the type of resource and a value. The game logic uses these to change the specified resource by the given amount (or change the specified plot flag).
- When the player selects a choice, the game logic will apply any effects related to that choice. It will then check whether the player has run out of any resources. If not, it will change the current node to the destination.
A data-driven design approach
As the concepts above include game logic, I had to isolate this from the concepts themselves. Specifically, there is logic to check the requirements, to apply the effects, and to change the active node. As I did not want to implement a scripting language for this game, some hard-coded game logic was necessary.
One approach could be to have different classes for each type of effect and requirement. Those classes would contain a Java-method to apply the the specific effect or perform the specific check. However, this would mean that these objects would contain game-specific logic.
Instead, I implemented a type property for these classes, with a type for each specific effect or requirement. For instance, I implemented a type for "change the number of credits", a type for "set plot flag", etc. I then combined this with a numeric property that specified the magnitude of the effect.
This meant that I could move the game logic into the game code itself. I implemented the game logic itself as Java methods, which would get the effect or requirement as an input parameter. These methods could then use the type and magnitude on the game object, to determine how to handle it.
Implementation
Designing a GUI to handle the above concepts was relatively simple. At any given moment, the player only sees the active node, and the choices related to this node. So I made a GUI to display this information (as well as the current resources). When the player presses a button, it applies the corresponding choice to the game world. It then refreshes the GUI with the contents of the new active node:
As for the implementation of the concepts above, the data model consists of four data-driven classes:
- A Node class, which contains a description, a node-number and a list of Choice objects.
- A Choice class, which contains a description, a Requirement object, a destination node number, and a list of Effect objects.
- A Requirement class, which contains a type and a magnitude.
- An Effect class, which contains a type and a magnitude.
Each of these classes only contains properties, no game logic. It would therefore be possible to import them from an external data file. The GUI dynamically shows the currently active Node object. It also determines which choice buttons should be active (depending on their Requirement object). And the choice buttons call the internal game logic, with their corresponding Choice object as a parameter. In all cases, the data-driven classes contain no game logic, only information about how the game logic should handle them.
Try "Above Your Clearance" now, to see the concepts in action!
Conclusion
Hard-coding your game logic directly into your game objects is fast, but it can be inflexible. By using data-driven design, you can handle your game objects and game logic as data, instead of code. This means that you can keep the code separate from your game objects.
Instead, you implement properties corresponding to the game logic you want to support. You can then assign these properties to your game objects. This can make it easier for non-coders to customize and tweak your game play. In addition, this also makes it easier for your game to support external editors, and even modding.