This page is a WIP
Around June of 2022 I began working on a personal project to explore and prototype a 2D Turn Based Tactics game inspired by the Fire Emblem franchise. I re-used this system and converted it to 3D to develop Chimera for my final year project at University.
In the beginning of December of 2024, I decided to revisit this project, and use it as a tool to learn Unreal Engine 5. Since then, I have been rebuilding the tactics based combat system using the C++ blueprints system of Unreal Engine 5.
Role: Game & Level Designer
Tools Used: Unreal Engine 5, C++ Blueprints, Git / GitHub, Jira, Miro, Excel
Timeline: December 2024 - Current
Responsibilities Include:
Using Jira to keep track of the projects progress.
Using Miro and Pen & Paper to design flow charts and design concepts.
Using Excel to design unit stat formulas.
Using GitHub for version control.
Programming the tactics combat system using C++ Blueprints.
The idea for the prototype was inspired by a Dungeons & Dragons campaign that I have played in with several friends since 2019. I wanted to re-create the characters and the story of the campaign as a Tactics RPG inspired by the Fire Emblem franchise.
The grid system is built up of multiple different blueprints, enumerators and data structures. It is made up of four core blueprints.
BP_Grid
BP_GridModifier
BP_GridPathfinding
BP_GridVisual
In order to function, the grid also needed several other utilities.
BFL_TileData: A blueprint function library for tile data.
BP_GridMeshInst: A blueprint for the mesh instance of the grid. It is a seperate blueprint to allow different grid views such as a tactical view.
E_TileState: An Enumerator for each of the possible Tile States.
E_TileType: An Enumerator for each of the possible Tile Types.
S_IntPointArray: A structure for an array of int points. Used for a map of tiles associated with a specific tile state or tile type.
S_PathfindingData: A structure for the data of a specific tile during pathfinding.
S_TileData: A structure for the data of a specific tile.
This section will explain the major functions in each of the major blueprints. The full Unreal project is available on GitHub here.
The BP_Grid blueprint is the main blueprint that allows this system to function, and handles the grid data. Each level should only have one grid blueprint.
The BP_Grid has two child actors.
Child Actor Grid Pathfinding
Child Actor Grid Visual
The Event Graph of the BP_Grid blueprint is empty, as all of the functionality in runtime is called by dev tools or during the loading of the level. All of the functions in the blueprint
Spawn Grid
When the grid is generated, Spawn Grid is called.
Saves input values into grid variables.
Destroys any previous grid generated.
Calls the Initialize Grid Visual function of the Child Actor BP_GridVisual.
Calls function to calculate and assign variables for the center and bottom left corner of the grid.
Begins For Loop to loop through each index of the grid.
For each index, set the position of the tile on the grid.
Check if the Use Environment bool is true.
If true:
Call the trace for ground function to calculate ground position.
Call the Add Grid Tile function.
If false:
Call the Add Grid Tile Function.
Trace For Ground
Trace For Ground function is called.
Sets Radius variable based on the current grid tile size.
Calls a multi sphere trace function:
Uses the tile location and traces from 1000 to -1000 on the z axis.
Uses the radius set previously for the multi sphere radius.
Checks if multi sphere trace outputs any hits.
If false, return location with tile type set to None.
If true, set Ret Type variable to Normal as default.
Set Is Height Found bool to false.
Begin for each loop for each hit location.
On loop completion, call return node with the variables Ret Type, location X and location Y, and Ret Z.
Inside for each loop:
Break the hit result of current loop.
Cast hit actor to BP_GridModifier.
If cast succeeded:
Set Ret Type as BP_GridModifier Type.
If BP_GridModifier is used for tile height, set Is Height Found to true.
Break location vector and calculate height and snap to grid.
Set Ret Z variable.
Get Cursor Location on Grid
Gets current player index.
Gets the hit result under cursor for only the grid.
If there is a hit, return the location on the grid.
If there is no hit, checks if there is a hit result for the Ground and Grid Modifiers collision layer.
If there is a hit, return the location of the hit.
If there is no hit, go to the final branch.
Convert the mouse location to world space.
Call the line plane intersection function with the following inputs:
World location of mouse as line start.
World location of mouse plus the world direction of mouse multiplied by 99999 as line end.
Plane from grid center location as point, and vector 0,0,1 as normal as APlane.
If there is a hit, return hit location.
If there is no hit, return location as vector -999, -999, -999 to make it invalid.
The BP_GridPathfinding blueprint handles all of the pathfinding for the grid. It uses the A* pathfinding algorithm to determine the cheapest possible route to the destination.
Using BP_GridModifiers, you can alter the cost to move through certain tiles and what units can access specific tile types.
The BP_GridPathfinding is a child actor of the main BP_Grid blueprint. Its functionality is called in other blueprints.
Event Graph
A custom event, FindPathWithDelay is called. This is to help optomise the game for larger maps.
Using a sequence, we open the gate to loop through the function during runtime.
Set the loop start time variable.
Checks if there are more tiles to analyze for pathfinding.
If true:
calls the Analyze Next Discovered Tile function.
If false:
Call On Pathfinding Complete Event Dispatcher with an invalid path to target (Target cannot reach the tile).
If the function found the final tile in the pathfinding:
Call the Generate Path function.
Call the On Pathfinding Complete Event Dispatcher.
If the function did not find the final tile in the pathfinding:
Check if the max ms of tile data that can be processed in one frame has been surpassed. If false, analyze another tile. If true, delay function until the end of delay timer.
Find Path
Find Path is the main function of the Blueprint. When the game needs to generate pathfinding data, it calls the "Find Path" function
Call "Find Path" function.
Save all of the necessary variables including:
Start Index: An Int Point of the index of the tile to start the pathfinding from.
Target Index: An Int Point of the index of the target tile the pathfinding is trying to reach.
Include Diagonals: A bool to allow the algorithm to use diagonal tile movement.
Valid Tile Types: An array of an Enum for Tile Types. Decides what tile types can be walked on during the pathfinding.
Delay Between Iterations: A float for the delay value between each iteration of the pathfinding data. Used for performance to generate path over multiple frames.
Max Ms Per Frame: A float for how much Ms can be used each frame when generating the pathfinding data.
Call the "Close Delay Gate" custom event which can be found in the Event Graph.
Call the "Clear Generated Data" function to clear all previously generated pathfinding data.
Call the "Is Input Data Valid" function to ensure input data is valid.
Branch if the data is valid:
If false, return an empty array as pathfinding data.
If true, continue function.
Call the "Discover Tile" function with the following inputs:
The Start Index.
The minimum cost to target. Calculated with the "Get Minimum Cost Between Two Tiles" function.
Branch if the delay between minimum iterations is less than or equal to zero.
If false, Call the "Find Path with Delay" custom event which can be found in the event graph.
If true, continue function.
Start a while loop for all discovered tile indexes.
If completed, it means the function failed to generate a path.
Return an empty array as the path.
Call the "Analyze Next Discovered Tile" function.
Branch if the function found the target tile.
If true, call the "Generate Path" function.
Call the "On Pathfinding Completed" event dispatcher.
Return the path generated.
Analyze Next Discovered Tile
Call the "Pull Cheapest Tile Out of Discovered List
This function loops through all discovered tiles and decides which one has the lowest cost to move towards the destination using the A* pathfinding algorithm.
Returns the cheapest tiles tile data.
Call "On Pathfinding Data Updated" Event dispatcher.
Call "Get Valid Tile Neighbours" Function to get all valid neighbours available.
Set "Current Neighbours" variable array.
Call a while loop to discover the next neighbour.
If neighbour is the destination of the pathfinding, return true.
If the destination is not discovered, return false.
Generate Path
Set the local variable Current equal to the target index.
Loop through a while loop until Current is equal to the start index.
In the while loop, add Current to a local array Inverted Path.
Set the new current to the next index down.
(This loop is done to invert the path, as after generation the first tile in the sequence is the destination tile).
After the while loop is completed, call the Reverse For Each Loop function.
During the loop, add each value to the local array Ret.
Once the loop is completed, return the Ret array.
The BP_GridModifier blueprint is much smaller than the other core blueprints. A grid modifier is placed around the level to change how the grid works, altering what tiles are on the grid.
There are several different types of Grid Modifiers, and it is easy to add more as it all uses an Enumerator.
Construction Script
Set the static mesh for the modifier's shape.
Set the material for the modifier (Used for development and not seen during gameplay).
Call the Set Vector Parameter Value on Materials function.
Use a Select Function to select the colour of the material based on what type of modifier is desired.
Call the Set Scalar Parameter Value on Materials function.
Call the Set Collision Response to Channel function.
Set the Channel to GroundAndGridModifiers
Call the Set Actor Hidden In Game function and set New Hidden to true.
E_TileType
An enumerator for all of the tile types in the game. For development, there are very few.
None - No tile present in this location.
Normal - A normal tile with no modifiers
Obstacle - An obstacle on the map to block movement.
Double Cost - Tiles that cost double to move through for the BP_GridPathfinding blueprint.
Triple Cost - Tiles that cost triple to move through for the BP_GridPathfinding blueprint.
Flying Units Only - Tiles that only flying units can cross.
The BP_GridVisual blueprint handles all of the visuals for the grid generation.
It renders all of the tiles for the game and allows us to have multiple different views for the grid such as a "Tactical View".
Initialize Grid Visual
Set a variable reference to the BP_Grid.
Set the Grid Mesh Inst variable as the BP_GridMeshInst Child Actor.
Set the Grid Mesh Inst Tactical as the BP_GridMeshInst_Tactical Child Actor.
Call the Initialize Grid Mesh Inst function for the Grid Mesh Inst variable.
Give the function the Flat Mesh and Flat Material variables from the S_GridShapeData structure for its Mesh and Material input.
Call the Initialize Grid Mesh Inst function for the Grid Mesh Inst Tactical variable.
Give the function the Mesh and Mesh Material variables from the S_GridShapeData structure for its Mesh and Material input.
These give the grid mesh instance a 3d shape.
Call the Set Actor Location function.
Set the Actor to 0,0,0. As it is a Child Actor of the BP_Grid, it will center itself to the parent actor.
Call the Set Offset from Ground function giving it the float OffsetFromGround.
Call the Set Grid Lowest Z function.
Call the Set is Tactical function giving it the bool Is Tactical.
Update Tile Visual Tactical
Update Tile Visual Tactical is called, given a variable for the Tile data.
Start a sequence and continue to the first step.
If the tile data has a lower Location Z than the Grid Lowest Z variable, call the Set Grid Lowest Z function.
Once this is checked, continue to the next step in the sequence.
Check if Is Tactical is set to true.
If false, set the "Need To Re Generate Tactical on Next Enable" bool to true.
Call the Remove Instance function on Grid Mesh Inst Tactical.
If the tile type is not equal to None continue function.
Call the Add Instance function on Grid Mesh Inst Tactical.
Using the data transform, take away the Grid Lowest Z value.
Using the z size of the Mesh, add that onto the result.
Then divide the whole result by the grid z size to get a value for how far down on the Z axis the tactical tiles should extend.
The BP_GridMeshInst is an Instance of the Grid that handles all of the visuals using an Instanced Static Mesh.
The visuals are kept as a seperate function to allow for different views of the grid during gameplay and to have different views
Add Instance
Call the Remove Instance function giving it the Index for the new tile instance.
Call the Unreal Add Instance function on Instanced Static Meshes giving it the transform of the new tile.
Add the Index to the array of Int Points named Instance Indexes.
Set the local integer i to the index of the new tile in the array.
Call the Get Color From States or Type function giving it the Tile Type and the array of Tile States.
Call the Set Custom Data Value function of the Instanced Static Mesh giving it the i integer and the R color.
Repeat the above step while increasing the custom data index by 1 and using the G and B values.
Repeat one more time using the Is Filled return value from Get Color From States or Type.
This last step is used to change the color of the Material Instances each tile uses and whether it should only show the tile outline or fill the tile in.
Get Color From States or Type
Check if the "Is Color Based on Tile Type" bool is set to true.
If true, using the Select function with the Tile Type variable as input, return the Color and Is Filled values.
Check if the length of tile states is equal to 0.
If true, return the default colour and Is Filled values.
Call a for each loop to loop through each tile state.
Check if the states variable contains this iterations state.
If true, return the colour value and Is Filled value for that state.
These values are decided by two Select functions.
If no state is found, return the default colour and Is Filled values.