{"id":3248,"date":"2021-01-02T19:30:34","date_gmt":"2021-01-02T11:30:34","guid":{"rendered":"http:\/\/blog.coolcoding.cn\/?p=3248"},"modified":"2021-01-02T19:40:04","modified_gmt":"2021-01-02T11:40:04","slug":"using-data-binding-with-unreal-motion-graphics","status":"publish","type":"post","link":"https:\/\/blog.coolcoding.cn\/?p=3248","title":{"rendered":"Using Data Binding with Unreal Motion Graphics"},"content":{"rendered":"\n<p>So far, we&#8217;ve been assigning static values to the attributes of our UI widgets. However, what if we want to be more dynamic with widget content, or parameters such as border color? We can use a principle called data binding to dynamically link properties of our UI with variables in the broader program.<\/p>\n\n\n\n<p>Unreal uses the Attribute system to allow us to bind the value of an attribute to the return value from a function, for example. This means that changing those variables will automatically cause the UI to change in response, according to our wishes.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">How to do it&#8230;<\/h1>\n\n\n\n<ol><li>Create a new\u00a0GameModeBase\u00a0subclass called\u00a0AttributeGameMode.<\/li><li>Update the\u00a0AttributeGameMode.h\u00a0file to the following:\u00a0<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>#pragma once\n\n#include \"CoreMinimal.h\"\n#include \"GameFramework\/GameStateBase.h\"\n#include \"SlateBasics.h\"\n#include \"AttributeGameMode.generated.h\"\n\n\/**\n * \n *\/\nUCLASS()\nclass CHAPTER_14_API AAttributeGameMode : public AGameModeBase\n{\n GENERATED_BODY()\n\n TSharedPtr&lt;SVerticalBox> Widget;\n FText GetButtonLabel() const;\n\npublic:\n virtual void BeginPlay() override;\n \n};<\/code><\/pre>\n\n\n\n<ol><li>Add the implementation for\u00a0BeginPlay\u00a0within the\u00a0.cpp\u00a0file:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>void AAttributeGameMode::BeginPlay()\n{\n    Super::BeginPlay();\n\n    Widget = SNew(SVerticalBox)\n        + SVerticalBox::Slot()\n        .HAlign(HAlign_Center)\n        .VAlign(VAlign_Center)\n        [\n            SNew(SButton)\n            .Content()\n        [\n            SNew(STextBlock)\n            .Text(TAttribute&lt;FText>::Create(TAttribute&lt;FText>::FGetter::CreateUObject(this, &amp;AAttributeGameMode::GetButtonLabel)))\n        ]\n        ];\n    GEngine->GameViewport->AddViewportWidgetForPlayer(GetWorld()->GetFirstLocalPlayerFromController(), Widget.ToSharedRef(), 1);\n\n}<\/code><\/pre>\n\n\n\n<ol><li>Also, add an implementation for\u00a0GetButtonLabel():<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>FText AAttributeGameMode::GetButtonLabel() const\n{\n    FVector ActorLocation = GetWorld()->GetFirstPlayerController()->GetPawn()->GetActorLocation();\n    return FText::FromString(FString::Printf(TEXT(\"%f, %f, %f\"), ActorLocation.X, ActorLocation.Y, ActorLocation.Z));\n}<\/code><\/pre>\n\n\n\n<ol><li>Compile your code and launch the editor.<\/li><li>Override the game mode in&nbsp;World Settings&nbsp;to be&nbsp;AAttributeGameMode.<\/li><li>Note that, in a&nbsp;Play In Editor&nbsp;session, the value on the UI&#8217;s button changes as the player moves around the scene:<\/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\/2154c05b-1acb-4ebe-b7ec-beea999cb541.png\" alt=\"\"\/><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">How it works&#8230;<\/h1>\n\n\n\n<p>Just like almost all the other recipes in this chapter, the first thing we need to do is create a game mode as a convenient host for our UI. We create the UI in the same fashion as in the other recipes, that is, by placing&nbsp;Slate&nbsp;code inside the&nbsp;BeginPlay()&nbsp;method of our game mode.<\/p>\n\n\n\n<p>The interesting feature of this recipe concerns how we set the value of our button&#8217;s label text:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">.Text( <br> TAttribute&lt;FText&gt;::Create(TAttribute&lt;FText&gt;::FGetter::Creat<br> eUObject(this, &amp;AAttributeGameMode::GetButtonLabel)))<\/pre>\n\n\n\n<p>The preceding syntax is unusually verbose, but what it is actually doing is comparatively simple. We assign something to the&nbsp;Text&nbsp;property, which is of the type&nbsp;FText. We can assign&nbsp;TAttribute&lt;FText&gt;&nbsp;to this property, and the&nbsp;TAttribute Get()&nbsp;method will be called whenever the UI wants to ensure that the value of&nbsp;Text&nbsp;is up to date.<\/p>\n\n\n\n<p>To create&nbsp;TAttribute, we need to call the static&nbsp;TAttribute&lt;VariableType&gt;::Create()&nbsp;method. This function expects a delegate of some description. Depending on the type of delegate that&#8217;s passed to&nbsp;TAttribute::Create,&nbsp;TAttribute::Get()&nbsp;invokes a different type of function to retrieve the actual value.<\/p>\n\n\n\n<p>In the code for this recipe, we invoke a member function of&nbsp;UObject. This means that we know we will be calling the&nbsp;CreateUObject&nbsp;function on some delegate type.We can use&nbsp;CreateLambda,&nbsp;CreateStatic, or&nbsp;CreateRaw&nbsp;to invoke a&nbsp;lambda, a&nbsp;static, or a&nbsp;member&nbsp;function on a raw C++ class, respectively. This will give us the current value for the attribute.<\/p>\n\n\n\n<p>But what delegate type do we want to create an instance of? Because we&#8217;re templating the&nbsp;TAttribute&nbsp;class on the actual variable type that the attribute will be associated with, we need a delegate that is also templated on the variable type as its return value.<\/p>\n\n\n\n<p>That is to say, if we have&nbsp;TAttribute&lt;FText&gt;, the delegate that&#8217;s connected to it needs to return an&nbsp;FText.<\/p>\n\n\n\n<p>We have the following code within&nbsp;TAttribute:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">template&lt;typenameObjectType&gt;<br> classTAttribute<br> {<br> public:<br> \/**<br> * Attribute 'getter' delegate<br> *<br> * ObjectTypeGetValue() const<br> *<br> * @return The attribute's value<br> *\/<br> DECLARE_DELEGATE_RetVal(ObjectType, FGetter);<br> (...)<br> }<\/pre>\n\n\n\n<p>The&nbsp;FGetter&nbsp;delegate type is declared inside the&nbsp;TAttribute&nbsp;class, so its return value can be templated on the&nbsp;ObjectType&nbsp;parameter of the&nbsp;TAttribute&nbsp;template. This means that&nbsp;TAttribute&lt;Typename&gt;::FGetter&nbsp;automatically defines a delegate with the correct return type of&nbsp;Typename. So, we need to create a UObject-bound delegate of type and signature for&nbsp;TAttribute&lt;FText&gt;::FGetter.<\/p>\n\n\n\n<p>Once we have that delegate, we can call&nbsp;TAttribute::Create&nbsp;on the delegate to link the delegate&#8217;s return value to our&nbsp;TextBlock&nbsp;member variable&nbsp;Text. With our UI defined and a binding between the&nbsp;Text&nbsp;property, a&nbsp;TAttribute&lt;FText&gt;, and a delegate returning&nbsp;FText, we can now add the UI to the player&#8217;s screen so that it&#8217;s visible.<\/p>\n\n\n\n<p>Every frame, the game engine checks all of the properties to see if they are linked to&nbsp;TAttributes. If there&#8217;s a connection, then the&nbsp;TAttributeGet()&nbsp;function is called, invoking the delegate and returning the delegate&#8217;s return value so that Slate can store it inside the widget&#8217;s corresponding member variable.<\/p>\n\n\n\n<p>For our demonstration of this process,&nbsp;GetButtonLabel&nbsp;retrieves the location of the first player pawn in the game world. We then use&nbsp;FString::Printf&nbsp;to format the location data into a human readable string, and wrap that in an&nbsp;FText&nbsp;so that it can be stored as the&nbsp;TextBlock&nbsp;text value.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Controlling widget appearance with Styles<\/h1>\n\n\n\n<p>So far in this chapter, we&#8217;ve been creating UI elements that use the default visual representation. This recipe shows you how to create a Style in C++ that can be used as a common look and feel across your whole project.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">How to do it&#8230;<\/h1>\n\n\n\n<ol><li>Create a new class for your project by using the&nbsp;Add C++ Class&nbsp;wizard and selecting None as your parent class:&nbsp;<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/64c3708b-d1e9-480b-95c7-28fcaf1f9d69.png\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>Under the name option, use&nbsp;CookbookStyle&nbsp;and click on the&nbsp;Create Class&nbsp;button:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/ebb36faa-5690-4154-aae9-0569f96d67d4.png\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>Replace the code in the\u00a0CookbookStyle.h\u00a0file with the following code:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>#pragma once\n#include \"SlateBasics.h\"\n#include \"SlateExtras.h\"\n\nclass FCookbookStyle\n{\npublic:\n    static void Initialize();\n    static void Shutdown();\n    static void ReloadTextures();\n    static const ISlateStyle&amp; Get();\n    static FName GetStyleSetName();\n\nprivate:\n    static TSharedRef&lt; class FSlateStyleSet > Create();\nprivate:\n    static TSharedPtr&lt; class FSlateStyleSet > CookbookStyleInstance;\n};<\/code><\/pre>\n\n\n\n<ol><li>Open the\u00a0CookbookStyle.cpp\u00a0file and use the following code for it:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>#include \"CookbookStyle.h\"\n#include \"SlateGameResources.h\"\n\nTSharedPtr&lt; FSlateStyleSet > FCookbookStyle::CookbookStyleInstance = NULL;\n\nvoid FCookbookStyle::Initialize()\n{\n    if (!CookbookStyleInstance.IsValid())\n    {\n        CookbookStyleInstance = Create();\n    FSlateStyleRegistry::RegisterSlateStyle(*CookbookStyleInstance);\n    }\n}\n\nvoid FCookbookStyle::Shutdown()\n{\n    FSlateStyleRegistry::UnRegisterSlateStyle(*CookbookStyleInstance);\n    ensure(CookbookStyleInstance.IsUnique());\n    CookbookStyleInstance.Reset();\n}\n\nFName FCookbookStyle::GetStyleSetName()\n{\n    static FName StyleSetName(TEXT(\"CookbookStyle\"));\n    return StyleSetName;\n}\n<\/code><\/pre>\n\n\n\n<ol><li>Add the following content below the previously created script in the\u00a0CookbookStyle.cpp\u00a0file to describe how to draw the screen:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( FPaths::GameContentDir() \/ \"Slate\"\/ RelativePath + TEXT(\".png\"), __VA_ARGS__ )\n#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( FPaths::GameContentDir() \/ \"Slate\"\/ RelativePath + TEXT(\".png\"), __VA_ARGS__ )\n#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( FPaths::GameContentDir() \/ \"Slate\"\/ RelativePath + TEXT(\".png\"), __VA_ARGS__ )\n#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( FPaths::GameContentDir() \/ \"Slate\"\/ RelativePath + TEXT(\".ttf\"), __VA_ARGS__ )\n#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( FPaths::GameContentDir() \/ \"Slate\"\/ RelativePath + TEXT(\".otf\"), __VA_ARGS__ )\n\nTSharedRef&lt; FSlateStyleSet > FCookbookStyle::Create()\n{\n    TSharedRef&lt;FSlateStyleSet> StyleRef = FSlateGameResources::New(FCookbookStyle::GetStyleSetName(), \"\/Game\/Slate\", \"\/Game\/Slate\");\n    FSlateStyleSet&amp; Style = StyleRef.Get();\n\n    Style.Set(\"NormalButtonBrush\",\n        FButtonStyle().\n        SetNormal(BOX_BRUSH(\"Button\", FVector2D(54, 54), FMargin(14.0f \/ 54.0f))));\n    Style.Set(\"NormalButtonText\",\n        FTextBlockStyle(FTextBlockStyle::GetDefault())\n        .SetColorAndOpacity(FSlateColor(FLinearColor(1, 1, 1, 1))));\n    return StyleRef;\n}\n\n#undef IMAGE_BRUSH\n#undef BOX_BRUSH\n#undef BORDER_BRUSH\n#undef TTF_FONT\n#undef OTF_FONT\n\nvoid FCookbookStyle::ReloadTextures()\n{\n    FSlateApplication::Get().GetRenderer()->ReloadTextureResources();\n}\n\nconst ISlateStyle&amp; FCookbookStyle::Get()\n{\n    return *CookbookStyleInstance;\n}<\/code><\/pre>\n\n\n\n<ol><li>Create a new&nbsp;GameModeBase&nbsp;subclass,&nbsp;StyledHUDGameMode:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/a6078305-d562-4c27-b878-7e635d381cfd.png\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>Once Visual Studio opens, add the following code to its declaration:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">#pragma once<br><br>#include \"CoreMinimal.h\"<br>#include \"GameFramework\/GameModeBase.h\"<br>#include \"SlateBasics.h\"<br>#include \"StyledHUDGameMode.generated.h\"<br><br>\/**<br> * <br> *\/<br>UCLASS()<br>class CHAPTER_14_API AStyledHUDGameMode : public AGameModeBase<br>{<br>    GENERATED_BODY()<br>    <br>    TSharedPtr&lt;SVerticalBox&gt; Widget;<br><br>public:<br>    virtual void BeginPlay() override;<br>};<\/pre>\n\n\n\n<ol><li>Likewise, implement\u00a0GameMode:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>#include \"StyledHUDGameMode.h\"\n#include \"CookbookStyle.h\"\n\nvoid AStyledHUDGameMode::BeginPlay()\n{\n    Super::BeginPlay();\n\n    Widget = SNew(SVerticalBox)\n        + SVerticalBox::Slot()\n        .HAlign(HAlign_Center)\n        .VAlign(VAlign_Center)\n        [\n            SNew(SButton)\n            .ButtonStyle(FCookbookStyle::Get(), \"NormalButtonBrush\")\n        .ContentPadding(FMargin(16))\n        .Content()\n        [\n            SNew(STextBlock)\n            .TextStyle(FCookbookStyle::Get(), \"NormalButtonText\")\n        .Text(FText::FromString(\"Styled Button\"))\n        ]\n        ];\n    GEngine->GameViewport->AddViewportWidgetForPlayer(GetWorld()->GetFirstLocalPlayerFromController(), Widget.ToSharedRef(), 1);\n\n}<\/code><\/pre>\n\n\n\n<ol><li>Lastly, create a 54 x 54 pixel PNG file with a border around it for our button:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/73922731-36ea-47f4-9e56-48280866be32.png\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>Save it to the&nbsp;Content&nbsp;|&nbsp;Slate&nbsp;folder with the name&nbsp;Button.png, creating the folder if needed:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/293da37b-9933-4c8f-b2e5-d749a70f8302.png\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>You may be asked if you&#8217;d like to import the image into your project. Go ahead and say yes.<\/li><li>Finally, we need to set our game&#8217;s module to properly initialize the style when it is loaded. In your game module&#8217;s implementation file (Chapter_14.h), ensure it looks like this:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>#pragma once\n\n#include \"CoreMinimal.h\"\n#include \"CookbookStyle.h\"\n\nclass Chapter_14Module : public FDefaultGameModuleImpl\n{\n    virtual void StartupModule() override\n    {\n        FCookbookStyle::Initialize();\n    };\n    virtual void ShutdownModule() override\n    {\n        FCookbookStyle::Shutdown();\n    };\n};<\/code><\/pre>\n\n\n\n<ol><li>Then, go to the&nbsp;Chapter_14.cpp&nbsp;file and modify the code to the following:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">#include \"Chapter_14.h\"<br>#include \"Modules\/ModuleManager.h\"<br><br>IMPLEMENT_PRIMARY_GAME_MODULE(<strong>Chapter_14Module<\/strong>, Chapter_14, \"Chapter_14\" );<\/pre>\n\n\n\n<ol><li>Compile the code and set your game mode override to the new game mode, like we did in the other recipes in this chapter.<\/li><li>When you play the game, you will see that your custom border is around the button, and that the text is white rather than black:<\/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\/0a88391a-3f1f-4343-be79-7a38b5271782.png\" alt=\"\"\/><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">How it works&#8230;<\/h1>\n\n\n\n<p>For us to create styles that can be shared across multiple Slate widgets, we need to create an object to contain the styles and keep them in scope.<\/p>\n\n\n\n<p>Epic provides the&nbsp;FSlateStyleSet&nbsp;class for this purpose.&nbsp;FSlateStyleSet&nbsp;contains a number of styles that we can access within Slate&#8217;s declarative syntax to skin widgets.<\/p>\n\n\n\n<p>However, it&#8217;s inefficient to have multiple copies of our&nbsp;StyleSet&nbsp;object scattered through the program. We really only need one of these objects.<\/p>\n\n\n\n<p>Because&nbsp;FSlateStyleSet&nbsp;itself is not a singleton, that is, an object that can only have one instance, we need to create a class that will manage our&nbsp;StyleSet&nbsp;object and ensure that we only have the single instance.<\/p>\n\n\n\n<p>This is the reason we have the&nbsp;FCookbookStyle&nbsp;class. It contains an&nbsp;Initialize()&nbsp;function, which we will call in our module&#8217;s startup code. In the&nbsp;Initialize()&nbsp;function, we check if we have an instance of our&nbsp;StyleSet. If we do not have a valid instance, we call the private&nbsp;Create()&nbsp;function to instantiate one.<\/p>\n\n\n\n<p>We then register the style with the&nbsp;FSlateStyleRegistry&nbsp;class.<\/p>\n\n\n\n<p>When our module is unloaded, we will need to reverse this registration process, then erase the pointer so that it doesn&#8217;t dangle.<\/p>\n\n\n\n<p>We now have an instance of our class that was created during module initialization by calling&nbsp;Create(). You&#8217;ll notice that&nbsp;Create&nbsp;is wrapped by a number of macros that all have a similar form. These macros are defined before the function, and undefined after it.<\/p>\n\n\n\n<p>These macros make it easier for us to simplify the code that&#8217;s required within the&nbsp;Create&nbsp;function by eliminating the need to specify a path and extension for all the image resources that our Style might want to use.<\/p>\n\n\n\n<p>Within the&nbsp;Create&nbsp;function, we create a new&nbsp;FSlateStyleSet&nbsp;object using the&nbsp;FSlateGameResources::New()&nbsp;function.&nbsp;New()&nbsp;needs a name for the style, and the folder paths that we want to search for in this Style Set.<\/p>\n\n\n\n<p>This allows us to declare multiple Style Sets that are pointing to different directories, but using the same names for the images. It also allows us to skin or restyle the whole UI simply by switching to a Style Set in one of the other base directories.<\/p>\n\n\n\n<p>New()&nbsp;returns a shared reference object, so we retrieve the actual&nbsp;FStyleSet&nbsp;instance using the&nbsp;Get()&nbsp;function.<\/p>\n\n\n\n<p>With this reference in hand, we can create the styles we want this set to contain. To add styles to a set, we use the&nbsp;Set()&nbsp;method. Set expects the name of the style, and then a style object. Style objects can be customized using the&nbsp;builder&nbsp;pattern.<\/p>\n\n\n\n<p>We first add a style called&nbsp;&#8220;NormalButtonBrush&#8221;. The name can be arbitrary. Because we want to use this style to change the appearance of buttons, we need to use&nbsp;FButtonStyle&nbsp;for the second parameter.<\/p>\n\n\n\n<p>To customize the style to our requirements, we use the Slate builder syntax, chaining whatever method calls that we need to set properties on our style.<\/p>\n\n\n\n<p>For the first style in this set, we just change the visual appearance of the button when it isn&#8217;t being clicked or is in a non-default state. This means that we want to change the brush that&#8217;s used when the button is in the normal state, and so the function we use is&nbsp;SetNormal().<\/p>\n\n\n\n<p>Using the&nbsp;BOX_BRUSH&nbsp;macro, we tell Slate that we want to use&nbsp;Button.png, which is an image of 54 x 54 pixel size, and that we want to keep the 14 pixels in each corner unstretched for the purposes of nine-slice scaling.For a more visual explanation of the nine-slice scaling functionality, take a look at&nbsp;SlateBoxBrush.h&nbsp;in the engine source.<\/p>\n\n\n\n<p>For the second style in our Style Set, we create a style called&nbsp;&#8220;NormalButtonText&#8221;. For this style, we don&#8217;t want to change everything from defaults in the style; we just want to alter one property. As a result, we access the default text style and clone it using the copy constructor.<\/p>\n\n\n\n<p>With our fresh copy of the default style, we then change the color of the text to white, first creating a linear color of R=1 G=1 B=1 A=1, and then convert that into a Slate color object.<\/p>\n\n\n\n<p>With our Style Set configured with our two new styles, we can then return it to the calling function, which is&nbsp;Initialize.&nbsp;Initialize&nbsp;stores our Style Set reference and eliminates the need for us to create further instances.<\/p>\n\n\n\n<p>Our style container class also has a&nbsp;Get()&nbsp;function, which is used to retrieve the actual&nbsp;StyleSet&nbsp;for use in Slate. Because&nbsp;Initialize()&nbsp;has already been called at module startup,&nbsp;Get()&nbsp;simply returns the&nbsp;StyleSet&nbsp;instance that was created within that function.<\/p>\n\n\n\n<p>Within the game module, we add the code that actually calls&nbsp;Initialize&nbsp;and&nbsp;Shutdown. This ensures that while our module is loaded, we will always have a valid reference to our Slate Style.<\/p>\n\n\n\n<p>As always, we create a Game Mode as the host for our UI, and we override&nbsp;BeginPlay&nbsp;so that we can create the UI when the game starts.<\/p>\n\n\n\n<p>The syntax for creating the UI is exactly the same as we&#8217;ve used in previous recipes&nbsp;\u2013&nbsp;creating a&nbsp;VerticalBox&nbsp;using&nbsp;SNew, and then using Slate&#8217;s declarative syntax to populate the box with other widgets.<\/p>\n\n\n\n<p>It is important to note the two following lines:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">.ButtonStyle(FCookbookStyle::Get(), \"NormalButtonBrush\")<br> .TextStyle(FCookbookStyle::Get(), \"NormalButtonText\")<\/pre>\n\n\n\n<p>The preceding lines are part of the declarative syntax for our button, and the text that makes its label. When we set the style for our widgets using a&nbsp;&lt;Class&gt;Style()&nbsp;method, we pass in two parameters.<\/p>\n\n\n\n<p>The first parameter is our actual Style Set, which is retrieved by using&nbsp;FCookbookStyle::Get(), and the second is a string parameter with the name of the style that we want to use.<\/p>\n\n\n\n<p>With these minor changes, we override the styling of the widgets to use our custom styles so that when we add the widgets to the player&#8217;s viewport, they display our customizations.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>So far, we&#8217;ve been assigning static values to the [&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\/3248"}],"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=3248"}],"version-history":[{"count":4,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/posts\/3248\/revisions"}],"predecessor-version":[{"id":3293,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/posts\/3248\/revisions\/3293"}],"wp:attachment":[{"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3248"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3248"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3248"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}