Overview
The Skill Set Forge generates skill set databases — loadout templates that
group skills into typed slots with threshold-based set bonuses
that activate when enough skills from the set are equipped. Think item set bonuses from Diablo
or gear sets from MMOs, but applied to skills.
Generated databases implement ISimpleSkillSetDataSource and produce the standard 4-file
output with the suffix Set (e.g., ARPGSetType.cs,
ARPGSetDatabase.cs).
Open the Skill Set Forge wizard from the Unity menu:
Window > Living Failure > Simple Skill Forge > Skill Set Forge
Key features:
- Typed slots with required/optional designation
- Skill code references from linked Skill Forge databases
- Threshold-based set bonuses (e.g., "Equip 3/5 for bonus")
- SAF modifier assets on bonuses
- Single-level dynamic properties on each set
- Runtime manager with equip/unequip, bonus tracking, and snapshots
- Character class restrictions via SAF Character Templates
The 5-Step Wizard
The Skill Set Forge follows the same 5-step wizard pattern as all forges.
Step 1: Setup
Configure the database name, namespace, class prefix, and template. Link Skill Forge database
assets for searchable skill code dropdowns in Step 3. Link SAF Character Template assets for
class restriction support. Supports multiple linked databases with duplicate detection.
Step 2: Definitions
Define single-level dynamic properties for your sets (e.g., "Set Type",
"Max Slots", "Is Starter Set"). JSON schema export/import is available for AI-assisted
content generation with context-aware usage hints.
Step 3: Builder
Split-panel editor (280px left panel). The right panel shows foldout sections for:
- Identity — code, display name, description, icon
- Categories / Flags / Numerics / Texts — single-level properties
- Slots — ReorderableList with 3 fields per slot (slotType, skillCode, isRequired)
- Bonuses — ReorderableList with threshold count, description, and SAF modifier assets
- Character Class Restrictions — allowed character classes
Step 4: Settings
Output paths, custom paths toggle, generation toggles, and file preview.
Step 5: Generate
Two-phase generation producing {Prefix}SetType.cs,
{Prefix}SetDatabase.cs, {Prefix}SetDatabaseEditor.cs, and the
.asset file with all slot, bonus, and modifier data baked in.
Slots
Each set contains an array of SimpleSkillSetSlot structs. A slot defines a position
in the loadout with a type label, an assigned skill code, and a required flag.
| Field |
Type |
Description |
slotType |
string |
Slot type label (e.g., "Active1", "Active2", "Passive1", "Ultimate"). Used for
categorizing and filtering slots. Free-form string — your game defines the
slot type conventions. |
skillCode |
string |
Skill code referencing a Skill Forge database entry. In the wizard, this is
presented as a searchable dropdown when skill databases are linked. |
isRequired |
bool |
Whether this slot must be filled for the set to be considered valid. Optional
slots can be left empty at runtime. |
Querying Slots
// Get all slots for a set
SimpleSkillSetSlot[] slots = database.GetSlotsForSet("WARRIOR_KIT");
// Convenience accessors on the set struct
int totalSlots = set.SlotCount;
int requiredSlots = set.RequiredSlotCount;
// Get all unique slot types
string[] types = set.GetSlotTypes(); // e.g., ["Active", "Passive", "Ultimate"]
// Get slots of a specific type
SimpleSkillSetSlot[] activeSlots = set.GetSlotsByType("Active");
// Check if the set contains a specific skill
bool hasSlash = set.ContainsSkill("SLASH");
// Get all skill codes in the set (non-empty)
string[] skillCodes = set.GetSkillCodes();
// Find all sets containing a skill
SimpleSkillSetDefinition[] setsWithSlash = database.FindSetsContainingSkill("SLASH");
Bonuses
Each set can have multiple SimpleSkillSetBonus entries that activate when enough
skills from the set are equipped. This follows the same threshold pattern as item set bonuses.
| Field |
Type |
Description |
requiredCount |
int |
Number of equipped skills from this set required to activate the bonus |
bonusDescription |
string |
Human-readable description of the bonus effect (e.g., "+20% fire damage") |
modifiers[] |
ScriptableObject[] |
SAF modifier assets applied when this bonus is active. Requires Simple Attribute
Forge to be installed for the modifier assets — the field still exists without SAF,
but modifiers will be empty. |
Querying Bonuses
// Get all bonuses for a set
SimpleSkillSetBonus[] bonuses = database.GetBonusesForSet("FIRE_MAGE_KIT");
// Convenience accessors on the set struct
int bonusCount = set.BonusCount;
// Get the highest-tier bonus that is active for a given equipped count
SimpleSkillSetBonus? best = set.GetBonusForCount(equippedCount: 3);
// Get ALL bonuses active at a given count (stacking)
SimpleSkillSetBonus[] active = set.GetActiveBonuses(equippedCount: 3);
Example: Tiered Bonuses
A set with 5 slots might define bonuses like this:
| Required Count |
Bonus Description |
| 2 |
+10% fire damage |
| 3 |
+20% fire damage, fire skills cost 15% less mana |
| 5 |
+50% fire damage, all fire skills gain area of effect |
With 3 skills equipped, both the "2-piece" and "3-piece" bonuses are active.
GetActiveBonuses(3) returns both.
Single-Level Properties
Skill sets use single-level dynamic properties on the set definition itself.
// Access set-level properties
int setType = set.GetCategoryValue(0);
bool isStarterSet = set.GetFlagValue(0);
float maxSlots = set.GetNumericValue(0);
string flavor = set.GetTextValue(0);
// Get property definition labels
string[] catLabels = database.GetCategoryLabels(); // e.g., ["Set Type", "Class Association"]
string[] catEntries = database.GetCategoryEntries(0); // e.g., ["Class", "Build", "Loadout"]
string[] flagNames = database.GetFlagNames(); // e.g., ["Is Starter Set", "Allows Custom Slots"]
string[] numNames = database.GetNumericNames(); // e.g., ["Max Slots", "Difficulty Rating"]
string[] textNames = database.GetTextNames(); // e.g., ["Set Description", "Flavor Text"]
// Filter sets by property values
SimpleSkillSetDefinition[] classSets = database.GetSetsByCategory(0, 0);
SimpleSkillSetDefinition[] starterSets = database.GetSetsByFlag(0, true);
SimpleSkillSetDefinition[] bigSets = database.GetSetsByNumericRange(0, 4f, 10f);
Runtime: SimpleSkillBarManager
SimpleSkillBarManager is a runtime manager that handles equipping/unequipping skills
to set slots, tracks threshold-based set bonuses, fires events on changes, and supports
serializable snapshots for save/load.
Setup
// Create a manager from your set database
var manager = new SimpleSkillBarManager(setDatabase);
// Initialize sets for tracking (must be called before equip/query)
manager.InitializeSet("WARRIOR_KIT");
manager.InitializeSet("FIRE_MAGE_KIT");
Equip and Unequip
// Equip a skill to a specific slot index
manager.EquipSkill("WARRIOR_KIT", slotIndex: 0, "SLASH");
manager.EquipSkill("WARRIOR_KIT", slotIndex: 1, "SHIELD_BASH");
manager.EquipSkill("WARRIOR_KIT", slotIndex: 2, "CHARGE");
// Unequip a specific slot
manager.UnequipSkill("WARRIOR_KIT", slotIndex: 1);
// Clear all slots in a set
int cleared = manager.ClearLoadout("WARRIOR_KIT");
Auto-swap: If you equip a skill to a slot that already has a skill,
the previous skill is automatically unequipped first. Both OnSkillUnequipped
and OnSkillEquipped events fire.
Queries
// Get what is equipped in a specific slot
string skill = manager.GetEquippedSkill("WARRIOR_KIT", slotIndex: 0); // "SLASH"
// Get the number of filled slots
int equipped = manager.GetEquippedCount("WARRIOR_KIT");
// Check if all required slots are filled
bool valid = manager.AreRequiredSlotsFilled("WARRIOR_KIT");
// Get currently active bonuses
SimpleSkillSetBonus[] bonuses = manager.GetActiveBonuses("WARRIOR_KIT");
// Check if a specific bonus (by index) is active
bool has3Piece = manager.IsBonusActive("WARRIOR_KIT", bonusIndex: 1);
// Check if a skill is equipped in ANY initialized set
bool equipped = manager.IsSkillEquipped("SLASH");
// Get all equipped skill codes across all sets
string[] allEquipped = manager.GetAllEquippedSkillCodes();
// Find which set and slot a skill is in
string setCode;
int slotIndex;
if (manager.FindEquippedSkill("SLASH", out setCode, out slotIndex))
Debug.Log($"SLASH is in {setCode}, slot {slotIndex}");
Events
manager.OnSkillEquipped += (setCode, slotIndex, skillCode) =>
Debug.Log($"Equipped {skillCode} to {setCode} slot {slotIndex}");
manager.OnSkillUnequipped += (setCode, slotIndex, previousSkillCode) =>
Debug.Log($"Unequipped {previousSkillCode} from {setCode} slot {slotIndex}");
manager.OnSetBonusActivated += (setCode, bonusIndex) =>
Debug.Log($"Bonus {bonusIndex} activated in {setCode}");
manager.OnSetBonusDeactivated += (setCode, bonusIndex) =>
Debug.Log($"Bonus {bonusIndex} deactivated in {setCode}");
manager.OnLoadoutChanged += (setCode) =>
Debug.Log($"Loadout changed in {setCode}");
Snapshots (Save/Load)
// Save current state
SimpleSkillBarSnapshot snapshot = manager.CreateSnapshot();
string json = JsonUtility.ToJson(snapshot);
// Load state
var loaded = JsonUtility.FromJson<SimpleSkillBarSnapshot>(json);
manager.RestoreFromSnapshot(loaded);