Announcement

Collapse
No announcement yet.

How I Mix UMG and C++

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    [TUTORIAL] How I Mix UMG and C++

    A few people in Slack were asking about good practices when using UMG, especially when mixing with C++

    EDIT: Nick showed me a much better way of setting up the pointers. See here.

    I did the UI for both Satellite Command (Mobile & PC) and my Hovertank / Battlezone Inspired Pet-Project, and I've come up with what I think is a decent, reusable and importantly fast approach to designing and updating my widgets, so figured I'd share my workflow.

    First of all, some pr0-tipsTM for getting the most out of UMG - or just rando things that I've picked up.
    • The less Widget Components you use (that's the list of items in the Panel on the left in UMG designer) - the less time it takes the widget to draw. Try to be conscious of that when putting a widget together.
    • Anything that should NEVER recieve mouse hits, or be interactable, set it to 'Self Hit Test Invisible'. Adding Widgets to the hit test grid isn't free.
    • Bindings are naughty and have an inherent cost of their own, avoid them if you can.
    • Widgets that go off-screen will be set to 'Hidden', will no longer draw or call NativeTick. In some cases, it's best to add Widgets to a CanvasPanel
    • Use the Invalidation Panel. Get into the habit of dropping one in as the root widget and testing things regularly as you add functionality. This saves a metric f-tonne of draw time, especially on mobile.
    • The console command stat slate is your new best friend. Use it, a lot.
    • Treat your HUD class as a 'Manager' for your widgets. Store references to them there, set your default classes there. Having a nice central place to access all of your widgets from anywhere in the game makes life very easy.
    • Use TWeakObjectPtr if you're referencing other widgets or objects in the world. Too often I've seen people filling up memory with rubbish that either piles up for the garbage collector, or can't be removed at all because you're still referencing it.
    • If you're creating / destroying widgets often (such as for Tool Tips etc), cache them. This is good practice for any game scenario really..


    Onto the meat of the subject. I use the UMG designer for two things - designing / placing widgets and animations in the designer, and using events to drive the animations in the event graph. That's pretty much it - if I can do any code to update/set text, colours or say; the percentage of a progress bar, I always do it in C++. It'll save you heaps of time and performance later.

    One lesson I learnt the hard way, is the importance of splitting up your widgets into sensible chunks. While making Satellite Command I thought it would be easier if I put all of our game widgets into a master 'InGameWidget', which turned out to be a huge mistake and massively overcomplicated things. Take a look at this mess (this is after splitting off some chunks into their own Widgets elsewhere). Don't do this:

    Click image for larger version

Name:	Ouch.JPG
Views:	1
Size:	145.1 KB
ID:	1189123

    So moving onto better practices, since I created this recently (today actually) - here's an example of how I'm doing things now. The widget below is for the upcoming Steam version of Satellite Command, and is designed to function as a tool tip. When a Player hovers over a "Satellite Item Widget" in the main User Interface, this badboy pops up to show some extra information.

    Click image for larger version

Name:	SatWidget.JPG
Views:	1
Size:	46.5 KB
ID:	1189124

    And here's the list of UWidgets in this UMG Widget, to give you some idea of how it's put together. Notice how NONE of these things are variables (usually indicated by bold text), and each one is uniquely named to something that makes it easy to identify. Naming widgets properly is a good habit to get into, and we'll need to do that so we can find and access them in C++.

    Click image for larger version

Name:	ItemList.JPG
Views:	1
Size:	48.5 KB
ID:	1189125

    This is (currently) the entire Blueprint Graph for this widget.

    Click image for larger version

Name:	Graph.JPG
Views:	1
Size:	33.4 KB
ID:	1189126

    So the first thing I recommend, is creating a 'Base Widget' class that all of your subsequent interface widgets inherit from. This is useful for declaring static variables that are used all over the User Interface and make it easy to keep things like colour schemes / themes in line accross the whole UI, and also for creating common functionality accross the whole UI too. Since most of the widgets in Satellite Command are animated for example - I created generic events / functions for handling closing & opening of widgets etc.

    Here's a chunk of code from our Base Widget class in Satellite Command. I use a whole bunch of statics to declare variables / colours / text that I'll resuse all over the place. Materials / textures too. These are all Protected Static vars in the Header, most of the time Const too.

    BaseWidget.cpp
    Code:
    USoundBase* UGESGame_BaseWidget::UnfoldAudio = nullptr;
    USoundBase* UGESGame_BaseWidget::ConfirmAudio = nullptr;
    USoundBase* UGESGame_BaseWidget::DeclineAudio = nullptr;
    USoundBase* UGESGame_BaseWidget::SwapAudio = nullptr;
    USoundBase* UGESGame_BaseWidget::OnAudio = nullptr;
    USoundBase* UGESGame_BaseWidget::OffAudio = nullptr;
    USoundBase* UGESGame_BaseWidget::WarnBadAudio = nullptr;
    USoundBase* UGESGame_BaseWidget::WarnNeutralAudio = nullptr;
    USoundBase* UGESGame_BaseWidget::BadActionAudio = nullptr;
    
    UTexture2D* UGESGame_BaseWidget::Bronze_TextureAsset = nullptr;
    UTexture2D* UGESGame_BaseWidget::Silver_TextureAsset = nullptr;
    UTexture2D* UGESGame_BaseWidget::Gold_TextureAsset = nullptr;
    UTexture2D* UGESGame_BaseWidget::Padlock_TextureAsset = nullptr;
    
    UTexture2D* UGESGame_BaseWidget::Facebook_TextureAsset = nullptr;
    UTexture2D* UGESGame_BaseWidget::Tick_TextureAsset = nullptr;
    UTexture2D* UGESGame_BaseWidget::Cross_TextureAsset = nullptr;
    
    const FLinearColor UGESGame_BaseWidget::Colour_Green = FLinearColor(0.05f, 1.f, 0.05f, 1.f);
    const FLinearColor UGESGame_BaseWidget::Colour_Red = FLinearColor(1.f, 0.05f, 0.05f, 1.f);
    const FLinearColor UGESGame_BaseWidget::Colour_LightTurquoise = FLinearColor(0.15f, 1.f, 1.f, 1.f);
    const FLinearColor UGESGame_BaseWidget::Colour_DarkTurquoise = FLinearColor(0.07f, 0.5f, 0.5f, 1.f);
    const FLinearColor UGESGame_BaseWidget::Colour_PrepColour = FLinearColor(1.f, 0.5f, 0.05f, 1.f);
    
    const FLinearColor UGESGame_BaseWidget::ToolColour_NONE = FLinearColor(0.6f, 0.6f, 0.6f, 1.f);
    const FLinearColor UGESGame_BaseWidget::ToolColour_HCAM = FLinearColor(1.f, 0.5f, 1.0f, 1.f);
    const FLinearColor UGESGame_BaseWidget::ToolColour_UVIR = FLinearColor(0.75f, 0.25f, 1.f, 1.f);
    const FLinearColor UGESGame_BaseWidget::ToolColour_LIDAR = FLinearColor(0.f, 0.5f, 1.f, 1.f);
    const FLinearColor UGESGame_BaseWidget::ToolColour_KLAS = FLinearColor(0.5f, 1.f, 1.f, 1.f);
    const FLinearColor UGESGame_BaseWidget::ToolColour_WAVE = FLinearColor(1.f, 1.f, 0.55f, 1.f);
    const FLinearColor UGESGame_BaseWidget::ToolColour_EMIT = FLinearColor(0.5f, 1.f, 0.5f, 1.f);
    
    const FText UGESGame_BaseWidget::ToolText_NONE = FText::FromString(TEXT("None"));
    const FText UGESGame_BaseWidget::ToolText_HCAM = FText::FromString(TEXT("H-CAM"));
    const FText UGESGame_BaseWidget::ToolText_UVIR = FText::FromString(TEXT("UVIR"));
    const FText UGESGame_BaseWidget::ToolText_LIDAR = FText::FromString(TEXT("LIDAR"));
    const FText UGESGame_BaseWidget::ToolText_KLAS = FText::FromString(TEXT("KLAS"));
    const FText UGESGame_BaseWidget::ToolText_WAVE = FText::FromString(TEXT("WAVE"));
    const FText UGESGame_BaseWidget::ToolText_EMIT = FText::FromString(TEXT("EMIT"));
    
    UMaterialInterface* UGESGame_BaseWidget::ToolIcon_NONE = nullptr;
    UMaterialInterface* UGESGame_BaseWidget::ToolIcon_HCAM = nullptr;
    UMaterialInterface* UGESGame_BaseWidget::ToolIcon_UVIR = nullptr;
    UMaterialInterface* UGESGame_BaseWidget::ToolIcon_LIDAR = nullptr;
    UMaterialInterface* UGESGame_BaseWidget::ToolIcon_KLAS = nullptr;
    UMaterialInterface* UGESGame_BaseWidget::ToolIcon_WAVE = nullptr;
    UMaterialInterface* UGESGame_BaseWidget::ToolIcon_EMIT = nullptr;
    
    UGESGame_BaseWidget::UGESGame_BaseWidget(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
    {
    	OwningGESPlayer = NULL;
    	OwningGESHUD = NULL;
    
    	bBindAndroidEvents = false;
    
    	// Shared Resources
    	static ConstructorHelpers::FObjectFinder<UTexture2D>BronzeTex(TEXT("Texture2D'/Game/UI/Textures/Medals/T2D_MBronze.T2D_MBronze'"));
    	Bronze_TextureAsset = BronzeTex.Object;
    	static ConstructorHelpers::FObjectFinder<UTexture2D>SilverTex(TEXT("Texture2D'/Game/UI/Textures/Medals/T2D_MSilver.T2D_MSilver'"));
    	Silver_TextureAsset = SilverTex.Object;
    	static ConstructorHelpers::FObjectFinder<UTexture2D>GoldTex(TEXT("Texture2D'/Game/UI/Textures/Medals/T2D_MGold.T2D_MGold'"));
    	Gold_TextureAsset = GoldTex.Object;
    	static ConstructorHelpers::FObjectFinder<UTexture2D>LockTex(TEXT("Texture2D'/Game/UI/Textures/Medals/T2D_UILocked.T2D_UILocked'"));
    	Padlock_TextureAsset = LockTex.Object;
    
    	static ConstructorHelpers::FObjectFinder<UTexture2D>FacebookTex(TEXT("Texture2D'/Game/UI/Textures/Icons/T2D_FBLogo.T2D_FBLogo'"));
    	Facebook_TextureAsset = FacebookTex.Object;
    	static ConstructorHelpers::FObjectFinder<UTexture2D>TickTex(TEXT("Texture2D'/Game/UMG_Assets/Textures/icon_CheckTrue.icon_CheckTrue'"));
    	Tick_TextureAsset = TickTex.Object;
    	static ConstructorHelpers::FObjectFinder<UTexture2D>CrossTex(TEXT("Texture2D'/Game/UMG_Assets/Textures/icon_CheckFalse.icon_CheckFalse'"));
    	Cross_TextureAsset = CrossTex.Object;
    
    	// Reused Audio
    	static ConstructorHelpers::FObjectFinder<USoundBase>DeclineAsset(TEXT("SoundCue'/Game/GESAudio/UI/Unfold/SC_Unfold.SC_Unfold'"));
    	DeclineAudio = DeclineAsset.Object;
    	static ConstructorHelpers::FObjectFinder<USoundBase>ConfirmAsset(TEXT("SoundCue'/Game/GESAudio/UI/Unfold/SC_Unfold.SC_Unfold'"));
    	ConfirmAudio = ConfirmAsset.Object;
    	static ConstructorHelpers::FObjectFinder<USoundBase>UnfoldAsset(TEXT("SoundCue'/Game/GESAudio/UI/Unfold/SC_Unfold.SC_Unfold'"));
    	UnfoldAudio = UnfoldAsset.Object;
    	static ConstructorHelpers::FObjectFinder<USoundBase>SwapAsset(TEXT("SoundWave'/Game/GESAudio/UI/Swap/SW_UISwapPageA.SW_UISwapPageA'"));
    	SwapAudio = SwapAsset.Object;
    	static ConstructorHelpers::FObjectFinder<USoundBase>OnAudioAsset(TEXT("SoundWave'/Game/GESAudio/UI/Toggles/SW_UIOn.SW_UIOn'"));
    	OnAudio = OnAudioAsset.Object;
    	static ConstructorHelpers::FObjectFinder<USoundBase>OffAudioAsset(TEXT("SoundWave'/Game/GESAudio/UI/Toggles/SW_UIOff.SW_UIOff'"));
    	OffAudio = OffAudioAsset.Object;
    	static ConstructorHelpers::FObjectFinder<USoundBase>WarnBadAsset(TEXT("SoundWave'/Game/GESAudio/UI/Mission/SW_MissionFailed.SW_MissionFailed'"));
    	WarnBadAudio = WarnBadAsset.Object;
    	static ConstructorHelpers::FObjectFinder<USoundBase>WarnNeutralAudioAsset(TEXT("SoundWave'/Game/GESAudio/UI/Warnings/SW_UIWarnNeutral.SW_UIWarnNeutral'"));
    	WarnNeutralAudio = WarnNeutralAudioAsset.Object;
    	static ConstructorHelpers::FObjectFinder<USoundBase>BadActionAudioAsset(TEXT("SoundWave'/Game/GESAudio/UI/Warnings/SW_UIBadAction.SW_UIBadAction'"));
    	BadActionAudio = BadActionAudioAsset.Object;
    
    	// Reused Icons
    	static ConstructorHelpers::FObjectFinder<UMaterialInterface>ToolIcon_NoneAsset(TEXT("MaterialInstanceConstant'/Game/UI/MaterialInstances/Tools/MInst_NONE.MInst_NONE'"));
    	ToolIcon_NONE = ToolIcon_NoneAsset.Object;
    	static ConstructorHelpers::FObjectFinder<UMaterialInterface>ToolIcon_HCAMAsset(TEXT("MaterialInstanceConstant'/Game/UI/MaterialInstances/Tools/MInst_HCam.MInst_HCAM'"));
    	ToolIcon_HCAM = ToolIcon_HCAMAsset.Object;
    	static ConstructorHelpers::FObjectFinder<UMaterialInterface>ToolIcon_UVIRAsset(TEXT("MaterialInstanceConstant'/Game/UI/MaterialInstances/Tools/MInst_UVIR.MInst_UVIR'"));
    	ToolIcon_UVIR = ToolIcon_UVIRAsset.Object;
    	static ConstructorHelpers::FObjectFinder<UMaterialInterface>ToolIcon_LIDARAsset(TEXT("MaterialInstanceConstant'/Game/UI/MaterialInstances/Tools/MInst_MAG.MInst_MAG'"));
    	ToolIcon_LIDAR = ToolIcon_LIDARAsset.Object;
    	static ConstructorHelpers::FObjectFinder<UMaterialInterface>ToolIcon_KLASAsset(TEXT("MaterialInstanceConstant'/Game/UI/MaterialInstances/Tools/MInst_KLAS.MInst_KLAS'"));
    	ToolIcon_KLAS = ToolIcon_KLASAsset.Object;
    	static ConstructorHelpers::FObjectFinder<UMaterialInterface>ToolIcon_WAVEAsset(TEXT("MaterialInstanceConstant'/Game/UI/MaterialInstances/Tools/MInst_WAVE.MInst_WAVE'"));
    	ToolIcon_WAVE = ToolIcon_WAVEAsset.Object;
    	static ConstructorHelpers::FObjectFinder<UMaterialInterface>ToolIcon_EMITAsset(TEXT("MaterialInstanceConstant'/Game/UI/MaterialInstances/Tools/MInst_EMIT.MInst_EMIT'"));
    	ToolIcon_EMIT = ToolIcon_EMITAsset.Object;
    }
    The next step is updating the items you've placed in the designer. The easiest way to do this IMO, is create a list of raw pointers (they don't need to be UPROPERTY(), the widget already references them elsewhere) - and set them all when the Widget runs it's 'NativeConstruct()' function. I also create a 'HasValidData()' function in the header, so that I can always check to ensure the widget has been properly initialized before doing something.

    NativeConstruct() runs when adding a Widget to the Viewport (which will also create all of it's children) - so always ensure you add newly created widgets to the viewport / to a slot before calling functions on them.

    CODE REMOVED - SEE POST 3!

    Now updating all of these items can be done easily from C++, here's a chunk for example:

    Code:
    void USCGame_SatInfo::UpdateDynamicInfo(const float InDeltaTime)
    {
    	ASSERTV(HasValidData() == true, TEXT("Info Widget Not Initialized"));
    
    	const AGESGame_Satellite* SatContext = CurrentContextItem->GetAssignedSatellite();
    	ASSERTV(SatContext != nullptr, TEXT("Invalid Satellite"));
    
    	// Interpolate values for nicer transition effect
    	BattProgBar_Ptr->SetPercent(FMath::FInterpTo(BattProgBar_Ptr->Percent, SatContext->SatData.Battery_CurrentChargeRatio, InDeltaTime, ValueInterpSpeed));
    	FuelProgBar_Ptr->SetPercent(FMath::FInterpTo(FuelProgBar_Ptr->Percent, SatContext->SatData.Fuel_CurrentLevelRatio, InDeltaTime, ValueInterpSpeed));
    	HullProgBar_Ptr->SetPercent(FMath::FInterpTo(HullProgBar_Ptr->Percent, SatContext->SatData.Hull_CurrentLevelRatio, InDeltaTime, ValueInterpSpeed));
    	HeatProgBar_Ptr->SetPercent(FMath::FInterpTo(HeatProgBar_Ptr->Percent, SatContext->SatData.Heat_CurrentLevelRatio, InDeltaTime, ValueInterpSpeed));
    
    	// Interpolate Upgrade Values
    	Current_UpgradeRatios[0] = FMath::FInterpTo(Current_UpgradeRatios[0], Target_UpgradeRatios[0], InDeltaTime, ValueInterpSpeed);
    	Current_UpgradeRatios[1] = FMath::FInterpTo(Current_UpgradeRatios[1], Target_UpgradeRatios[1], InDeltaTime, ValueInterpSpeed);
    	Current_UpgradeRatios[2] = FMath::FInterpTo(Current_UpgradeRatios[2], Target_UpgradeRatios[2], InDeltaTime, ValueInterpSpeed);
    	Current_UpgradeRatios[3] = FMath::FInterpTo(Current_UpgradeRatios[3], Target_UpgradeRatios[3], InDeltaTime, ValueInterpSpeed);
    	Current_UpgradeRatios[4] = FMath::FInterpTo(Current_UpgradeRatios[4], Target_UpgradeRatios[4], InDeltaTime, ValueInterpSpeed);
    
    	static FName ProgName = TEXT("Progress");
    	ToolHexDMI_Ptr->SetScalarParameterValue(ProgName, Current_UpgradeRatios[0]);
    	BattHexDMI_Ptr->SetScalarParameterValue(ProgName, Current_UpgradeRatios[1]);
    	FuelHexDMI_Ptr->SetScalarParameterValue(ProgName, Current_UpgradeRatios[2]);
    	HullHexDMI_Ptr->SetScalarParameterValue(ProgName, Current_UpgradeRatios[3]);
    	HeatHexDMI_Ptr->SetScalarParameterValue(ProgName, Current_UpgradeRatios[4]);
    
           ...
    This should give you most of the info you need to know to get started. I personally feel as though this system is nice and clean, keeps my code in C++ (where I like it) and makes things nice and accesible. As a bonus, here's how I create and reference all my widgets in C++, using the HUD class as a manager.

    MyHud.h
    Code:
    public:
    	FORCEINLINE USCGame_SatelliteBank* GetActiveBankWidget() const { return ActiveBankWidget; }
    	FORCEINLINE USCGame_SatInfo* GetActiveInfoWidget() const { return ActiveInfoWidget; }
    
    protected:
    	//////////////////////////////////
    	///// Game Widget Management /////
    	//////////////////////////////////
    
    	// Widget Classes
    	UPROPERTY(EditDefaultsOnly, Category = "Game Widgets")
    	TAssetSubclassOf<USCGame_SatelliteBank> BankWidget;
    	UPROPERTY(EditDefaultsOnly, Category = "Game Widgets")
    	TAssetSubclassOf<USCGame_SatInfo> InfoWidget;
    
    	// Active Widgets
    	USCGame_SatelliteBank* ActiveBankWidget;
    	USCGame_SatInfo* ActiveInfoWidget;
    
    	// Functions
    	void CreateGameWidgets();
    	void RemoveGameWidgets();
    Notice how I always call 'AddToViewport()' before anything else after creating the widget, to ensure the pointers are valid.
    MyHud.cpp
    Code:
    void AGESGame_ClientHUD::CreateBankWidget()
    {
    	ASSERTV(ActiveBankWidget == nullptr, TEXT("Bank Widget Already Initialized"));
    
    	ActiveBankWidget = CreateWidget<USCGame_SatelliteBank>(GetOwningPlayerController(), BankWidget.LoadSynchronous());
    	ASSERTV(ActiveBankWidget != nullptr, TEXT("Unable to Create Bank Widget"));
    
    	ActiveBankWidget->AddToViewport(0);
    	ActiveBankWidget->SetPositionInViewport(FVector2D(0.f, 0.f));
    	ActiveBankWidget->SetDesiredSizeInViewport(FVector2D(0.f, 174.f));
    	ActiveBankWidget->SetAlignmentInViewport(FVector2D(0.f, 1.f));
    	ActiveBankWidget->SetAnchorsInViewport(FAnchors(0.f, 1.f, 1.f, 1.f));
    
    	// Creates all Children etc.
    	ActiveBankWidget->OnAddedToViewport();
    }
    
    void AGESGame_ClientHUD::CreateInfoWidget()
    {
    	ASSERTV(ActiveInfoWidget == nullptr, TEXT("Info Widget Already Initialized"));
    
    	ActiveInfoWidget = CreateWidget<USCGame_SatInfo>(GetOwningPlayerController(), InfoWidget.LoadSynchronous());
    	ASSERTV(ActiveInfoWidget != nullptr, TEXT("Unable to Create Bank Widget"));
    
    	ActiveInfoWidget->AddToViewport(1);
    	ActiveInfoWidget->SetPositionInViewport(FVector2D(0.f, 0.f));
    	ActiveInfoWidget->SetVisibility(ESlateVisibility::Hidden);
    }
    If this thread proves interesting to people, I'll also provide examples of how I safely manage animations / interaction via state machines etc. Let me know if this is of use to you!
    Last edited by TheJamsh; 09-08-2016, 04:52 AM.

    #2
    Thanks you for share this!
    Hevedy - Instance Tools: https://hevedy.itch.io/hevedyinstances
    Hevedy - Image Tools: https://hevedy.itch.io/imagetools

    Comment


      #3
      So less than a couple of minutes after posting this and Nick shows me something that both blew my mind and makes life even easier than shown above...

      Code:
      UPROPERTY(meta = (BindWidget))
      USizeBox* Box_CoolBox;
      
      you can define that in a native base class, and i magically hook it up in the compiler, rather than create a new variable
      rather than that FindWidget junk.
      
      you can also do
      
      UPROPERTY(meta = (BindWidget, OptionalWidget = true))
      UTextBlock* OptionalText;
      Essentially, you can avoid my system of Finding widgets and setting pointers altogether using this. BRB going back and rewriting my UI.

      Comment


        #4
        Thanks!
        Discovered the cost of Binding just a couple days ago and was wondering if there is anything else like this!
        Really helpful stuff 8)
        SuperGrid: Marketplace Page | Feedback Thread | Demo | Website
        Level design and prototyping for newbies

        Comment


          #5
          [MENTION=155]TheJamsh[/MENTION], so how did you rewrite the UI to avoid Finding stuff?

          Comment


            #6
            [MENTION=155]TheJamsh[/MENTION], " BRB going back and rewriting my UI. " Did it work out the way you wanted?

            Comment


              #7
              Yes indeed, it's definitely the smart way to do it.

              Simply, instead of using all the 'Find Widget' stuff I did, and validation, you can just do this:

              Code:
              UPROPERTY(umeta = (BindWidget)
              UTextBlock* SomeTextVar;
              Create a Text Block in a UMG (ensuring the C++ class is the parent class), and call it 'SomeTextVar', and make sure it's a variable - everything will be bound to it and you cna make the updates directly to SomeTextVar in C++. Works with any widget type, including custom ones and even child widgets. You can also use BindWidgetOptional if you don't want to force the UMG designer to have a widget of that name (but you have to manage accessing nullptrs in the UMG class yourself).

              Doesn't work with animations yet. Have asked [MENTION=424484]Nick[/MENTION]Darnell to consider that for a future engine version.

              Also, this means you don't have to add the widget in the Viewport for those pointers to be valid - they're initialized at construction time.

              Edit: Note that the Text Block needs to be marked as a variable in BP for this to work also.
              Last edited by TheJamsh; 01-30-2017, 07:16 AM.

              Comment


                #8
                Hi,

                Interesting method and very useful method. I wish I heard about it when I started my UMG UI, it would have same my day^^

                But I'm not sure to properly understand how everything works.
                So this UProperty is never assigned in the UMG Designer BP part, correct?
                Is It making the link automatically between the C++ Property and UMG Widget just by the name?

                What happen is the var name are not the same? Does that mean, we have 1 textBlock in C++ et 1 create by the designer whereas if the name match, we have 1 for all?

                With the "BindWidgetOptional", does that mean is no UMG Designer Widget is found with the same name, the C++ variable will be nullptr?

                Thanks

                Comment


                  #9
                  Correct, you don't have to set anything up, the widget compiler automatically hooks it up. If the names are not the same, then the compiler won't match them up. You have to have the same variable name in C++ and the designer.

                  And yes the C++ value will be nullptr if you use the optional flag. Non-Optional bindings mean that your widget blueprint won't actually compile.

                  I might do a video tutorial on this later today actually, much easier to explain!

                  Comment


                    #10
                    OK that's clear.

                    And if the NonOption binding is not compiling, you can then enforce that your widget have the required widget in the designer which is really great.

                    This should go into the Docs, it's so awesome and solve a lot of issue where the BP code is mess compared to C++ one.

                    Comment


                      #11
                      Could you please give a detailed example howto get the "UPROPERTY(meta = (BindWidget))" approach working? I tried to implement it as described, but my C++ pointer is not bound (ie. it remains a nullptr) to the UMG-Designer-Widget.

                      What I have tried:

                      In *.h file:
                      Code:
                      UPROPERTY(meta = (BindWidget))
                      UTextBlock* TB887;
                      In *.cpp file:
                      Code:
                      TB887->SetText(FText::FromString("Set from code."));
                      And a screenshot of the UMG-Designer:
                      Click image for larger version

Name:	Unbenannt2.PNG
Views:	1
Size:	24.4 KB
ID:	1122317

                      Comment


                        #12
                        [MENTION=689373]chrarlait[/MENTION], I have not yet done this myself, but does your widget have the C++ as it's parent?

                        Comment


                          #13
                          Wow, this is great. Would love to see your implementation of animations and interactions!

                          Comment


                            #14
                            Thanks, being able to link pointers to widget components saved my weekend!

                            What is the best practice from where to access widgets in C++? Currently I'm storing pointers to widgets in the PlayerController. Would it be better for a multiplayer game to use GameMode or its HUD class instead? If yes, why?
                            Client side prediction with GameplayAbilitySystem | GameplayAbilitySystem at stackoverflow

                            Comment


                              #15
                              Does this still work? I'm getting "Unknown Variable Specifier 'umeta'" when compiling.
                              Last edited by blueshifted; 07-16-2017, 03:02 PM.

                              Comment

                              Working...
                              X