DEVELOPMENT HIGHLIGHTS
FEATURING CUSTOM DATA MANAGEMENT TOOLS,
TILE VALIDITY SYSTEMS, AND ENEMY AI LOGIC



Operation Cumulonimbus is a challenging project that requires drawing upon all the knowledge and programming skills I have accumulated over years of game development.
I modularized my code and optimized it so that it can be reused across many different systems. When existing tools were insufficient, I created my own, such as an Excel macro-based data management system or customized for loop utilities.
I put great importance on clean and elegant programming, and this applies even to my personal projects. In the future, if others become interested in collaborating on my projects, the clarity and structure of my code will make teamwork smoother and more efficient.

Unit Data

Coordinate Calculator in Excel

Unit Name Move Offsets Move Edge Pos Fire Offsets
Fighter (3,-3); (3,-2); (3,-1); (3,0); (3,1); (3,2); (3,3); (4,-2); (4,-1); (4,0); (4,1); (4,2); (5,-1); (5,0); (5,1); (6,0) (3,-3); (3,3); (4,-2); (4,2) (0,-3); (0,-2); (0,-1); (0,1); (0,2); (0,3); (1,-2); (1,-1); (1,0); (1,1); (1,2); (2,-1); (2,0); (2,1); (3,0)
Gunship (8,0); (7,-1); (7,0); (7,1); (6,-2); (6,-1); (6,0); (6,1); (6,2); (5,-3); (5,-2); (5,-1); (5,0); (5,1); (5,2); (5,3); (4,-4); (4,-3); (4,-2); (4,-1); (4,0); (4,1); (4,2); (4,3); (4,4); (3,-5); (3,-4); (3,-3); (3,-2); (3,-1); (3,0); (3,1); (3,2); (3,3); (3,4); (3,5); (3,-5); (3,5); (4,-4); (4,4); (5,-3); (5,3) (6,0); (5,-1); (5,0); (5,1); (4,-2); (4,-1); (4,0); (4,1); (4,2); (3,-3); (3,-2); (3,-1); (3,0); (3,1); (3,2); (3,3); (2,-4); (2,-3); (2,-2); (2,-1); (2,0); (2,1); (2,2); (2,3); (2,4); (1,-5); (1,-4); (1,-3); (1,-2); (1,-1); (1,0); (1,1); (1,2); (1,3); (1,4); (1,5); (0,-6); (0,-5); (0,-4); (0,-3); (0,-2); (0,-1); (0,1); (0,2); (0,3); (0,4); (0,5); (0,6); (-1,-5); (-1,-4); (-1,-3); (-1,-2); (-1,-1); (-1,0); (-1,1); (-1,2); (-1,3); (-1,4); (-1,5); (-2,-4); (-2,-3); (-2,-2); (-2,-1); (-2,0); (-2,1); (-2,2); (-2,3); (-2,4); (-3,-3); (-3,-2); (-3,-1); (-3,0); (-3,1); (-3,2); (-3,3); (-4,-2); (-4,-1); (-4,0); (-4,1); (-4,2); (-5,-1); (-5,0); (-5,1); (-6,0);
Balloon (3,0); (2,-1); (2,0); (2,1); (1,-2); (1,-1); (1,0); (1,1); (1,2); (0,-3); (0,-2); (0,-1); (0,1); (0,2); (0,3); (-1,-2); (-1,-1); (-1,0); (-1,1); (-1,2); (-2,-1); (-2,0); (-2,1); (-3,0); (3,0); (2,-1); (2,0); (2,1); (1,-2); (1,-1); (1,0); (1,1); (1,2); (0,-3); (0,-2); (0,-1); (0,1); (0,2); (0,3); (-1,-2); (-1,-1); (-1,0); (-1,1); (-1,2); (-2,-1); (-2,0); (-2,1); (-3,0);
Airship (7,0); (6,-2); (6,-1); (6,0); (6,1); (6,2); (5,0); (4,0); (3,0); (2,0); (1,0); (6,-2); (6,2) (4,-2); (4,-1); (4,0); (4,1); (4,2); (3,-3); (3,-2); (3,-1); (3,0); (3,1); (3,2); (3,3); (2,-3); (2,-2); (2,2); (2,3); (1,-3); (1,-2); (1,2); (1,3); (0,-3); (0,-2); (0,2); (0,3); (-1,-3); (-1,-2); (-1,2); (-1,3); (-2,-3); (-2,-2); (-2,2); (-2,3); (-3,-3); (-3,-2); (-3,-1); (-3,0); (-3,1); (-3,2); (-3,3); (-4,-2); (-4,-1); (-4,0); (-4,1); (-4,2);

Unit Data manages the move range and action range data for the four unit types.
By setting the unit’s position as (0, 0) and using the Up direction as the default, I organized coordinates in Microsoft Excel and then converted them into a CSV file to import into Unreal Engine.

Since manually entering coordinates is inefficient and unintuitive, I created a Coordinate Calculator using Excel’s macro feature.
This tool allows me to visually draw ranges directly in the spreadsheet within a 19×19 area (A2–S20), marking the unit’s position in blue and the desired range in orange.
All orange cells are automatically converted into coordinate data.

It can also take coordinate input and visualize it in the same grid.
Thanks to this tool I made, unit design and balance testing have become much more efficient.
It will be more useful in the future when collaborating with others or developing additional unit types.

Data Integration in Unreal Engine

Since these coordinate values are stored as strings, they are converted into Vector2D arrays after being imported into Unreal Engine and then stored in a publicly accessible map within the GameInstance.
UnitData is later utilized in the CameraPawn blueprint which is responsible for player control, through the functions IsValidMove() and IsValidFire().
These functions determine whether the player’s selected tile lies within the unit’s movement range or whether a target unit is within the action range.
Enemy AI also uses these two functions to locate and attack player units, ensuring consistent logic between player and AI behavior.

Tile Validity Systems

EValidBlock

Each tile (block) has validity to determine whether the player’s selected tile lies within the unit’s move range—GetValidMove()—or whether a target unit is within the action range—IsValidFire().

Enum Color Description
Invalid White The tile is outside the unit’s move range.
ForceMove Yellow The plane unit must move forward beyond this tile each turn.
ValidMove Orange The unit can move to this tile.
ValidMoveEdgeLeft Red The unit can move to this tile and change direction to the left.
ValidMoveEdgeRight Red The unit can move to this tile and change direction to the right.
ValidFire Blue The unit can perform an action targeting this tile or the unit on this tile.
Unrechable Dark Brown The unit is ahead of this tile and cannot move back to it.

Event Show Range

This custom event sets each tile’s validity and visualizes via coloring them.
According to the game’s current state, it shows unit’s move range or action range.
For each tile in the level, first reset the validity and color, and then get the coordinate and direction of the active unit (the unit the player selected or the currently running enemy AI). Then runs GetValidMove() or IsValidFire() to determine which validity the tile has for the active unit.

IsValidUnrechable() & IsValidForceMove()

Before showing the move range of the active unit, if the unit is a plane, each block must pass two checks: IsValidUnreachable() and IsValidForceMove().
IsValidUnreachable() determines whether the selected tile is behind the unit. Since a plane cannot move backward, if the selected tile is located behind the unit, that tile’s type is immediately set to Unreachable.
IsValidForceMove() determines whether the selected tile is one of the two tiles directly in front of the unit. Since a plane must move forward at least three tiles every turn, if the selected tile is one of those two tiles, its type is immediately set to ForceMove.
GetValidMove() runs only when the selected tile does not fall into either of these two cases is.

GetValidMove()

GetValidMove() gets information about the currently active unit and rearranges the coordinate array loaded from Unit Data based on the unit’s current coordinate and direction.
It then checks whether the selected tile exists within the rearranged coordinate array. After that, it sets the corresponding tile type and calls the tile’s custom event Event Block Show Range.
This system separates logic and visuals: all calculations and checks are handled by CameraPawn, while each tile handles its own visual updates, resulting in a clean and organized structure.

IsValidFire()

IsValidFire() uses the same logic as GetValidMove().
The inputs are separated into Unit and UnitCoordinate to make the function more versatile and reusable by the EnemyAI.

Enemy AI

Fighter & Gunship AI Loop

Fighters and gunships are offensive units that actively focus on attacking the player. They first search for the player units within their move range + action range. The implementation of this search method is explained later.

When the enemy AI finds a valid target, it attacks first and then checks whether it can move directly in front of the player’s gunship or fighter. This behavior takes advantage of the plane unit’s rule of being forced to move forward every turn, allowing the enemy to obstruct the player’s path or even tempt the player into a collision that destroys both units.
If no target is available, the enemy AI searches for nearby clouds and enters to increase the evasion rate.
The following is more specific demonstration of Fighter & Gunship AI Loop.

Enemy AI evaluates player units in the following priority order: Balloon > Gunship > Fighter.

Balloons have powerful abilities such as altering terrain but are extremely fragile, being destroyed instantly by any attack. Thus, the enemy AI prioritizes intercepting balloons. If the player considers the balloon crucial for future strategy, they must surround it with ally units or place it beside a thundercloud to ensure safety. On the other hand, the player may choose to sacrifice the balloon to lure enemies into a trap.
Gunships have a very large action range and can attack twice per turn, giving them high strategic value. Therefore, the enemy AI prioritizes attacking gunships if there is no balloon within the range. The player can leverage the gunship’s large movement and action range to escape to positions unreachable by enemy units.
Fighters always receive 50% reduced damage, making it the lowest priority target. The player should anticipate enemy movement and position fighters in advance or place them near other allied units so that they can absorb incoming attacks in their stead.

This AI behavior gives players the impression that the enemy is an intelligent opponent that fully understands the game’s systems. New players can learn effective tactics by observing enemy actions, while advanced players can exploit the AI’s predictable traits to attempt bold and creative strategies.

Enermy Turn Loop

The enemy AI logic is combined with a state machine to process tasks cleanly.
When the Event Enemy Beginning is called by the GameManager, a blueprint controlling the state machine, all current enemies are added to the Enemies in Sequence array. The first element of this array becomes the active unit, and the AI begins operating on that unit.
After the AI finishes its related processes, the turn state changes to Enemy Finishing.
If there are still enemies left in the array, the current enemy is removed, and the system returns to the Enemy Searching state to process the next enemy.
If the current enemy is the last one, the Turn State transitions to Ending, closing the turn and starting a new one.

Behavior Tree Task: Can Attack

The currently active enemy searches for player units through its Behavior Tree. This is handled by the behavior tree task “Can Attack.”
The fundamental logic I designed for detecting whether a target exists within the enemy’s move range + action range works as follows:

The enemy iterates through every tile in its movement range. For each tile, it simulates expanding its action range from that position to determine whether a target unit exists within it. Here are the details:

When Event Can Attack is triggered, the enemy first calls GetCoordinateValidityMap(), which retrieves the coordinates and validity of all tiles within its movement range based on its current coordinate and facing direction. This method uses the same logic as GetValidMove() described earlier.

Next, the enemy collects all player units of the type (Balloon, Gunship, or Fighter) and sorts them in order of proximity. This allows the enemy to evaluate targets from nearest to farthest.
With preparations complete, the system begins scanning using two Customized For Each Loop with Delay executions.
The first loop iterates over the sorted player-unit array. The second loop iterates over all movement-valid tiles.

Within these nested loops, the enemy simulates its actions: It assumes its position to be at the tile from the second loop, expands its action range, and checks whether the target unit from the first loop lies within that simulated range.
Because the simulation must consider the unit’s movement range, action range, direction, and coordinates all at once, implementing this logic required significant time. Specifically, restructuring everything into a clean, unified system took the most effort. To accomplish this, I refactored GetValidMove(), IsValidFire(), and the direction-setting event SetDirection so that both player units and enemy units could use the same shared logic.

If IsValidFire() confirms that the target unit from the first loop is within attackable range, the enemy moves to the tile found in the second loop and attacks the target. To prevent other AI units from running prematurely or skipping logic during movement or attack animations, the system calls Event Enemy Move Finished after the movement. This ensures that the behavior tree task registers as succeeded.
Modifying this event so that both enemy units and player units could use the same Event Move Finished required additional adaptation.

Continue to Next Project

Circus Mania

Click the image to see details of the most surprising game nominee, Circus Mania.