Creating new console commands

2021/01 02 13:01

During development, console commands can be very helpful by allowing a developer or tester to easily bypass content or disable the mechanics that are irrelevant to the current test being run. The most common way to implement this is via console commands, which can invoke functions during runtime. The console can be accessed using the tilde key (~) or the equivalent in the upper-left area of the alphanumeric zone of your keyboard:

Getting ready

If you haven’t already followed the Creating a new editor module recipe, do so, as this recipe will need a place to initialize and register the console command.

How to do it…

  1. Open your editor module’s header file (Chapter_10Editor.h) and add the following code:
class FChapter_10EditorModule: public IModuleInterface 
{
virtual void StartupModule() override;
virtual void ShutdownModule() override;

TArray< TSharedPtr<IAssetTypeActions> > CreatedAssetTypeActions;

TSharedPtr<FExtender> ToolbarExtender;
TSharedPtr<const FExtensionBase> Extension;

IConsoleCommand* DisplayTestCommand;
IConsoleCommand* DisplayUserSpecifiedWindow;
  1. Add the following code within the implementation of StartupModule:
DisplayTestCommand = IConsoleManager::Get().RegisterConsoleCommand(TEXT("DisplayTestCommandWindow"), TEXT("test"), FConsoleCommandDelegate::CreateRaw(this, &FChapter_10EditorModule::DisplayWindow, FString(TEXT("Test Command Window"))), ECVF_Default);

DisplayUserSpecifiedWindow = IConsoleManager::Get().RegisterConsoleCommand(TEXT("DisplayWindow"), TEXT("test"), FConsoleCommandWithArgsDelegate::CreateLambda(
[&](const TArray< FString >& Args)
{
FString WindowTitle;
for (FString Arg : Args)
{
WindowTitle += Arg;
WindowTitle.AppendChar(' ');
}
this->DisplayWindow(WindowTitle);
}

), ECVF_Default);
  1. Inside ShutdownModule, add the following code:
if(DisplayTestCommand)
{
IConsoleManager::Get().UnregisterConsoleObject(DisplayTestCommand);
DisplayTestCommand = nullptr;
}

if(DisplayUserSpecifiedWindow)
{
IConsoleManager::Get().UnregisterConsoleObject(DisplayUserSpecifiedWindow);
DisplayUserSpecifiedWindow = nullptr;
}
  1. Implement the following function in the editor module (Chapter_10Editor.h):
void DisplayWindow(FString WindowTitle) 
{ 
  TSharedRef<SWindow> CookbookWindow = SNew(SWindow) 
  .Title(FText::FromString(WindowTitle)) 
  .ClientSize(FVector2D(800, 400)) 
  .SupportsMaximize(false) 
  .SupportsMinimize(false); 
  IMainFrameModule& MainFrameModule = 
FModuleManager::LoadModuleChecked<IMainFrameModule>
(TEXT("MainFrame")); if (MainFrameModule.GetParentWindow().IsValid()) { FSlateApplication::Get().AddWindowAsNativeChild
(CookbookWindow, MainFrameModule.GetParentWindow()
.ToSharedRef()); } else { FSlateApplication::Get().AddWindow(CookbookWindow); } }
  1. Compile your code and launch the editor.
  2. Play the level, and then hit the tilde key to bring up the console.
  3. Type DisplayTestCommandWindow and hit Enter:
  1. You should see our tutorial window open up:

How it works…

Console commands are usually provided by a module. The best way to get the module to create the command when it is loaded is to place the code in the StartupModule method.

IConsoleManager is the module that contains the console functionality for the engine.

As it is a sub-module of the core module, we don’t need to add any additional information to the build scripts to link in additional modules.

To call functions within the console manager, we need to get a reference to the current instance of IConsoleManager that is being used by the engine. To do so, we invoke the static Get function, which returns a reference to the module in a similar way to a singleton.

RegisterConsoleCommand is the function that we can use to add a new console command and make it available in the console:

virtual IConsoleCommand* RegisterConsoleCommand(const 
TCHAR* Name, const TCHAR* Help, const
FConsoleCommandDelegate& Command, uint32 Flags);

The parameters for the function are as follows:

  • Name: The actual console command that will be typed by users. It should not include spaces.
  • Help: The tooltip that appears when users are looking at the command in the console. If your console command takes arguments, this is a good place to display usage information to users.
  • Command: This is the actual function delegate that will be executed when the user types in the command.
  • Flags: These flags control the visibility of the command in a shipping build, and are also used for console variables. ECVF_Default specifies the default behavior wherein the command is visible, and has no restrictions on availability in a release build.

To create an instance of the appropriate delegate, we use the CreateRaw static function on the FConsoleCommand delegate type. This lets us bind a raw C++ function to the delegate. The extra argument that is supplied after the function reference, the FString”Test Command Window”, is a compile-time defined parameter that is passed to the delegate so that the end user doesn’t have to specify the window name.

The second console command, DisplayUserSpecifiedWindow, is one that demonstrates the use of arguments with console commands.

The primary difference with this console command, aside from the different name for users to invoke it, is the use of FConsoleCommandWithArgsDelegate and the CreateLambda function on it in particular.

This function allows us to bind an anonymous function to a delegate. It’s particularly handy when you want to wrap or adapt a function so that its signature matches that of a particular delegate.

In our particular use case, the type of FConsoleCommandWithArgsDelegate specifies that the function should take a const TArray of FStrings. Our DisplayWindow function takes a single FString to specify the window title, so we need to somehow concatenate all the arguments of the console command into a single FString to use as our window title.

The lambda function allows us to do that before passing the FString onto the actual DisplayWindow function.

The first line of the function, [&](const TArray<FString>& Args), specifies that this lambda or anonymous function wants to capture the context of the declaring function by reference by including the ampersand in the capture options, [&].

The second part is the same as a normal function declaration, specifying that our lambda takes in const Tarray, which contains an FString as a parameter called Args.

Within the lambda body, we create a new FString and concatenate the strings that make up our arguments together, adding a space between them to separate them so that we don’t get a title without spaces.

It uses a range-based for loop for brevity to loop over them all and perform the concatenation.

Once they’re all concatenated, we use the this pointer (captured by the & operator we mentioned earlier) to invoke DisplayWindow with our new title.

For our module to remove the console command when it is unloaded, we need to maintain a reference to the console command object.

To achieve this, we create a member variable in the module of type IConsoleCommand*, called DisplayTestCommand. When we execute the RegisterConsoleCommand function, it returns a pointer to the console command object that we can use as a handle later.

This allows us to enable or disable console commands at runtime based on gameplay or other factors.

Within ShutdownModule, we check to see if DisplayTestCommand refers to a valid console command object. If it does, we get a reference to the IConsoleManager object and call UnregisterConsoleCommand, passing in the pointer that we stored earlier in our call to RegisterConsoleCommand.

The call to UnregisterConsoleCommand deletes the IConsoleCommand instance via the passed-in pointer, so we don’t need to deallocate the memory ourselves – we just reset DisplayTestCommand to nullptr so that we can be sure that the old pointer doesn’t dangle.

The DisplayWindow function takes in the window title as an FString parameter. This allows us to either use a console command that takes arguments to specify the title, or a console command that uses payload parameters to hard-code the title for other commands.

The function itself uses a function called SNew() to allocate and create an SWindow object.

SWindow is a Slate Window, a top-level window that uses the Slate UI framework.

Slate uses the Builder design pattern to allow for easy configuration of the new window.

The Title, ClientSize, SupportsMaximize, and SupportsMinimize functions that are used here are all member functions of SWindow, and they return a reference to an SWindow (usually, the same object that the method was invoked on, but sometimes, a new object is constructed with the new configuration).

The fact that all these member methods return a reference to the configured object allows us to chain these method invocations together to create the desired object in the right configuration.

The functions used in DisplayWindow create a new top-level Window that has a title based on the function parameter. It is 800 x 400 pixels wide, and cannot be maximized or minimized.

With our new Window created, we retrieve a reference to the main application frame module. If the top-level window for the editor exists and is valid, we add our new window instance as a child of that top-level window.

To do this, we retrieve a reference to the Slate interface and call AddWindowAsNativeChild to insert our window in the hierarchy.

If there isn’t a valid top-level window, we don’t need to add our new window as a child of anything, so we can simply call AddWindow and pass in our new window instance.