Learning UE5

Learning UE5

Slate

Why Slate over UMG?

Slate is Unreal’s low level UI system upon which UMG was built. UMG is more of a Blueprint superset of Slate and is better suited when using the visual designer. Since I have a long history in developing UI using pure code, I wanted to use Slate, since it would afford me more control.

Slate is declarative, but with imperative concepts

One of the selling points of slate, is that it is declarative. I have found it relatively easy to work with with one or two minor conceptual differences.

In react we would do something like:

export const MyWidget: React.FC<{splash: SplashData}> = ({splash}) => {
  return(
    splash ? <SplashWidget /> : null
  )
};

whereas in Slate I need to redefine the ChildSlot when data changes. The verbose nature of C++ also makes this a bit meh, but still very understandable.

...
void MainMenuWidget::Construct(const FArguments& InArgs) {
  this->actor = InArgs._Actor;


  ChildSlot
  [
    // we start with an empty widget
    SNullWidget::NullWidget
  ];
}

// we have to manually call this
void MainMenuWidget::setCurrentSplash(TSharedPtr<SWidget> splash) {
  ChildSlot
  [
    // if we have a splash, draw it, otherwise we have to set it to null
    // there is also no way yet (that I can see yet), to inline map and return widgets. Needs more investigation
    splash == NULL
      ? SNullWidget::NullWidget
      : SNew(SOverlay)
        + SOverlay::Slot()
          .VAlign(VAlign_Fill)
          .HAlign(HAlign_Fill)
        [
          SNew(SColorBlock).Color(FLinearColor(0, 0, 0))
        ]
        + SOverlay::Slot()
        [
          splash.ToSharedRef()
        ]

  ];
}

Slate does obviously have a paint() method much like Java Swing/AWT, but that gets called on every frame, which is better suited to doing canvas based paint logic as apposed to ‘re-render when my data changes’

Use actors for more complex widget logic

When building complex UI that might have various types of FX, you often need to use things like timers, sound components and other sub-engines to run FX. Unreal engine has no shortage of engine components which can drive these kinds of effects and interactions, but they almost all need to be bound to an Actor.

For example, if you want to use an FTimelineComponent, and bind an interpolated value, you essentially need to bind it to a function wrapped in UFUNCTION(). Slate widgets are not descendants from Object which makes this difficult unless you use lambdas. I have therefore gone for a more view / controller approach where I bind these kinds of interactions to Actors, store and assign the widget to a pointer, and then update the widget as various FX components top their thing.

# !------ SplashWidget.cpp ------

void SplashWidget::Construct(const FArguments& InArgs) {
  // store the actor instance
  this->actor = InArgs._Actor;
  ...
}

FReply SplashWidget::OnMouseButtonDown(const FGeometry& MyGeometry,
                                       const FPointerEvent& MouseEvent) {

  // interaction events call functions on the actor
  this->actor->unmountCallback();
  return SCompoundWidget::OnMouseButtonDown(MyGeometry, MouseEvent);
}

# !------ SplashScreenActor.cpp ------

void ASplashScreenActor::init(InitOptions options) {
  ...
  // assign the widget since we can change properties on it.
  // we can also use the stored widget in parent widgets (as children)
  SAssignNew(this->widget, SplashWidget)
          .Image(brush)
          .Actor(this); // pass in the actor

  // handle logic such as sound components
  this->sound = AssetManager::getSound(options.sound);
  this->soundComponent = UGameplayStatics::SpawnSound2D(this, this->sound);
  this->soundComponent->Stop();
  this->soundComponent->RegisterComponent();
}

Gotchas

Things I have run into while learning the engine from a C++ perspective.

Shared pointers cause crashes on exiting PIE preview.

I have found that using

TSharedPtr<UActor> myActor;

Often results in the Unreal editor crashing because the editor also holds a reference to the object (probably the details pane). I get this kind of nullptr error even after I have destroyed all remaining actors and reset all the pointers themselves. I suspect this is because the actors don’t go through immediate garbage collection. What has fixed this for me however, is opting for standard pointers, but that are marked with UProperty().

UPROPERTY()
UActor* myActor;

If you do not mark the pointer as a UPROPERTY(), Unreal engine will aggressively garbage collect your actors which results in even more nullptr’s. I have had luck storing other things in shared pointers though, as in I think the issue is mainly with storing actors.

Null pointers and other errors are not handled well in the Unreal editor

In every case I’ve run into, the Unreal editor crashes when the engine experiences things like null pointers, invalid memory access or strange assertions where objects are no longer valid. Ive come to the conclusion that this is because the editor is more the engine itself, and acting as your game / project directly. This often causes my editor to crash as I’m working with hot module reloading, and then very often but not always invalidates the project game module that I’m working on in Rider. This requires a complete clean and rebuild of the solution which takes a fair amount of time.