{"id":3254,"date":"2021-01-02T19:31:50","date_gmt":"2021-01-02T11:31:50","guid":{"rendered":"http:\/\/blog.coolcoding.cn\/?p=3254"},"modified":"2021-01-02T19:38:36","modified_gmt":"2021-01-02T11:38:36","slug":"creating-a-custom-swidget-uwidget","status":"publish","type":"post","link":"https:\/\/blog.coolcoding.cn\/?p=3254","title":{"rendered":"Creating a custom SWidget\/UWidget"},"content":{"rendered":"\n<p>The recipes in this chapter so far have shown you how to create UIs using the existing primitive widgets.<\/p>\n\n\n\n<p>Sometimes, it is convenient for developers to use composition to collect a number of UI elements to define a button class that automatically has a&nbsp;TextBlock&nbsp;as a label rather than manually specifying the hierarchy every time they are declared, for example.<\/p>\n\n\n\n<p>Furthermore, if you are manually specifying the hierarchy in C++, rather than declaring a compound object consisting of subwidgets, you won&#8217;t be able to instantiate those widgets as a group using UMG.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Getting ready<\/h1>\n\n\n\n<p>This recipe shows you how to create a compound&nbsp;SWidget&nbsp;that contains a group of widgets and exposes new properties to control elements of those subwidgets. It will also show you how to create a&nbsp;UWidget&nbsp;wrapper, which will expose the new compound&nbsp;SWidget&nbsp;class to UMG so that it can be used by designers.<\/p>\n\n\n\n<p>This recipe shows you how to create a compound\u00a0SWidget\u00a0that contains a group of widgets and exposes new properties to control elements of those subwidgets. It will also show you how to create a\u00a0UWidget\u00a0wrapper, which will expose the new compound\u00a0SWidget\u00a0class to UMG so that it can be used by designers.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">How to do it&#8230;<\/h1>\n\n\n\n<ol><li>We need to add the UMG module to our module&#8217;s dependencies.<\/li><li>Open up&nbsp;&lt;YourModule&gt;.build.cs, which in our case is&nbsp;Chapter_14.Build.cs, and add UMG to the following code:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">using UnrealBuildTool;<br><br>public class Chapter_14 : ModuleRules<br>{<br>  public Chapter_14(ReadOnlyTargetRules Target) : base(Target)<br>  {<br>    PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;<br>  <br>    PublicDependencyModuleNames.AddRange(new string[] { \"Core\", \"CoreUObject\", \"Engine\", \"InputCore\" });<br><br>    PrivateDependencyModuleNames.AddRange(new string[] { });<br><br>    \/\/ Uncomment if you are using Slate UI<br>    PrivateDependencyModuleNames.AddRange(new string[] { \"Slate\", <br>    \"SlateCore\"<strong>, \"UMG\"<\/strong> });<br>    <br>    \/\/ Uncomment if you are using online features<br>    \/\/ PrivateDependencyModuleNames.Add(\"OnlineSubsystem\");<br><br>    \/\/ To include OnlineSubsystemSteam, add it to the plugins <br>    \/\/ section in your uproject file with the Enabled attribute<br>    \/\/ set to true<br>  }<br>}<\/pre>\n\n\n\n<ol><li>Create a new class based on the Slate Widget parent class (SCompoundWidget):<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/1a17774f-7605-4d12-ad72-aa538589875a.png\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>When asked for a name, call it&nbsp;CustomButton.<\/li><li>Once created, 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 \"Widgets\/SCompoundWidget.h\"<br><br>class CHAPTER_14_API SCustomButton : public SCompoundWidget<br>{<br>    SLATE_BEGIN_ARGS(SCustomButton)<br>        : _Label(TEXT(\"Default Value\"))<br>        , _ButtonClicked()<br>    {}<br>    SLATE_ATTRIBUTE(FString, Label)<br>        SLATE_EVENT(FOnClicked, ButtonClicked)<br>        SLATE_END_ARGS()<br><br>public:<br>    void Construct(const FArguments&amp; InArgs);<br>    TAttribute&lt;FString&gt; Label;<br>    FOnClicked ButtonClicked;<br>};<\/pre>\n\n\n\n<ol><li>Implement the class with the following in the corresponding&nbsp;.cpp&nbsp;file:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">#include \"CustomButton.h\"<br>#include \"SlateOptMacros.h\"<br>#include \"Chapter_14.h\"<br><br><br>void SCustomButton::Construct(const FArguments&amp; InArgs)<br>{<br>    Label = InArgs._Label;<br>    ButtonClicked = InArgs._ButtonClicked;<br>    ChildSlot.VAlign(VAlign_Center)<br>        .HAlign(HAlign_Center)<br>        [SNew(SButton)<br>        .OnClicked(ButtonClicked)<br>        .Content()<br>        [<br>            SNew(STextBlock)<br>            .Text_Lambda([this] {return FText::FromString(Label.Get()); })<br>        ]<br>        ];<br>}<\/pre>\n\n\n\n<ol><li>Create a second class, this time based on&nbsp;Widget:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/70f5329f-c812-4ee8-9108-2c186e2130c5.png\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>Call this new class&nbsp;CustomButtonWidget&nbsp;and press&nbsp;Create Class.&nbsp;<\/li><li>Add the bold code in the following snippet to the&nbsp;CustomButtonWidget.h&nbsp;file:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">#pragma once<br><br>#include \"CoreMinimal.h\"<br>#include \"Components\/Widget.h\"<br><strong>#include \"CustomButton.h\"<\/strong><br><strong>#include \"SlateDelegates.h\"<\/strong><br>#include \"CustomButtonWidget.generated.h\"<br><br><strong>DECLARE_DYNAMIC_DELEGATE_RetVal(FString, FGetString);<\/strong><br><strong>DECLARE_DYNAMIC_MULTICAST_DELEGATE(FButtonClicked);<br><\/strong><br>UCLASS()<br>class CHAPTER_14_API UCustomButtonWidget : public UWidget<br>{<br>    GENERATED_BODY()<br>    <br><strong>protected:<\/strong><br><strong>    TSharedPtr&lt;SCustomButton&gt; MyButton;<\/strong><br><br><strong>    virtual TSharedRef&lt;SWidget&gt; RebuildWidget() override;<\/strong><br><br><strong>public:<\/strong><br><strong>    UCustomButtonWidget();<\/strong><br><strong>    \/\/multicast<\/strong><br><strong>    UPROPERTY(BlueprintAssignable)<\/strong><br><strong>    FButtonClicked ButtonClicked;<\/strong><br><br><strong>    FReply OnButtonClicked();<\/strong><br><br><strong>    UPROPERTY(BlueprintReadWrite, EditAnywhere)<\/strong><br><strong>        FString Label;<\/strong><br><br><strong>    \/\/MUST be of the form varnameDelegate<\/strong><br><strong>    UPROPERTY()<\/strong><br><strong>        FGetString LabelDelegate;<\/strong><br><br><strong>    virtual void SynchronizeProperties() override;<\/strong><br>};<\/pre>\n\n\n\n<ol><li>Now, create the implementation for&nbsp;UCustomButtonWidget:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">#include \"CustomButtonWidget.h\"<br>#include \"Chapter_14.h\"<br><br>TSharedRef&lt;SWidget&gt; UCustomButtonWidget::RebuildWidget()<br>{<br>    MyButton = SNew(SCustomButton)<br>        .ButtonClicked(BIND_UOBJECT_DELEGATE(FOnClicked, OnButtonClicked));<br>    return MyButton.ToSharedRef();<br>}<br><br>UCustomButtonWidget::UCustomButtonWidget()<br>    :Label(TEXT(\"Default Value\"))<br>{<br><br>}<br><br>FReply UCustomButtonWidget::OnButtonClicked()<br>{<br>    ButtonClicked.Broadcast();<br>    return FReply::Handled();<br>}<br><br><br>void UCustomButtonWidget::SynchronizeProperties()<br>{<br>    Super::SynchronizeProperties();<br>    TAttribute&lt;FString&gt; LabelBinding = OPTIONAL_BINDING(FString, Label);<br>    MyButton-&gt;Label = LabelBinding;<br>}<\/pre>\n\n\n\n<ol><li>Save your scripts and compile your code.<\/li><li>Create a new Widget Blueprint by right-clicking on the&nbsp;Content Browser&nbsp;and selecting&nbsp;User Interface&nbsp;and then&nbsp;Widget Blueprint:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/019ade72-7ac5-4639-84ec-59fbf045dcbd.png\" alt=\"\"\/><\/figure>\n\n\n\n<p>You can use the mouse wheel in the context menu to scroll to the&nbsp;User Interface&nbsp;section.<\/p>\n\n\n\n<ol><li>Open your new&nbsp;Widget Blueprint&nbsp;by double-clicking on it.<\/li><li>Find the&nbsp;Custom Button Widget&nbsp;in the Widget Palette:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/7ec28d12-c477-458d-bb1d-e22f94528e26.png\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>Drag an instance of it out into the main area.<\/li><li>With the instance selected, change the&nbsp;Label&nbsp;property in the&nbsp;Details&nbsp;panel:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/5941564e-b55b-4f07-b642-a24507181855.png\" alt=\"\"\/><\/figure>\n\n\n\n<p>Verify that your button has changed its label.<\/p>\n\n\n\n<ol><li>Now, we will create a binding to demonstrate that we can link arbitrary blueprint functions to the label property on our widget, which, in turn, drives the Widget&#8217;s textblock label.<\/li><li>Click on&nbsp;Bind&nbsp;to the right of the&nbsp;Label&nbsp;property and select&nbsp;Create Binding:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/e4e52462-a9f1-4ed4-a5fd-8cbe749f1003.png\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>Within the graph that is now displayed, place a&nbsp;Get Game Time in Seconds&nbsp;node by right-clicking within the main area:<\/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\/6f13155b-1c21-4fcc-9bd0-3e49c3ef08fc.png\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>Link the return value from the Get Game Time node to the&nbsp;Return Value&nbsp;pin in the function:<\/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\/6421b9a8-c550-46aa-a2c4-22482c2699f5.jpg\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>A&nbsp;Convert Float to String&nbsp;node will be automatically inserted for you:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/508844dd-cbe0-48c4-a007-d9ecb4fbafff.jpg\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>Compile the blueprint to ensure it is working correctly.<\/li><li>Next, open the&nbsp;Level Blueprints&nbsp;by clicking on the&nbsp;Blueprints&nbsp;button on the taskbar and then selecting&nbsp;Open Level Blueprint:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/a7e07af8-6327-4b45-9fd2-8b8ee267eba1.png\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>To the right of the&nbsp;Event BeginPlay&nbsp;node, place a&nbsp;Create Widget&nbsp;node into the graph:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/287da0f3-30e0-48de-a7fb-7524a6bc0782.png\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>Select the&nbsp;Class&nbsp;of widget to spawn it as the new Widget Blueprint that we created a moment ago within the editor:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/926ba980-6528-4778-8868-bf71d895ccb8.jpg\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>Click and drag away from the&nbsp;Owning Player&nbsp;pin on the create widget node and place a&nbsp;Get Player Controller&nbsp;node:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/cd0d48b2-dc8f-4b71-805c-639f0510e06e.jpg\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>Likewise, drag away from the return value of the&nbsp;Create Widget&nbsp;node and place an&nbsp;Add to Viewport&nbsp;node:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/e005b240-36a4-4bde-9cb9-fea79fb1ed0e.jpg\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>Lastly, link the&nbsp;BeginPlay&nbsp;node to the execution pin on the create widget node:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/29583739-bbd2-4a2f-9750-d150412bd044.png\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>Preview your game and verify that the widget we&#8217;ve displayed onscreen is our new custom button, with its label bound to the number of seconds that have elapsed since the game started:<\/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\/6e798818-3319-43c4-a5c3-24c71cc70708.png\" alt=\"\"\/><\/figure>\n\n\n\n<p>Button displaying the elapsed time in the level\n\n<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">How it works&#8230;<\/h1>\n\n\n\n<p>To use the&nbsp;UWidget&nbsp;class, our module needs to include the UMG module as one of its dependencies, because&nbsp;UWidget&nbsp;is defined inside the UMG module.<\/p>\n\n\n\n<p>The first class that we need to create, however, is our actual&nbsp;SWidget&nbsp;class.<\/p>\n\n\n\n<p>Because we want to aggregate two widgets together into a compound structure, we create our new widget as a&nbsp;CompoundWidget&nbsp;subclass.&nbsp;CompoundWidget&nbsp;allows you to encapsulate a widget hierarchy as a widget itself.<\/p>\n\n\n\n<p>Inside the class, we use the&nbsp;SLATE_BEGIN_ARGS&nbsp;and&nbsp;SLATE_END_ARGS&nbsp;macros to declare an internal&nbsp;struct&nbsp;called&nbsp;FArguments&nbsp;on our new&nbsp;SWidget. Within&nbsp;SLATE_BEGIN_ARGS&nbsp;and&nbsp;SLATE_END_ARGS, the&nbsp;SLATE_ATTRIBUTE&nbsp;and&nbsp;SLATE_EVENT&nbsp;macros are used.&nbsp;SLATE_ATTRIBUTE&nbsp;creates&nbsp;TAttribute&nbsp;for the type we give it. In this class, we declare a&nbsp;TAttribute&nbsp;called&nbsp;_Label, which is more specifically a&nbsp;TAttribute&lt;FString&gt;.<\/p>\n\n\n\n<p>SLATE_EVENT&nbsp;allows us to create member delegates that we can broadcast when something happens internally to the widget.<\/p>\n\n\n\n<p>In&nbsp;SCustomButton, we declare a delegate with the signature&nbsp;FOnClicked, called&nbsp;ButtonClicked.<\/p>\n\n\n\n<p>SLATE_ARGUMENT&nbsp;is another macro (which wasn&#8217;t used in this recipe)&nbsp;that creates an internal variable with the type and name you provide, appending an underscore to the start of the variable name.<\/p>\n\n\n\n<p>Construct()&nbsp;is the function that widgets implement to self-initialize when they are being instantiated. You&#8217;ll notice we also create&nbsp;TAttribute&nbsp;and&nbsp;FOnClicked&nbsp;instances ourselves, without the underscores. These are the actual properties of our object into which the arguments that we declared earlier will be copied.<\/p>\n\n\n\n<p>Inside the implementation of&nbsp;Construct, we retrieve the arguments that were passed to us in the&nbsp;FArgumentsstruct, and store them inside our actual member variables for this instance.<\/p>\n\n\n\n<p>We assign&nbsp;Label&nbsp;and&nbsp;ButtonClicked&nbsp;based on what was passed in, and then we actually create our widget hierarchy. We use the same syntax as usual for this with one thing to note, namely the use of&nbsp;Text_Lambda&nbsp;to set the text value of our internal text block. We use a&nbsp;lambda&nbsp;function to retrieve the value of our&nbsp;Label&nbsp;TAttribute&nbsp;using&nbsp;Get(), convert it into&nbsp;FText, and store it as our text block&#8217;s&nbsp;Text&nbsp;property.<\/p>\n\n\n\n<p>Now that we have our&nbsp;SWidget&nbsp;declared, we need to create a wrapper&nbsp;UWidget&nbsp;object that will expose this widget to the UMG system so that designers can use the widget within the&nbsp;<strong>WYSIWYG<\/strong>&nbsp;editor. This class will be called&nbsp;UCustomButtonWidget, and it inherits from&nbsp;UWidget&nbsp;rather than&nbsp;SWidget.<\/p>\n\n\n\n<p>The&nbsp;UWidget&nbsp;object needs a reference to the actual&nbsp;SWidget&nbsp;that it owns, so we place a protected member in the class that will store it as a shared pointer.<\/p>\n\n\n\n<p>A constructor is declared, as well as a&nbsp;ButtonClicked&nbsp;delegate that can be set in Blueprint. We also mirror a&nbsp;Label&nbsp;property that is marked as&nbsp;BlueprintReadWrite&nbsp;so that it can be set in the UMG editor.<\/p>\n\n\n\n<p>Because we want to be able to bind our button&#8217;s label to a delegate, we add the last of our member variables, which is a delegate that returns a&nbsp;String.<\/p>\n\n\n\n<p>The&nbsp;SynchronizeProperties&nbsp;function applies properties that have been mirrored in our&nbsp;UWidget&nbsp;class across to the&nbsp;SWidget&nbsp;that we are linked with.<\/p>\n\n\n\n<p>RebuildWidget&nbsp;reconstructs the native widget that&nbsp;UWidget&nbsp;is associated with. It uses&nbsp;SNew&nbsp;to construct an instance of our&nbsp;SCustomButton&nbsp;widget, and uses the Slate declarative syntax to bind the UWidget&#8217;s&nbsp;OnButtonClicked&nbsp;method to the&nbsp;ButtonClicked&nbsp;delegate inside the native widget. This means that when the native widget is clicked, the&nbsp;UWidget&nbsp;will be notified by having&nbsp;OnButtonClicked&nbsp;called.<\/p>\n\n\n\n<p>OnButtonClicked&nbsp;re-broadcasts the clicked event from the native button via the UWidget&#8217;s&nbsp;ButtonClicked&nbsp;delegate. This means that UObjects and the UMG system can be notified of the button being clicked without having a reference to the native button widget themselves. We can bind to&nbsp;UCustomButtonWidget::ButtonClicked&nbsp;so that we&#8217;re notified about this.<\/p>\n\n\n\n<p>OnButtonClicked&nbsp;then returns&nbsp;FReply::Handled()&nbsp;to indicate that the event does not need to propagate further. Inside&nbsp;SynchronizeProperties, we call the parent method to ensure that any properties in the parent are also synchronized properly.<\/p>\n\n\n\n<p>We use the&nbsp;OPTIONAL_BINDING&nbsp;macro to link the&nbsp;LabelDelegate&nbsp;delegate in our&nbsp;UWidget&nbsp;class to&nbsp;TAttribute, and, in turn, the native button&#8217;s label. It is important to note that the&nbsp;OPTIONAL_BINDING&nbsp;macro expects the delegate to be called&nbsp;NameDelegate&nbsp;based on the second parameter to the macro.<\/p>\n\n\n\n<p>OPTIONAL_BINDING&nbsp;allows the value to be overridden by a binding made via UMG, but only if the UMG binding is valid.<\/p>\n\n\n\n<p>This means that when&nbsp;UWidget&nbsp;is told to update itself, for example, because the user customizes a value in the&nbsp;Details&nbsp;panel within UMG, it will recreate the native&nbsp;SWidget&nbsp;if necessary, and then copy the values set in Blueprint\/UMG via&nbsp;SynchronizeProperties&nbsp;so that everything continues to work as expected.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The recipes in this chapter so far have shown you how t [&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\/3254"}],"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=3254"}],"version-history":[{"count":3,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/posts\/3254\/revisions"}],"predecessor-version":[{"id":3288,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/posts\/3254\/revisions\/3288"}],"wp:attachment":[{"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3254"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3254"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3254"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}