Adding a UI into the world

ui tutorials

11/8/2024

Martin Bozhilov

Integrating an interactive UI directly into the game world creates a more immersive experience, allowing players to engage with the interface as a natural part of the environment.

Overview

In this tutorial, we’ll guide you through adding an existing UI to the game world and seamlessly blending it with the environment using Gameface’s in-world UI feature in Unreal Engine. We’ll enhance our Tetris UI sample by adding effects and animations, helping to integrate it naturally into the scene for a more authentic, immersive look.

Resources

The Assets for this tutorial have been taken from Sketchfab and fab .

List of assets:

Getting started - Unreal Engine

We’ll begin by setting up the project in Unreal Engine. Use the getting started guide from our documentation to install the Gameface plugin and integrate it into an existing project or use the sample project created by the installer.

Once that’s complete, create a new blank map in Unreal Engine to start configuring your setup.

Setting up the scene

We’ll start by importing our assets into a newly created level and setting up a simple scene of a dark room with a console and a TV for demonstration purposes.

Adding in-world UI

Next, we’ll add the in-world UI. To do this, go to the Gameface plugin’s dropdown menu and select the Add in-world UI option. This will add a CohtmlPlane actor to your world, containing a plane mesh with the cohtmlMaterial applied to display the UI. The cohtmlMaterial is used as the material for the plane mesh, rendering the UI onto it. To learn more about Gameface’s in-world UI feature, you can reference our documentation .

To display your UI, go to the plane’s details panel. In the properties of the Cohtml Gameface Component, locate the URL field and specify the path to the HTML page you want to display in your world.

Important: Ensure your HTML page is included in your game’s uiresources folder otherwise, the page will not render.

And thats it! Now, if you hit play, your HTML page will be displayed on the plane.

Adjusting the plane to fit the screen

To make the plane blend naturally with the TV and achieve the classic curved appearance of old CRT screens, we’ll use Unreal Engine’s Modeling Tool to slightly bend the plane. Alternatively, you can model a curved plane in a 3D software like Blender, import it into Unreal, and replace the plane in the CohtmlPlane actor.

Bending the plane

Click here for more information on how we did our plane modeling.

First, resize and overlay your plane on top of the TV screen to better gauge how much to bend the plane.

Next up, open the modeling tool and select Deform, and set the resolution to 10 on all axes.

Now, start bending the plane. The simplest method is to select the outer rows and columns one by one and adjust their position on the Y-axis, as shown below:

Here, we lowered the Y-axis values of the outer points of the mesh by 2 to bend it backward

Apply the same method to the remaining sections until you achieve the desired curvature.

A useful tip is to toggle the visibility of the TV object to check how the plane aligns with it and identify areas needing further adjustments. In the image below, the plane is almost finished, but the center needs to be bent slightly more forward.

Here’s the finished result:

Making the TV emit light

To give the TV a more realistic glow, we’ll create a simple actor with a Point Light component as its child. Place the light slightly in front of the plane and adjust its position until it looks natural.

Feel free to experiment with the light settings and adjust the color to match your UI. In our example, we used the color #00F1ACFF.

To add dynamic behavior, we’ll attach some logic to the light, which will later sync with a CSS effect to make the TV appear to turn on and off procedurally.

Setting up light logic

  1. Set the initial intensity of the Point Light to 0.

  2. Open the blueprint editor for the light actor and create a custom event called SetLightIntensity. This event will accept one argument—a float value to set the light’s intensity.

The logic for this event is straightforward. We’ll increase the light’s intensity in two stages, with brief delays in between

And then simply connect the logic to Event BeginPlay to trigger it when the level starts.

Setting up TV turn off logic

To make the TV turn off, we’ll reuse the SetLightIntensity event we created earlier by calling it from the UI with an argument of 0 for the light intensity.

Open the blueprint for the CohtmlPlane actor. In the Event BeginPlay node, register the Cohtml Component for a JavaScript event. For more details on how to trigger events from the UI, refer to our documentation ,

Then you need to specify the custom event which will trigger from the UI. In this case, we’ll call it QuitGame. The event will simply get the light actor and call the SetLightIntensity event but with intensity of 0, effectively turning off the light.

And with that, we’ve completed the Unreal Engine setup! Next, we’ll move on to setting up the frontend to bring the UI to life.

Getting started - Frontend

For the UI of this tutorial, we will be enhacing one of our existing samples - the Tetris UI, which you can find in the ${Gameface package}/Samples/uiresources/TetrisInventoryUI directory. Or if you have installed our Unreal Coherent Sample project you can find it in the Content/uiresources/TetrisInventoryUI directory.

Because it is an existing project, everything is already setup for us, now let’s just modify it.

Making TV on/off animations

To create the effect of the TV turning on and off, we’ll modify the HTML by adding a wrapper element around the content and applying animations to it. These animations will control the width, height, and opacity of the container holding the content, simulating the TV’s behavior.

Start by wrapping the entire content inside the <body> tag with a <div> element that has the class content-container

index.html
1
<body>
2
<div class="content-container">
3
<!-- Rest of the HTML here -->
4
</div>
5
</body>

Next, slightly modify the body styles and define the styles for the content-container and its animations:

style.css
1
body {
6 collapsed lines
2
background-color: rgb(19, 19, 19);
3
margin: 0;
4
padding: 0;
5
width: 100vw;
6
height: 100vh;
7
display: flex;
8
flex-direction: column;
9
align-items: center;
10
justify-content: center;
11
overflow: hidden;
12
}
13
14
.content-container{
15
width: 100%;
16
height: 100%;
17
display: flex;
18
justify-content: center;
19
align-items: center;
20
flex-direction: column;
21
position: relative;
22
overflow: hidden;
23
animation: tvOn 1s forwards;
24
}
25
26
.content-container.screen-off{
27
animation: tvOff 1s forwards;
28
}
29
30
@keyframes tvOn {
31
0% {
32
width: 0%;
33
height: 1%;
34
opacity: 0;
35
}
36
37
75% {
38
width: 100%;
39
height: 2%;
40
}
41
42
100% {
43
width: 100%;
44
height: 100%;
45
opacity: 1;
46
}
47
}
48
49
@keyframes tvOff {
50
0% {
51
width: 100%;
52
height: 100%;
53
opacity: 1;
54
}
55
56
25% {
57
width: 100%;
58
height: 2%;
59
}
60
61
100% {
62
width: 0%;
63
height: 1%;
64
opacity: 0;
65
}
66
}

After applying the styles, the UI will transition as if it opens from the center of the screen. To “turn off” the TV, we’ll later add the class screen-off to the content-container via JavaScript to trigger the opposite animation.


Slightly adjusting the UI

In the preview, you may notice the bottom-right corner of the UI is slightly cut off due to the plane’s curvature. To fix this, adjust the button’s CSS properties as follows:

style.css
7 collapsed lines
1
.buttons {
2
width: 100%;
3
position: absolute;
4
display: flex;
5
justify-content: flex-end;
6
align-items: center;
7
font-family: 'Evil Dead';
8
bottom: 1vmax;
9
right: 1vmax;
10
bottom: 3vmax;
11
right: 3vmax;
12
color: var(--white)
13
}

Adding the old tv screen noise effect

To add the effect of a game being played on an old CRT TV, let’s add an effect throug css and overlay it on top of the content. We are going to achieve this effect by adding a gradient background to the overlay element and animate it’s background position slightly to simulate the lines flickering.

Start by adding a <div> with the class overlay inside the content-container:

index.html
1
<div class="content-container">
2
<div class="overlay"></div>
3
<!-- Rest of the content -->

Now, define the styles for the overlay element and add an animation for the static noise effect:

styles.css
1
.overlay {
2
position: absolute;
3
top: 0;
4
left: 0;
5
right: 0;
6
bottom: 0;
7
background-image: linear-gradient(rgba(0, 146, 68, 0.15) 30%, rgba(255, 255, 255, 0.15));
8
background-size: 100% 20px;
9
animation: tv 1s forwards linear infinite;
10
z-index: 999;
11
}
12
13
@keyframes tv {
14
from {
15
background-position: 0% 0%;
16
}
17
18
to {
19
background-position: 0% -10%;
20
}
21
}

Once the styles are applied, the overlay will create a subtle, animated noise effect reminiscent of an old CRT TV.

Implementing a pause menu to turn off the TV

To simulate the player interacting with the game on the TV, we’ll add a simple pause menu. When the player opens the menu and clicks Quit, the TV will turn off.

Begin by adding the HTML for the pause menu

index.html
1
<div class="content-container">
2
<div class="overlay"></div>
3
<div class="pause-menu">
4
<div class="menu-btn play-btn">Play</div>
5
<div class="menu-btn quit-btn">Quit</div>
6
</div>
7
<!-- Rest of the content -->

And the styles

style.css
1
.pause-menu {
2
display: none;
3
position: absolute;
4
top: 0;
5
left: 0;
6
right: 0;
7
bottom: 0;
8
flex-direction: column;
9
justify-content: center;
10
align-items: center;
11
font-size: 4vmax;
12
color: var(--white);
13
font-weight: bold;
14
background-color: rgb(0, 0, 0);
15
line-height: 1.5;
16
z-index: 1;
17
}
18
19
.pause-menu.open{
20
display: flex;
21
}
22
23
.menu-btn:focus{
24
color: white;
25
outline: none;
26
}

Next, add a visual indicator on the bottom left corner of the screen for the player to know which button to press to open the pause menu:

index.html
3 collapsed lines
1
<div class="main-container">
2
<!-- main container content -->
3
</div>
4
<div class="pause-text">
5
<div>Press "OPTION"</div>
6
<div>to pause</div>
7
</div>
10 collapsed lines
8
<div class="buttons">
9
<span class="rotate-btn hidden buttons-mr-1 flex">
10
Rotate Item
11
<div class="button-square"></div>
12
</span>
13
<span class="select-btn buttons-mr-1 flex">
14
<div class="button-cross-text">Select Item</div>
15
<div class="button-cross"></div>
16
</span>
17
</div>
style.css
1
.pause-text {
2
width: 100%;
3
position: absolute;
4
display: flex;
5
justify-content: flex-start;
6
flex-direction: column;
7
font-family: 'Evil Dead';
8
bottom: 3vmax;
9
left: 10vmax;
10
color: var(--white)
11
}

Now let’s make the menu interactable so we can trigger the QuitGame event we defined in Unreal Engine earlier.

First, get references to the pause menu and content container:

script.js
3 collapsed lines
1
let selected = null;
2
let itemElements = [];
3
4
const pauseMenu = document.querySelector('.pause-menu');
5
const contentContainer = document.querySelector('.content-container');
6
const pauseMenuClassList = pauseMenu.classList;
7
const spatialNavigation = interactionManager.spatialNavigation;

Update the existing focusin event to avoid breaking previous logic when interacting with the pause menu:

script.js
1
document.addEventListener('focusin', ({ target }) => {
2
if(target.classList.contains('menu-btn')) return;
3
4
focusedItem.item = Inventory[target.index];
5
engine.synchronizeModels();
6
});

Modify the handleEnterPress function to handle pause menu interactions:

  • The Play button will close the menu.
  • The Quit button will trigger the QuitGame event in Unreal Engine and execute the TV’s “turn off” animation.
script.js
1
function handleEnterPress() {
2
const activeElementClassList = document.activeElement.classList;
3
4
if (pauseMenuClassList.contains("open")) {
5
if(activeElementClassList.contains('play-btn')) {
6
pauseMenuClassList.remove("open");
7
}
8
else if(activeElementClassList.contains('quit-btn')) {
9
ppauseMenuClassList.remove("open");
10
contentContainer.classList.add('screen-off');
11
engine.trigger('QuitGame');
12
}
13
14
spatialNavigation.focusFirst("default");
15
return;
16
}
17
18
if (!selected) return selectItem();
19
placeItem();
20
}

Create a function to open the pause menu, initialize spatial navigation , and focus the Play button:

script.js
1
function openPauseMenu() {
2
if(selected) return;
3
4
pauseMenuClassList.add("open");
5
spatialNavigation.add([{ area: 'pauseMenu', elements: ['.menu-btn'] }]);
6
spatialNavigation.focusFirst('pauseMenu');
7
}

Now we need to attach both functions and since the Tetris UI supports playstation controller input with the Interaction manager we can easily extend our logic to be supported by a gamepad,

script.js
11 collapsed lines
1
interactionManager.keyboard.on({
2
keys: [KEYS.ENTER],
3
callback: handleEnterPress,
4
type: 'press',
5
});
6
7
interactionManager.gamepad.on({
8
actions: ['playstation.x'],
9
callback: handleEnterPress,
10
});
11
12
interactionManager.keyboard.on({
13
keys: [KEYS.P],
14
callback: openPauseMenu,
15
type: 'press',
16
});
17
18
interactionManager.gamepad.on({
19
actions: ['playstation.options'],
20
callback: openPauseMenu,
21
});

Players can now open the pause menu by pressing P on a keyboard or the Start button on a PlayStation controller. The menu allows them to resume the game or quit, triggering the TV “turn off” effect.

With this the UI is completed!

On this page