Why not? - Make your own Fire Emblem movement system in Godot!

I have been gallivanting around Europe the last couple of months, and have been out of the saddle when it comes to working on Knott and the Waking Wilds. I decided to create a little Demo project to get myself back into the Game Development Space.
Recently I have been playing around with Custom resources, with these I have been able to add property sheets to nodes such as player stats and weapon details. With it, I seem to have created a pretty decent solution for a Advanced Wars/Fire Emblem board type system.
First, I made a custom MissionMap node which extends TileMapLayer, As well as a dictionary with all the current units. The MissionMapās has two goals, Hold the Unit Database and supply tile info. These are marked with a Custom Layer (Open, Mountain, Blocked Ect.) You can pull these properties with the following line of code and set up your own TileSet in Figure 1.
get_cell_tile_data( *VECTOR2I* ).get_custom_data( *CUSTOM_LAYER_NAME* )

Next, I created a new resource called a MapQuery. This allows me to make map queries and refer to them the same way you would create a Vector2 or similar. MapQuery.new(UNIT,MAP), The movement ranges are saved in a dictionary.
The code works as follows,
- First it gets the unit location. Using the unit location. It sets the dictionary record as followed, Key is the Vector2i where the unit is, the value is set to the max_move of the character.
- Then the following is looped to adjacent spaces with the movement weight being reduced by one until no more spaces can be done.
- Is movement equal to or less than zero. If so cancel check.
- Is the tile requested out of bounds, If so cancel check.
- Is there an existing record for this tile in the movement_range dictionary, If so does that space have a higher movement weight? If the movement is larger cancel check. (No need to check the tile as its neighbors have already been checked.)
- Check the MissionMap Unit Database to see if a unit is on that tile, If there is a unit, If it is from a different team, cancel the check.
- Check the weight of the tile being checked, If āblockedā (Solid Wall) cancel check. (Data from Figure 1 screenshot)
- If the remaining weight is less than -1. Cancel check.
- Finally, This is a valid tile! Add this to the movement_range dictionary.
- Then schedule the tile at the North South East and West to be checked. Eventually every possible tile will be correct,
func query_move(coordinates,movement):
#Cancel if no movement remains
if movement < 1:
return
#Cancel if tile is Out of Bounds
if map.is_vector_OOB(coordinates):
return
#Check if an existing record has a higher movement record.
if movement_range.has(coordinates):
if movement_range[coordinates] >= movement:
return
#Check if an enemy unit is on the space cancel if so.
var occupying_unit = map.get_unit_by_vector(coordinates)
if occupying_unit != null:
if occupying_unit.team != unit.team:
return
var weight = 1
#Assign movement costs for special tile types.
match map.get_cell_tile_data(coordinates).get_custom_data("TileProperty"):
"Blocked":
return
"Water":
weight = 4
"Mountain":
weight = 2
#Cancel if not enough movement remains
if movement - weight < -1:
return
movement_range[coordinates] = movement
schedule_next(coordinates,movement - weight)
func schedule_next(coordinates,movement):
if coordinates != initial_space:
check_neighbours(coordinates,movement)
func check_neighbours(coordinates,movement):
query_move(coordinates + Vector2i.UP , movement) ##ABOVE TILE
query_move(coordinates + Vector2i.RIGHT , movement) ##RIGHT TILE
query_move(coordinates + Vector2i.DOWN , movement) ##BELOW TILE
query_move(coordinates + Vector2i.LEFT , movement) ##LEFT TILE
Now that we have the players squares that can move too, we can query the movement_range database for the attack range. For each movement square, we will do the same the same thing as the movement layer and check checking the weight of each attack tile. However we do not need the weight of terrain or if there is a unit blocking the attack.
func calculate_attack_range():
for coordinates in movement_range:
query_attack(coordinates,unit.properties.Attack_Range)
How query_attack() works.
- If it is out of range, Cancel check.
- If it is out of bounds, Cancel check.
- If attack_range already has a record, If it is larger than the current range. Cancel Check.
- Finally this is a valid tile add to the attack_range dictionary.
- Continue to check neighbours.
func query_attack(coordinates,range):
if range < 0:
return
if map.is_vector_OOB(coordinates):
return
if attack_range.has(coordinates):
if attack_range[coordinates] > range:
return
attack_range[coordinates] = range
query_attack(coordinates + Vector2i.UP , range - 1)
query_attack(coordinates + Vector2i.RIGHT , range - 1)
query_attack(coordinates + Vector2i.DOWN , range - 1)
query_attack(coordinates + Vector2i.LEFT , range - 1)
In the end you should have two ranges with the following distances. For each tile in the dictionary I paint the tiles on a separate TileMapLayer. See Figure 2 for what the weight distribution would be for a unit that has 3 move and 2 attack range.

The next steps for this demo will be to query the attack range and deal damage to the enemy. Looking forward to play around with this demo again.
If you would like to support my games,, Please check out my site with games on the the Apple App Store or the Google Play Store!

Thanks for your time!
Nathaniel