Custom Extensions - frasergeorgeking/UE4_BP_MazeGen_MIT GitHub Wiki
The generator can easily be extended to include new custom geometry or behaviour. Please see the Demo Application Tutorial for an example of how to extend the functionality of the project from a code and logic perspective. The remainder of this section will focus on the implementation of custom maze tile geometry.
- Table of Contents
- Required Tile Meshes
- Importing Custom Geometry
- Export Settings
- Importing into UE4
- Blueprint Class Setup
- Tile Data Table Setup
The generator works by combining a few tiling meshes together to create a seamless maze. Please find renders of the included tiles in both textured and un-textured form below:
These modular, tiling meshes can then be slotted together and/or rotated to form mazes - this is effectively what the generator does algorithmically. Please see an example of this below.
This section will guide you through the process of adding bespoke geometry from an external modelling program into the generator (this specific guide will utilise Blender, but the same principles apply to other modelling suites, such as 3DS Max or Maya). It is assumed that the geometry and textures of your models are in a game-ready state (e.g. Consistent smoothing groups, no overlapping verts, faces etc...). The example tiles used for this guide can be seen below:
The pictured tiles are purposefully basic as to make this guide as generally useful and wide-purpose as possible. From left to right, the tiles are as follows: Straight, Crossroads, Dead-End, T-Junction & Corner.
It is possible to include more than one of each variation of the five basic tile meshes (for example, having two or more corner tiles with varying levels of cosmetic damage, different textures or swapped props etc...). Having additional variation within your tileset can drastically reduce repetition and provide the illusion that content isn't procedurally generated.
Before we start the process of exporting the geometry out of Blender and into Unreal Engine, we need to double check a few things first! Please perform the following on each of your meshes:
- Ensure the origin of each of your tiles is in the center of the mesh, snapped adjacent to the bottom-most vertex (as per the image below).
-
Name each tile object in the scene descriptively and appropriately - no "Cube.049" or "tile_TEMP_FINAL". You'll want to include the tile type (straight, crossroads, tjunc etc...), variation number (if applicable) and perhaps a reference to the aesthetic/setting of the tiles.
- A good example name for a theoretical tile would be
desert_tile_crossroads_var02
with a further sub-variation of the tile being nameddesert_tile_crossroads_var02_crumbled
.
- A good example name for a theoretical tile would be
-
Measure the width and length of each tile to ensure they are the same across both axes (remember to measure from the longest point across each individual axis). Make sure you jot this number down as we'll need it later when we import the tiles into UE4!
A measurement of the example straight tile (exactly 2.5m across in both the x and y axes).
- Create a few quick test mazes by snapping different combinations of tiles together (this will help identify any issues with tile geometry early on). If the final maze has no overlapping tiles or gaps, the tiles should be ready for export.
An example maze snapping test. All tiles fit together in varying orientations as expected. These tiles are ready for export.
Whilst the export settings used will largely differ depending on modelling suite, it is always important to consider the units your models are based on. 1 Unreal unit (uu) is equal to 1 centimetre (cm) by default - make sure that you're happy with the resulting scale that this causes.
For reference, the image below demonstrates the settings that were used to export the demo tiles out from Blender. Each tile was exported into its own individual .fbx file, however it is also possible to group the meshes into a single file and separate them inside of Unreal Engine.
Navigate into an appropriate folder where you would like to save the tile meshes. A Content/Geometry/Meshes/Tiles
directory already exists in the project and will be used for this example (after a new subfolder DemoTiles
has been created).
Once you've navigated to the desired directory, click the Import
button and navigate to the folder where you exported your tiles. Select all the tiles and click Open
.
If you exported your assets into an .fbx format, Unreal Engine will open an FBX Import Options
window as seen below. Feel free to adjust these options as necessary for your assets before clicking Import All
.
Once the import process has been completed, you may wish to check the collision of your tiles (or separately import this from a dedicated collision model). Due to the simple, low poly nature of these example assets, Collision Complexity
has been set to Use Complex Collision As Simple
.
Lastly, ensure that any textures/materials you have created have also been successfully imported, otherwise you may wish to import and apply them separately. The pictured debug tiles do not have dedicated textures or uniform UVs and, as such, will have a very basic material applied.
Each tile also requires a corresponding Blueprint class to be created (these classes are then referenced inside of a data table and fetched by the generator as required). The easiest way of creating a corresponding Blueprint class is to right-click on each tile, navigate to Asset Actions
and select Create Blueprint Using This...
.
As with the tile geometry from the previous step, you will need to create an appropriate directory to save the Blueprint Classes. For this example, the DemoTiles
folder was created in the Content/Blueprints/Tiles
directory. Remember to create the class with an appropriate prefix (all Blueprint classes in the project are prefixed with BP_
, however you may wish to use a different convention).
The newly created classes should look like the image below, with the base mesh of your tile as the root component.
As these Blueprint classes are the basis of what is spawned at runtime, feel free to add any necessary props, Blueprint code or components as required for your implementation.
With the necessary Blueprint classes created, the final requirement is to create a master data table that references these Blueprint tile actors, as well as the values of the walls (i.e. true or false for the north, east, south and west walls of each tile). The images below demonstrate wall values for the PSXStoneTiles
bundled with the project.
The default corner tile wall values.
The default crossroads tile wall values.
The default dead-end tile wall values.
The default straight tile wall values.
The default t-junction tile wall values.
In addition to the default states listed above, we also need to keep track of each tile in it's 90°, 180° & 270° orientations (this provides the generator with the flexibility it needs to find a matching tile for any possible tile cell state). The tile data table for the tiles bundled with the project can be seen below for reference.
Tile data tables such as those seen above can be created in two primary ways - automatically via a BP_TileWallIdentifier
actor or by manually creating a data table inside of Unreal Engine natively. The automatic method utilises a line trace approach and, as such, may not work universally with all tiles (for example, tiles with non-uniform gaps may cause issues). Documentation on each method has been broken down into individual sections below.
Before the step-by-step guide begins, please see here for an overview of the specific details of each variable and event related to the BP_TileWallIdentifier
class (this is the class responsible for controlling line traces and serialising wall values to file).
Open the TileWallIdentifierScene
found in the root level of the project content folder.
After the level has loaded, navigate to the right island titled 'Automated Tile Wall Identifier', as seen below.
Drag each of the tile blueprints into the scene, spacing them out for visual clarity.
With the tile actors in the scene, select the BP_TileWallIdentifier
and add a reference to each of the tiles in the TargetTileActors
array.
Add an array element for each tile - the example demo tileset has five tiles, so five array elements have been added. Use the Asset Picker eyedropper to reference the tile blueprints.
A completed list of Blueprint tile references.
Now that the BP_TileWallIdentifier
has a reference to each tile, the execute-in-editor events seen above can be called to ensure that the properties of the identifier are correct. The best place to start is typically to call the Preview Line Trace Range With Debug Lines
event to ensure that the Line Trace Range
is long enough to reach each wall - an example of this is shown below.
An example of the line trace range being previewed with debug lines. Notice how all debug lines comfortably reach the walls of each tile.
If, after performing a debug line test, the default range is too small to reach the tile's walls, increase the value of Line Trace Range
and try again. In the case shown above, the default value of 350
is a bit high and whilst this shouldn't cause issues with the wall identification process, the overlapping lines make it difficult to visualise how the system works. The screenshot below demonstrates the results of lowering the Line Trace Range
to 200
.
After ensuring a valid Line Trace Range
has been defined, it is next important to ensure that the value of ZAxis Line Trace Offset
is compatible with your tilset. ZAxis Line Trace Offset
is used to create a buffer between the origin of your tile geometry and the origin/starting location of the line trace. For example, the Shinto tile below - graciously provided by Sam McMillan for testing - contains gaps in the geometry which would cause a line trace to miss, thereby invalidating the automatically generated wall values.
The default line trace missing due to a gap in the tile geometry.
A close-up of the offending gap.
This issue is rectified by increasing the value of ZAxis Line Trace Offset
to 130
, thereby causing the line trace to hit the support beam of the geometry - this is demonstrated in the screenshot below.
The resulting effect of adjusting the ZAxis Line Trace Offset
value of the BP_TileWallIdentifier
.
With an appropriate Line Trace Range
and ZAxis Line Trace Offset
defined, the Test Line Trace
event can be called to perform a test line trace to verify if the correct walls are hit - an example of this is shown below.
Please remember that any existing debug lines that have been drawn to the screen can be flushed by calling Flush Debug Lines
(Tile%20Wall%20Identifier%20Properties#flush-debug-lines).
If all line traces performed by Test Line Trace
hit the expected walls, the configuration of BP_TileWallIdentifer
is complete and the Exported CSV File Name
value can be populated. In this case, a value of DemoTiles_250Units
has been selected, whereby DemoTiles
is the name of the tileset and 250Units
is the width/length of the tiles, as per the pre-export checklist. Naming the file this way isn't strictly necessary, however it will greatly help to manage your tile data tables (especially if you have multiple tilesets)!
The final step in the process is to call the Export Values to CSV
event. If successful, a CSV file will be output to a TileDataTableCSVs
folder in the root of the project directory, as per below.
The exported CSV file. The 'TILE_' prefix is automatically added.
The tile CSV can now be imported into the project as a data table of data table row type TileDataStructure
, as per below. If you wish to use the Blueprint Utility Widget to generate mazes inside of the editor, you will need to import the data table into the Content/Blueprints/TileDataTables
directory. Remember to save the asset after importing it!
Please see Using the Generator for details on both the runtime and in-editor generation process. If you have any difficulties with implementing your automatically generated tile data table, your tileset may be incompatible and a manual table may have to be created.
If your tileset is incompatible with the automatic tile identification process (e.g. your tile geometry doesn't have clearly defined walls, non-uniform gaps etc...), you can manually create a tile data table by following the steps below.
Open the TileWallIdentifierScene
found in the root level of the project content folder.
After the level has loaded, navigate to the left island titled 'Manual Tile Wall Assistant', as seen below.
Drag the first tile blueprint of your tileset into the scene, using the transform section of the 'Details' panel to snap the tile to a location of X: 0.0, Y: 0.0, Z: 0.0
. Make sure to double-check that the rotation of the blueprint actor is also at a default value of X: 0.0, Y: 0.0, Z: 0.0
.
Navigate to the Content/Blueprints/TileDataTables
directory in the content browser and create a new data table of row structure TileDataStructure
, as per below. The name of this data table should be TILE_TilesetName_xUnits
whereby TilesetName
is the name of your tileset and x
is the width/length of the tiles, as per the pre-export checklist. For this example, a data table name of TILE_DemoTiles_250Units
will be used.
The Data Table class can be found under the 'Miscellaneous' tab of the 'Create Advanced Asset' menu.
The 'Pick Row Structure' box. Tile data tables must be of row structure type TileDataStructure
.
The newly created tile data table - remember to save your changes to the project!
Once you have created the data table, open it to be greeted with Unreal Engine's data table interface. This is the interface where we'll be adding all of the necessary actor references and tile values. If possible, you may wish to dock this window to another monitor, or leave it floating above the main editor window, as we will be frequently using both.
Click the 'Add' button at the top of the data table interface, as seen below.
Fill the first two fields (TileName
and TileActor
) with the name of the current tile in the scene, as well as a reference to the respective blueprint class. In the example below, the current tile in the scene is BP_base_tile_corner
- this has been given an abbreviated name of base_corner
for the TileName
field and a reference to the blueprint class has been established within the TileActor
field.
The remaining fields of NorthWall?
, EastWall?
, SouthWall?
, WestWall?
and Rotation
all refer to the physical properties of the tile in the scene. Use the text labels of the 'Manual Tile Wall Assistant' to act as a guide to the current wall values. For example, the corner tile shown in the screenshot below would have values of NorthWall? = true
, EastWall? = false
, SouthWall? = false
and WestWall? = true
, as the geometry of the tile only has walls in the north and west directions. This logic follows the same pattern as that shown in images from the initial Tile Data Table Setup section.
Finally, the Rotation
field is determined by the current value of the Z axis rotation - this should currently equal 0, as we have not adjusted the rotation of the tile yet. With all fields now populated, the first record looks like the following.
Next, select the tile in the scene and rotate it by 90° in the Z axis, as seen below.
As previously described in the Tile Data Table Setup section, each individual tile needs to be tracked in it's 0°, 90°, 180° & 270° orientations as to provide the generator with the flexibility it needs. With the tile now rotated in the scene, we repeat the previous steps to name the tile, add a reference to the actor and track the wall and rotation values, resulting in the second record entry seen below.
This process now needs to be repeated two more times with the tile's Z axis rotation being set to 180° and 270°. The screenshot below demonstrates all of the records for the base_corner
tile.
Finally, repeat this process with each of the remaining tiles within your tileset to complete the data table. Please see the finalised data table below.
You may have noticed that some of the tile data table records contain duplicate wall values; a good example of this is seen in the numerous rotations of the crossroads tile - a tile with no walls will never have walls, regardless of rotation. These duplicate records are not explicitly required and may even increase the computational overhead of the generator (the generator has to iterate over these data tables to find matching tiles and, as such, more data table records = slower generation). The image below demonstrates the tile data table after it has had records with duplicate wall values removed.
All duplicate crossroads records have been removed, in addition to the 180° & 270° variants of the straight tile.
Please see Using the Generator for details on both the runtime and in-editor generation process.