Tweakable game settings
An overview of the various ways in which a game can be engineered to allow key values and attributes to be amended quickly and easily, including at run-time.
In any complex game there are often hundreds of settings and values which influence the way the game plays. Being able to change these rapidly and easily makes for much faster development and can facilitate testing and bug-fixing, especially if you have team members who are less comfortable changing the code. And in a live game like an MMO, if done well they can allow you to fix problems without deploying patches or even restarting servers.
Let's have a look at how we can do this, starting with the worst case scenario and then presenting some options to improve the 'tweakability' of the values.
Examples are in C#-like pseudocode with the Unity engine in mind but should generalise to most programming languages.
Worst case - magic numbers in code
Let's imagine we have a simple RPG-like game where the player can attack a monster and deal some amount of damage to it. Let's also imagine that we have an magician in the game who can cast a shield spell on monsters, meaning they take less damage from attacks. A very simple implementation could look like this:
public float CalculateMeleeDamage()
{
// Calculate the normal amount of damage for this weapon
float damage_dealt = current_weapon.BaseDamage();
// Decrease it if the monster is shielded
if (monster.HasShieldSpell)
{
damage_dealt *= 0.85f;
}
return damage_dealt;
}
The best thing about this code is that it was quick and easy to write! However, it has an obvious disadvantage - the hard-coded 'magic number' of 0.85. Writing the literal value inline is fine when the code is as simple as this, but this function is likely to get more and more complex as functionality is added, and it may no longer be clear why there is a 0.85 in there. Worse, if we need to perform damage calculations in multiple places we could end up with this value in several places in the code, leading to subtle bugs if we ever change it in one place but forget to change it in the other.
Constants in code
The simple improvement here is to make it a named constant - this will make the algorithm more readable in future and also allows a programmer to change the value in a single place, even if it is used by more than one routine.
private const float ShieldSpellDamageMultiplier = 0.85f;
public float CalculateMeleeDamage()
{
// Calculate the normal amount of damage for this weapon
float damage_dealt = current_weapon.BaseDamage();
// Decrease it if the monster is shielded
if (monster.HasShieldSpell)
{
damage_dealt *= ShieldSpellDamageMultiplier;
}
return damage_dealt;
}
This is a simple fix, but still has limitations. To change the value, the programmer needs to stop the program, rebuild it, and run it again. That might not be the most convenient thing if you were trying out different multipliers to see which one gave the best game balance. What can we do?
Statics in code
By converting the constant value to a static value, we gain the ability to change it in the debugger. This lets a programmer tweak the value during a session by running the game in the debugger, setting a breakpoint, and then altering the value in the debugger at runtime.
private static float ShieldSpellDamageMultiplier = 0.85f;
public float CalculateMeleeDamage()
{
...as above...
}
The only change here is literally one word on line 1, but for a programmer this could potentially save them a lot of time in future.
But what if it's not a programmer who wants to change the value, but a designer, or any other team member who doesn't have a debugger handy (or isn't comfortable using one)?
Editor Properties
In an engine like Unity, it is relatively easy to expose a value like this in the editor so that it can be easily changed by anyone on the development team. A very naive implementation might look like this, assuming the code below is part of a MonoBehaviour or ScriptableObject:
[SerializeField]
[Range(0.0f, 1.0f)]
private float ShieldSpellDamageMultiplier = 0.85f;
public float CalculateMeleeDamage()
{
...as above...
}
Here we're also using the capability to restrict the range of the value in the editor and displaying it as a convenient slider, like so:
In Unreal Engine 4 you can achieve a similar effect with a UPROPERTY, in Godot you can export a variable, and so on. With this capability anyone on the development team has the ability to edit these variables and tweak them.
But it's not always obvious where to find the variable you need, especially if you have hundreds of objects/scenes/prefabs/etc, each with their own set of values.
Even if you fix that - perhaps by having a central object that stores all the tweakable values - it's still only developers who can do this, because it requires access to the editor (or the ability to edit the sometimes-esoteric serialization format that the data is stored in). It's not ideal for QA who might be working with the built executables.
Can we do better?
Settings/configuration files
The next step is to consider loading values like this from the game settings, so that not only can they be changed in the debugger, but they can be set before runtime by editing a simple human-readable file.
static float ShieldSpellDamageMultiplier = 0.85f;
public void LoadSettings()
{
var settingsFile = new SettingsFile("gamesettings.ini");
if (settingsFile.HasSection("CombatValues")
{
var combatValuesSection = settingsFile.GetSection("CombatValues");
if (combatValuesSection.HasValue("ShieldSpellDamageMultiplier"))
{
ShieldSpellDamageMultiplier =
combatValuesSection.GetFloatValue("ShieldSpellDamageMultiplier");
}
}
}
public float CalculateMeleeDamage()
{
...as above...
}
This example (using a fictitious .INI loading library) will load in the gamesettings.ini file and looks for a CombatValues section, and inside there it looks for the ShieldSpellDamageMultiplier value. If the value is not found, it will continue to use the default as set in code, but if it is found, the value in the file will override it. Depending on personal preference you might want to use a different format such as JSON, YAML, or XML - each will allow you to set up some named variables grouped conveniently into sections.
(In practice, you probably wouldn't have each part of your code opening the settings file itself - you'd open it in one place and then pass it to whichever objects needed to configure themselves from that file.)
So now you have a system where programmers can set defaults in code and any developer can override the values in the settings file. All they have to do is edit the file and restart the program. Better still, you might even be able to set it up so that the program can re-read the settings file during play at the press of a button, allowing for some very quick testing and iteration!
Not only is this good for developers in the course of them iterating on features, but it also allows balance changes to be done simply by deploying a new configuration file, without needing to recompile the executable. And it allows for easy implementation of 'feature flags', by putting boolean values into the configuration and checking those values in code to decide which path to take. This is useful when testing experimental features, doing A/B tests, or perhaps just leaving your options open to disable something later.
But we can still do a bit better. This approach requires us to know the exact variable names that we expect to see in the settings files. (The alternative of simply putting all variables in the settings from the start gets unwieldy quickly as the file will need to change frequently and developers with locally-modified copies will find themselves merging it on a regular basis.) It also has no simple protection against us inserting erroneous values.
In-game console
What would be ideal is to be able to tweak these values directly from inside the game. It's still useful to load them from a settings file but for transient changes it helps to be able to change them directly in-game without using an external editor.
A common approach is to add a "debug console" which looks like a chat or terminal window where you enter commands and see results in text form. For our purposes we might want a "SET" command which allows us to change values.
// Globally-shared set of configurable values
Dictionary<string, float> FloatValues = new Dictionary<string, float>();
void ProcessConsoleCommand(string command)
{
// Split the command text up at each space
string[] commandWords = command.Split(' ');
if (commandWords[0] == "SET")
{
string variableName = commandWords[1];
string variableValueString = commandWords[2];
if (float.TryParse(variableValue, out float variableValue))
{
FloatValues[variableName] = variableValue;
ShowDebugConsoleOutput($"Set {variableName} to {variableValue}.");
}
}
... etc ...
}
public float CalculateMeleeDamage()
{
// Pull this value from the shared values collection
float ShieldSpellDamageMultiplier =
FloatValues["ShieldSpellDamageMultiplier"];
... rest of the code unchanged ...
}
In this example we would allow the user to type something like SET ShieldSpellDamageMultiplier 0.5
into the debug console and the appropriate entry in the FloatValues dictionary would be set, meaning other parts of the code can immediately see that new value.
Obviously you're not limited to float values - you can have separate dictionaries for strings, bools, and so on, or keep everything in one dictionary with a variant type. An even cleaner way to handle it might be to have your game systems register relevant values with the debug console and for the console to tell the system when to update them, but implementation is left as an exercise for the reader!
Often we can go one further and integrate a scripting language like Lua into the game and expose it to the console. This means that rather than hardcoding commands like "SET" and being restricted to rigid syntax, we can write more complex expressions, and add more commands easily. This can be a lot of effort but is worthwhile, especially if the scripting language can be used in other ways (testing, automation, in-game scripted events, etc).
Still, this might be too complex for your needs. And even if you do have a debug console, it's not necessarily the most friendly user experience for non-technical users. Adding auto-complete and a way to see a list of valid commands can help, but it only goes so far.
In-game UI
What might be better is to have a debug UI which exposes these values in a more visual form. This means no more remembering the syntax, quicker editing of values, and the ability to group and display the tweakable aspects in a way that suits you.
The classic approach here is to use a so-called 'immediate-mode' GUI library. Immediate mode UI is a poor choice for your game's main user interface because it ties the visuals tightly to the code, and is not suited to interfaces which are complex, need custom graphics, or have animations. However, they are very quick for programmers to implement and as such make a perfect way of implementing debug user interfaces.
In Unity the old IMGUI system is adequate for this task, and provides a quick way to not only tweak in-game values but to limit those tweaks to reasonable ranges.
private float ShieldSpellDamageMultiplier = 0.85f;
void OnGUI ()
{
var windowPositionRect = new Rect (25, 25, 250, 200);
GUI.Window(1, windowPositionRect,
ShowTweakableValuesWindow, "Tweakable Values");
}
private void ShowTweakableValuesWindow(int id)
{
// Show label
var labelPositionRect = new Rect (25, 25, 200, 75);
GUI.Label (labelPositionRect, "Shield Spell Damage Multiplier");
// Show slider
var sliderPositionRect = new Rect (25, 50, 200, 75);
const float MinValue = 0.0f;
const float MaxValue = 1.0f;
ShieldSpellDamageMultiplier =
GUI.HorizontalSlider(
sliderPositionRect,
ShieldSpellDamageMultiplier,
MinValue,
MaxValue);
// Show value
var valuePositionRect = new Rect (175, 70, 200, 75);
GUI.Label (valuePositionRect, ShieldSpellDamageMultiplier.ToString());
}
This gives output a bit like this:
With this sort of capability, backed up by the ability to change defaults via configuration files, we make our game about as convenient as possible to tweak in real-time, making development quick and easier. And even after the game is shipped, we can still test and deploy some rapid changes merely by altering the configuration file. For most games this will be enough.
But what if you have an online multiplayer game, and you have not only a game client to think about, but a game server too? Often you'll need to make changes to the game servers, but taking the servers down for a code change causes downtime that annoys and loses players. You can load values in from your settings file but even if you have a live reload facility it's not usually the case that your team will have direct access to the settings files once deployed.
You can even have a way to log into the game and perform debug commands from a trusted account, if you are confident about being able to correctly authenticate and authorize such an account, but this is harder to make secure and awkward in that it requires someone to log directly into the game.
In-game web server
A better way might be for the game to expose the various tweakable values via a HTTP API. This allows you to create a simple UI in HTML and Javascript which connects to the game server and requests the changes directly.
This might sound like a huge undertaking but these days is quite easy to set up. Most languages come with rudimentary HTTP server functionality built in, and this is enough to implement a simple web interface that lets you change variables while the game is running. Not only that, you can implement commands (as with the debug console) that allow arbitrary actions while the program is running, such as respawning enemies, toggling a player's invulnerability mode for testing, etc.
And why stop there - you could extend the interface to provide any diagnostic information you care about such as frame rates, player locations, bandwidth usage, etc., and render them out conveniently in the browser using HTML or Javascript.
The code for this is a little longer but still very manageable - usually it's just a few lines to set up a HTTP server and then a function to handle requests by examining the URL and query string, and then to return a rudimentary response. This is a Unity code example but the only Unity-specific parts are the MonoBehaviour
and the Debug.Log
calls.
This is not production-quality code - you'd want to have different endpoints for different functionality, look up values dynamically, add validation and error-checking, etc. It is also more common to return data in JSON format rather than HTML so that Javascript can process it effectively. But, this gives an indication of how you can start building such a system. If you add this script to a GameObject in a Unity scene and run the game, you can connect to http://127.0.0.1:8888/
in a web browser and see the current value of ShieldSpellDamageMultiplier
. And if you enter http://127.0.0.1:8888/set?ShieldSpellDamageMultiplier=0.7
into your browser instead, it will change the value in-game.
Summary
Today's game engines and programming languages give us a variety of ways to change values during play and get to quickly test things without needing to recompile the game or even to restart it. Hopefully with the ideas above you can implement one or more of these into your game today and start to reap the benefits immediately.