I recently made a runtime console in unity. This will be part of Goat Utils once that package gets all setup!
Creating Commands/Command Flow
ConsoleCommands.Add(string command, Action<string[]> action, string info);
command
is the full string of the command that should run some method. Like player add item
action
is the method/delegate that will run when the command executes. These methods must have 1 parameter with type string[], which stores all the args getting sent through. For the player example it may be something like Player.Local.AddItem(string[] args)
It’s possible to set action
to null if, for example, you wanted to add info
to subcommands to show their own subcommands, without some subcommands executing anything. Like if you wanted info on the player
command, but didn’t want the player
command to do anything on its own.
info
is optional info about the command that will show up when using the help
subcommand.
Each word of a command is stored in a Command class, each which has a dictionary of other Commands, and an action for that Command. The dictionary points the next word in the command to another Command class, which has its own dictionary, so we can create a nest of commands.
Below is a visual breakdown of this for a few commands. The args of the commands are in bold, and I’ll talk about args in a moment.
Using this structure, we can go through each commandDict with each word, until we end up at the command we want. We can then add a delegate, the ‘action’, which gets called when the command is run!
Args
After a valid command is input, any words after that command are put into that command as a string array, args. This allows methods to be a bit “customized” when calling them, letting them do more than just one thing which is always helpful. So the teleport
would read the first 3 elements of the args array, looking for the x, y, z coords to teleport the player too. It would have to handle its own error handling if the args aren’t correct, there’s not enough of them, etc. Having too many args isn’t really a problems since each method will just stop reading the args after they have all the ones they need.
Autocomplete
Autocomplete was a bit fiddly to implement, but ultimately not too difficult. Using our nested commands, we can just take our most current completed command, get a list of all subcommands, filter by which ones our current input text fit with, sort alphabetically, then take the first one in the list, if any remain. There’s a couple weird edge cases of when to properly show the subcommands, but other than that it was fairly straightforward.
So if we type pla
our autocomplete shows player
. player a
shows player add
, since add
is the only/first subcommand of player
that starts with a
. "player "
(with the space at the end) will show player add
autocomplete as well, since add
sorts alphabetically before teleport
One other feature I think would be good to add is a way to see all possible subcommands of the current command. A dropdown menu of some sort that just shows a selectable list of all possible subcommands.
Help Command
Each command/subcommand can have a Help section added to it. command help
will show the help info for that command. This lists all subcommands, can be used to provide info on what the command does, and what args it may expect. The info and args info have to be set manually when creating the command.
UI
Keeping the inputfield selected after hitting enter is a bit weird, we have to run the .Select()
command on the TMP_InputField each time we use it. Thankfully there are some events we can listen to to handle this.
Awake()
{
...
inputField.onSubmit.AddListener(OnInputFieldSumbitHandler);
...
}
private void OnInputFieldSumbitHandler(string value)
{
AddToCommandHistory(value);
commandHistoryIndex = -1;
ConsoleCommands.RunCommand(value);
inputField.text = "";
inputField.ActivateInputField();
}
Tie in Unity Debug
Unity has a event Application.logMessageRecived, or Application.logMessageReceivedThreaded, which lets us get incoming messages going to the Unity console, main interest here being Debug.Log messages! We could tie into this and anytime a lot gets send add it to out own runtime console, so we can see our logs in the app itself! Neat!
Better Args
I wanted to keep this system rather lightweight, so I didn’t spend a lot of time with a feature that could be nice, which is args definitions. Basically a way for the autocomplete text to show info about the args that the command is expecting, to help the user put in the proper stuff.
I believe on way to do this is have some Args class, and each command has an array of Args. The Args class has the arg name, and expected type to help populate info. Then when autocomplete gets to a valid command it shows the args info in the autocomplete text rather than subcommands. Maybe it could then send a list of Args rather than strings for the receiving method to parse.
The biggest problem is this requires a lot of manual setup when creating the commands, each arg needing to get defined and their info added. This could get tedious, especially when this console idea is more focused as a debugging tool.
Leave a Reply