[Tutorial] Create a User Interface


How to create a simple User Interface with Valkyrie Engine


In this tutorial, you will learn how to position your UI elements on the screen.
We recommend that you read the Introduction to interactions tutorial in order to make your GUI interactive.

Depending on the look and feel you’re after, you may want to design your GUI in the image editor of your choice.

What you will need:

  • Valkyrie Engine
  • (Optional) GUI images in PNG, TGA or JPG format.
  • 30 minutes.

Now without further ado, let’s get started!

Target User Interface

We will reproduce the GUI featured in the concept art below:

Notice the 5 UI sections in the design:

Let’s first learn how to build the Bottom center bar element (UI Section 3), and then we will take a closer look at the Right side collapsible menu (UI Section 5).

Setup UI Layer

Create a new application in Valkyrie Engine or use an existing one.

Copy your assets in the ./data folder of your application.
The image assets associated with each section must be imported in either JPG, TGA or PNG format.

Create a new layer by drag & dropping the Layer entity from the library to your scene.

You can assign a name to this layer if you wish to. In this case, we will name it GUI.

We need to enable the following flags:

  • PainterDrawSet: This flag allows Valkyrie Engine to know which layer should be displayed first depending on its position in the scene’s hierarchy.
  • Camera: This flag specifies that the layer contains its own camera. Otherwise the camera of the parent layer will be used.
  • ViewportStrech: This flag enables the aspect ratio of the Viewport to be changed without affecting the position of the UI elements.
  • ViewportCenterH: Center the viewport horizontally on the screen.
  • ViewportCenterV: Center the viewport vertically on the screen.

Now that we’ve got that out of the way, let’s proceed to create a Camera of the GUI layer.
Drag & drop the camera entity from the library to your GUI layer. We will call this camera GUI-Cam.

All assets entering the field of view of this camera will be visible in the GUI layer.

Bottom center bar element (UI Section 3)

Create a primitive by drag & dropping the Primitive entity into your GUI layer and position it within the field of view of your GUI-Cam.

The primitive you create is a box by default, let’s make it into a plane.
Select your primitive and navigate into its properties, in the Primitive type, select PlaneHz.

Your level should look like this:

To add more context, let’s add a texture to the primitive. Navigate to your asset library and select one of the bitmaps you saved in the ./data folder. You will have to drag & drop it into the primitive’s diffuse channel.

Our UI element is now textured, but the texture appears distorted. That’s because the primitive’s aspect ratio doesn’t match the texture’s aspect ratio. To fix this, select your primitive and scale it accordingly to match your texture resolution.

Don’t forget to enable the transparency flag in the material editor if your bitmap features an alpha channel.

The bottom UI element from section 3 is now displayed on the screen.
However, we must now anchor it correctly in the viewport.

To do so, we will use dummies.
Drag & drop a dummy entity from the library to your GUI layer. We will name it UI-Bottom-Anchor.

Drag your primitive into the dummy and make sure they have the same position on the Z axis.

Set your primitive relative to the dummy with its Base Flag. This means that the transformations applied to the dummy will also affect the assets contained within it.

We will now create the anchor. Navigate to the actions library and drag & drop the ViewportAnchor action on your dummy.
You can select the horizontal & vertical anchor settings by clicking on the ViewportAnchor action in your timeline.
In this case, we want the UI-Bottom-Anchor to be centered horizontally and anchored at the bottom of the screen:

Press ALT+SPACE to return to the camera view and press CTRL+SPACE to play the scene.
The UI element is now centered horizontally & anchored at the bottom of the screen vertically.

You can tweak the primitive’s position if there is a gap between the asset and the screen’s limit.

Our UI element is now correctly anchored in the viewport.

Top Left UI element (UI Section 1)

We will repeat this process for all UI elements.

UI Sections 2 and 4 are skipped in this tutorial as there is little added value to show it.

Right side collapsible menu (UI Section 5)

One of the UI elements is partially hidden. So let’s see how to handle this kind of exception.

Duplicate one of your UI dummies and change the settings of the ViewportAnchor accordingly. In this case Horizontally right and vertically centered.

Assign the correct bitmap in your material editor and scale your primitive to match the texture’s aspect ratio.

As you may have noticed, the ViewportAnchor action is active on the dummy. The dummy’s position will be updated depending on the ViewportAnchor settings. That being said, the primitive relative to the dummy can be moved. Its anchor point is the dummy’s position.
This allows you to handle exceptions easily and we’ll demonstrate that in a moment.

Let’s pretend that when clicked, the inventory menu we just created should be revealed completely.
We will need to create 2 additional dummies to specify the start and end positions of our UI element.

Disable your primitive’s relative flag for the moment.

Create a new dummy and position it at the exact location of your primitive. Let’s name this dummy Start.
Tick the relative flag in the dummy’s settings.
Drag your primitive into the Start primitive and enable its relative flag.

Duplicate the Start dummy and drag it to the left before renaming it to End. This will be the destination of our UI element when it is revealed.

Now create 2 AnimMoveTo actions and drag them onto your primitive.
We will create 2 animations.
One when the UI element is revealed and another for when it collapses into its initial state.

Press CTRL+SPACE to play your scene. You will now see the UI element expand and collapse as the timeline plays.

Naturally, we want to trigger this animation only when the user interacts with the UI element.
To do so, let’s add constraints to the timeline.

Drag & Drop 2 TimelineJumpTo actions on your primitive. Adjust their lengths so that they are active after the AnimMoveTo actions.

When the TimelineJumpTo action is triggered, you can move the current time to a specific time. It is useful to create state machines.

In this case, every time the action is triggered, we want to the move the current time to the beginning of the last AnimMoveTo animation.

In order to make this work, we need to select our primitive and tick the LocalTimeline flag as well as setting ticking the Loop checkbox and make sure that the Playback is set to Playing.

Play your scene and notice that the constraints are now in place! But there is still no way for the user to reveal the UI so we’ll have to create an interaction for that.

Navigate to the events library and drag & drop a Finger event in your GUI layer.
Navigate to the actions library and drag & drop an InputRayCast action onto the Finger event.

User clicks and finger taps are now detected in the scene.

Now let’s create 2 EnterRayHit events in our primitive.

As we didn’t create specific hitboxes for our UI element, its entire surface can receive clicks.
By using 2 EnterRayHit events we can specify what happens when the UI element is clicked regardless of its status.

Let’s add a TimelineJumpTo action to both EnterRayHit events and set the time accordingly.

Behold, your UI is now complete! Clicking on the UI element reveals and collapses it. Notice also how the UI elements are anchored to the right side of the screen.

Last word

With what you’ve learned, you can add more granularity to your own UI elements and create sophisticated user interfaces.
The interactions you create in your UI layer can affect elements in the 3D layers underneath, so go ahead and try.

1 Like


@Capsilus you were asking for it long ago. Sorry we have been pretty busy :sweat_smile: