{"id":3103,"date":"2021-01-02T13:58:18","date_gmt":"2021-01-02T05:58:18","guid":{"rendered":"http:\/\/blog.coolcoding.cn\/?p=3103"},"modified":"2021-01-02T19:43:36","modified_gmt":"2021-01-02T11:43:36","slug":"creating-new-console-commands","status":"publish","type":"post","link":"https:\/\/blog.coolcoding.cn\/?p=3103","title":{"rendered":"Creating new console commands"},"content":{"rendered":"\n<p>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:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/fc5a434d-25b5-4071-8ef7-bbbad9ac670c.jpg\" alt=\"\"\/><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">Getting ready<\/h1>\n\n\n\n<p>If you haven&#8217;t already followed the\u00a0<em>Creating a new editor module<\/em>\u00a0recipe, do so, as this recipe will need a place to initialize and register the console command.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">How to do it&#8230;<\/h1>\n\n\n\n<ol><li>Open your editor module&#8217;s header file&nbsp;(Chapter_10Editor.h)&nbsp;and add the following code:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">class FChapter_10EditorModule: public IModuleInterface <br>{ <br>    virtual void StartupModule() override;<br>    virtual void ShutdownModule() override;<br><br>    TArray&lt; TSharedPtr&lt;IAssetTypeActions&gt; &gt; CreatedAssetTypeActions;<br><br>    TSharedPtr&lt;FExtender&gt; ToolbarExtender;<br>    TSharedPtr&lt;const FExtensionBase&gt; Extension;<br><br><strong>    IConsoleCommand* DisplayTestCommand;<\/strong><br><strong>    IConsoleCommand* DisplayUserSpecifiedWindow;<\/strong><\/pre>\n\n\n\n<ol><li>Add the following code within the implementation of&nbsp;StartupModule:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">DisplayTestCommand = IConsoleManager::Get().RegisterConsoleCommand(TEXT(\"DisplayTestCommandWindow\"), TEXT(\"test\"), FConsoleCommandDelegate::CreateRaw(this, &amp;FChapter_10EditorModule::DisplayWindow, FString(TEXT(\"Test Command Window\"))), ECVF_Default);<br><br>    DisplayUserSpecifiedWindow = IConsoleManager::Get().RegisterConsoleCommand(TEXT(\"DisplayWindow\"), TEXT(\"test\"), FConsoleCommandWithArgsDelegate::CreateLambda(<br>        [&amp;](const TArray&lt; FString &gt;&amp; Args)<br>    {<br>        FString WindowTitle;<br>        for (FString Arg : Args)<br>        {<br>            WindowTitle += Arg;<br>            WindowTitle.AppendChar(' ');<br>        }<br>        this-&gt;DisplayWindow(WindowTitle);<br>    }<br><br>    ), ECVF_Default);<\/pre>\n\n\n\n<ol><li>Inside&nbsp;ShutdownModule, add the following code:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">if(DisplayTestCommand)<br>{<br>    IConsoleManager::Get().UnregisterConsoleObject(DisplayTestCommand);<br>    DisplayTestCommand = nullptr;<br>}<br><br>if(DisplayUserSpecifiedWindow)<br>{<br>    IConsoleManager::Get().UnregisterConsoleObject(DisplayUserSpecifiedWindow);<br>    DisplayUserSpecifiedWindow = nullptr;<br>}<\/pre>\n\n\n\n<ol><li>Implement the following function in the editor module (Chapter_10Editor.h):<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">void DisplayWindow(FString WindowTitle) \n{ \n  TSharedRef&lt;SWindow&gt; CookbookWindow = SNew(SWindow) \n  .Title(FText::FromString(WindowTitle)) \n  .ClientSize(FVector2D(800, 400)) \n  .SupportsMaximize(false) \n  .SupportsMinimize(false); \n  IMainFrameModule&amp; MainFrameModule = <br>   FModuleManager::LoadModuleChecked&lt;IMainFrameModule&gt;<br>   (TEXT(\"MainFrame\")); \n  if (MainFrameModule.GetParentWindow().IsValid()) \n  { \n    FSlateApplication::Get().AddWindowAsNativeChild<br>     (CookbookWindow, MainFrameModule.GetParentWindow()<br>     .ToSharedRef()); \n  } \n  else \n  { \n    FSlateApplication::Get().AddWindow(CookbookWindow); \n  } \n}<\/pre>\n\n\n\n<ol><li>Compile your code and launch the editor.<\/li><li>Play the level, and then hit the tilde key to bring up the console.<\/li><li>Type&nbsp;DisplayTestCommandWindow&nbsp;and hit&nbsp;<em>Enter<\/em>:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/639eb05b-4ada-4f76-96ed-3e94259015d3.png\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>You should see our tutorial window open up:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/learning.oreilly.com\/library\/view\/unreal-engine-4x\/9781789809503\/assets\/f08671c4-aad4-4102-85ab-e0906f8a10ca.jpg\" alt=\"\"\/><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">How it works&#8230;<\/h1>\n\n\n\n<p>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&nbsp;StartupModule&nbsp;method.<\/p>\n\n\n\n<p>IConsoleManager&nbsp;is the module that contains the console functionality for the engine.<\/p>\n\n\n\n<p>As it is a sub-module of the core module, we don&#8217;t need to add any additional information to the build scripts to link in additional modules.<\/p>\n\n\n\n<p>To call functions within the console manager, we need to get a reference to the current instance of&nbsp;IConsoleManager&nbsp;that is being used by the engine. To do so, we invoke the static&nbsp;Get&nbsp;function, which returns a reference to the module in a similar way to a singleton.<\/p>\n\n\n\n<p>RegisterConsoleCommand&nbsp;is the function that we can use to add a new console command and make it available in the console:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">virtual IConsoleCommand* RegisterConsoleCommand(const <br> TCHAR* Name, const TCHAR* Help, const <br> FConsoleCommandDelegate&amp; Command, uint32 Flags);<\/pre>\n\n\n\n<p>The parameters for the function are as follows:<\/p>\n\n\n\n<ul><li>Name: The actual console command that will be typed by users. It should not include spaces.<\/li><li>Help:&nbsp;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.<\/li><li>Command:&nbsp;This is the actual function delegate that will be executed when the user types in the command.<\/li><li>Flags:&nbsp;These flags control the visibility of the command in a shipping build, and are also used for console variables.&nbsp;ECVF_Default&nbsp;specifies the default behavior wherein the command is visible, and has no restrictions on availability in a release build.<\/li><\/ul>\n\n\n\n<p>To create an instance of the appropriate delegate, we use the&nbsp;CreateRaw&nbsp;static function on the&nbsp;FConsoleCommand&nbsp;delegate type. This lets us bind a raw C++ function to the delegate. The extra argument that is supplied after the function reference, the&nbsp;FString&#8221;Test Command Window&#8221;, is a compile-time defined parameter that is passed to the delegate so that the end user doesn&#8217;t have to specify the window name.<\/p>\n\n\n\n<p>The second console command,&nbsp;DisplayUserSpecifiedWindow, is one that demonstrates the use of arguments with console commands.<\/p>\n\n\n\n<p>The primary difference with this console command, aside from the different name for users to invoke it, is the use of&nbsp;FConsoleCommandWithArgsDelegate&nbsp;and the&nbsp;CreateLambda&nbsp;function on it in particular.<\/p>\n\n\n\n<p>This function allows us to bind an anonymous function to a delegate. It&#8217;s particularly handy when you want to wrap or adapt a function so that its signature matches that of a particular delegate.<\/p>\n\n\n\n<p>In our particular use case, the type of&nbsp;FConsoleCommandWithArgsDelegate&nbsp;specifies that the function should take a&nbsp;const TArray&nbsp;of&nbsp;FStrings. Our&nbsp;DisplayWindow&nbsp;function takes a single&nbsp;FString&nbsp;to specify the window title, so we need to somehow concatenate all the arguments of the console command into a single&nbsp;FString&nbsp;to use as our window title.<\/p>\n\n\n\n<p>The lambda function allows us to do that before passing the&nbsp;FString&nbsp;onto the actual&nbsp;DisplayWindow&nbsp;function.<\/p>\n\n\n\n<p>The first line of the function,&nbsp;[&amp;](const TArray&lt;FString&gt;&amp; 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,&nbsp;[&amp;].<\/p>\n\n\n\n<p>The second part is the same as a normal function declaration, specifying that our lambda takes in&nbsp;const Tarray, which contains an FString as a parameter called&nbsp;Args.<\/p>\n\n\n\n<p>Within the lambda body, we create a new&nbsp;FString&nbsp;and concatenate the strings that make up our arguments together, adding a space between them to separate them so that we don&#8217;t get a title without spaces.<\/p>\n\n\n\n<p>It uses a range-based&nbsp;for&nbsp;loop for brevity to loop over them all and perform the concatenation.<\/p>\n\n\n\n<p>Once they&#8217;re all concatenated, we use the&nbsp;this&nbsp;pointer (captured by the&nbsp;&amp;&nbsp;operator we mentioned earlier) to invoke&nbsp;DisplayWindow&nbsp;with our new title.<\/p>\n\n\n\n<p>For our module to remove the console command when it is unloaded, we need to maintain a reference to the console command object.<\/p>\n\n\n\n<p>To achieve this, we create a member variable in the module of type&nbsp;IConsoleCommand*, called&nbsp;DisplayTestCommand. When we execute the&nbsp;RegisterConsoleCommand&nbsp;function, it returns a pointer to the console command object that we can use as a handle later.<\/p>\n\n\n\n<p>This allows us to enable or disable console commands at runtime based on gameplay or other factors.<\/p>\n\n\n\n<p>Within&nbsp;ShutdownModule, we check to see if&nbsp;DisplayTestCommand&nbsp;refers to a valid console command object. If it does, we get a reference to the&nbsp;IConsoleManager&nbsp;object and call&nbsp;UnregisterConsoleCommand,&nbsp;passing in the pointer that we stored earlier in our call to&nbsp;RegisterConsoleCommand.<\/p>\n\n\n\n<p>The call to&nbsp;UnregisterConsoleCommand&nbsp;deletes the&nbsp;IConsoleCommand&nbsp;instance via the passed-in pointer, so we don&#8217;t need to&nbsp;deallocate&nbsp;the memory ourselves&nbsp;\u2013 we&nbsp;just reset&nbsp;DisplayTestCommand&nbsp;to&nbsp;nullptr&nbsp;so that we can be sure that the old pointer doesn&#8217;t dangle.<\/p>\n\n\n\n<p>The&nbsp;DisplayWindow&nbsp;function takes in the window title as an&nbsp;FString&nbsp;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.<\/p>\n\n\n\n<p>The function itself uses a function called&nbsp;SNew()&nbsp;to allocate and create an&nbsp;SWindow&nbsp;object.<\/p>\n\n\n\n<p>SWindow&nbsp;is a Slate Window, a top-level window that uses the Slate UI framework.<\/p>\n\n\n\n<p>Slate uses the&nbsp;Builder&nbsp;design pattern to allow for easy configuration of the new window.<\/p>\n\n\n\n<p>The&nbsp;Title,&nbsp;ClientSize,&nbsp;SupportsMaximize, and&nbsp;SupportsMinimize&nbsp;functions that are used here are all member functions of&nbsp;SWindow, and they return a reference to an&nbsp;SWindow&nbsp;(usually, the same object that the method was invoked on, but sometimes, a new object is constructed with the new configuration).<\/p>\n\n\n\n<p>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.<\/p>\n\n\n\n<p>The functions used in&nbsp;DisplayWindow&nbsp;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.<\/p>\n\n\n\n<p>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.<\/p>\n\n\n\n<p>To do this, we retrieve a reference to the Slate interface and call&nbsp;AddWindowAsNativeChild&nbsp;to insert our window in the hierarchy.<\/p>\n\n\n\n<p>If there isn&#8217;t a valid top-level window, we don&#8217;t need to add our new window as a child of anything, so we can simply call&nbsp;AddWindow&nbsp;and pass in our new window instance.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>During development, console commands can be very helpfu [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[27,1],"tags":[26],"_links":{"self":[{"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/posts\/3103"}],"collection":[{"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=3103"}],"version-history":[{"count":1,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/posts\/3103\/revisions"}],"predecessor-version":[{"id":3108,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/posts\/3103\/revisions\/3108"}],"wp:attachment":[{"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3103"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3103"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3103"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}