Today, let’s prepare a few things to make our game parameters ready for in-game display!
This article is also available on Medium.
In the past tutorials, we’ve added a few game parameters in particular for our sound system. The issue is that, for now, those parameters are defined in Scriptable Objects – meaning in project assets that can only be modified by developers and game designers. Once the game has been compiled and shipped to the players, those settings aren’t accessible anymore! Wouldn’t it be sweet if the player could decide whether or not to have the background music? Or control the volume of ambient sounds? Or even toggle off the FOV for a “sandbox” game?
To do that, we’ll create an in-game settings panel that displays some of our parameters. But first, we’ll need to rewrite our
GameSoundParameters classes a bit, and to create our own Unity inspector to make it easier to setup our parameters…
GameParameters with some inheritance
So far, we’ve put most of our options in the
GameParameters class, and some new parameters related to our sound system in a dedicated
GameSoundParameters class. The problem is that this setup is not ideal for automating the display.
What I want is to be able to associate each class with a “game options menu”, i.e. have one menu for global game parameters, another for sound parameters, etc. At the moment, it’s pretty hard to do without writing somewhere by hand the list of all menus. And as always: writing by hand means running the risk of inconsistencies and loosing a lot on scalability. Whenever I’ll add a new system, I’ll have to remember to update this list, check for the display… this is not the right way to go!
Instead, we are going to refactor our game parameters classes with a little inheritance logic so that the
UIManager can later pick up the proper options menu to display – and what to put inside of each!
Basically, we are going to go from this current configuration (where
GameSoundParameters are two Scriptable Objects class in parallel)…
… to this new one (where we moved all the current contents from
GameParameters to a new class,
GameGlobalParameters, and we’ve re-created an empty
GameParameters class to be the parent of the two others):
This way, we’ll be able to list
GameParameters classes and automatically get all of our systems well-sorted.
This is pretty easy to do; we simply need to:
- rename the current
GameGlobalParameters(don’t forget to also rename the file itself)
- create a new
GameParametersscript that just inherits from the
ScriptableObjectbuilt-in but doesn’t have any fields for now
- have the
GameSoundParametersclasses inherit from the new
Here is the complete code of the 3 updated classes:
Note: I’ve also renamed the asset menu to “Game Global Parameters” to really match the new class name.
Be careful: because we’ve changed the “meaning” of
GameParameters, the asset that you created before using this Scriptable Object is now invalid! You’ll need to recreate it (by using our new “Create > Scriptable Objects > Game Global Parameters” menu) and refill in the variables.
Updating the references to
Also, now that we’ve changed the
GameParameters class and moved everything to the
GameGlobalParameters class, we are going to have some compilation errors, namely because of the
DayAndNightCycler. Why? Because both these scripts had a reference to our
GameParameters asset instance and therefore are completely lost with the missing data.
Let’s fix this problem and improve our setup a bit at the same time!
First things first: we’re going to take care of the
GameManager class. We’ll just change the type and rename the game parameters variable; and then remember to drag and drop our new asset in the inspector:
To do this renaming, either use the “refactor” feature of your IDE or do it by hand; if so, make sure to also change the
BuildingPlacer.cs file. It has a reference to the
GameManager.instance.gameParameters that needs to be changed to
Then, we can take care of our
DayAndNightCycler. Indeed for now it doesn’t compile because it has a reference to the old game parameters… even though it could get the new ones directly from the
GameManager singleton instance! To avoid confusion and errors, we’re going to remove this variable from the
DayAndNightCycler script and instead go through the singleton.
What we need to be wary of is the scripts lifecycle: at the moment, we’re accessing the game parameters in the
Awake() function of the
DayAndNightCycler, but we’re only setting up the
GameManager singleton instance later on, during its
Start() function. So just swapping the references will result in a null reference.
Therefore, we also need to move the initialisation logic of the day-and-night cycler to its
Start() function, so the singleton has been properly initialised when we call it:
Finally, to insure that scripts run in the proper order, we’ll configure the “Script Execution Order” in the project settings. This is a nice Unity trick that allows us to make the scripts order deterministic:
Thanks to this setup, we’re absolutely sure that the
Start() method will have run before the
Start() method does.
So – we’re done! No more compilation errors – we’re back on track 😉
Giving a name to our game parameters
In order to print them in the UI, we’ll need to have an easy access to the display name of our various game parameters classes. For example, I’d like the
GameGlobalParameters to be shown in the settings panel in a little “Global” menu; but how do I define this “Global” label?
For that, I’m going to use another C# OOP feature: abstract classes and methods. As explained in the Microsoft docs:
abstractmodifier indicates that the thing being modified has a missing or incomplete implementation.
Roughly put, by defining a class or a method as abstract, you’re telling the program that its logic is set up elsewhere, and more precisely in its children classes. Here, we’re going to create a little getter for the name – and we’ll add the
abstract modifier so we’re sure each child game parameters class defines it:
Trying out listing our game parameters
To check that everything’s working as expected so far and that we won’t have any issue displaying the parameters in-game, let’s do a test run. I won’t actually be doing any UI stuff in this article, but I’ll simply list the various systems and print their names thanks to our brand new
GetParametersName() method. I’ll put it in the
GameManager script, in the
If you start the game and look at the console, you’ll see “Global” and “Sound” printed: the script has managed to automatically fetch our game parameters assets and show their display names.
That’s nice! After this refactor, it’s pretty easy for us to get our various systems and printing out their display names 🙂
Choosing the “in-game displayable” parameters
There is one important thing we need to prepare for our game parameters in-game display: an easy way to pick which ones we actually want the player to see! With out current setup, it’s an “all-or-nothing” situation: if we were to automate the display of the fields of a given game parameters class, we’d either display all of them or none at all.
The ideal scenario would be if we could toggle on and off the “in-game display” on each field individually. So let’s make that happen: we’re going to create our own custom Unity editor so that our game parameters asset show up with an additional togglable icon!
Before doing any actual customisation, let’s add a new field in our
GameParameters class: the list of the fields that should be shown in-game – this is the list that we’ll update with our custom icons. To better encapsulate the data and avoid some headaches later on with our fields automatic listing, we’ll set the field as
protected, and we’ll define some util functions to go along with it. Here is the full code of the updated
Because the field is not
public, by default Unity won’t serialise it, meaning that it won’t get properly saved when we close the editor, or if we build the game. To insure that it does, we have to add the
[SerializeField] custom attribute – this forces the serialisation of this particular bit of data along with the rest of the object.
Now, we can take care of our custom editor. To create our own inspector, we need to create a new script that it is a the “Editor” folder anywhere in our assets directory (for now, I’ll put it at the root) – I’ll call it
Let’s see what to actually write in this script. We’re going to proceed in three steps:
- first, we’ll create a basic custom editor that retrieves the game parameters asset we’re editing and prints its display name
- then, we’ll do some C# reflection to dynamically get the type of the fields and show appropriate GUI editors
- finally, we’ll add the in-game toggle as a little button that adds or removes the field name to/from the list of fields to show in-game
Step 1: Setting up the custom editor
To begin with, we need to be using the
UnityEditor package so our class can inherit from the
Editor Unity built-in. Then we need to override the
OnInspectorGUI() method to replace the default inspector with our own.
This function is called regularly to redraw the various UI elements we give it in the custom editor window.
Also, you’ll notice that we have an attribute above the class (inside the square brackets):
CustomEditor. This tells Unity what type of assets this custom editor should be shown for. The
true parameter enables us to extend this customisation to all children classes of the given type which is absolutely crucial in our case! (Remember that the
GameParameters class is abstract so we are never going to create instances of this one – instead, we’ll always be instantiating its child classes!)
We can start diving in the Unity’s
EditorGUILayout class to actually display some UI. To write the name of our game parameters menu, we’ll use the
LabelField function – it gives us a basic read-only text:
EditorStyles class provides us with lots of utilities to change the way our UI elements are stylised. Here for example we’ve given the label a bold font face.
If you go back to Unity and click on one of game parameters asset, you’ll now see a very basic inspector that simply shows its display name: we’ve successfully injected our custom editor in place of the default one, yay! 🙂
Step 2: Time to reflect!
Alright – that was the easy part. The real meat of this script is going to be all about reflection. I’ve talked about it in the last episode of this tutorial and in another recent article on C# about generics – in C#, reflection allows you to dynamically interact with your object types. Here, we’re going to use it to get the type of each of the fields in our game parameters asset and display a nice value editor that depends on this type.
For example, we’re going to have a text input for strings, a checkbox for booleans, a number input for floats and ints and even special inputs for colours or vectors! For other variable types, we’ll show a default “any object” input that allows us to drag and drop audio clips, game objects, transforms…
Note: we’ll see later on that we can automate this and get a better display thanks to some additional Unity built-ins; but I find it valuable to actually show the “long” version for now, and focus more on the topic of reflection.
All of these UI inputs work the same: you give them a current value, optionally some styling; and you get back the updated return value whenever the user changes the content.
This piece of code is quite boring to write but there’s nothing too hard about it – we just try and tackle as many special cases as possible, or revert to the default “object” field input 😉
Also, I’ve used the
System.Attribute.IsDefined built-in methods to check whether or not there is a custom “HideInInspector” attribute above the field that should prevent it from appearing. We’ll talk more about attributes in the next section!
In the end, the real trick is that, since we’re using reflection, we can’t directly get the current value and assign the return value to the variable. Instead, we have to use the C# reflection tools
field.SetValue() that allow us to get or assign a value from/to a given field in an object by using a dynamic field reference.
EditorGUILayout.EndHorizontal() are a UI utility to display the name of the fields and their matching editor in a row.
Go back to Unity – the script should recompile and the editor automatically update to show you the new edit fields:
Step 3: Adding our “show in-game” toggles
To add the buttons at the beginning of the row, we’ll simply call the
GUILayout.Button() function before we print the field name (just after we’ve started our horizontal group). This Unity’s UI method is a bit peculiar: to define the callback that should be run when clicking on the button, you actually put the
GUILayout.Button() inside an
if statement, and whatever is in this conditional branch will be executed when you click on the button:
We’ll also change the text on the button from “+” to “-” as we toggle the option on and off – this way, it will be easier to know which fields are currently visible in-game.
But we’d like this specific “prefix” content to be easily changeable if we decide on something else in the future. So to decouple the “prefix” logic from the “show the field logic”, we are going to pass it to the
_DrawField() function we just created using a delegate. If you want more info on this C# variable type, you can check out one of my recent articles on this topic; in short, delegates are a way to pass in methods to other methods as parameters. You first define the prototype of functions you want to pass in (because C# remains a strongly-typed language!) and you can then “implement” the delegate by creating actual functions with a body and some logic that follow this prototype.
Here, we want to create a delegate that can receive the asset and the field we’re working on, and display a UI button. It doesn’t return anything, so it’s a void method. The prototype, and therefore the delegate declaration, will be something like:
private delegate void DDrawPrefix(Object obj, FieldInfo field);
D prefix for delegate variables is a personal convention that I’m currently trying out; it’s inspired by the more conventional
I prefix for instances… 😉
Now, we can “implement” (or “instantiate”) this delegate in our code to deal with the UI button creation, and pass it to the
_DrawField() function, like this:
Thanks to delegates, we can very easily change the way the “prefix” of our fields is displayed, and we could quickly export it to somewhere place to share it between multiple scripts (as opposed to having to copy again and again the same snippet of code).
Here is the updated editor:
In the code, you’ll notice that we have two weird lines:
Those are here to force Unity to register this data update and have it saved when we close the editor or we build our game. The first line is a way of telling the program that this field has been modified and should be considered obsolete – we often talk of “dirty” states when we mean “something is not up-to-date, it’s not proper anymore”. The second line directly tells Unity to update the assets so that if we close the editor after we’ve clicked the “+”/”-” button, the change is indeed written in the data.
On top of that: using sliders for ranged integers and floats
There is actually another little improvement we can make to our custom editor, once again by leveraging C# reflection tools. When we have integers and floats, sometimes, we want their value to be restricted to a given range.
For example, the volume of the background music and the sound effects (SFX) can be represented by an int between 0 and 100, so we have a percentage.
Note: we’ll see in the next episode that this range is not really the best for our RTS and that it’s better to use decibel values, but it’s easier to picture at the moment, so let’s stick with this theoretical percentage mindset…
To do this on the data side, it’s pretty easy: Unity provides us with a built-in custom attribute,
Range, that lets us define a min and max value for our variable:
If we had the default Unity inspector, we’d automatically get a slider for our variables, now that they have a
Range attribute. The only problem is… that our custom editor simply ignores this attribute. So a user could enter just any value, even if it falls out of the range!
To make sure that int and float ranges use sliders, we’ll need to do some reflection to check out the field’s attributes and, if need be, replace our
EditorGUILayout.IntField with an
EditorGUILayout.IntSlider (same for the
FloatField that will be replaced by
Slider). This is possible thanks to the C#
And we get two nice sliders! 🙂
Bonus: re-testing it!
To wrap this up and test it out, let’s extend our little loop from before, in the
GameManager, to also show the list of fields that are displayed in-game. This is quite straight-forward – I’m updating the little test code snippet I put in my
Start() method previously:
Say we’ve toggled some options on in our custom editor by clicking the “+” icon (so it’s now displaying a “-” instead): the
enableFOV parameters. This means that the player will eventually be able to update those values in the in-game settings panel.
And we can check that our debugs indeed match this list!
Now that we’ve checked everything’s working fine, you can clean up the lines we added in the
Start() function of the
GameManager to revert to what we had before – in the next article, we won’t be doing this plain console stuff anymore but rather displaying actual UI elements!
This article was a bit more refactoring than usual, but it’s really going to pay off in the next episode, when we create our in-game settings panel. Thanks to this preliminary work, we’ll be able to list our game parameters assets in a breeze and quickly create matching menus. Also, we’re now familiar with how to display type-dependent UI elements, so it will help us design said panel 🙂