Managing Configuration
Endeavour: triangle
This post relates to triangle , an arcade, ARPG, factory game I’m working on.
Before making the game available for playtesting, I wanted the player to be able to configure the game to some degree.
As a starting point, my keyboard layout is colemak
, and I doubt that the
controls I use would suit the majority of players.
I am putting off a UI based config management option down the road (did I mention that I do not enjoy GUI work?). As such I’ve been pondering alternative configuration options.
Platform independence
Before I even get to that, the first problem I need to solve is a way to determine the location for the config files independent of the platform.
Fortunately, known-folders came to the rescue and provided an easy to use framework that can be used to determine the various relevant locations for multiple platforms.
|
|
Locations
There are three real locations of relevance for triangle
- The binary / package
- Config, technically, split into two
- user (or in Windows parlance, the remote dir, and can be shared across computers)
- system (in windows parlance, local, and is specific to that system)
- Save Data
User & Game Config Files
With that sorted out, the next bit is to identify the relevant config files. I expect that triangle will continue to use these, and will eventually just get a UI config option as well.
User Config
There are two main bits of user configuration
- Preference like controls
- System details like resolution
I am currently unsure when it’ll support system config.
Game Config
There are two bits of config that the game will store. One set of config is to remember game choices the user has made.
Remember Player Actions
For example, it will be useful to show the user details of changes to the game since they last played. To do this, we need to track the last set of changes that the user saw.
The game will show a notice on startup about its extremely early access status, and provide an option for the user to hide that in the future. We need to save that somewhere too.
Telemetry
The second bit of config is metrics. While a lot of games will simply send telemetry information directly to the developer, player privacy is really important to me. I recognise that I will get far less data because of this, and that there will be a bit of survivorship bias with the data - but I feel that privacy is more important.
The way I want telemetry to work is that it will all be saved in a human readable telemetry file in the config file location.
The data is stored only locally, and is never automatically sent. The player is welcome to use this data for themselves if they wish and also share at their discretion. The information will be stored in a human readable format that should be as easy to understand as possible - no data dumps.
The location will also store logs (if enabled).
Setting Config
In terms of allowing the player to manage config, there are a couple of challenges:
- Providing enough documentation that it is easy to do
- Allowing for updates, particularly to the addition of new keys
To tackle this, I am going to provide an annotated template file with all the config options. The user can create a separate file based on this with only the config they wish to override.
It will be tricky to change how particular parameters are configured. E.g. If a single value key needs to switch to an array. I’d be loathe to sprinkle the code with checks for legacy keys/formats. We’ll play it by ear.
I considered updating the config file automatically, but this would discard any comments the user had added. While I could offer an option to merge changes in, it’s not straightforward enough to implement just yet.
Format
I’ve been considering toml
and yaml
for this, with
zig-toml
and
zig-yaml
respectively.
zig-yaml
seems to be more active (more stars, forks, issues and pr’s and
currently also the more recent commit).
I am also more familiar with and prefer yaml.
However, it does not currently support default values. I would like the user to
have to specify only the config they’d like to override. zig-yaml
currently
expects all the keys to be defined if you want to parse it into a struct.
#85 should bring it in, but I could not get it to work
So, I tried out zig-toml
and the test worked the first time.
|
|
Using the config
The final part is to use the config.
Loading config
All config is loaded at startup and attached to a Config
struct, which is in
turn part of a Context
struct that is passed around.
|
|
User config (controls)
This one involves a little translation as we need to know the rl.KeyboardKey
for each mapping to be able to detect it.
I use a std.StringArrayHashMapUnmanaged(rl.KeyboardKey)
to map the string to
each key
|
|
Game Config
As a starting point, we’ll probably only have one config entry here - the last time the news was marked as read by the player.
We’ll store this as an i64
and loading it is exactly the same as above.
The main difference with the Game
config class is that on writing any value,
it will also save it to disk.
|
|
I ran into a bug where
the api for serialization was broken
.
There is (currently)
a pending pr #33 to resolve it
One of the challenges of using emerging language and ecosystem is that you’re more likely to run into bugs. One of the great joys of working with such ecosystem is the greater opportunity to contribute and get involved!