Creating an Optomized Grid in Unreal Engine with C++

Introduction

Grids are a commonly used structure in games, encountered by players across various genres. Whether it’s positioning units in a strategy game like XCOM, placing structures in Hay Day or SimCity to allow players to design their spaces evenly, enabling procedural generation in large open worlds like No Man's Sky or Minecraft, or facilitating complex pathfinding systems on terrain, such as in Age of Empires, grids play an integral role.

Example of grid uses in games

Grids can take many shapes and can be entirely 2D or 3D. From planar to matrix grids, they always consist of evenly spaced cells in a world. Grids can be complex and even performant at large scales, where each cell must spawn an object holding state. In this article, I will guide you through the process of developing an optimized grid in Unreal Engine using C++ by utilizing procedural mesh generation and algebra to add functionality to it.

Mesh Generation and Customization

To optimize grid creation in Unreal Engine, we generate the grid visually using procedural meshes instead of relying on individual tile actors. To understand how this works, let’s start by explaining the core concept of rendering a line using triangles.

Rendering a Line

Line rendering visual, explaining how to add vertices of 2 trianlges to create a rectangle

To render a line in our grid, we use the CreateLine function, which constructs a line by defining a set of vertices and triangles. The function starts by calculating the direction of the line, based on the start and end points provided. Once we have this direction, we calculate a perpendicular vector to define the line's thickness using cross vector.

Next, we determine the line’s four vertices to form two triangles. Each triangle is created by appending three vertices in the appropriate order. For example, the first triangle consists of vertices (2, 1, 0), the second trianlge of vertices (2, 3, 1). As you can see both triangles share the vertices 2 and 1 which makes them align seamlessly to represent a rectangle that visually represents the line.

Finally, these vertices are offset by half the thickness in both positive and negative directions to create a line with the desired width and lenght.

Check Code - Create Line

Making the Grid

To construct the grid using lines, we start by generating both horizontal and vertical lines with the help of the CreateLine function.

First, we create the horizontal lines by iterating through each row, we calculate the LineStart based on the row’s index and the TileSize an the LineEnd using this position and the total grid width.

Next, we do similar for the vertical lines by iterating through each column, based on the column index and the grid's total height.

Check Code - Making The Grid

Customizing the Grid

The grid customization allows for precise control over its appearance and functionality. You can specify the number of rows and columns, as well as the tile size and line thickness, all of which contribute in the calculations for mesh rendering. Additionally, you can adjust the line opacity and line color, which are applied to a dynamic material that enhances the visual characteristics of the mesh.

World Position and Grid Cell Mapping

In grid-based systems, the main core utility we can add is the mapping between world location and grid tiles. using this grid system that is actors less for each cell, we have to take an algebraic approach in linking between these two locations.

First, to transform world location coordinates into grid cell indices for X and Y, we first subtract the grid's root location from the target location This centers the calculation around the grid's origin.
For the row index, we divide this adjusted location by the grid width multiplied by the number of columns. Similarly, for the column index, we divide the adjusted location by the grid height multiplied by the number of rows. We use the FFloor function to ensure the result returns whole numbers, which represent the tile indices.

Second, to transform the X and Y indices of the grid into world coordinates, we first calculate the X coordinate by multiplying the Row index by the tile size and then adding the location X of the grid's root. For the Y coordinate, we multiply the Column index by the tile size and add the root Y location of the grid. This calculation returns the bottom-left coordinate of the grid cell. To obtain the center of the cell, we simply add half tile size to each coordinate.

Check Code - Location to Grid Mapping

Potential use Cases and Extenstions

This modular grid system is dynamic and can be adapted for various gameplay mechanics. In my implementation, I made the tiles clickable, allowing a character to move to a specific tile. I also added functionality to spawn actors on the tiles and created a data structure for each tile to store relevant information, such as whether it is walkable, spawnable, and the actor reference it currently holds.

For visual tile selection, I implemented a visual indicator using the create line function that matches the tile size to show which tile is currently selected. Additionally, I designed two procedural meshes: one for non-walkable tiles and another for non-spawnable tiles, enhancing the visual representation of the grid system.