In-world hologram UI - Showing an in world screen with a message (Part 2)

ui tutorials

1/7/2025

Kaloyan Geshev

In this tutorial, we will continue building our game with in-world hologram elements.

You can find the rest of the Hologram UI series here.

Showcase Overview

In this part, we’ll add an in world screen on top of the box created in the previous tutorial. This screen will display a message indicating that the player can access their inventory when near the box. Additionally, we’ll add smooth transitions to the UI - the screen will close when the player opens their inventory by pressing the P key and reopen when the inventory is closed. However, the backend implementation of this functionality will be covered in the next tutorial and in this one we will focus on the UI.

Adding an in-world view

To begin, we’ll add an in-world UI. Since we’ll use multiple in-world views (e.g., the current one and a 3D player inventory menu), it’s more efficient to create a separate Blueprint class for each view rather than adding them directly via the Gameface plugin menu’s Add In-World UI option, as described in the documentation here .

Creating the In-World Plane

  1. Locate the CohtmlPlane class in Content/Plugins/Gameface Content using the Content Browser.

Note: If the CohtmlPlane class isn’t visible, first use the Add In-World UI option in the plugin to generate the class.

  1. Duplicate the CohtmlPlane class and rename it InWorldPlane for this tutorial.

  1. Drag and drop the InWorldPlane Blueprint class into the world and position it on top of the box.

Setting Up the In-World Plane

  1. Open the InWorldPlane Blueprint class and access the Details panel.

  2. Select the Cohtml component and change the URL to coui://uiresources/HologramUI/in-world-screen.html.

  1. Disable game input for the in-world view - in the Cohtml component, uncheck the Receive Input option.

With this setup, the UI from the in-world-screen.html page will render on the plane above the box.

Remove collision from the in world view

To allow the player to pass through the in-world view, remove its collision:

  1. Open the InWorldPlane Blueprint class and access the Details panel.
  2. Select the StaticMesh component.
  3. Search for Collision and set Collision Presets to No Collision.

After changing this, the player will be able to walk through the plane, as shown below:

Make in-world view accessible in the level blueprint

To program the behavior of the in-world view, we’ll make it accessible within the Level Blueprint:

  1. Extend the InitVariables method created in the previous tutorial here.
  2. Create a new variable in the Level Blueprint: 2.1. Name: In World Screen 2.2. Type: Gameface Component
  3. Set the variable:
    • Use the Get All Actors of Class node with Actor Class set to InWorldPlane.
    • Retrieve the first item from the results.
    • Access the Cohtml component of the retrieved actor and assign it to the In World Screen variable.

With this setup, you can now reference the in-world view in the Level Blueprint to implement functionality such as closing and opening the screen when the player toggles their inventory. This functionality will be detailed in the next tutorial.

Frontend

For the frontend, we’ll reuse the animated overlay effect from the first part of this tutorial series. This effect creates a noise-like appearance with animated overlay lines.

First let’s start with the HTML structure for the UI:

Content/uiresources/HologramUI/in-world-screen.html
1
<!DOCTYPE html>
2
<html lang="en">
3
4
<head>
5
<link
6
rel="stylesheet"
7
href="./in-world-screen-styles.css"
8
>
9
</head>
10
11
<body>
12
<div class='in-world-screen'>
13
<div class="overlay">
14
<div class="overlay-lines"></div>
15
<div class="overlay-radial"></div>
16
</div>
17
<div class="wrapper">
18
<div class="icon"></div>
19
<div class="text">
20
Inventory
21
</div>
22
</div>
23
</div>
24
<script src="../javascript/cohtml.js"></script>
25
<script>
26
const screen = document.querySelector('.in-world-screen');
27
28
engine.whenReady.then(() => {
29
engine.on('playerMenuOpened', () => {
30
screen.classList.toggle('hide-screen', true);
31
});
32
33
engine.on('playerMenuClosed', () => {
34
screen.classList.toggle('hide-screen', false);
35
});
36
});
37
</script>
38
</body>
39
40
</html>

Wrapper Structure

The main wrapper (in-world-screen) contains:

  1. An overlay element for the noise effect with two layers:
    • overlay-lines for animated horizontal lines.
    • overlay-radial for a radial gradient that subtly fades the corners.
  2. A wrapper element holding the icon and the text (Inventory), arranged vertically with a flex column layout.

Interactivity

  1. The cohtml.js library is included to enable communication between the engine and the UI.
  2. The UI listens for two events triggered by the engine:
    • playerMenuOpened: Hides the in-world screen with a transition by adding the hide-screen class.
    • playerMenuClosed: Displays the in-world screen again by removing the hide-screen class.

CSS and Animations

The noise effect is achieved using CSS animations:

  1. overlay-lines uses a linear-gradient background animated along the x-axis to simulate moving noise lines.
  2. overlay-radial uses a radial-gradient with opacity animations to create a flickering effect.

Icon animation is achieved by rotating the icon along the Y-axis between and 180°, then back to .

For complete details, check the in-world-screen-styles.css file source:

Content/uiresources/HologramUI/in-world-screen-styles.css
1
body {
2
width: 100vw;
3
height: 100vh;
4
}
5
6
.overlay,
7
.overlay-lines,
8
.overlay-radial {
9
position: absolute;
10
width: 100%;
11
height: 100%;
12
top: 0;
13
left: 0;
14
right: 0;
15
bottom: 0;
16
z-index: 999;
17
}
18
19
.overlay {
123 collapsed lines
20
mask-image: radial-gradient(circle, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.5327380952380952) 65%, rgba(255, 255, 255, 0) 100%);
21
mask-size: 90% 90%;
22
mask-position: center;
23
}
24
25
.overlay-lines {
26
background-image: linear-gradient(rgba(0, 200, 228, 0.1) 0%, rgba(0, 200, 228, 0.1) 30%, rgba(255, 255, 255, 0) 30%, rgba(255, 255, 255, 0) 100%);
27
background-size: 100% 30px;
28
animation: overlay-anim 3s forwards linear infinite;
29
}
30
31
.overlay-radial {
32
background-image: radial-gradient(circle, rgba(0, 200, 228, 0.2) 0%, rgba(255, 255, 255, 0) 90%, rgba(255, 255, 255, 0) 100%);
33
background-size: 200%;
34
background-position: center;
35
background-repeat: no-repeat;
36
opacity: 0;
37
animation: overlay-radial-anim 3s forwards linear infinite;
38
z-index: 998;
39
}
40
41
@keyframes overlay-anim {
42
from {
43
background-position: 0% 0%;
44
}
45
46
to {
47
background-position: 0% -10%;
48
}
49
}
50
51
@keyframes overlay-radial-anim {
52
0% {
53
opacity: 0;
54
}
55
56
1% {
57
opacity: 1;
58
}
59
60
5% {
61
opacity: 0;
62
}
63
64
19% {
65
opacity: 0;
66
}
67
68
20% {
69
opacity: 1;
70
}
71
72
25% {
73
opacity: 0;
74
}
75
76
59% {
77
opacity: 0;
78
}
79
80
60% {
81
opacity: 1;
82
}
83
84
65% {
85
opacity: 0;
86
}
87
88
100% {
89
opacity: 0;
90
}
91
}
92
93
.in-world-screen {
94
background-color: rgb(0, 0, 0, 0);
95
color: white;
96
width: 100vw;
97
height: 100vh;
98
position: absolute;
99
display: flex;
100
align-items: center;
101
justify-content: center;
102
transition: transform 500ms;
103
transform-origin: bottom;
104
}
105
106
.hide-screen {
107
transform: scaleY(0);
108
}
109
110
.wrapper {
111
display: flex;
112
flex-direction: column;
113
justify-content: center;
114
align-items: center;
115
}
116
117
.icon {
118
background-image: url(./assets/iconBackpackSpecial.svg);
119
background-repeat: no-repeat;
120
background-size: contain;
121
height: 35rem;
122
width: 35rem;
123
animation: rotate-icon 2.5s ease-in-out forwards infinite;
124
}
125
126
@keyframes rotate-icon {
127
0% {
128
transform: rotateY(0deg);
129
}
130
131
25% {
132
transform: rotateY(180deg);
133
}
134
135
50% {
136
transform: rotateY(0deg);
137
}
138
}
139
140
.text {
141
font-size: 7rem;
142
}

The icon used for this example is as follows:

What is next?

In the next part of this tutorial, we’ll focus on implementing the backend in Unreal Engine to support the inventory menu.

On this page