7thSage again. This time we’ll be working on making our units a bit more modular. We’ll be stripping out several pieces from our character prefabs and building them up from building blocks. This will allow us to use the same base components, while building more individualized characters. While we’re at it, we’ll also be adding alliances, so we can have actual enemies on the field.
Hero Prefab
Lets start by opening the “Hero.tscn” prefab scene we created earlier.
Before we get into stripping down the Hero Prefab to its base, we need to save out the work we did creating it, primarily the spells we created in the last lesson. Our skills are split into two sections, the Attack skill, and an ability catalog with separate categories and skills within each category. We’re going to be saving out each attack individually into its own file, and organizing them into folders matching their categories, with the Attack skill placed in a “Common” folder, because it does not have a category of its own.
Let’s start by creating a folder inside the “Data” folder, named “Abilities” and inside that create a folder name “Common” and one for each of the categories within the Ability Catalog, “White Magic”, “Sagacity Skill” and “Bonus Skills”. If you have named your categories differently, feel free to use the category names you have chosen.

Now that we have the folders to organize our skills, lets begin to export them. We’ll start with the Attack skill. Right click on the node “Attack” and choose “Save Branch as Scene”. In the popup navigate to the folder “Data->Abilities->Common” and save the file as “Attack.tscn”


Next we’ll do the same thing with all the other skills, saving each in their respective folders.
- In the folder “Data->Abilities->White Magic” save the skills “Cure”, “Holy”, and “Raise” as a “.tscn” file.
- In the folder “Data->Abilities->Sagacity Skill” save the skills “Blind”, “Drain”, and “Water” as a “.tscn” file.
- And last, in the folder “Data->Abilities->Bonus Skills” save the skills “EarthQuake” and “Shove” as a “.tscn” file.

When the dialog box shows up, it will give you a name suggestion to save the skills as. You can use that, or name it exactly as the skill is called with .tscn at the end. Whichever name is used though, we’ll have to match it later on when we begin to create the recipes to build our characters.
You may need to update nodes in the inspector for abilities such as “Drain”. The “Absorb” node watches for “Damage”, and when saving out to the new tree, the connection can get lost. So go through the nodes to double check that everything is still correct.

Once all the skills have been saved, we can start to remove most of the nodes on the prefab. We don’t need to worry about exporting the other nodes as we’ll be recreating them with code. So, aside from the Jumper node and its children, we can delete everything else. It should look like the following image when we are done.

Do the same thing with the Monster.tscn prefab scene. Be sure to save each when you are done. Once we are done saving the units, we’ll move them so they are in a central place along with the other pieces to build the units. Create a new folder inside the folder “Data” named “Units” and move both Hero.tscn and Monster.tscn into it.
Battle Controller
Now that we have moved the Hero.tscn prefab, and we will also be assigning it from the recipes we create later, its time to remove it from our Battle Controller. Open up BattleController.gd and delete the line:
var heroPrefab = preload("res://Prefabs/Hero.tscn")
Recipes
Now that we’ve taken our characters apart, its time to start building the factory to put them back together again. We’ll start with several “recipes” that will guide what goes into the various parts.
Let’s start by creating a new folder under “Scripts” named “Factory”. And inside that folder we’ll create our first script or recipe, named “UnitRecipe.gd”
extends Resource class_name UnitRecipe @export var model:String @export var job:String @export var attack:String @export var abilityCatalog:String @export var locomotion:MoveType.Locomotions @export var alliance:AllianceType.Alliances
The first 4 variables are stings that will be names of resources that we will load. The reason that a string was went with here is so that we could load the values from some external source such as a spreadsheet, where we could store a huge list of characters all in one easy to manage place. This is probably also a good time to remind people to be careful of resource files.
Instead of nesting the next class into one, I’ll be splitting each one into their own separate file. In the same folder “Scripts->Factory”, we’ll create a script named “Category.gd”. This will hold a name for the category, which should match the folder name he have that set of skills in, and an array of entries, which will hold the name of each skill.
extends Resource class_name Category @export var name:String @export var entries:Array[String]
Our next recipe is the “AbilityCatalogRecipe.gd”. Create it in the same folder “Scripts->Factory”. Without Category nested in this script it comes out very simple. Just an array of Categories that we’ll have on our unit.
extends Resource class_name AbilityCatalogRecipe @export var categories:Array[Category]
In our first recipe, we had two more variables that we don’t have types for yet, Locomotion and Alliances, so lets create those.
First up, Locomotions. Create a new script in the folder “Scripts->Enums Extensions” named “MoveType.gd” and inside this we’ll create our named enum to hold each movement type.
class_name MoveType
enum Locomotions
{
Walk,
Fly,
Teleport
}
Alliances are another enum. In the same folder create another script named “AllianceType.gd”. We’ll have four possible alliances, including one for no alliance. These are each stored with bitmasks, which we can use later on to easily check for different alliances, or even multiple alliances. Such as an attack that targets both Heroes and Neutral units.
class_name AllianceType
enum Alliances
{
None = 0,
Neutral = 1 << 0,
Hero = 1 << 1,
Enemy = 1 << 2
}
The next thing we’ll need is a way to hold the alliance on the character itself, so we’ll create a script that we’ll attach to the unit via a node.
In the folder “Scripts->View model Component->Actor” create a new script named “Alliance.gd”. I know the names are a bit similar, but remember that a unit has a singular alliance, out of possible alliances.
extends Node class_name Alliance var type:AllianceType.Alliances
Creating Recipes
In Unity, we needed a script to add our recipes to the menu, but in Godot, once we have them created and they have a class_name, we can create assets of the type, so we don’t need to create any extra scripts for this step.
For our first recipe, lets start by creating a folder in “Data” named “Ability Catalog Recipes”. Once the folder is created, right click on it and “Create New->Resource”. Find “AbilityCatalogRecipe” in the list and click create. Give it a name, “DemoCatalog.tres”


Open the file and in the inspector set the array size to 3. For each array element, click on where it says “” and select “New Category”.
Name will correspond to the skill types such as “White Magic”, “Sagacity Skill”, and “Bonus Skills”. While the entries will correspond to the skills within those categories. Create entries for each of the skills we saved out into files. Be sure that the values match what each was saved as(minus the extension). Once you are done, save the scene.
| Name | White Magic | Sagacity Skill | Bonus Skills |
|---|---|---|---|
| Entries | Cure | Water | Earthquake |
| Raise | Blind | Shove | |
| Holy | Drain |

If you’d like to create more specific recipes for each class, now that we have a complete catalog, we can simply duplicate the file, and delete the sections you don’t need.
Next up are the recipes for each unit we want to create. We’ll take this moment to also add some enemies to use.
Create a new folder in “Data” named “Unit Recipes”. Just like before right click and “Create New->Resource” This time we’ll look for the type “UnitRecipe”. Click Create and rename the file. We’ll create 6 units in total, “Alaois.tres”, “Hania.tres”, “Kamau.tres”, “Enemy Warrior.tres”, “Enemy Wizard.tres”, and “Enemy Rogue.tres”. Open the files and in the inspector set the various values based on the table below.

| Alaois.tres | Hania.tres | Kamau.tres | Enemy Warrior.tres | Enemy Wizard.tres | Enemy Rogue.tres | |
|---|---|---|---|---|---|---|
| Model | Hero | Hero | Hero | Monster | Monster | Monster |
| Job | Warrior | Wizard | Rogue | Warrior | Wizard | Rogue |
| Attack | Common/Attack | Common/Attack | Common/Attack | Common/Attack | Common/Attack | Common/Attack |
| Ability Catalog | DemoCatalog | DemoCatalog | DemoCatalog | DemoCatalog | DemoCatalog | DemoCatalog |
| Locomotion | Walk | Walk | Walk | Walk | Teleport | Fly |
| Alliance | Hero | Hero | Hero | Enemy | Enemy | Enemy |
A note from the original Unity tutorial on the names
- Alaois – means “famous warrior” so I used it for my warrior hero.
- Hania – means “spirit warrior” so I used it for my mage.
- Kamau – means “quiet warrior” so I used it for my rogue.

Movement
Before we get to the unit factory, lets make a quick change in the script “Movement.gd” from the folder “View Model Component->Movement”
We have a few variables that are set up in the _init() function, but when we get into building the unit factory, we’ll be attaching the movement after the _init() or even the _ready() function call. We could move those into a different function to call manually, but I think we should give them a “get:” like range and jumpHeight. At the top with the other variables, replace the original variables “unit”, “jumper” and “stats” with these implementations and remove them from the _init() function.
var unit: Unit:
get:
return get_node("../")
var jumper: Node3D:
get:
return get_node("../Jumper")
var stats:Stats:
get:
return get_node("../Stats")
Unit Factory
Now that we have all the prep work, lets create the actual factory script. In the folder “Scripts->Factory” create a new script named “UnitFactory.gd”
We’ll be using “Static” for the functions of this class. They don’t need to hold onto any variables, and it will allow us to call the functions without creating an instance of the class itself. We’ll start with a simple script to load the files. We’ll just be using this for the objects themselves, and not the recipes. With this we’ll assume we are loading a scene file with the extension “.tscn” and will instantiate it before returning it.
extends Node
class_name UnitFactory
static func InstantiatePrefab(path:String, name:String):
var prefab:PackedScene = load(path + name + ".tscn")
if prefab == null:
print("No Prefab for name: " + name)
return null
return prefab.instantiate()
The Create() function is pretty simple, we load the recipe file, and as long as it isn’t null, we call CreateRecipe where the bulk of the work gets done.
static func Create(name:String, level:int, _owner:BattleController):
var recipe:UnitRecipe = load("res://Data/Unit Recipes/" + name + ".tres")
recipe.resource_name = name
if recipe == null:
print("No Unit recipe for name: " + name)
return null
return CreateRecipe(recipe, level, _owner)
CreateRecipe() acts as the central hub of the unit factory. It starts off loading the main unit scene and attaches it to the battle controller. Once that is done, it adds all the components that each unit will need. These have all been split up to keep things clean, and allows us to see at a glance what is being added and gives us the ability to change the order if need be.
If in the future we decide to add or remove additional components, adding them here instead of directly in the unit’s prefab scene allows us to quickly make changes if they are needed. With one or two models its not much of a problem, but if we were to have dozens of different models, it would be much more time consuming making small changes without a system like this in place.
static func CreateRecipe(recipe:UnitRecipe, level:int, _owner:BattleController):
var obj:Unit = InstantiatePrefab("res://Data/Units/" , recipe.model)
obj.name = recipe.resource_name
_owner.add_child(obj)
AddStats(obj)
AddLocomotion(obj, recipe.locomotion)
AddStatus(obj)
AddEquipment(obj)
AddJob(obj, recipe.job)
AddRank(obj, level)
AddHealth(obj)
AddMana(obj)
AddAttack(obj, recipe.attack)
AddAbilityCatalog(obj, recipe.abilityCatalog)
AddAlliance(obj, recipe.alliance)
return obj
Most of the functions are pretty simple, create a new object of the correct type or load one, set a parameter or two, give it a name and attach it to the unit. The original Unity version had a couple of these that weren’t broken off, but because we’re taking a couple extra lines compared to Unity, to add the node as a child and giving the node a name, I split them off along with the others to keep CreateRecipe() function as clean as possible.
static func AddStats(obj:Node):
var s:Stats = Stats.new()
obj.add_child(s)
s.SetStat(StatTypes.Stat.LVL, 1)
s.name = "Stats"
static func AddStatus(obj:Node):
var status:Status = Status.new()
status.name = "Status"
obj.add_child(status)
static func AddEquipment(obj:Node):
var equipment:Equipment = Equipment.new()
equipment.name = "Equipment"
obj.add_child(equipment)
static func AddHealth(obj:Node):
var health:Health = Health.new()
health.name = "Health"
obj.add_child(health)
static func AddMana(obj:Node):
var mana:Mana = Mana.new()
mana.name = "Mana"
obj.add_child(mana)
static func AddJob(obj:Node, name:String):
var job:Job = InstantiatePrefab("res://Data/Jobs/" , name)
obj.add_child(job)
job.Employ()
job.LoadDefaultStats()
static func AddAlliance(obj:Node, type:AllianceType.Alliances):
var alliance:Alliance = Alliance.new()
alliance.name = "Alliance"
alliance.type = type
obj.add_child(alliance)
static func AddRank(obj:Node, level:int):
var rank = Rank.new()
rank.name = "Rank"
obj.add_child(rank)
rank.Init(level)
static func AddLocomotion(obj:Node, type:MoveType.Locomotions):
var move:Movement
match type:
MoveType.Locomotions.Walk:
move = WalkMovement.new()
MoveType.Locomotions.Fly:
move = FlyMovement.new()
MoveType.Locomotions.Teleport:
move = TeleportMovement.new()
obj.add_child(move)
move.name = "Movement"
Attack works in a similar way, but we’re also creating the node “Abilities” that we’ll also be attaching the rest of the ability catalog to. Because of this, we’ll need to make sure we call AddAttack() before AddAbilityCatalog()
static func AddAttack(obj:Node, name:String):
var abilities = Node.new()
abilities.name = "Abilities"
obj.add_child(abilities)
var attack = InstantiatePrefab("res://Data/Abilities/" , name)
abilities.add_child(attack)
AddAbilityCatalog() is a bit more complicated. We start by grabbing the “Abilities” node we created in “AddAttack()”. We create an AbilityCatalog object and attach it to the Abilities node. Once we have our base nodes set, we load our recipe.
We then create a node for each Category in our recipe, and for each Category, we loop through and add each entry from the recipe.
static func AddAbilityCatalog(obj:Node, name:String):
var abilities = obj.get_node("Abilities")
var abilityCatalog:AbilityCatalog = AbilityCatalog.new()
abilityCatalog.name = "Ability Catalog"
abilities.add_child(abilityCatalog)
var recipe:AbilityCatalogRecipe = load("res://Data/Ability Catalog Recipes/" + name + ".tres")
if recipe == null:
print("No Ability Catalog Recipe Found: " + name)
return
for i in recipe.categories.size():
var category:Node = Node.new()
category.name = recipe.categories[i].name
abilityCatalog.add_child(category)
for j in recipe.categories[i].entries.size():
var path = "res://Data/Abilities/" + recipe.categories[i].name + "/"
var ability:Ability = InstantiatePrefab(path, recipe.categories[i].entries[j])
ability.name = recipe.categories[i].entries[j]
category.add_child(ability)
Init Battle State
Before we can test our factory, we need to do one last thing. In “InitBattleState.gd” we’ll be replacing the function SpawnTestUnits() with this implementation.
We start off creating an Array of the unit recipes we’ll be creating. These names will need to match the recipes we created. After that we get a list of all the tiles on the board. We’ll use those to select some random starting points.
For each unit, we pick a random level and tell the UnitFactory to create our character. We set it at a random position, and remove the tile from the list of choices so we don’t try setting another character on it. Once we are done looping through the characters, we set the selection indicator to the first unit.
func SpawnTestUnits(): var recipes:Array[String] = [ "Alaois", "Hania", "Kamau", "Enemy Rogue", "Enemy Warrior", "Enemy Wizard" ] var locations:Array[Tile] locations.assign(board.tiles.values()) for recipe in recipes: var level = randi_range(9,11) var unit:Unit = UnitFactory.Create(recipe, level, _owner) var random = randi_range(0, locations.size() - 1) var randomTile:Tile = locations[random] locations.remove_at(random) unit.Place( randomTile) unit.dir = randi_range(0,3) unit.Match() units.append(unit) SelectTile(units[0].tile.pos)
There is still some temp code here, but its starting to look more complete. For instance we still randomly place units on the board, and we have which units to use defined in a list. In a fully fledged implementation, we’d want to have units start at more specific spawn points, and we’d want to define which monsters spawn from the level file, while the heroes we’d bring in from the save data.
Demo
With all that done, we should be able to test our map. Aside from having a couple extra units and finally having both “Hero” and “Monster” units on the board, everything should once again work like it did in the previous lesson.

Summary
With these additions, it should now be much easier to add new characters and begin swapping out the character models we use on the board. I don’t have any new models to add, but with the recipes, all we need is a minimal setup of our prefab, and the factory will take care of the rest.
If you have any questions, feel free to comment below, check out the Github repository to compare your code, or find me on Discord to ask more detailed questions.