3D Map with UI markers - Project setup & displaying markers (part 1)

ui tutorials

2/13/2025

Martin Bozhilov

Overview

In this tutorial series, we’ll use Unreal Engine v5.4 and blueprints to design a 3D map level. The map will consist of UI elements (such as points of interest and area markers) that are dynamically moved on the HUD based on the location of in-game objects. Additionally, the points of interest will be hoverable and will display a tooltip with information about the location.

This tutorial will be split into two parts. In this part, we will focus on displaying the map markers on the map. In the second part, we will implement interaction features such as showing a tooltip when a POI element is hovered over by the mouse.

For the UI, we will use Gameface’s native and UE data-binding features to bind game data to the UI.

Resources

Project Initialization - 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.

After that’s done, you can create your 3D level. In this tutorial, we are going for a pirate game theme, and our map will be a deserted island.

Setting Up Player Movement

The first step is to set up the movement of the player. For our project, we followed:

After following the tutorials, create a Game State Blueprint and add it to the Game Mode of our level. The major part of our logic will be handled and managed by the Game State Blueprint. In our case, we named it 3DMapGameState.

Placing Actors for POIs and Area Markers

The next major task is to extract the data from the game objects to use it in the UI. We took the approach of placing Actors in our world, on top of our points of interest (enemies, treasure chests, etc.), which actors we will get the world position of to accurately place our UI elements.

After creating and placing the actors, we must label them for easy access. We are going to place 2 entries in the Tags array:

  • Element with index 0 will be the name of the POI (dock, enemy, treasure) which we will show in our UI when the POI is hovered.
  • Element 1 will be used to get all actors and manipulate them with the Get All Actors of Class with Tag Blueprint node.

When that is done, we are going to do the same for our area markers.

Data-Bind Model setup

Before we begin setting up any blueprint logic, we must set up our model which we will use in the UI - as it plays a very central role in our project.

To achieve this, we must do the following:

  1. Create structures for the points of interest, the area names, and the model itself.
  2. In the GameState blueprint, create variables that will hold the state and update the model dynamically.
  3. On Event Tick, update the model with the correct position of our elements relative to the screen.
  4. Send these changes to the UI with data-binding.

Creating the Structures

For this map UI, we are going to have 2 main UI elements - points of interest (POI) and area markers. Let’s create structs for both.

In the content browser, create a POIStruct that will hold all the information we need for the POIs. Create a struct with fields:

  • coordinates - type: Vector 2D - to hold the x and y coordinates of the actor.
  • name - type: name - to hold the name of the POI.
  • description - type: string - to hold the description of the POI that will be shown in the tooltip.

Let’s do the same for the area markers. The fields will be the same, except we don’t need description as the elements will be just text.

The last struct we need is for the model we are going to use in the UI. Create a structure named MapModel that will hold:

  • An array of POIStruct elements.
  • An array of AreaMarkers elements.
  • zoomed - boolean flag we will use to toggle styles in the UI relative to the zoom level of the map.

Each element in these arrays will be bound to the actors in our world.

Setting Up the Points of Interest

After the actors have been placed in the world, we can now work with their data.

Head to the Event graph in the GameState BP and create a new method called Init Variables. There we will initialize and connect the actors with the structs we have just created, as well as initialize some other variables.

To fill the structs with information for the POIs, create a variable called Marker Descriptions with Map type where we will define the description of the POIs.

Lastly, create all the other variables we are going to need for our logic.

Expand to view variable description
  • Gameface HUD - Will be used to hold reference to the GameFace HUD object.
  • Actor POI - Here we will store the actors for the points of interest.
  • Map Model - This is our model, which will interact with our UI.
  • POIs - An array of POIStruct elements, which will hold the info about the POIs data.
  • POI - Instance of the POIStruct type, which we will use to map POI data and add it to the POIs array.
  • Area Names Actor - Same purpose as the Actor POI variable but for the area markers.
  • Area Names - Same purpose as the POI variable but for the area markers.
  • Area Names Array - Same purpose as the POIs variable but for the area markers.
  • Player Camera - Will be used to hold reference to the camera object.

With that out of the way, let’s finally set up the blueprint logic for this method.

  1. First, set the reference for the GameFace HUD and the Camera objects.
  2. Then, get all actors with tag POI and set them in the Actor POI array.
  3. Loop through the array and for each entry, get the tag with index 0 - which is the POI’s type (enemy, treasure, etc.) and find the corresponding element from the Marker Descriptions array to find the appropriate description for the POI.
  4. Next, set the members (name and description) in POIStruct.
  5. Lastly, add the newly created POI to the POIs array.
  6. Repeat the process for the area markers, with the exception that they don’t have a description field, so this part is skipped.

Creating the Data-Bind Model

Once the Init Variables function is ready, we can proceed with configuring the game model for the UI.

In the Game State Blueprint, on Event BeginPlay:

  1. Call Init Variables to initialize all required variables.
  2. Set the MapModel structure with the now populated POIs and Area Names Array variables.

Additionally, add a boolean variable named Are Bindings Ready. This will be used as a conditional check to ensure the UI models can be updated from the game. Without this check, attempts to update the models before the bindings are ready will fail.

Next, use Create Data Model From Struct followed by Synchronize Models to create the mapModel model from the MapModel structure.

Displaying Points of Interest and Area Markers

After initializing our model and all necessary variables, we need to update the x and y coordinates of each element in the model on Event Tick to display the UI elements at the correct place in our HUD.

To achieve this, we will create a method in the GameState Blueprint called Map World Location to Screen. This method will project the actors’ locations into 2D screen coordinates, which we will use to display the POI and area markers at the correct locations relative to the player’s view.

Next, we will create another method - Update POI State that will:

  1. Accept the new coordinates and the element’s index.
  2. Update the element in the POIs array at that index.
  3. Update the model and call Update Whole Data Model from Struct and Synchronize Models to reflect the changes in the UI.

What’s left is to connect everything in the Event Graph.

We will check if the bindings are ready with the Are Bindings Ready boolean we defined earlier. When that returns true, we will map through all the POI actors and call both methods we just defined.

After we finish looping through the Actor POI array, we will do the exact same for the area names. Note that we have to create a new method Update Area Names State, which will have the exact same logic as the Update POI State method. However, since it is a different structure, we can’t reuse the one we used for the POIs.

Project Setup - Frontend

Let’s quickly set up the frontend so we can display our map elements.

We’ll begin by creating an index.html file in the uiresources folder.

Import cohtml.js

To enable communication between the UI and the game, the cohtml.js library must first be imported.

Creating the UI elements

Next up, let’s create our UI elements and bind them to the model with data-binding.

index.html
1
<body>
2
<div class="poi-container" data-bind-for="poi:{{MapModel.Pois}}">
3
<div class="poi"
4
data-bind-style-left="{{poi.coordinates.X}}"
5
data-bind-style-top="{{poi.coordinates.Y}}">
6
<div class="poi-content">
7
<div class="poi-image" data-bind-style-background-image-url="'./pois/' + {{poi.name}} + '.png'"></div>
8
</div>
9
</div>
10
</div>
11
<div class="area-marker-container" data-bind-for="marker:{{MapModel.AreaMarkers}}">
12
<div class="area-marker"
13
data-bind-for="marker:{{MapModel.AreaMarkers}}"
14
data-bind-style-left="{{marker.coordinates.X}}"
15
data-bind-style-top="{{marker.coordinates.Y}}"
16
data-bind-value="{{marker.name}}">
17
</div>
18
</div>
19
<script src="./cohtml.js"></script>
20
</body>

Here, we are using data-bind-for to loop through all elements in the Pois array in the model.

We are controlling the position on the screen of each map element with the data-bind-style-left and data-bind-style-top attributes, which change the left and top CSS properties of the element every time the model updates (which in our case is every tick).

Finally, we are creating the path to the background image with data-bind-style-background-image-url by dynamically creating a string from the name of the POI. In this case, the images are located relative to the index.html file in a pois folder.

The logic for the area markers is the same, but we are updating the value of the HTML element with data-bind-value instead of updating a background image.

The only thing left is to add some styles to our elements.

styles.css
1
@font-face {
2
font-family: GodOfWar;
3
src: url(./GODOFWAR.TTF);
4
}
5
6
body {
7
width: 100vw;
8
height: 100vh;
9
overflow: hidden;
10
margin: 0;
11
perspective: 2000px;
12
font-family: GodOfWar;
13
}
14
15
.poi-container {
16
transform-style: preserve-3d;
17
}
18
19
.poi {
20
position: absolute;
21
width: 3vmax;
22
height: 3vmax;
23
/* Translation is applied so the element is positioned from its center as opposed to the top left corner which is by default */
24
transform: translate(-50%, -50%);
25
z-index: 1;
26
}
27
28
.poi-content {
29
width: 100%;
30
height: 100%;
31
display: flex;
32
align-items: center;
33
justify-content: center;
34
border: 0.2vmax solid rgb(168, 143, 0);
35
background-color: #333333;
36
transform: rotateX(25deg) rotateZ(-45deg);
37
box-shadow: 0px 0px 6px 2px rgba(0,0,0,0.5);
38
}
39
40
.poi-image {
41
width: 100%;
42
height: 100%;
43
transform: rotate(45deg);
44
background-size: 75% 75%;
45
background-position: center center;
46
background-repeat: no-repeat;
47
}
48
49
.area-marker-container {
50
transform-style: preserve-3d;
51
}
52
53
.area-marker {
54
position: absolute;
55
font-size: 2.5vmax;
56
letter-spacing: 1rem;
57
color: white;
58
opacity: 1;
59
text-transform: uppercase;
60
z-index: 2;
61
text-shadow: 1px 1px 10px black;
62
transform: rotateX(25deg) translate(-50%, -50%);
63
}

To enhance the depth and the 3D effect of the UI, we have added a perspective on the body element to enable 3D CSS transformations. This allows us to rotate our elements in 3D space, making it feel like the area markers and POIs are laying directly on top of the world, as opposed to being part of the UI.

Result

If you save, compile and play your level In Unreal you should see similar results!

What is next?

In the next tutorial, we’ll enhance our map by adding interactivty.

Check it out here 3D Map with UI markers (part 2)

On this page