While the BindWidget metatag is useful in allowing the native environment to access widgets directly in the panel, I would strongly advise against using it in an actual production environment for a few reasons:
- You run a serious risk of introducing a cyclic dependency between the native code and the Blueprint, whereby the native code can’t compile correctly because it relies on assets in the widget Blueprint to do so, but the widget blueprint simultaneously depends on the native code. The dangerous thing about cyclic dependencies (also called circular references) is that they won’t always fail because they’re going to be dependent on compile order, and will rarely if ever fail in the editor, so you can wind up with something that looks like it’s working but will actually fail intermittently in packaged builds.
If you hit this assertion in a packaged build (UE5 EA 2):
Assertion failed: Level == 1 [File:D:/build/++UE5/Sync/Engine/Source/Runtime/CoreUObject/Private/Serialization/AsyncLoading2.cpp] [Line: 4017]
That’s what’s happening.
If you look at the struct FScopedLoadRecursionVerifier at that location, you’ll see that it’s looking for these sorts of recursive definitions and asserting out.
I don’t know whether UE4 does a similar check but this jumped up and bit me in UE5.
In my case this made my project intermittently unable to run its packaged builds - I would make a build that succeeded, make a change, try again and it would fail, then revert back to the previously working build and it would still fail, because the issue wasn’t explicitly in code but in the compile order.
I was able to resolve this by eliminating the use of BindWidget tags an instead making BlueprintImplementableEvent calls from native to the widget blueprint to pass data, and handling all the user display within the context of the Blueprint.
- The other reason why you don’t want to use these BindWidget tags is you’re creating invisible failure points for your UI artist. This isn’t so much of a problem if you’re designing a game alone, but if you’re working on a team, you don’t want to make choices in the native binaries that unnecessarily constrict the artist’s work downstream, since they may not have visibility into the source code. When you pass the class to the UI artist, they have no way of knowing that if they rename an element in the UI, their class could suddenly fail to compile. Better to use BlueprintImplementableEvents to let them see that you’re giving them an event with data, and allow them to depict that data to the player however they’d like.
I’ve found in practice that it’s a good idea to keep a meaningful separation between the work you do in UI native classes and in their Blueprint classes. It seems to work best if you think of the native environment as the place to do all of your functional logic - hold the data there, generate events, etc., and use the Blueprint environment solely to draw the results of those events and to accept input from the player. i.e., C++ == the logic layer, UMG Blueprint == the UI painting layer.
Anyway, just wanted to share this since it made for a gnarly day of debugging to track down.
(UDN thread here: [UE5.0EA2] Packaged build crashes in AsyncLoading2.cpp, Line 4017 (unrealengine.com) )
Thank btw for an absolutely fantastic course!