Creating Widgets with Unreal & Angelscript
Using Angelscript in Unreal can make creating Widgets way faster and easier to iterate on. Let's take a look at how to build a simple example!
Contents
General Principles
Terms
There are some terms I'm going to use throughout this post, so let's make things clear from the start:
- Widget - the actual interface that users will interact with, displaying information and handling input
- Data - any sort of data that is underlying the widget (could be assets, game state, whatever)
- ViewModel - an intermediary class that converts the Data into something the Widget can use, and also runs operations on the Data
A ViewModel may not always be necessary - in the simplest case, all you need is a Widget that has simple display and operational logic for working with the underlying Data.
ℹ️ Note
I won't be using a ViewModel in the following basic example, to keep things simple.
Approach
You could consider two main paths to be taken when implementing Widgets in Unreal:
-
Interface first - Start with a sketch of the user interface and use that to guide how you approach the structure of the Widgets and ViewModel.
- This implies you know what problem you are trying to solve, but maybe not what data you will work with yet.
-
Data first - Start with the data you know you need to work with, and figure out a good way to display it (with or without a ViewModel).
- This implies you have some existing data and need to work out the best way to expose it to the user.
In reality, I find that the best way results in a feedback loop between both of these approaches, refining the data and the interface as you work on it. Build, iterate, build, iterate!
In this example, we're going to build the interface first because it's just a simple tutorial with no particular goal in mind other than to cover the basics.
Prerequisites
If you want to follow everything I'm covering here, you'll need to have downloaded and installed or built Hazelight's UnrealEngine-Angelscript fork. Access to the UnrealEngine-Angelscript Github repository requires access to Epic's Github repositories too, so set that up first by following this guide.
⚠️ Important
Do this first or very little of this will work or make any sense! You can find installation instructions here.
Make sure to install Visual Studio Code (a.k.a. VSCode) for working with Angelscript files. Information about the Unreal Angelscript Extension for VSCode is also mentioned in the guide above, and you should make sure to have it installed too.
ℹ️ Note
The general approaches described below will still apply if you want to write your code in C++ or entirely in Blueprint, but that is not my goal here, so adjust your expectations accordingly if you are not using Angelscript at all.
Create a base class in Angelscript
Let's start by creating a "base class" for our Widget - make a new ExampleWidget.as file somewhere in your Unreal project's /Script/
folder.
- It should inherit from the
UUserWidget
class if we are making a general-use Widget that can be used at runtime or in the Editor. - It should also be
Abstract
, because it can't exist without a child implementation to specify the Widget bindings.
/** An example Widget for this tutorial */
Abstract
;
This is the simplest base class example that does not specify any properties or methods, so it is fundamentally no different to its parent class of UUserWidget
at this point.
Add some dependent child Widgets
Now if we want to specify that some Widgets are required to display specific information or handle user inputs, we can specify them here using the BindWidget
keyword on a property.
/** An example Widget for this tutorial */
Abstract
;
By using the BindWidget
keyword, we tell Unreal that these sub-widgets must exist on any implementation of this Widget class, and this will result in a Widget Blueprint compile error if we fail to specify them.
⚠️ Save & hot reload
Save the Angelscript file so that Unreal will hot-reload this script.
Create a Widget Blueprint subclass
Once you have saved the above file, Angelscript should hot-reload in Unreal and you can now create a Widget Blueprint based on our new Angelscript base class.
-
In the Content Browser, right-click in empty space (not on an asset) to bring up the context menu.
- Choose User Interface -> Widget Blueprint near the bottom of the list:
- Note that if you want to create an Editor Utility Widget that you can launch as a tool in Unreal Editor, instead of just a regular Widget, you must instead choose Editor Utilities -> Editor Utility Widget from the right-click context menu.
-
In the window that pops up, filter or search for our
ExampleWidget
class to use as a base:- You might need to click the All Classes header to show the list. Unreal strips the
U
prefix, but you can still pasteUExampleWidget
here if you want to filter on the exact class name. - Also note that our comment over the Angelscript class shows up as a tooltip for the user!
- You might need to click the All Classes header to show the list. Unreal strips the
-
Click Select, and you will go back to the Content Browser with a new asset selected - give your new asset a clear and simple name like
WBP_Example
:✅ Tip: Naming conventions
I find it's useful to prefix assets in this way (
WBP
= Widget BluePrint) to tell different types apart at a glance, especially in text lists or logs.
Set up the Widget Blueprint
If you open the asset we just created, you might notice a ⚠️ icon next to the Compile button in the Widget Blueprint toolbar.
If you click Compile, you should see the following errors in the Compiler Results pane: These compile errors show up because we haven't yet created the child Widgets that our base class expects to be bound. You can toggle the Compiler Results pane from Window -> Compiler Results if you don't see it immediately.
Add the child Widgets
First, make sure that the Palette panel (Window -> Palette) and the Hierarchy panel (Window -> Hierarchy) are visible, so that we can find and drag the Widgets we need into our Widget's hierarchy.
-
Since we have more than one child Widget to add, we first need a container Widget that can hold more than one Widget - let's use a simple Horizontal Box.
Search the Palette panel for
Horizontal
to show the Horizontal Box widget, then click and drag it over to the root Widget in the Hierarchy pane to create an instance of it: -
Search the Palette panel for
Text
, then click and drag the Text Widget over the Horizontal Box Widget in the Hierarchy pane to add an instance of it as a child: Click on the new Text widget or press F2 to rename it to the exact name we specified above -ExampleText
. -
Search the Palette panel for
Button
, then click and drag the Button Widget over the Horizontal Box Widget in the Hierarchy pane to add an instance of it as a child: Click on the new Button widget or press F2 to rename it to the exact name we specified above -ExampleButton
.
Now when you click Compile again, the errors should no longer show up, and there should be a ✅ next to the Compile button.
If this isn't the case, double-check that the Widgets you added have the correct names and are the correct type for the BindWidget
properties you specified in the Angelscript base class.
Change the preview mode
✅ Tip: Layout preview
At this point I often change the Widget preview mode by using the button in the top right of the Designer view.
I recommend changing it from the default "Fill Screen" (which is rarely what you actually want) to the more clear "Desired" mode, which causes the Widget to take up the actual amount of space it needs at a minimum: You can change this at any time to preview how your Widget will look at different screen or window sizes.
Add simple logic in Angelscript
Now we've set up how it will be displayed, that's all we need to do in the Widget Blueprint! Let's go back to VSCode and add some logic into our Angelscript base class.
Implement a PreConstruct
method
The PreConstruct
method is available to all UserWidget subclasses, and can be overridden in Angelscript.
It will be called on your Widget at edit time and at runtime before it is Constructed, so you can use it to show a more accurate preview of the Widget while you're editing it in the Widget Blueprint editor.
To define this method, just start typing Pre
and the Unreal Angelscript extension should prompt you to auto-complete to a full method definition, just hit the Enter key or click on this context menu entry to let VSCode save you some typing!
Then let's just add the following code inside the UExampleWidget
class, immediately after the property declarations:
BlueprintOverride
void bool bIsDesignTime
What this code is doing is split over three lines:
- Generating a random number using
Math::Rand()
and storing it in theRandomNumber
variable. - Formatting the random number into a string using the
f""
simple string formatting, then converting that toFText
usingFText::AsCultureInvariant
(because we do not want to localize this text, it's just a number). - The resulting
FText
is then set as the text to display on theExampleText
Widget.
⚠️ Save & hot reload
Save the Angelscript file so that Unreal will hot-reload this script.
Now if you switch back to Unreal, when you hit Compile in the Widget Blueprint editor, you should see that there is now a random number displayed in your ExampleText
Widget where it used to say Text Block.
If you hit Compile repeatedly, you will see this number change, because the PreConstruct
method is being called each time, generating a new random number and setting it on the Text Block.
Hook up a function callback to the Button click event
Override the Construct
method in the same way we overrode PreConstruct
before - start typing Con
and use VSCode's autocomplete to generate the method.
Inside this method, we're going to bind a method to the OnClicked
event of the ExampleButton
Widget:
BlueprintOverride
void
You'll notice that VSCode underlines this line in red, and if you hover over the line, it should say something like Function ExampleButtonClicked does not exist in type UExampleWidget
. This is (unsurprisingly) because the function we're asking for doesn't exist yet!
✅ Tip: VSCode method generation
There's an easy way to fix this via the Unreal Angelscript extension - click the little blue bulb icon to the left of the line, and choose Generate Method: ExampleButtonClicked from the pop-up menu, as shown below.
private void
Now whenever you press the button, the event we bound in OnInitialized
will cause the ExampleButtonClicked
method to be called.
We can put whatever code we want to execute in here - for example, if we wanted to replace the random number in the ExampleText
block with a specific string, we can do it like so:
private void
This time, we're using NSLOCTEXT
because we want our "Override!"
string to be localizable into other languages. Text is not required to be localized for editor tooling, but if you're making something user-facing, then you want to consider this!
The first two arguments are the Namespace
and the Key
which uniquely identifies this text, and the third argument is the default (in this case English) string to use when localization has not been applied.
⚠️ Save & hot reload
Save the Angelscript file so that Unreal will hot-reload this script.
Testing it all together in an Editor Utility Widget
Unfortunately, since buttons aren't clickable in the Widget Blueprint editor, we need to create a usable instance of our WBP_ExampleWidget
somewhere.
We could do that on a game HUD, but a simpler approach is just to put it inside a new Editor Utility Widget that we can just run directly in Unreal Editor.
-
In the Content Browser, right-click in empty space (not on an asset) to bring up the context menu, and choose Editor Utilities -> Editor Utility Widget in the middle of the list: Once you select this, a popup may ask you what type of Widget you want to use as the root - it doesn't really matter for our purposes here, so you can just pick None.
-
Open the new asset (likely called
NewEditorUtilityWidgetBlueprint
, you can rename it if you like) by double-clicking on it or right-clicking on it and choosing Edit from the context menu. -
Search the Palette panel for
Example
, then click and drag the WBP Example Widget over the root of the Editor Utility Widget in the Hierarchy pane to add an instance of it as a child: -
Click Compile, make sure it has ✅ compiled successfully, and then click ▶️ Run Utility Widget in the toolbar of the current Widget Blueprint Editor:
This should cause the new Utility Widget to open in a new window. It should be displaying a random number as we expect. Now, if you click the button, the text should change to "Override!".
Parting thoughts & further work
The key thing about this approach is that we have kept the style of the Widget (defined in the Widget Blueprint) totally separate from the definition and logic of the Widget (defined in the Angelscript base class).
This is nice because the Angelscript code doesn't really have to have any opinion on the visual style of the Widget, it just needs to define the child Widgets it knows it needs complete the functions required of it.
The style can be layered on top by editing the Widget Blueprint - you can use any sort of layout you like, colors, images etc., and none of this requires any changes to the underlying script that drives it all.
Composing & nesting Widgets
Extending this simple example, we could do something a bit more advanced by defining multiple different Widgets which each perform a specific function, and then nesting them inside each other or binding callbacks from one Widget to another, to create more complex setups.
ViewModels
I didn't cover this here, but when things start getting more complicated (e.g. tools that manage large amounts of data), you might find it handy to implement an extra object type as a ViewModel, to act as an intermediary between the Widget and any data you are operating on.
This is basically taking the concepts we already covered here (separating the Widget's design from the logic) and bringing it a step further, so that instead of the Widget dealing with data directly, it will deal with a simpler API via the ViewModel. This allows the same Widget to be able to work with many different types of data, as long as they all share a similar way of working.
ℹ️ Note
I'm using the term ViewModel here because that's how I tend to think about it (like in MVVM), but you could think about it as a Controller if you prefer, such as in the MVC pattern. The main point is that is is a separate entity from both the Data and the Widget.
I'll probably do a deeper dive into this in another blog post later.
Relevant Links
- My UnrealFest 2022 talk on building Editor Widgets - getting a bit old now, but covers similar topics
- benui's tutorial on creating a UserWidget in C++ - his site is a great resource for Unreal developers!
- Hazelight's Unreal Engine Angelscript documentation - vital for getting set up with Unreal Angelscript
- Epic's documentation for Editor Utility Widgets - covers some Unreal Blueprint tools basics
- UnrealEngine-Angelscript Discord Server - where Angelscript devs hang out (my name is
MoP
in there)