Three Types of Game State in Even the Ocean
In some games, it’s necessary to track the player’s progress through the game. This way, when the player saves and exits the game, the next time they play, they’re able to restore their progress.
At its core, this is a simple problem — for each important event, say, the player viewing an important cutscene, maintain a single variable that determines whether or not the player saw the cutscene. For most cases, a boolean (true or false) will do. Let’s use my game Even the Ocean as an example. At various points in the game, the protagonist, Aliph, must talk to the Mayor of the city. This constitutes a cutscene. So I’ll keep a variable that’s ‘1’ if the player’s seen the cutscene, and ‘0’ otherwise.
I’ll refer to this set of variables as “game state”, where ‘state’ is the same ‘state’ as ‘the state of the union’.
Why think about scaling?
As illustrated above, with the right planning, managing game state is a simple task. However, let’s say there are over one hundred things to track. As the game scales in complexity, it’s not unlikely that we might need to track more than a few cutscenes.
In Even the Ocean, I need to track a few things: the player’s inventory, important events such as the order in which the main areas are visited, and whether or not cutscenes have been viewed.
This game state can all be reduced to a list of numbers — 0, 1, etc — that we need to save. However, it ignores a concern, which is the cost of developing the game. As you make a game with many events and small variables, it can become confusing to track them, and working on cutscenes or events, debugging, can lead to lots of repetition in cross-checking variable names. So, it’s important to think about the organization and distinguishing of the types of data your game state is comprised of.
Looking back on Even the Ocean’s game state, the three main types of data — event flags, inventory, and dialogue state. When saved to a file, these are just integers in a text file. But in the context of the game, they serve different purposes.
Event flags can be referenced in a wide variety of places, from environment object code, to player character code, to cutscene scripts. They are largely hidden from the player.
Inventory is the most player-visible type of game state. If you have an item, it can be seen in the inventory. Otherwise, you can’t see it.
And dialogue state consists of things that answer questions like “how many times have I talked to this person?”, “where did I leave off in a conversation with this person,” and the most important, “was this important cutscene viewed by the player?”
Because of these different concerns, I’ve found it useful to keep the lists of each types of state organized in different ways.
Some abstract traits of each datatype can be — player visibility and locality. Player visibility meaning whether the particular variable’s state is obvious to the player (like inventory), locality meaning how often a variable is referenced — event state being referenced globally, dialogue state usually only referenced in a few event scripts.
I’m not speaking for best practices here: each game has different concerns. I’ll say that what I have employed for Even the Ocean is not ideal, and that I can think of improvements or at least questions about each system. However, what I’ve used for Even the Ocean has been sufficient for a small team and a 10–20 hour+ game on a small budget.
How it works
The metadata for the inventory is stored in an inventory data file, stored in a basic JSON-like object format, whose simple and lightweight parser I implemented myself.
// World map
type s “MAP”
path s “sprites/ui/map/world_map_in.png”
w i 16
h i 16
This denotes that there’s a property called “type” which is a string, and its value is “MAP”.
Likewise there’s a property, “w” (frame width of the sprite), which is an integer, value “16”.
How it’s used
In-game, this data is parsed into a map/dict-like format, so that each field of each metadata entry can be accessed if needed. I write wrapper functions for a lot of this, but here’s an example of not using any wrapper functions to figure out the item’s type.
R.inventory.table.get(“18”).get(“type”); //Returns “MAP”
An advantage to this method is that every item automatically has its own, unique and static ID. In this case, the world map object has ID ‘18’. It also has an arbitrary (meaning I can add more fields to item 18 if I want, and I can access them in-game) list of metadata associated with it, which is easily editable when looking up its entry in the metadata file. It means I can easily add new items without having to worry about other files — if I add an entry, the item will exist in the game’s logic. The trade-off here, is that I have to use numbers when changing the state of having an item or not from the code, which can become confusing. The solution there would be to create dedicated named constants “ITEM_WORLD_MAP = 18”, which I sometimes use.
The other fields are for convenience. In this case, a map can be viewed in-game, so I have three other fields showing the location of the map’s image file, as well as its dimensions in pixels. The game’s code will look up these fields in order to load in an image.
Another advantage to this method is that because the metadata file is human-editable, if I needed to, I could edit this file and reload it while the game is running.
Items also have text data associated with them that must be localized. This is stored in the main dialogue files. Thus if I was verifying something about item metadata and the associated text labels (such as item name and description), I would have to flip between two files. However, because the text data must be localized, it’s far more convenient to store the text data in a different file. This sort of trade-off of conveniences comes up now and then with the design and coding of these metadata management systems.
-If the inventory was a huge deal (there aren’t many items in ETO) I probably would have made some basic program that keeps the metadata and text separate, but gives me a visual interface of viewing everything about a given item at once.
-A counteradvantage to note is that by skipping a secondary program and getting straight to getting things working, I’m able to progress with the game faster rather than get caught figuring out if a choice is perfect. It’s hard to do and I’m not able to do the following perfectly, but being able to intuit how much flexibility a particular choice will leave you is an important skill.
-Items have an in-inventory image associated with them. Each image must be the same size, because the item’s ID is used as an index into a spritesheet. The limitation here, of course, is that each in-inventory image can only have one picture, and every image of an item in the inventory must be the same size. There are easy workarounds for this, but I didn’t see much point generalizing the system to have custom sprite sizes for something we didn’t even have final art for yet.
The dialogue state system is tied to the dialogue storage format. Lines of dialogue follow a hierarchy where they are classified into what section or area of the game they are in, and then what particular scene — or set of lines of dialogue — they are in. For example, if I’m talking to a signpost in the SHORE area, and the signpost says “This is a sign”, then it appears in the dialogue file like so:
This is a sign.
// more scenes here
The dialogue is stored in a flat file format, plain text. Dialogue state variables are automatically generated from this file, two for each “SCENE” entry. A dialogue manager module handles all the interfacing with the dialogue state, so in a script, I’d write
To set state variable 2 of the above set of dialogue to 1 (or any other integer I want.)
Serialization and loading of this system is easy. In-memory, the state exists as a String->String->String->Int (or something) map, so the serialization code just reads through all of this and outputs a line for each scene. Moreover, this serialization system lets me also store other metadata about a scene of dialogue, such as the number of times the scene has looped, or where a player stopped talking to an NPC in the case where an NPC says different things if you talk more than once.
In practice, the metadata was overengineering — there are few instances where it’s necessary to track the number of loops or where a player stopped talking. This occurred because of my previous game, Anodyne, where such behavior with the dialogue was common.
On the other hand, the choice of two hard-coded state variables per scene was not a bad idea, because I often only need to use zero or one. So I’ve saved time by not coding a generalized system allowing infinite state variables per scene, however, extending behavior to do this wouldn’t be hard.
How it’s used
The 99% time use of this state system is during scripted events, e.g., Aliph is talking to the Mayor. A scene variable is set to ‘1’ to denote that the scripted event has been watched. This allows me to manage where the player is in the game.
For example, after each power plant level is finished, Aliph must return to the city. There is a trigger near the city exit that can prevent you from leaving until Aliph ends the day by sleeping. There’s a state variable associated with the dialogue of Aliph sleeping, and if it is set to ‘0’ while the game knows you’ve just returned to the city, the trigger won’t let you leave. However, once that state variable is set to ‘1’, you can leave.
What this system does is gives me an easy way to access and modify this state, as well as provide a wrapper for checking if a particular variable changes.
Because there exist two state variables per scene, this makes it very easy to manage things like the above example, which occur maybe a hundred times or so throughout the game. I don’t have to remember to make a new variable somewhere or remember its name, because its name is tied to that map-scene dialogue file entry.
I can also make other one-off variables by adding an empty map-scene dialogue file entry. In rare cases I use this to manage some edge cases.
The Map-Scene format is relatively easy to CTRL-F through to check dialogue because I can just ctrl-f “MAP SHORE” and then “SCENE SIGN” to find the desired dialogue. Moreover, any reference to dialogue in the game must go through this system, meaning anything int he code or scripts can be easily found.
More variables than I need. This one is arguable, but if this was a large team and there was a ton of dialogue, there’d probably be some way to specify that there shouldn’t be a state variable generated for a map-scene pair.
Slow loading times in the editor. This is complicated because of poor decisions in making the editor that I won’t go into, but often I want to reset a particular dialogue state variable, but the only way to do this is to re-generate the entire file’s metadata. This takes a few seconds. However, I’ve since just begun to practice resetting the particular state variable in an event script, and skipping the entire re-generating.
Granularity of the state variables may be too big for games that have lots of branching or whatever. This system definitely isn’t suitable for a visual novel, which needs some kind of visual interface. The entire dialogue storage and state system only works because Even the Ocean’s needs do not include lots of branching or state dependent dialogue (though there is a janky system for doing branching, it’s just hard to follow.)
Unrelated to the dialogue state system, the dialogue being stored in one flat file is a consequence of initially not thinking the game would need much dialogue. Separate files for each map, as well as a way of managing localization updates, would have been ideal — probably with some software. However, with this format I am able to add metadata to the dialogue on the fly — stuff like “does the next line play right away”, or “change this state variable”, or “show this person’s portrait”, etc.
Because of human error in typing those in, I’ve written some scripts to help generate the metadata. But still, I think it would have been worth writing some basic flat-file-database-manager myself.
You can think of the event system as implemented similarly to the inventory system. However, instead of items, we have labeled variables like “SHORE_POWERPLANT_DONE”. These are set or read in event scripts and manage things like the order a player visits places, whether an area’s been done, etc.
This works pretty well, I’ve added an interface to modifying the event state array that prints out to the console that the variable’s been changed (for debugging). When doing sensitive stuff like this, I want to make sure that it’s easy to trace down when something’s been changed.
Entities (pods, lasers, doors, save points…)
The entities in the game — things Aliph interacts with — do not have state, and thus nothing about them needs to be serialized. There are still a few cases where I need some state, so when they do need to be modified, I usually create a dialogue variable and add some special interfacing behavior to the particular entity. Messy, but works for the one-off case.
The game USED to manage entity state — like if a blocking-in-game-raisable-wall is open. This was because I planned to have things like doors that stay open, quicksaving, checkpointing during the power plant segments. This ended up sort of working, but being more complicated than we needed. Rather than checkpoints, we just used save points. And we got rid of quicksaves, because we made the checkpoints into save points.
The decisions to add checkpoints and quicksaves were done too early on, done in 2013 (when the game’s main programming has finished up in 2015 and 2016). I would probably avoid adding such things, or at least avoid the complicated consistency of quicksaving, until I am further along.
I can go into how I did manage entity state another day, the main idea is that some entity variables, by their name, are detected by the serialization system as needing to be saved. This is then written to a file when the player saves or quicksaves or into memory when the player checkpoints, and on reload or respawning from death, the changed variables are applied on top of the existing entities.
Being able to do this is contingent on having a system that saves the data of the entities somewhere, however. Because Even the Ocean has an in-game level editor, it was easy to apply this state system on top of the entities.
This was messy, poorly implemented, and super slow to load the game and patch in all the changes. One day I’ll write about how I could have done it better, and without 10+ second wait times when the player loads their game.
Useful things for Debugging and Testing -Human readable data formats are very useful, or at least a very good way of interfacing with the data if your data is small and simple. Just save things to text files, load them into memory (store the data as a variable), and have a way of modifying them in game.
— Having an in-game way to modify any particular state variable is very useful, and for event scripting and testing triggers, almost completely necessary. I didn’t have a visual in-game editor of any metadata, though.
-Notifications for modifications (setting). For bugtesting. “Event 1 set to 4”, “Inventory item 2 obtained”, etc.
-’grep’ command in the command prompt. It’s a super fast command line tool for searching text files. Note this relies on an easy way to search your variables.
— Easy to find lists of your named constants (like the IS_SHORE_DONE variable). I keep mine as lists of static variables in separate files (because I use Haxe).
I’ve shown how to manage state for a semi-long and large game made by a small team with little budget to invest in outside tools. Hopefully these three (and a half) examples are helpful if you decide to scale your game to the point where you need more than a small list of variables.
Feel free to ask me any questions here, or at twitter.com/sean_htch