On with our RTS! Today, we’re going to create a debug console (with some cheat codes…!) 🙂
This article is also available on Medium.
The last two episodes were pretty dense! We talked about data serialisation and binary format, and we dived deep into C# reflection to automate fields extraction on our Scriptable Objects.
Today, let’s work on something a bit more straight-forward: an in-game debug console!
This console will allow us, as developers, to quickly reach a given state in the game without having to “replay” by hand all the required steps; but it will also be a nice feature for players who want to have fun and use cheat codes to “boost” their game 😉
Disclaimer: this tutorial is inspired by this really cool video by Matt from Game Dev Guide in which he describes a basic debug console system. I’ve added some features like help and autocomplete to make it more fun, but it relies on the same core ideas.
Preparing our console and binding it to an input
Making a new input binding
The first step is to prepare our main debug console class and have the console pop out when we press a given key. We’ll choose a key that isn’t used too often in “normal” play, so like Matt, let’s pick the backquote character.
To begin with, let’s add this input to our game input parameters asset. We just need to update our Scriptable Object with this new action:
For now, our
GameInputParameters only handles events that contain the colon
: character (because we only had the
Build:... events so far). So let’s update it:
Now, whenever we press the backquote key, we’ll be triggering a new “ShowDebugConsole” event (and remember that this event is automatically prefixed with the “<Input>” prefix by our
Implementing the basic console show/hide logic
Time to use this event!
Let’s make a new C# class in a
DebugConsole.cs file. In that class, we will listen to our new event and use it to toggle a boolean flag on:
_showConsole. If that flag is active, then we’ll use the
OnGUI() method we talked about a few episodes ago to display UI stuff with Unity’s IMGUI system:
Don’t forget to place this new MonoBehaviour somewhere in the scene! I decided to add it to my main “GAME” object, but you can also create another empty GameObject just for the debug console if you prefer 🙂
As you can see with the comments, for my console, I want to define two areas: a little input field at the very top of the screen where I can enter my commands and a “log area” below that will display various info – the history of previous commands, the help, the output of some commands…
I’ll start with a basic text field for the input and an empty box for the log area:
I also want to be able to close my console easily, using the
<Escape> key. I won’t go through all the normal input binding process for this because this console is more of a dev/hacky feature, so I don’t really need to worry about multi-inputs and user overrides.
I’ll simply look at the current GUI event and, if it’s a key press and the key is the
<Escape> key, I’ll toggle off my display flag:
Note: when you toggle the console on and off, you can also trigger our “PauseGame” and “ResumeGame” events if you want to freeze time and dim the music 😉
A last little improvement we could make is bring up the opacity a bit so that our console doesn’t mix with the scene in the background so much. There are several ways to do this: for example, we could create a
GUIStyle of our own with a custom texture and pass it to our elements when we draw them. But this gets more complicated or input fields, so instead we can use a little “hack”, which to simply draw additional “fake” boxes in the background that overlay each other and gradually bring up the opacity:
Note: be careful – the IMGUI is drawn in the same order as your lines of code, so you want to create the “fake boxes” first (at the top of your if-statement) to make sure they are drawn first and end up below the rest of the UI 😉
This gives us something more opaque and easier to read!
Getting the input and creating our first debug commands
Ok – we have a very simple debug console that we can open with the backquote key and close by pressing
<Escape>! Now, how do we actually add commands to run through this “interface” and make our console “consume” our input field content?
Checking for input validation
The second part is actually pretty quick to do: we’ll just check for another key press, this time on the
<Return> key, and if we do press this key we’ll call an
_OnReturn() function. This method will try and run the command (we’ll see very soon how to check if the command is valid and invoke it if possible) before clearing out our input:
If you try and write text to the input, you’ll see that after you press the return key the text is cleaned up and you can write a new command.
But of course, now, we want to actually do something when we enter some pre-defined commands in the console!
Describing our commands and invoking them
What we’ll do is create a list of valid commands that the player can list and use to run various actions. The commands we’ll create in this article will either be run “as-is” without any parameter or have a single parameter but the technique we’ll use can easily be extended to handle more parameters 🙂
Like Matt in his video, let’s start by defining a
DebugCommandBase class that contains the base data for our commands: a unique ID, a description and a format to help the developer/player know how it’s used:
This class doesn’t yet define any logic to run when we enter the command: it is just a blob of abstract data.
To actually get runnable commands, we’ll create another class that inherits from it, the
DebugCommand class, that also has an
Action C# type is a shorthand to write delegates that have no parameters and don’t return anything. It can be extended to take one or more parameters when you use its generic version,
DebugCommand instances will be simple commands that have no parameters and therefore use a plain
Action variable. We’ll also create a method to invoke this action directly:
Finally, we can make our lives easier by automatically registering any new command to the global list of commands in our
I’ve made it a dictionary to optimise the access later on, when we search through or list and check the “main keyword” (the first “word” in the format string) against our current input.
Then, we just have to implement our
_HandleConsoleInput() method to go through all available commands and check if the current input matches any main keywords in our
DebugCommands dictionary. If we find a match, then we’ll call the command’s
Invoke() method to trigger the associated action:
Here, I’m using polymorphism and downcasting to first iterate through my commands in an agnostic way and then actually access the action from my derived class.
Defining a simple command
With all that done, we are now ready to define basic commands in our
All we need to do is go to the
Awake() function and create a new instance of
DebugCommand with an ID, a description, a format and a callback action – for example, here is a simple command that toggles the FOV on and off:
And at that point, we can toggle on our console and run our first command! 🙂
Passing parameters to our commands
Calling basic commands is nice, but it would be better if we could also pass a parameter explicitly to our command, too. To do this, it’s actually quite easy: we simply need to create another class derived from the
DebugCommandBase that is a generic class and therefore accepts an additional value. It will be the
DebugCommand<T> class and it will have an
Action<T> field (instead of a plain
We can use this new class in the
Awake() method of our
DebugConsole class once again; and for example, we can create a command to add to our current amount of gold. This command will expect an integer value (the value to add to the current resource) and use it in its
Note that I’ve also made sure my format does inform the dev/player this command requires a parameter.
Now, let’s go back to our
_HandleConsoleInput() method and take care of this new case. The idea is basically that:
- the first “word” in the input is the main keyword and the rest are the parameters
- if the command that we’re trying to run is not a plain command, then we check if we indeed have a parameter, or else abort
- if we have a parameter, then we try and downcast the command to its specific
DebugCommand<int>type, and we try to cast the parameter to an int
- if all is fine, we invoke the command with this parameter
This logic translates to the following C# snippet of code:
Note: you could – and should, probably – log some errors to the console if the casts fail or if there are missing parameters so that the user understands why the command doesn’t run, but I’ll let you work on that for yourself 😉
You can now run the game again and try to use our new
add_gold command! 🙂
Similarly, we could replace our two GUI buttons to switch the current player by a command and remove them from our “normal” interface:
Bonus features: help and autocomplete
We now have a basic version of our debug console: we can define commands and run them, either with or without parameters, and toggle our console on and off.
But this console can still be improved in many ways! For example, we could add a little help feature that lists all the available commands, or an auto-complete tool that filters this list and only shows the commands that start with the current input…
Preparing the log area
First things first, let’s work a bit on our log area. Both these features will need to print something to this area, but we have to distinguish between the two states to show the right info.
So, first, I’ll create a little enum that defines the various display states I can be in:
I also defined a
None state that will be the default one and that I’ll reset to whenever my input changes.
Now, I can check this new variable in my
OnGUI() function and call the corresponding util function:
Finally, to handle the reset, I’ll extract the new input to a variable and compare it to my previous input value – if the values are different and I’m currently showing either the help or the auto-complete, I’ll go back to a null display:
Adding a help command
_ShowHelp() method will simply iterate through all the commands stored in our global
DebugCommands dictionary and print their format and their description (one per line):
Then, we’ll add a basic command with no parameters that changes our current display state:
This way, whenever I run the
? command, I’ll see the list of all available commands; and as soon as I write a new character in my input, the display is reset to an empty log area:
Adding an auto-complete tool
The auto-complete feature is pretty similar to the previous one: when we press the
<Tab> key, we’ll switch to the
Autocomplete display type and call our
_ShowAutocomplete() method; then, if our input changes, we’ll go back to a null display:
Here, I’m once again the Linq C# package to filter my commands and only display the ones that starts like my current input 🙂
If you re-run the game, you’ll now have an easy way of checking if your current input matches any command!
Thanks to our
DisplayType enum, we can easily add other display states for our log area – for example, if you want some of your commands to return info and display it in the console…
Today, we’ve created a simple debug console for our RTS that can be used either by developers to quickly change the state of the current session or even by players that like cheat codes 😉
It can be improved with lots of additional features, for example a history (rewindable with the arrow keys) using C# queues, error logs, custom GUI styles…
Next time, we’ll take a little break from coding and see how to use Unity terrain tools to model the landscape relief, paint textures and add meshes like trees or rocks to get a basic scene like the one I’m using in these demos 🙂