Generate Complete Enemy Databases, Squads, Spawn Tables, and More in Minutes.
Prerequisites, importing from the Asset Store, folder structure, and menu items.
Create your first enemy database in under 5 minutes with a step-by-step walkthrough.
The core wizard — define dynamic properties, build enemies, generate databases.
Group enemies into squads with slot assignments, count ranges, and level overrides.
Build weighted spawn tables with conditional pools and context-driven entry selection.
Define how enemy stats change with level or difficulty using 6 scaling types.
Design multi-wave sequences with enemy/squad spawning, timing, and loop settings.
Create priority-based condition-action rules for enemy AI behavior profiles.
Define factions with an N×N relationship matrix and 5 stance levels.
Export schemas for AI-powered content generation. Import hundreds of entries in one click.
6 genre templates (RPG, Soulslike, Survival, Tower Defense, Sci-Fi, RTS) with pre-configured properties.
Optional integration with Simple Attribute Forge for modifier references on enemies.
Optional integration with Simple Item Forge for loot table linking on enemies.
Interactive 7-tab Bestiary Demo showcasing all forge outputs at runtime.
Complete reference for all runtime types, interfaces, and helper classes.
Solutions for common issues with generation, schema import, wizards, and integrations.
Design philosophy, version history, and support information.
Get Simple Enemy Forge set up in your Unity project.
Window → Package Manager
Window → Simple Enemy Forge
After installation, these menu items are available under Window → Simple Enemy Forge:
| Menu Item | Priority | Purpose |
|---|---|---|
Enemy Forge Wizard |
100 | Create enemy databases with dynamic properties (Categories, Flags, Numerics, Texts) |
Squad Forge Wizard |
101 | Create squad databases that group enemies into formations with slot assignments |
Spawn Table Forge Wizard |
102 | Create spawn table databases with weighted pools and conditional entries |
Scaling Forge Wizard |
103 | Create scaling profiles that define how enemy stats change with level or difficulty |
Faction Forge Wizard |
104 | Create faction databases with relationship matrices (Allied, Friendly, Neutral, Hostile, etc.) |
Behavior Forge Wizard |
105 | Create behavior profiles with priority-based condition-action rules |
Wave Forge Wizard |
106 | Create wave sequences with multi-wave enemy/squad spawning and loop settings |
Generate Bestiary Demo |
201 | Generate an interactive 7-tab demo scene that showcases all forge outputs at runtime |
Documentation |
300 | Opens the offline documentation in your default browser |
If Simple Attribute Forge (sold separately) is installed in the same project, Simple Enemy Forge automatically detects it and unlocks modifier references on enemy definitions. This allows you to attach SAF attribute modifiers directly to enemies — useful for buffs, debuffs, auras, and stat modifications that enemies apply during combat. Detection is fully automatic — just import both packages and the features appear. You can add or remove SAF at any time with zero compilation errors.
See the SAF Integration page for full details on features, the detection lifecycle, and what happens when you install or remove SAF.
If Simple Item Forge (sold separately) is installed in the same project, Simple Enemy Forge automatically detects it and unlocks loot table linking on enemy definitions. This allows you to assign SIF loot tables to enemies so they can drop items on defeat. Detection is fully automatic — just import both packages and the features appear. You can add or remove SIF at any time with zero compilation errors.
See the SIF Integration page for full details on features, the detection lifecycle, and what happens when you install or remove SIF.
You're all set! Head to the Quick Start Guide to create your first enemy database in under 5 minutes.
Create a complete enemy database in under 5 minutes. This walkthrough covers the Enemy Forge wizard — the foundation everything else builds on.
Window → Simple Enemy Forge → Enemy Forge Wizard
The Generic RPG template pre-populates categories (Enemy Type, Rank, Element), flags (Is Boss, Is Flying, Is Undead), numeric properties (HP, ATK, DEF, Speed, Aggro Range, XP Reward), and text fields (Lore, Weakness Hints) — covering everything you need for a dark fantasy bestiary.
After generation, your project will contain:
The generated database ScriptableObject provides methods to access your enemies:
Property indices match the order you defined them in Step 2. For example, if “HP”
is the first numeric property you defined, it will be at numericValues[0].
Your database asset gets a professional custom Inspector with:
No more "Element 0", "Element 1" in the Inspector. Every property shows its real name.
Export a schema, paste it into ChatGPT or Claude, and import hundreds of enemies in one click.
AI Workflow →Create weighted spawn pools with conditions and context-sensitive logic.
Spawn Forge →Define how enemy stats scale with level, difficulty, or any custom parameter.
Scaling Forge →The Enemy Forge is the foundation wizard of the entire Simple Enemy Forge ecosystem. Every enemy you create here becomes available to Squad Forge (as squad members), Spawn Forge (as spawn entries), Scaling Forge (as scaling targets), Wave Forge (as wave entries), Behavior Forge (as behavior subjects), and Faction Forge (as faction members). The Enemy Forge is always your starting point — define your enemies once, use them everywhere.
Open the wizard via Window › Simple Enemy Forge › Enemy Forge Wizard. It
walks you through a 5-step process to define your enemy properties, create enemies,
configure output paths, and generate a complete database with type-safe enums and a custom Inspector
editor.
The wizard produces a ScriptableObject-based enemy database that is fully data-driven. Nothing is hardcoded — there is no built-in "HP" stat, no predefined "Rank" dropdown, no assumed "Aggro Range" field. You define every property your enemies need using four flexible types, and the system works for any genre: RPG, soulslike, survival horror, tower defense, sci-fi, RTS, or anything else you can imagine.
At the heart of the Enemy Forge is a dynamic property system that lets you define the exact shape of your enemy data. Rather than working around a rigid, genre-specific schema, you build a property system that matches your game perfectly. A fantasy RPG might need "Enemy Type" and "Base HP"; a soulslike might need "Poise" and "Stagger Threshold"; a tower defense might need "Speed" and "Armor Type". The Enemy Forge handles all of these through four property types.
| Property Type | What It Is | Stored As | Example |
|---|---|---|---|
| Category | A dropdown menu. Each category has a label and a list of entries the user picks from. Can be single-select (pick one value) or multi-select (pick multiple values via checkboxes). | int[] / SimpleIntArray[] |
"Enemy Type" with entries [Humanoid, Undead, Beast, Dragon], "Rank" with entries [Minion, Elite, Boss] |
| Flag | A boolean on/off toggle with a configurable default value. Flags are perfect for binary properties that either apply to an enemy or do not. | bool[] |
"Is Boss" (default: false), "Is Flying" (default: false), "Has Shield" (default: false) |
| Numeric | A number value with min/max constraints. Can be a single value or a range (stores both a min and max value). Can be integer or float. | float[] |
"HP" (integer, 1–99999, default 100), "Aggro Range" (float, 0.0–50.0, default 10.0), "XP Reward" (integer, 0–9999) |
| Text | A free-form string field with a lineCount hint (1–6) that controls UI
height. lineCount=1 renders as a single-line field;
lineCount=2+ renders as a multiline text area. |
string[] |
"Lore" (lineCount: 3), "Weakness Hints" (lineCount: 2), "Battle Cry" (lineCount: 1) |
Internally, each enemy stores parallel arrays whose indices map to the definitions
you create in Step 2. For example, if you define three categories — "Enemy Type" at index 0,
"Rank" at index 1, and "Element" at index 2 — then every enemy has a
categoryValues[] array of length 3, where categoryValues[0] holds the
Enemy Type selection, categoryValues[1] holds the Rank selection, and so on. The same
pattern applies to flags, numerics, and texts.
CategoryLabels, FlagNames, NumericNames,
TextNames, and entry arrays for each category) so that property indices always resolve
to human-readable names. You never need to remember that "index 2" means "Element" — the
database tells you.
This architecture means you can add, remove, or reorder property definitions at any time during
development. When you add a new category, the SyncAllEnemiesWithDefinitions() method
automatically extends every enemy's arrays to match. When you remove a property, the arrays shrink.
The wizard keeps your data in sync so you never end up with mismatched array lengths.
Step 1 of the wizard offers a template dropdown that pre-populates your property definitions with genre-appropriate categories, flags, numerics, and texts. Templates are a starting point — you can freely modify, add, or remove any property after applying a template. They are designed to save you the work of researching what properties a typical game in that genre needs.
Each template also includes example enemies that demonstrate realistic property values. These examples serve as reference data for AI content generation — when you export your schema, AI models can see how properties are used in practice and generate consistent, well-structured enemies.
| Template | Categories | Flags | Numerics | Texts | Examples | Highlights |
|---|---|---|---|---|---|---|
| None (Start Fresh) | 0 | 0 | 0 | 0 | 0 | Blank canvas — define everything from scratch |
| Generic RPG | 4 | 6 | 10 | 2 | 3 | Enemy Type, Rank, Element, base stats (HP, ATK, DEF, SPD) |
| Soulslike | 4 | 5 | 15 | 3 | 3 | Poise, stagger threshold, weakness system, attack patterns |
| Survival / Horror | 5 | 8 | 12 | 2 | 3 | Detection range, stealth modifier, fear factor, mutation tier |
| Tower Defense | 4 | 6 | 10 | 1 | 3 | Speed, armor type, wave group, path priority |
| Sci-Fi / Shooter | 5 | 5 | 14 | 2 | 3 | Shield type, tech level, faction allegiance, energy weapon resist |
| RTS / Strategy | 6 | 8 | 16 | 2 | 4 | Unit class, population cost, build time, supply cap |
All templates use generic, non-trademarked terminology and are safe for commercial use. No template references specific game names, trademarked abilities, or copyrighted content.
The Setup step configures the basic identity of your enemy database. Everything you set here determines the names of the generated files, the C# namespace they live in, and which genre template (if any) pre-populates your property definitions. Take a moment to choose meaningful names — they will appear throughout your codebase once generation is complete.
Dungeon generates DungeonType.cs, DungeonDatabase.cs,
Editor/DungeonDatabaseEditor.cs, and DungeonDatabase.asset. The prefix
must be a valid C# identifier if provided.
MyGame and suffix Enemy, the generated names become
MyGameEnemyType.cs, MyGameEnemyDatabase.cs, and
MyGameEnemyDatabase.asset. This is useful when you want generated names to be more
descriptive without making the prefix itself long.
Below the database configuration, you will find the Template Selection section. Choose a genre template from the dropdown to pre-populate your definitions with genre-appropriate properties, or select None (Start Fresh) to define everything from scratch. When a template is selected, the wizard displays a description of the template, the number of categories, flags, numerics, texts, and example enemies it includes. Templates can be freely modified after selection — they are a starting point, not a constraint.
If the Simple Attribute Forge package is installed in your project, an additional section appears in Step 1 titled "Simple Attribute Forge Integration". When SAF is detected, enemies gain the ability to reference modifier assets — ScriptableObject references to modifier assets created with Simple Attribute Forge's Modifier Wizard. These modifiers represent buffs, debuffs, or status effects that the enemy can apply to the player (e.g., a "Poison" modifier on a snake enemy, a "Burning" modifier on a fire elemental).
If the Simple Item Forge package is installed in your project, an additional section
appears that enables loot table linking. Each enemy can be assigned a
lootTableCode that references a loot table from SIF's Loot Forge. This creates a direct
connection between enemies and their drop tables — when an enemy is defeated, your game logic
can look up the enemy's loot table code and roll against it to determine item drops.
The bottom of Step 1 shows a Generated Names Preview that reflects your current
configuration. Based on your class prefix (and suffix, if enabled), it displays three generated
names: the Enum ({GP}Type), the Database ({GP}Database), and the Asset
({GP}Database.asset). The custom editor name is not shown here. This lets you verify
your naming before proceeding.
A validation panel at the very bottom checks your configuration in real time. It reports errors (empty database name, invalid C# identifiers) and warnings (empty namespace, existing files that will be overwritten). You cannot proceed to the next step until all errors are resolved.
This is where you design the "shape" of your enemy data. Every property you define here becomes available on every enemy in Step 3. If you selected a genre template in Step 1, the definitions will already be populated — you can modify, add, or remove any of them. If you started fresh, this step begins empty and you build your property system from scratch.
Each section uses a ReorderableList with drag-and-drop reordering and a count field in the header for quick bulk creation. Type a number into the count field and press Enter to instantly create that many entries, which you can then fill in.
Categories are dropdown menus your enemies will use. They are the most structured property type, ideal for classification and filtering. Each category definition has three components:
SimpleIntArray for Unity serialization).For example, an "Element" category with Allow Multiple ON and entries [Fire, Ice, Lightning, Poison] lets an enemy have both "Fire" and "Ice" selected simultaneously, which is perfect for dual-element bosses. Meanwhile, an "Enemy Type" category with Allow Multiple OFF ensures every enemy is classified as exactly one type.
Flags are boolean on/off toggles. They are the simplest property type, perfect for binary states that either apply to an enemy or do not. Each flag definition has two components:
false, but you might want a flag like "Is Hostile" to default to
true.In Step 3, flags appear as a vertical list of toggle checkboxes (one flag per row, full-width ToggleLeft). They are also useful for Behavior Forge conditions — a behavior rule might check "Is Flying" to determine whether to use a ground attack or aerial attack pattern.
Numeric properties store number values with configurable constraints. They are the most versatile property type, handling everything from HP to aggro range to XP rewards. Each numeric definition has these components:
numericValues[] for the min and
numericRangeMaxValues[] for the max.Text properties store free-form string content. They are ideal for lore, weakness hints, battle cries, ability descriptions, or any content that does not fit neatly into a category, flag, or number. Each text definition has two components:
1 renders a compact
single-line TextField. A lineCount of 2 or higher renders a multiline
TextArea whose height scales with the lineCount value. Use 1 for
short labels like "Battle Cry" or "Patrol Zone". Use 3 or higher for longer content
like "Lore" or "Combat Strategy Notes".At the top of Step 2, four buttons provide the Schema Export/Import system for AI-assisted content generation:
See the AI Workflow page for the full schema export/import guide, including the exact JSON format, tips for working with AI models, and the phased generation workflow.
schemaInfo section with version, generation timestamp, template tracking (whether the
template was modified and what changed), and a structure guide explaining each section. The
definitions section contains all four property type arrays with their full metadata
(labels, entries, defaults, constraints).
This is where you create and edit your enemies using the properties defined in Step 2. The interface uses a split-panel layout with an enemy list on the left and the enemy editor on the right. Select an enemy from the list to view and edit its properties. The wizard provides a rich set of tools for searching, filtering, sorting, and bulk-editing enemies.
The left panel contains the master enemy list with several tools above it:
[+] button at the bottom adds a new empty
enemy.[<] Page X of Y [>]). This keeps the list responsive with large databases.Select an enemy from the list to edit it in the right panel. The editor shows all of the enemy's properties organized into collapsible sections:
GOBLIN_WARRIOR,
SHADOW_DRAKE). Codes must be valid C# identifiers and are typically written in
UPPER_SNAKE_CASE. The Auto button next to the code field converts the enemy name
to UPPER_SNAKE_CASE automatically, handling spaces, invalid characters, and leading digits.Shows a header with the count (e.g., "Categories (4)") and a foldout arrow. When expanded, each category is displayed according to its Allow Multiple setting:
The section is always visible regardless of how many categories exist.
Shows all flags as a vertical list of toggle checkboxes — one flag per row, full-width ToggleLeft. Each toggle is labeled with the flag name. The header shows the count (e.g., "Flags (6)"). If no flags are defined, the section shows "No flags defined." as a placeholder. This is the only section in the wizard that displays a placeholder when its definition count is zero.
Shows each numeric property with the appropriate field type. Single values display as an IntField or FloatField (depending on the isInteger setting). Range values display with separate Min and Max fields arranged vertically, with the property name as a label above. All values are clamped to the constraints defined in Step 2. The header shows the count (e.g., "Numeric Properties (12)").
Shows each text property with a UI field sized according to lineCount. Properties with
lineCount=1 display as a single-line TextField. Properties with lineCount=2+
display as a multiline TextArea whose height scales with the lineCount value. Text areas with
content exceeding the fixed height show an inner scrollbar for overflow. The header shows the count
(e.g., "Text Properties (2)").
In the wizard, this section only appears when Simple Attribute Forge is installed (hidden otherwise).
In the generated custom editor, the section is always emitted in the code and auto-adapts at
runtime via Type.GetType() — when SAF is installed, the ObjectField uses the
correct modifier base type; when SAF is removed, it falls back to ScriptableObject.
No regeneration is needed when SAF is installed or uninstalled. It contains a ReorderableList
of modifier references (stored as ScriptableObject[]). These are Unity
asset references that link to modifier assets created with Simple Attribute Forge's Modifier Wizard.
Each entry is an ObjectField where you drag and drop a modifier asset. Example: a "Poison" modifier
that applies damage over time when the enemy attacks, or a "Slow" modifier that reduces player
movement speed. Note that modifiers are Unity object references and cannot be set via JSON import
— they must be assigned manually or via the generated custom editor.
In the wizard, this section only appears when Simple Item Forge is installed (hidden otherwise).
In the generated custom editor, the section is always emitted and auto-adapts at runtime via
Type.GetType(), just like the SAF section — no regeneration needed. It contains
two fields:
At the bottom of the right panel, a Duplicate Enemy button creates a copy of the currently selected enemy with " (Copy)" appended to the name and "_COPY" appended to the code.
The editor validates enemies in real time and shows colored messages directly in the enemy editor:
Configure where generated files are saved and which files to generate. This step gives you full control over your project's file organization.
A single Output Folder field with a Browse button that opens a
folder picker dialog. The path must be within the Assets folder. The default is
Assets/Generated/Enemies. Scripts are automatically placed in a Scripts
subfolder, the custom editor in a nested Scripts/Editor subfolder, and the asset file
in the output folder itself.
A Custom Paths toggle below the output folder reveals separate Scripts Folder and Assets Folder fields when enabled, each with its own Browse button. This lets you place scripts and data assets in different locations if your project structure requires it. When Custom Paths is disabled, the resolved paths are shown as read-only labels beneath the output folder field.
Two toggles control which files are generated. Each toggle shows the exact file name that will be created based on your class prefix:
| Toggle | Generates | Description |
|---|---|---|
| Generate Enum | {GP}Type.cs |
A C# enum with one entry per enemy code. Provides compile-time safety and IntelliSense support for enemy references in your code. Recommended. |
| Generate Database | {GP}Database.cs + .asset |
The main database class implementing ISimpleEnemyDataSource and the populated
data asset. Required for runtime use. Also generates the custom editor
(Editor/{GP}DatabaseEditor.cs). |
DungeonType.cs, DungeonDatabase.cs, DungeonDatabase.asset,
Editor/DungeonDatabaseEditor.cs.
Below the toggles, a Generated File Preview section shows the full list of files that will be created based on your current selections, including their complete paths. Checkmarks indicate which files are enabled. This lets you review exactly what will be written to disk before you proceed to generation.
The final step. Review your complete configuration and click Generate to create your enemy database. The wizard performs a two-phase generation process that handles Unity's domain reload automatically.
A read-only summary panel at the top shows all key metrics at a glance:
A list of every file that will be created, with full paths. This is your final chance to review before committing.
When you click Generate, the wizard writes all C# files to disk:
After writing the files, the wizard saves all wizard data (enemies, definitions, metadata) to a
temporary JSON file at Temp/SimpleEnemyForge_PendingAssetData.json and sets a
SessionState flag. It then triggers AssetDatabase.Refresh() to compile the
new scripts.
Unity recompiles the new scripts, which causes a domain reload. All static
variables are reset, but the SessionState flag and temp file survive. An
[InitializeOnLoad] handler detects the pending flag and temp file, then uses
EditorApplication.delayCall to wait until Unity is fully ready. A retry loop checks
whether the generated database type is loaded, then creates the ScriptableObject asset and populates
it with all enemy data and metadata arrays.
After successful generation, the wizard displays action buttons:
.asset file in the Unity Project windowThe Enemy Forge generates up to 3 script files (depending on your settings in Step 4), plus the data asset. Each file serves a specific purpose in your runtime architecture.
A strongly-typed enum with one entry per enemy code. This provides compile-time safety and full IntelliSense support, so you never need to pass raw strings around your codebase. The enum values are generated from the enemy codes you defined in Step 3.
A ScriptableObject class that implements ISimpleEnemyDataSource. It contains all enemy
data in serialized arrays and metadata arrays for category labels (with entries), flag names, numeric
property names, and text property names. This single asset is the complete runtime representation of
your enemy database.
The generated class includes implementations of all ISimpleEnemyDataSource methods:
enemy access (GetEnemyByCode, GetEnemyByName), dynamic filtering
(GetEnemiesByCategory, GetEnemiesByFlag,
GetEnemiesByNumericRange), and metadata queries (GetCategoryLabels,
GetCategoryEntries, etc.).
The generated database ScriptableObject implements ISimpleEnemyDataSource, which provides
all methods you need for enemy access, filtering, and metadata queries. Assign the database asset to a
serialized field on your MonoBehaviour or ScriptableObject, then call methods directly:
You can also use the ISimpleEnemyDataSource interface for polymorphic access, which is
how the other forges (Squad, Spawn, Wave, etc.) interact with your enemy databases:
A custom editor generated alongside your database that replaces Unity's default Inspector (which
shows unhelpful "Element 0", "Element 1" labels) with a professional split-panel editor. This editor
is placed in an Editor/ subfolder so it is automatically excluded from runtime builds.
It provides a full-featured editing experience directly in the Inspector window.
Enemy list on the left (280px), full editor on the right. Select any enemy from the list to view and edit all of its properties with proper field names and types.
Search by name or code, sort by Name or Code (A-Z / Z-A), and filter by any category value using two dropdown menus. An X button clears all filters at once.
Per-enemy checkboxes, a Select All toggle, and bulk buttons for Delete Selected, Duplicate Selected, and Set Icon (assign the same sprite to all selected enemies at once).
A "Show All" toggle (on by default) controls pagination. When unchecked, enemies are paginated
with a configurable "Per Page" count and page navigation buttons
([<] Page X of Y [>]). This keeps the editor responsive with large databases.
Every property displays its human-readable name from your definitions — not array indices. Categories show dropdown menus (or checkbox grids), flags show labeled toggles, numerics show named fields with constraints, and texts show appropriately-sized text areas.
The Modifiers section and Loot Table section are always emitted in the generated editor code.
They auto-adapt at runtime via Type.GetType(): when SAF or SIF is installed, the
ObjectFields use the correct types; when removed, they fall back to
ScriptableObject. No regeneration is needed when integration packages are installed
or uninstalled.
The ScriptableObject asset file containing all of your enemy data. This is the file you assign to a MonoBehaviour's serialized field or reference from any script that needs enemy data. It is a standard Unity asset that can be version-controlled, duplicated, and referenced from any scene or prefab. It is also the asset you assign to Squad Forge, Spawn Forge, Scaling Forge, Wave Forge, Behavior Forge, and Faction Forge as their linked enemy database.
The Enemy Forge uses a small set of runtime structs and an interface that live in the
SimpleEnemyForge namespace. These types are defined in the package's
Runtime/ folder and are available to all of your game scripts.
The core enemy struct. Each enemy stores fixed identity fields and dynamic property arrays whose indices correspond to the definitions you created in Step 2.
A wrapper struct for serializing arrays of int, since Unity cannot serialize jagged arrays
directly. Used by categoryMultiValues for multi-select categories.
The interface that all generated enemy databases implement. It provides methods for enemy access, metadata queries, and dynamic filtering. See the API Reference page for complete method documentation.
| Method Group | Key Methods |
|---|---|
| Enemy Access | GetEnemyDefinitions(), GetEnemyByCode(string),
GetEnemyByName(string), GetEnemyCodes(),
GetEnemyNames() |
| Metadata | EnemyCount, HasEnemy(string),
GetEnemyEnumType() |
| Property Definitions | GetCategoryLabels(), GetCategoryEntries(int),
GetFlagNames(), GetNumericNames(),
GetTextNames() |
| Dynamic Filtering | GetEnemiesByCategory(int, int), GetEnemiesByFlag(int, bool),
GetEnemiesByNumericRange(int, float, float) |
The interface is also used by the dependent forges for multi-database merging.
Squad Forge, Spawn Forge, Wave Forge, and others accept a List<ScriptableObject>
of enemy databases. Each database is cast to ISimpleEnemyDataSource using the
database is ISimpleEnemyDataSource pattern, and enemy codes are merged across all
databases (first-database-wins for duplicates). This means you can split your enemies across
multiple databases (e.g., one for dungeon enemies, another for world bosses) and link them all
to a single Squad or Spawn setup.
Even if you plan to customize everything, starting with a genre template gives you a solid foundation. It shows how properties are typically structured for your genre and saves time on the initial setup. You can always modify, add, or remove properties afterward.
Define 3–5 enemies manually in Step 3 to establish your patterns, then export your schema (Full JSON or Markdown) and let an AI model generate the remaining enemies. Import the JSON back and review. This workflow can produce dozens or hundreds of well-structured enemies in minutes. See AI Workflow for details.
Use the Auto button next to the Code field to convert enemy names to
UPPER_SNAKE_CASE automatically. This ensures consistent, valid C# identifiers and saves you
from typing codes manually. "Goblin Warrior" becomes GOBLIN_WARRIOR, "Shadow Drake
(Elder)" becomes SHADOW_DRAKE_ELDER.
If you plan to use the other forges, design your enemies with those systems in mind. Squads need groups of enemies that make sense together (a patrol, a boss + minions). Spawn tables need variety across difficulty tiers. Waves need different enemy types for early, mid, and late game. Scaling needs numeric properties with reasonable base values to multiply. Think about the whole pipeline.
Flags like "Is Boss", "Is Flying", "Has Shield", and "Can Summon" are directly useful to Behavior Forge. Behavior rules can check flag values as conditions — for example, a rule that triggers "SUMMON_MINIONS" when "Can Summon" is true and HP is below 50%. Design your flags with behavior conditions in mind.
Enemy codes are your primary identifiers across the entire ecosystem. Make them descriptive
enough to understand at a glance. Prefer GOBLIN_WARRIOR over ENEMY_01.
The Squad, Spawn, Wave, Scaling, Behavior, and Faction forges all reference enemies by code, so
clarity matters.
Use the Set Icon bulk operation to assign icons efficiently. Select all enemies of a type (use the category filter to show only Undead, for example), click Select All, then click Set Icon to assign a placeholder sprite to all of them at once. Replace with final art later in the generated custom editor.
You can regenerate your database at any time. The wizard overwrites existing files, so your generated code always matches your current wizard state. If you add enemies, change definitions, or update properties, just run Step 5 again. The only thing you lose is manual edits to generated files (which you should avoid).
Squad Forge groups enemies into reusable encounter configurations. Instead of spawning individual enemies one at a time, you define squads — named compositions of enemies with counts, level overrides, and custom properties — and reference them from Spawn Forge, Wave Forge, or your own game code.
Every squad links back to one or more Enemy Databases, so enemy codes are always validated against real data. You can attach dynamic properties (Categories, Flags, Numerics, Texts) to squads themselves — independent of the properties on individual enemies.
Open the wizard via Window › Simple Enemy Forge › Squad Forge Wizard.
Squad Forge is a 4-step wizard that walks you through the entire process:
Link one or more Enemy Databases and configure output naming. The wizard reads enemy codes from all linked databases so you can reference any enemy in your squads.
Define custom properties for your squads — Categories, Flags, Numerics, and Texts. Export and import schemas for AI-assisted content generation.
Build squad compositions in a split-panel editor. Add enemy slots with codes, counts, and level overrides. Set dynamic property values per squad.
Generate the database ScriptableObject, type-safe enum, and a custom Inspector editor with search, sort, pagination, and bulk operations.
Squad Forge requires at least one Enemy Database to function. You add databases using a ReorderableList — drag to reorder, click + to add, - to remove. You can link multiple Enemy Databases; the wizard merges all enemy codes together using a first-DB-wins deduplication strategy (if two databases define the same code, the first one in the list takes priority).
The linked databases are used to:
| Field | Description | Example |
|---|---|---|
| Database Name | Display name for the generated asset | My Squad Database |
| Class Prefix | Prefix for generated C# class names | My |
| Append Suffix | Toggle to append a suffix between the prefix and the type name | (toggle) |
| Suffix | Text appended after the prefix when Append Suffix is enabled (e.g., "Squad" produces MySquadDatabase) | Squad |
| Auto-Name from Database | Toggle that auto-fills naming fields (Database Name, Class Prefix, Namespace) from the first linked Enemy Database name | (toggle) |
| Namespace | C# namespace for generated code | MyGame.Data |
Define custom properties that apply to squads (not individual enemies). These are separate from and independent of the properties defined in Enemy Forge.
| Type | Definition Fields | Storage | Example |
|---|---|---|---|
| Categories | Label, entries[], allowMultiple | int[] categoryValues |
Squad Type (Patrol, Ambush, Boss), Difficulty (Easy, Medium, Hard) |
| Flags | Name, defaultValue | bool[] flagValues |
Is Elite, Has Healer, Is Stealth |
| Numerics | Name, isRange, isInteger, min/max | float[] numericValues |
Challenge Rating, Recommended Level, Gold Bonus |
| Texts | Name, lineCount | string[] textValues |
Encounter Description, Tactical Notes |
All four property sections are always visible in the wizard, even when no definitions exist. An empty section shows a "No X defined." placeholder.
Four buttons at the top of the Definitions step let you exchange schemas with AI tools:
See AI Workflow for the complete export/import guide.
The main builder step uses a split-panel layout:
Select a squad from the list to edit its details:
| Field | Description |
|---|---|
| Name | Display name (e.g., "Goblin Patrol") |
| Code | Unique identifier (e.g., "GOBLIN_PATROL"). Codes starting with "NEW_SQUAD" auto-generate from the name as you type. |
| Description | Optional text description with scrollable TextArea |
Each squad contains a ReorderableList of slots. A slot defines which enemy appears and in what quantity:
| Field | Description |
|---|---|
| Enemy Code | Text field with a [...] browse button that opens an Enemy Browser popup. The browser shows all enemies from linked databases with search and filtering. |
| Count Min | Minimum number of this enemy in the squad |
| Count Max | Maximum number (same as min for a fixed count) |
| Level | Optional level override (-1 uses the enemy's default level) |
Below the slots, a Properties section appears when at least one category, flag, or numeric is defined in Step 2. It shows Categories (as dropdowns), Flags (as toggles), and Numerics (as sliders) with values specific to this squad. Text properties are not rendered in the builder step.
To duplicate squads, use the Duplicate button in the bulk operations toolbar in the left panel. Select one or more squads via the checkboxes, then click Duplicate to copy them (including all slots and property values) with modified names.
Generation uses a two-phase process to handle Unity's domain reload:
AssetDatabase.Refresh() to trigger
compilation and domain reload.
[InitializeOnLoad] handler fires via
delayCall, reads squad data from a temporary JSON file, and creates
the ScriptableObject asset.
| File | Description |
|---|---|
{Prefix}SquadType.cs |
Enum with one entry per squad code (e.g., GOBLIN_PATROL, UNDEAD_HORDE) |
{Prefix}SquadDatabase.cs |
ScriptableObject implementing ISimpleSquadDataSource with all squad data and metadata |
Editor/{Prefix}SquadDatabaseEditor.cs |
Custom Inspector with split panel, search, sort, filter, multi-select, pagination, and bulk operations |
{Prefix}SquadDatabase.asset |
The actual data asset containing all squads |
All generated squad databases implement this interface:
| Member | Returns | Description |
|---|---|---|
SquadCount |
int |
Total number of squads in the database |
GetAllSquads() |
SimpleSquadGroup[] |
Get all squad groups |
GetSquadByCode(string code) |
SimpleSquadGroup? |
Get a squad by its code, or null if not found |
GetSquadsContainingEnemy(string enemyCode) |
SimpleSquadGroup[] |
Get all squads that contain a specific enemy |
GetCategoryLabels() |
string[] |
Get category labels |
GetCategoryEntries(int categoryIndex) |
string[] |
Get entries for a specific category |
GetFlagNames() |
string[] |
Get flag names |
GetNumericNames() |
string[] |
Get numeric property names |
GetTextNames() |
string[] |
Get text property names |
Create squads with different enemy mixes for the same encounter type. A "Forest Patrol" might have 3 variants: goblin-heavy, spider-heavy, and mixed. Spawn Forge or Wave Forge can then pick randomly between them.
Set different countMin and countMax values to introduce
natural variation. A squad slot with 2-4 goblins will feel different each time
without needing multiple squad definitions.
Squad slots reference enemies by code string. Use the [...] browse button to pick from your linked Enemy Databases rather than typing codes manually. If you rename an enemy code later, update it in your squads too.
Export your schema to an AI, describe your game's encounter design, and import dozens of squad compositions in seconds. The AI can reference any enemy code from your exported schema. See AI Workflow for details.
Spawn Forge creates spawn tables with weighted pools, conditional activation, and context-aware entry selection. It is the most complex of the seven forges, combining hierarchical data (Table → Pool → Entry) with a powerful condition system that reacts to game state at runtime.
Spawn tables reference enemies from your Enemy Database and optionally squads from your Squad Database. Each entry can be either a single enemy or an entire squad group.
Open the wizard via Window › Simple Enemy Forge › Spawn Table Forge Wizard.
The wizard has 5 steps: Setup, Definitions, Spawn Tables, Settings, Generate.
Spawn tables use a three-level hierarchy. Each level adds its own logic:
SimpleSpawnContext.
If conditions fail, skip the entire pool.
rollChance (0-100%). If the roll fails, skip the pool.
rollCountMin and
rollCountMax.
Conditions check values from the current game context (passed via SimpleSpawnContext)
to decide whether a pool activates or an entry is eligible. The same condition structure
is used at both pool and entry level.
| Field | Type | Description |
|---|---|---|
logic |
SimpleSpawnConditionLogic |
Chaining operator: None (first condition), And, Or, Not |
sourceType |
SimpleSpawnConditionSourceType |
What to check: Category, Flag, or Numeric |
sourceIndex |
int |
Index into the relevant context definition array |
comparison |
SimpleSpawnConditionComparison |
Equals, NotEquals, GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual |
value |
float |
Value to compare against (category index, flag 0/1, or numeric value) |
Multiple conditions are evaluated left-to-right. The first condition always has
logic = None. Subsequent conditions use And, Or,
or Not to combine with the running result:
Weight modifiers dynamically adjust an entry's base weight based on context numeric values. This lets spawn probabilities shift with game state without needing separate condition logic.
| Type | Formula | Example |
|---|---|---|
FlatPerPoint |
finalWeight = baseWeight + (numericValue * modifierValue) |
Base weight 40, modifier +2 per Player Level. At level 5: 40 + (5 * 2) = 50 |
PercentPerPoint |
finalWeight = baseWeight * (1 + numericValue * modifierValue / 100) |
Base weight 40, modifier +10% per Difficulty. At difficulty 3: 40 * (1 + 3 * 0.1) = 52 |
Weight modifiers stack. If an entry has multiple modifiers, they are applied sequentially. The final weight is clamped to a minimum of 0 (negative weights are not allowed).
Spawn Forge accepts two types of linked databases:
| Field | Description | Example |
|---|---|---|
| Database Name | Display name for the generated asset | My Spawn Tables |
| Type Prefix | Prefix for generated C# types | My |
| Namespace | C# namespace for generated code | MyGame.Data |
Definitions in Spawn Forge describe context properties — the game state values that conditions and weight modifiers reference. These are not stored on the spawn tables themselves; they define what the runtime context looks like.
| Type | Definition Fields | Usage | Example |
|---|---|---|---|
| Context Categories | Label, entries[] | Conditions check: category == entry index | Biome (Forest, Desert, Swamp), Time of Day (Dawn, Day, Dusk, Night) |
| Context Flags | Name | Conditions check: flag == true/false | Is Night, Is Raining, Boss Defeated |
| Context Numerics | Name | Conditions + weight modifiers | Player Level, Difficulty, Time Survived |
Note that Spawn Forge definitions do not include Texts — text properties are not meaningful for runtime condition evaluation.
The same four-button export/import system as other forges. Export Full JSON includes context definitions plus all spawn table data (pools, entries, conditions, weight modifiers). Import presents a 3-choice dialog for definitions and tables. See AI Workflow.
The main builder step uses a split-panel layout with nested ReorderableLists:
Select a table from the list to edit:
Name, Code (with Auto toggle), and Description — same pattern as all forges.
Each table contains a ReorderableList of pools. Expand a pool's foldout to see:
| Field | Description |
|---|---|
| Name | Display name (e.g., "Common Enemies", "Elite Reinforcements") |
| Roll Count Min/Max | How many times to roll this pool (random between min and max) |
| Roll Chance | Percentage chance (0-100) that this pool activates at all |
| Conditions | ReorderableList of SimpleSpawnCondition entries. Each shows sourceType,
sourceIndex (resolved to name), comparison, and value dropdowns. |
| Entries | ReorderableList of spawn entries (see below) |
Each entry within a pool has:
| Field | Description |
|---|---|
| Enemy/Squad Toggle | Switch between single enemy code and squad code |
| Code | Enemy code or squad code (with browse button) |
| Weight | Base weight for random selection (higher = more likely) |
| Conditions | Entry-level conditions (same structure as pool conditions) |
| Weight Modifiers | ReorderableList of modifiers that adjust weight based on context numerics |
Configure generation output settings — output folder path and any additional generation options. This step also shows a summary of what will be generated.
Same two-phase generation as all forges:
AssetDatabase.Refresh() and domain reload.
[InitializeOnLoad] handler creates the ScriptableObject from temporary JSON data.
| File | Description |
|---|---|
{Prefix}SpawnType.cs |
Enum with one entry per spawn table code |
{Prefix}SpawnDatabase.cs |
ScriptableObject implementing ISimpleSpawnDataSource |
Editor/{Prefix}SpawnDatabaseEditor.cs |
Custom Inspector with full editing UI |
{Prefix}SpawnDatabase.asset |
The data asset |
Spawn Forge includes three runtime helper classes for evaluating and rolling spawn tables in your game code:
A struct that holds current game state values, aligned with your context definitions:
Rolls a spawn table against a context, returning an array of results:
Evaluates conditions without performing a roll — useful for checking whether a pool or entry would be active under a given context:
| Member | Returns | Description |
|---|---|---|
SpawnTableCount |
int |
Total number of spawn tables |
GetSpawnTables() |
SimpleSpawnTable[] |
Get all spawn tables |
GetSpawnTable(string code) |
SimpleSpawnTable? |
Get a spawn table by code |
GetSpawnTableCodes() |
string[] |
Get all spawn table codes |
GetContextCategoryLabels() |
string[] |
Get context category labels (e.g., "Biome", "Time of Day") |
GetContextCategoryEntries(int index) |
string[] |
Get entries for a context category |
GetContextFlagNames() |
string[] |
Get context flag names |
GetContextNumericNames() |
string[] |
Get context numeric names |
GetTablesContainingEnemy(string enemyCode) |
SimpleSpawnTable[] |
Find all tables that can spawn a specific enemy |
GetTablesContainingSquad(string squadCode) |
SimpleSpawnTable[] |
Find all tables that reference a specific squad |
Use multiple pools per table to create interesting spawn distributions. A "Common" pool with 100% chance and 2-4 rolls plus an "Elite" pool with 20% chance and 1 roll creates natural variety without complex logic.
Weight modifiers that reference Player Level or Difficulty let spawn probabilities shift naturally as the game progresses. Stronger enemies gradually become more common without needing separate tables per level range.
Conditions are binary (pass/fail) while weight modifiers are gradual. Use conditions to set hard gates ("only after level 10") and modifiers for soft scaling ("more likely at higher levels").
Export your schema (including context definitions and enemy/squad codes) to an AI. Describe your game's biomes, difficulty curve, and spawn philosophy. The AI can generate dozens of spawn tables with properly configured pools, conditions, and weights. See AI Workflow.
Scaling Forge creates profiles that define how enemy numeric properties (HP, ATK, DEF, Speed, etc.) change as a function of level or difficulty. Instead of manually entering stat values for every level, you define a formula and let the math do the work.
Each scaling profile contains rules — one per numeric property you want to scale. A profile is typically paired with an enemy code (e.g., the "GOBLIN_WARRIOR" profile scales HP, ATK, and DEF for goblins), but you can also create shared profiles like "DEFAULT_SCALING" that apply to many enemies.
Open the wizard via Window › Simple Enemy Forge › Scaling Forge Wizard.
Scaling Forge has 3 steps: Setup, Scaling Rules, Generate. There is no Definitions step — Scaling Forge reads its numeric property names directly from the linked Enemy Database.
Each scaling rule specifies one of six types that determines how the base value transforms with level:
| Type | Formula | Use Case |
|---|---|---|
| None | value = base |
No scaling — value stays constant at all levels |
| Linear | base + (level - 1) * increment |
Steady, predictable progression. Good for HP, basic stats. |
| Percentage | base * (1 + (level - 1) * percentage / 100) |
Proportional growth. Higher base values scale faster in absolute terms. |
| Exponential | base * pow(exponentialBase, level - 1) |
Rapid power growth. Use sparingly — values explode quickly. |
| Curve | base + curve.Evaluate(level / maxLevel) * multiplier |
Full control via AnimationCurve. Design any progression shape. |
| Step | Lookup table of (level, value) pairs | Manual breakpoints. At level X, value becomes Y. No interpolation. |
Scaling Forge requires exactly one Enemy Database. The wizard reads the database's numeric property names (e.g., HP, ATK, DEF, Speed) and uses them as the target properties for scaling rules. If your Enemy Database has no numeric properties defined, you will need to add some in Enemy Forge first.
| Field | Description | Example |
|---|---|---|
| Database Name | Display name for the generated asset | My Scaling Profiles |
| Type Prefix | Prefix for generated C# types | My |
| Namespace | C# namespace for generated code | MyGame.Data |
The main editing step uses a split-panel layout:
Select a profile from the list to edit:
Name, Code (with Auto toggle), and Description.
Each profile contains a ReorderableList of rules. Each rule targets one numeric property from the linked Enemy Database:
| Field | Description |
|---|---|
| Target Property | Dropdown of numeric property names from the enemy database (HP, ATK, DEF, etc.) |
| Scaling Type | Dropdown: None, Linear, Percentage, Exponential, Curve, Step |
The remaining fields change based on the selected scaling type:
| Scaling Type | Visible Parameters |
|---|---|
| None | (no parameters) |
| Linear | increment — value added per level |
| Percentage | percentage — percent increase per level |
| Exponential | exponentialBase — base of the exponentiation |
| Curve | curve (AnimationCurve), curveMultiplier, curveMaxLevel |
| Step | steps — ReorderableList of (level, value) pairs, sorted by level |
Below each rule, a HelpBox displays the active equation using the rule's current parameters. This makes it easy to verify your math at a glance:
At the bottom of the profile editor, an IntSlider lets you pick a preview level. A 5-column table shows the scaling results for every rule in the profile:
| Column | Description |
|---|---|
| Property | The numeric property name |
| Type | The scaling type (Linear, Exponential, etc.) |
| Base | The unscaled base value from the enemy database |
| Scaled | The computed value at the selected level |
| Change | Delta percentage from base (e.g., +150%) |
This gives you immediate visual feedback on how your scaling rules behave across the level range.
Same two-phase generation as all forges:
AssetDatabase.Refresh() and domain reload.
[InitializeOnLoad] handler creates the ScriptableObject from temporary JSON data.
| File | Description |
|---|---|
{Prefix}ScalingType.cs |
Enum with one entry per scaling profile code |
{Prefix}ScalingDatabase.cs |
ScriptableObject implementing ISimpleScalingDataSource |
Editor/{Prefix}ScalingDatabaseEditor.cs |
Custom Inspector with split panel, slider preview, nested rules with foldouts |
{Prefix}ScalingDatabase.asset |
The data asset |
A static utility class that computes scaled values using the same formulas as the generated
databases. Use this when you need to scale values outside of the database's
GetScaledValue method.
| Member | Returns | Description |
|---|---|---|
ProfileCount |
int |
Total number of scaling profiles |
GetScalingProfiles() |
SimpleScalingProfile[] |
Get all scaling profiles |
GetScalingProfile(string code) |
SimpleScalingProfile? |
Get a profile by code, or null if not found |
GetProfileCodes() |
string[] |
Get all profile codes |
GetNumericNames() |
string[] |
Get numeric property names (copied from enemy DB at generation time) |
GetScaledValue(string profileCode, int numericIndex, float baseValue, int level) |
float |
Compute the scaled value for a specific profile, property, base value, and level |
Linear scaling is the easiest to understand and balance. Start there for most stats (HP, ATK, DEF), then switch to other types only when you need specific curve shapes. Percentage is a natural next step for proportional growth.
Step scaling gives you complete control over exact values at specific levels. It is ideal for tier-based systems (Bronze/Silver/Gold) or when designers need to hand-tune specific breakpoints rather than rely on formulas.
Always use the slider preview to check your scaling at extreme levels. A formula that looks fine at level 10 might produce absurd values at level 50. The Change% column makes it easy to spot runaway scaling.
Check level 1 (should match base values), your expected mid-game level, and your max level. Exponential scaling in particular can produce values that break game balance if the exponentialBase is too high. A value of 1.05-1.15 is usually safe.
Wave Forge orchestrates enemies and squads over time. It lets you design wave sequences for tower defense, horde mode, roguelike encounters, and any game mode that spawns enemies in timed waves. Each wave sequence is built from a three-level hierarchy: Sequences > Waves > Entries.
Wave Forge links to your Enemy Database (required) for individual enemy
spawns and optionally to a Squad Database for spawning pre-configured
squad groups. Open it from
Window → Simple Enemy Forge → Wave Forge Wizard.
The wizard has 5 steps: Setup, Definitions, Sequences, Settings, and Generate.
Wave sequences use a three-level nesting structure. A Sequence contains multiple Waves, and each wave contains multiple Entries that define what to spawn and when.
In this example, the "Forest Assault" sequence plays three waves in order. The first wave sends scouts and wolves, the second brings the main force including a full squad group, and the final wave spawns a boss.
Each entry within a wave is either a single enemy or a squad group, toggled in the wizard. Entries have the following fields:
| Field | Type | Description |
|---|---|---|
enemyCode |
string | Enemy code from the linked Enemy Database. Empty if using a squad. |
squadCode |
string | Squad code from the linked Squad Database. Empty if using a single enemy. |
count |
int | How many to spawn from this entry. |
spawnDelay |
float | Delay in seconds between each individual spawn within this entry. |
startTime |
float | Time offset in seconds from the start of the wave before this entry begins spawning. |
The IsSquad property returns true if the entry references a squad code,
allowing your spawn handler to branch between single-enemy and squad-group spawning logic.
Each wave sequence has built-in loop settings for endless or escalating game modes:
| Field | Type | Description |
|---|---|---|
loopAfterLast |
bool | When true, the sequence restarts from Wave 1 after the last wave finishes. |
difficultyScalePerLoop |
float | Multiplier applied each loop iteration (e.g., 1.2 means 20% harder per loop). Compounds across loops. |
maxLoops |
int | Maximum number of loop iterations. 0 means infinite looping. |
Loop settings make Wave Forge ideal for endless modes. A sequence with
loopAfterLast = true, difficultyScalePerLoop = 1.15, and
maxLoops = 0 creates an infinitely escalating challenge.
Configure the basics and link your databases.
ForestWavesType.cs, ForestWavesDatabase.cs, etc.)
Multiple databases are supported for both types. When duplicates exist across databases, the first database's version wins.
Define the dynamic property system for your wave sequences. These properties are attached to sequences, not individual waves or entries.
This step also provides schema export and import for AI-assisted content creation. Export your definitions as Full JSON, Light JSON, or Markdown, send them to an AI, and import the generated sequences back.
Build your wave sequences in a split-panel editor. The left panel shows a paginated list of sequences, and the right panel shows the selected sequence's details.
For each sequence, you define:
Waves and entries use nested ReorderableLists with foldouts, so you can reorder waves by dragging and expand/collapse each one to manage complex sequences.
Choose output paths for the generated scripts and database asset.
.cs files (enum, database, editor).asset database fileClick Generate to create all output files. Wave Forge uses the same two-phase generation as all other forges:
{Prefix}Type.cs (enum), {Prefix}Database.cs
(ScriptableObject), and {Prefix}DatabaseEditor.cs (custom editor).
Triggers AssetDatabase.Refresh() and domain reload.
[InitializeOnLoad] handler detects a SessionState
flag and creates the {Prefix}Database.asset from temporary JSON data
via delayCall.
After generation, your project will contain:
SimpleWaveRunner is a pure C# event-driven wave runner with no MonoBehaviour
dependency. You drive it by calling Update(deltaTime) each frame.
| Method | Description |
|---|---|
Start(sequence) |
Begin running a wave sequence from the beginning. |
Update(deltaTime) |
Drive the state machine forward. Call each frame. |
Pause() |
Pause the runner. No spawns are dispatched until resumed. |
Resume() |
Resume after pausing. |
Stop() |
Stop the runner and reset to idle. |
SkipCurrentWave() |
Skip remaining spawns in the current wave and advance to the next. |
SkipToWave(index) |
Jump to a specific wave index. |
| Property | Type | Description |
|---|---|---|
CurrentSequence |
SimpleWaveSequence? |
The wave sequence currently being run, or null if idle/stopped. Read-only. |
Phase |
WaveRunnerPhase |
Current phase: Idle, PreDelay, Spawning, PostDelay, or Complete. |
CurrentWaveIndex |
int | Index of the current wave within the sequence. |
LoopCount |
int | Number of completed loop iterations (0 on first play-through). |
CurrentDifficultyScale |
float | Cumulative difficulty scale (starts at 1.0, multiplied each loop). |
ElapsedTime |
float | Total elapsed time since Start, excluding paused time. |
IsRunning |
bool | True if actively running (not idle, not complete, not paused). |
IsComplete |
bool | True if the sequence has finished. |
IsPaused |
bool | True if the runner is paused. |
| Event | Args | Description |
|---|---|---|
OnWaveStarted |
(int waveIndex, string waveName) |
Fires when a wave's preDelay ends and spawning begins. |
OnSpawnRequested |
(WaveSpawnRequest request) |
Fires once per individual spawn. Handler should instantiate the enemy or squad. |
OnWaveCompleted |
(int waveIndex, string waveName) |
Fires when all entries in a wave have been dispatched. |
OnSequenceCompleted |
(none) | Fires when the entire sequence is done (no more waves or loops). |
OnLoopStarted |
(int loopCount, float difficultyScale) |
Fires when the sequence loops back to the beginning. |
OnPaused |
(none) | Fires when the runner is paused. |
OnResumed |
(none) | Fires when the runner is resumed. |
A complete wave sequence with identity, waves, loop settings, and dynamic properties.
A single wave within a sequence, containing timed spawn entries.
A single spawn entry within a wave — either an enemy or a squad.
Interface implemented by generated wave databases.
Give players a breather between waves. A 3-5 second postDelay lets players prepare, while a short preDelay can display a "Wave incoming!" warning.
Combine individual enemy spawns with squad groups in the same wave. Use squads for coordinated groups and individual entries for stragglers or bosses.
Use different startTime values within a wave to create staggered spawns.
Fast enemies at 0s, ranged at 2s, and heavies at 5s creates natural pressure waves.
Enable loopAfterLast with a difficultyScalePerLoop of 1.1-1.3
for escalating endless modes. Use maxLoops = 0 for truly infinite play.
Behavior Forge defines condition-based action rules for enemy AI. Instead of hardcoding
behavior trees or finite state machines, you author behavior profiles
that map conditions to action codes. At runtime, the SimpleBehaviorEvaluator
checks conditions against the current game state and returns the best action to take.
Conditions reuse the same SimpleSpawnCondition system from Spawn Forge,
checking dynamic numeric properties against thresholds. Actions are user-defined string
codes (like FLEE, HEAL, AOE_ATTACK) that your
game code interprets.
Open it from Window → Simple Enemy Forge → Behavior Forge Wizard.
The wizard has 4 steps: Setup, Definitions, Behavior Rules, and Generate.
A behavior profile is a named collection of rules. Each rule has conditions that check dynamic properties, an action code to trigger, a priority for conflict resolution, and an optional cooldown.
When evaluated, the Goblin Warrior first checks if it should flee (highest priority). If health is fine, it checks whether to call reinforcements. If neither emergency applies, it attacks if in range, or approaches the target as a fallback.
When multiple rules match the current conditions, the highest priority wins. Rules are evaluated in priority order (highest first), and the first matching rule is selected as the best action.
This creates a natural decision hierarchy:
A rule with no conditions always matches, making it an ideal fallback at low priority.
Each rule has a cooldown field (in seconds) that prevents the same action
from triggering repeatedly. After a rule fires, it cannot match again until the
cooldown expires. This is tracked per-rule at runtime.
Set cooldown to 0 for actions that should always be available (like
basic attacks or movement). Use higher cooldowns for powerful abilities or
behaviors that should not spam (like calling reinforcements every 10 seconds).
Configure the basics for your behavior database.
GoblinBehaviorType.cs, GoblinBehaviorDatabase.cs, etc.)
Unlike Wave Forge or Squad Forge, Behavior Forge does not require linking to an Enemy Database. Behavior profiles are standalone — you assign them to enemies in your own code by matching profile codes to enemy types.
Define the dynamic property system for your behavior profiles and the context properties that conditions check against.
These properties are stored on each behavior profile:
Context properties define what the conditions can check against at runtime. These
are the numeric values that your game provides in a SimpleSpawnContext
when evaluating behavior. Examples include Health Percent, Ally Count, Target Distance,
Mana Percent, or any game-state value.
This step also provides schema export and import for AI-assisted content creation.
Build your behavior profiles in a split-panel editor. The left panel shows a paginated list of profiles, and the right panel shows the selected profile's rules.
For each profile, you define:
For each rule within a profile:
FLEE, HEAL)SimpleSpawnCondition checks. Each condition
tests a context numeric property against a threshold using a comparison operator
(equals, not equals, less than, greater than, etc.). All conditions must pass for the rule to match.Click Generate to create all output files using the standard two-phase process.
After generation, your project will contain:
The generated custom editor features a split-panel layout with search, sort, pagination, bulk operations, nested condition editing, and an "Open Behavior Forge Wizard" button for quick access back to the wizard.
SimpleBehaviorEvaluator is a static helper class that evaluates behavior
profile rules against a SimpleSpawnContext. It reuses
SimpleSpawnEvaluator.EvaluateConditions since behavior conditions are
the same SimpleSpawnCondition arrays used in Spawn Forge.
| Method | Returns | Description |
|---|---|---|
EvaluateBestAction(profile, context) |
SimpleBehaviorRule? |
Returns the highest-priority matching rule, or null if no rules match. |
GetMatchingActionCodes(profile, context) |
string[] |
Returns all matching action codes sorted by priority descending. |
EvaluateProfile(profile, context) |
SimpleBehaviorRule[] |
Returns all matching rules sorted by priority descending. Use this when you need access to full rule details (priority, cooldown, conditions). |
A named behavior profile containing condition-action rules and dynamic properties.
A single rule that maps conditions to an action code.
Interface implemented by generated behavior databases.
Add a low-priority rule with no conditions as the last rule in every profile.
This ensures the evaluator always returns an action, even when no other
conditions match. Use actions like IDLE or APPROACH_TARGET.
Reserve high priorities (10+) for emergency behaviors like fleeing or healing. Mid-range (5-9) for tactical decisions. Low priorities (1-4) for default combat and movement. This creates clear decision hierarchies.
Set cooldown to 0 for always-available actions (basic attacks, movement). Use 3-10 seconds for abilities and special moves. Use 10+ seconds for powerful or dramatic behaviors (calling reinforcements, enraging).
Use the Bestiary Demo's Behavior tab to test profiles with different context
values interactively. Build a SimpleSpawnContext with test values
and verify which actions trigger under different scenarios.
Faction Forge defines factions with an N×N relationship matrix that determines how factions interact with each other. Each pair of factions has a stance ranging from Allied to Hostile, enabling your game to query whether two entities should fight, cooperate, or ignore each other.
Each faction also supports dynamic properties (Categories, Flags, Numerics, Texts) for storing custom metadata like faction traits, reputation thresholds, or territorial boundaries.
Open it from Window → Simple Enemy Forge → Faction Forge Wizard.
The wizard has 4 steps: Setup, Definitions, Factions, and Generate.
The relationship matrix is an N×N grid where each cell defines the stance between two factions. The matrix is symmetric — if Humans are Hostile toward Orcs, then Orcs are automatically Hostile toward Humans.
Key properties of the matrix:
List<int> in row-major order for efficient serializationThe matrix grows quadratically with faction count. 4 factions produce a 4×4 matrix (16 cells), 10 factions produce a 10×10 matrix (100 cells). Due to symmetry, only N×(N-1)/2 unique relationships need to be defined.
Faction Forge uses the SimpleFactionStance enum with 5 possible stances,
stored as integer values from -2 to 2:
| Stance | Value | Meaning |
|---|---|---|
| Hostile | -2 | Openly hostile — attack on sight, maximum aggression |
| Unfriendly | -1 | Negative disposition — may attack under certain conditions |
| Neutral | 0 | Indifferent — won't attack unprovoked, won't assist |
| Friendly | 1 | Positive disposition — will not attack, may cooperate |
| Allied | 2 | Same side — full cooperation, shared resources and information |
The numeric values allow easy comparison. Stances less than 0 are negative, 0 is neutral, and greater than 0 are positive. This makes range checks straightforward in game code.
Configure the basics and optionally link enemy databases.
WarFactionsType.cs, WarFactionsDatabase.cs, etc.)
Define the dynamic property system for your factions. These properties are attached to each faction definition, letting you store custom metadata.
This step also provides schema export and import for AI-assisted content creation. The schema includes the relationship matrix, which round-trips through JSON import with full symmetric validation.
Build your factions in a split-panel editor. The left panel shows the faction list, and the right panel shows the selected faction's details.
Each faction has a Name, Code (auto-generated from name), and Description.
All four property sections (Categories, Flags, Numerics, Texts) are always visible with values for each property defined in Step 2.
The relationships section shows a stance dropdown for every other faction in the database. When you change A's stance toward B, B's stance toward A is automatically updated to match (symmetric write).
Adding or removing a faction automatically expands or shrinks the relationship matrix for all existing factions.
Click Generate to create all output files using the standard two-phase process.
After generation, your project will contain:
The generated custom editor features a split-panel layout with search, sort, pagination, bulk operations, dynamic property editing, and per-faction relationship editing with symmetric matrix writes.
SimpleFactionHelper is a static helper class that provides convenience
methods for querying faction relationships beyond what
ISimpleFactionDataSource offers directly.
| Method | Returns | Description |
|---|---|---|
IsHostileOrUnfriendly(db, codeA, codeB) |
bool | True if stance is Hostile or Unfriendly (value < 0). |
IsFriendlyOrAllied(db, codeA, codeB) |
bool | True if stance is Friendly or Allied (value > 0). |
GetStanceValue(db, codeA, codeB) |
int | Returns the numeric stance value from -2 (Hostile) to 2 (Allied). |
GetFactionsWithStance(db, code, stance) |
string[] | Returns all faction codes that have the exact specified stance toward the given faction. |
GetNonHostileFactions(db, code) |
string[] | Returns all faction codes with stance of Unfriendly or higher (not Hostile). |
The stance enum with integer values for easy comparison.
A single faction with identity, color, and dynamic properties.
The relationship matrix is stored as a flat List<int> in row-major order.
For N factions, the matrix has N×N entries. To get the stance from faction
index A to faction index B:
In practice, you should use the ISimpleFactionDataSource interface
methods rather than indexing the matrix directly.
Interface implemented by generated faction databases.
Begin with a small number of factions to keep relationships manageable. You can always add more later — the matrix expands automatically. A classic setup might be Players, Neutral Wildlife, Enemy Horde, and Undead.
The wizard and generated editor both enforce symmetric relationships automatically. When you set Humans→Orcs to Hostile, Orcs→Humans is updated to match. You only need to define each relationship once.
Store faction-specific metadata in dynamic properties. Use numerics for Military Strength or Reputation Threshold, categories for Government Type or Alignment, and texts for lore, leader names, or capital cities.
The relationship matrix has N×N cells. 5 factions produce 25 cells (manageable), but 20 factions produce 400 cells. Keep faction count reasonable for your game's needs — most games work well with 4-8 factions.
All 7 wizards support schema export for AI content generation. Export your property definitions, paste them into ChatGPT or Claude, and import hundreds of enemies, squads, spawn tables, scaling profiles, wave sequences, behavior profiles, or factions in one click.
Most wizards offer 3 export buttons. Enemy Forge and Scaling Forge offer 2 (Full JSON + Markdown only — no Light JSON):
| Mode | Includes | Best For |
|---|---|---|
| Export Full JSON | Definitions + existing data + linked database metadata + comprehensive instructions | AI has everything — no questions needed |
| Export Light JSON | Definitions + instructions only (no existing data, no linked databases) | Smaller file, AI asks user for context first |
| Export Markdown | Same as Full but in human-readable Markdown format | ChatGPT/Claude chat windows where JSON is hard to read |
Exported schemas are comprehensive and designed for AI consumption:
Name, version, description, linked databases, template tracking (was template modified?)
Every category with its entries, every flag with defaults, every numeric with constraints, every text with lineCount. AI sees your exact property system.
10+ sections of guidance: what AI can/cannot do, property type explanations, balance guidance, format examples, ecosystem overview, step-by-step workflow.
Already-created enemies, squads, or other content serve as examples. AI sees your naming patterns and property usage.
The Enemy Forge schema includes the following AI instruction sections. Other forges follow the same pattern with content adapted to their domain:
The schema tells AI about all 7 forges so it generates content with the full pipeline in mind:
| If You'll Use... | AI Should Generate... |
|---|---|
| Squad Forge | Varied enemy types for group composition (tanks, DPS, healers, ranged) |
| Spawn Forge | Enemies at different power levels for weighted pools (common, uncommon, rare spawns) |
| Scaling Forge | Enemies with clear stat baselines for level scaling (HP, ATK, DEF with sensible ranges) |
| Wave Forge | Enemies suited for progressive wave difficulty (early fodder through late-game elites) |
| Behavior Forge | Enemies with distinct combat roles (aggressive melee, cautious ranged, support, boss phases) |
| Faction Forge | Enemies belonging to distinct factions (Undead, Demons, Wildlife, Bandits) |
The AI instructions prompt it to ask the user which systems they plan to use before generating, so it creates the right mix of enemies.
When importing AI-generated JSON, the system validates everything:
After import, a result popup shows: total in file, successfully imported, skipped with reasons, and any warnings.
| Wizard | Definitions Exported | Data Exported |
|---|---|---|
| Enemy Forge | Categories (with entries), Flags, Numerics, Texts | Enemies + modifiers (if SAF) + loot table codes (if SIF) |
| Squad Forge | Categories, Flags, Numerics, Texts | Squads + slots + available enemy data from linked Enemy DB |
| Spawn Forge | Context Categories, Context Flags, Context Numerics | Spawn tables + pools + entries + available enemy data |
| Scaling Forge | (uses enemy numerics from linked DB) | Scaling profiles + rules + step entries |
| Wave Forge | Categories, Flags, Numerics, Texts | Wave sequences + waves + entries + available enemy/squad data |
| Behavior Forge | Context Categories, Context Flags, Context Numerics | Behavior profiles + rules + conditions |
| Faction Forge | Categories, Flags, Numerics, Texts | Factions + N×N relationship matrix |
Full mode gives AI all the context it needs. Use Light mode for follow-up generations when the AI already knows your game.
Add 3-5 enemies manually before exporting. AI learns from your naming patterns, stat ranges, and category usage.
Pick a genre template in Enemy Forge Step 1, modify it to fit your game, then export. AI gets a comprehensive property system to work with.
Generate 20-50 enemies at a time, review, then ask for more. The existing data in subsequent exports helps AI maintain consistency.
Simple Enemy Forge includes 6 genre templates that pre-populate your property definitions (categories, flags, numerics, texts) and example enemies in a single click. Templates are applied in Enemy Forge Step 1 (Setup) and serve as starting points that you can freely modify to match your game.
| Template | Categories | Flags | Numerics | Texts | Examples | Focus |
|---|---|---|---|---|---|---|
| Generic RPG | 4 | 6 | 10 | 2 | 3 | Classic RPG enemies with types, ranks, elements, base stats |
| Soulslike | 4 | 5 | 15 | 3 | 3 | Dark fantasy with poise, stagger, weakness, elemental defenses |
| Survival / Horror | 5 | 8 | 12 | 2 | 3 | Detection, stealth, fear, infection, spawn triggers |
| Tower Defense | 4 | 6 | 10 | 1 | 3 | Speed, path, armor type, special abilities, wave values |
| Sci-Fi / Shooter | 5 | 5 | 14 | 2 | 3 | Shield types, factions, weapon loadouts, weak points |
| RTS / Strategy | 6 | 8 | 16 | 2 | 4 | Unit class, population, build time, resources, formations |
Standard RPG enemy system with types, ranks, elements, combat stats, and reward values. Works for most traditional RPGs, JRPGs, action RPGs, and MMOs.
Soulslike enemy system with poise, stagger thresholds, damage types, weakness multipliers, and multi-phase boss support. Designed for punishing, pattern-based combat.
Survival horror enemy system with detection ranges, aggro behavior, sensory systems, infection/contamination mechanics, and fear factor. For horror, zombie, and survival games.
Tower defense enemy system with movement types, armor categories, resistances, special abilities, and wave/economy values. For TD, strategy, and base defense games.
Sci-fi shooter enemy system with armor types, shield systems, weapon loadouts, weak points, and combat AI profiles. For FPS, TPS, and sci-fi action games.
RTS enemy/unit system with unit classes, armor types, attack categories, build costs, population values, and tactical properties. For RTS, 4X, and tactical strategy games.
When you generate a database after applying a template, the generated custom Inspector editor automatically adapts to your property definitions:
The generated editor reflects your template exactly. If you chose Generic RPG, you get Enemy Type and Rank dropdowns, Is Boss and Is Flying toggles, HP/MP/Attack fields, and Bestiary Entry text areas. If you chose RTS, you get Unit Class, Armor Class, Gold Cost, Lumber Cost, Build Time fields instead.
Every template is fully editable. Add your game-specific categories, remove properties you don't need, rename anything. Templates save time — they don't constrain you.
Apply the template, review the definitions in Step 2, make adjustments, then generate. Once generated, you can still change definitions and regenerate at any time.
Templates are designed to work with the AI workflow. Apply a template, export the schema, and AI will generate enemies that perfectly match the template’s property system.
Start with one template and pull ideas from another. Copy the poise system from Soulslike into your RPG template, or add RTS resource costs to a Tower Defense setup.
Simple Enemy Forge has optional integration with Simple Attribute Forge (SAF), sold separately. When both packages are installed in the same project, the Enemy Forge automatically detects SAF and unlocks modifier references on enemies. No manual configuration is required — just import both packages and the features appear.
When SAF is detected, one additional capability appears in the Enemy Forge wizard and generated editors:
Each enemy can hold drag-and-drop references to SAF Modifier ScriptableObjects for effects
like poison, burn, slow, stun, or buff auras. Stored as ScriptableObject[] on
the generated database. Useful for defining what effects an enemy can apply to the player or
other entities during combat.
| Location | What Changes |
|---|---|
| Enemy Forge — Step 1 (Setup) | A “Simple Attribute Forge Integration” section appears, confirming SAF is detected and modifier support is active. |
| Enemy Forge — Step 3 (Enemies) | Each enemy gets a Modifiers section with a ReorderableList of ScriptableObject references. Drag modifier assets directly from your project to assign them. |
| Schema Export | Exported schemas include modifier names (as strings) for each enemy. AI can reference modifier names, but Unity asset references cannot be set via JSON import — modifiers must be assigned manually via drag-and-drop. |
| Generated Custom Editors | The generated Inspector editor shows a Modifiers section for each enemy, with ObjectField pickers filtered to SAF modifier assets when SAF is installed. |
SAF detection is fully automatic and bidirectional. An [InitializeOnLoad] detector runs
every time Unity performs a domain reload (after compilation, script changes, or package imports). It
uses reflection only — no hard assembly references — so it compiles
cleanly whether SAF is present or not.
[InitializeOnLoad] attribute on
AttributeForgeDetector causes it to run automatically. It calls
Type.GetType("SimpleForgeFramework.SimpleModifierAssetBase, SimpleModifierSystem.Runtime")
to probe for SAF’s modifier types.
SIMPLE_ATTRIBUTE_FORGE to
Player Settings → Scripting Define Symbols. It also populates
SAFBridge with the resolved ModifierAssetBaseType and builds a
GetModifierMetadata delegate via reflection.
SAFBridge.IsAvailable checks now activates. The Modifiers section appears in
the Enemy Forge wizard, and generated editors gain modifier ObjectFields.
[InitializeOnLoad] detector runs. This time,
Type.GetType() returns null — SAF types are no longer present.
SIMPLE_ATTRIBUTE_FORGE from the scripting defines. It also clears
all SAFBridge properties (sets ModifierAssetBaseType and
GetModifierMetadata to null).
ScriptableObject[] in runtime types (see below).
Simple Enemy Forge never has a hard assembly reference to Simple Attribute Forge. Instead, all editor code accesses SAF through a static bridge class:
| Component | Role |
|---|---|
AttributeForgeDetector |
[InitializeOnLoad] class that probes for SAF types via
Type.GetType(). If found, populates SAFBridge with resolved types and
a reflection-built metadata delegate. If not found, clears everything. Also manages the
SIMPLE_ATTRIBUTE_FORGE define symbol. |
SAFBridge |
Static class with properties: ModifierAssetBaseType (System.Type),
GetModifierMetadata (delegate that extracts name, description, tag, duration,
effects from a modifier SO), and IsAvailable (bool).
All wizard and editor code checks SAFBridge.IsAvailable before showing modifier UI. |
This pattern means:
using SimpleForgeFramework; statements in the main editor codedefineConstraints and hard using references to SAF would seem simpler,
but it creates a dangerous edge case: if SAF is deleted while the define symbol still lingers, the
conditional assembly tries to compile with missing references, causing errors that prevent the
[InitializeOnLoad] detector from running — leaving the project stuck. The
reflection-based SAFBridge pattern avoids this entirely.
SAF integration is split into two layers: unconditional runtime types that always compile, and conditional editor code that only activates when SAF is present.
The SimpleEnemyDefinition struct in the runtime namespace always includes:
| Field | Type | Purpose |
|---|---|---|
modifiers |
ScriptableObject[] |
Array of modifier references (SAF modifier assets when available) |
Because modifiers are stored as ScriptableObject[] (not a SAF-specific type), the
generated database class always compiles — the field exists whether SAF is installed or not.
This is what makes the install/remove cycle error-free.
The following editor features are gated behind SAFBridge.IsAvailable runtime checks
and only activate when SAF is installed:
Type.GetType()
for ObjectField type filtering — auto-adapts when SAF is installed/uninstalled without regeneration)When SAF is removed, these sections simply disappear. The wizard reverts to its base feature set with no visible errors or broken UI.
Understanding how generated databases behave across SAF install/remove cycles:
When you generate an Enemy Database while SAF is available and modifiers are assigned:
modifiers array holds references to modifier .asset filesScriptableObject[] is
an unconditional Unity type.asset file — Unity
serializes ScriptableObject references regardless of the asset’s type.asset files were
removed along with SAFIf you open the Enemy Forge wizard and regenerate without SAF installed:
.asset data is overwritten with the new (empty) dataPlayer Settings → Scripting Define Symbols and verify
SIMPLE_ATTRIBUTE_FORGE is listed. If not, the detector may not have run yet.Assets → Reimport All or make a
trivial edit to any script to trigger compilation and a domain reload.SIMPLE_ATTRIBUTE_FORGE is still in
Scripting Define Symbols after deleting SAF, remove it manually and recompile..asset files. These references
survive regeneration as long as the modifier assets still exist at the same paths.This should never happen if Simple Enemy Forge is the only package using SAF. If you see errors:
SIMPLE_ATTRIBUTE_FORGE was removed from Scripting Define SymbolsSimple Enemy Forge has optional integration with Simple Item Forge (SIF), sold separately. When both packages are installed in the same project, the Enemy Forge automatically detects SIF and unlocks loot table linking on enemies. No manual configuration is required — just import both packages and the features appear.
When SIF is detected, two additional fields appear on each enemy:
A string field with a dropdown populated from your SIF loot database. Select which loot table
this enemy should use for drops. Example: GOBLIN_LOOT, BOSS_DRAGON_LOOT.
The code is stored as a plain string, so it remains valid even if SIF is removed.
A direct ScriptableObject reference to the SIF loot table asset. Drag the loot table
.asset file directly from your project. This gives your game code direct access to
the loot table object at runtime without needing to look it up by code.
| Location | What Changes |
|---|---|
| Enemy Forge — Step 1 (Setup) | A “Simple Item Forge Integration” section appears, confirming SIF is detected. An ObjectField lets you assign your SIF Loot Database, which populates the loot table code dropdown in Step 3. |
| Enemy Forge — Step 3 (Enemies) | Each enemy gets a Loot Table section with two fields: a dropdown for loot table code (populated from the linked SIF database) and an ObjectField for direct loot table asset reference. |
| Generated Custom Editors | The generated Inspector editor shows loot table fields for each enemy. The code field uses
a dropdown when SIF is available; the ObjectField uses runtime Type.GetType() to
filter the picker to SIF loot data source assets, automatically adapting when SIF is
installed or uninstalled without requiring regeneration. |
SIF detection follows the same automatic pattern as SAF detection. An [InitializeOnLoad]
detector runs on every domain reload and uses reflection only — no hard assembly
references.
[InitializeOnLoad] attribute on
ItemForgeDetector causes it to run automatically. It calls
Type.GetType("SimpleItemForge.ISimpleLootDataSource, SimpleItemForge.Runtime")
to probe for SIF’s loot types.
SIMPLE_ITEM_FORGE to
Player Settings → Scripting Define Symbols. It also populates
SIFBridge with the resolved LootDataSourceType,
ItemDataSourceType, and reflection-built delegates for GetLootTableCodes
and GetItemCodes.
SIFBridge.IsAvailable checks now activates. The loot table fields appear in
the Enemy Forge wizard and generated editors.
[InitializeOnLoad] detector runs. This time,
Type.GetType() returns null — SIF types are no longer present.
SIMPLE_ITEM_FORGE from the scripting defines. It also clears
all SIFBridge properties (sets types and delegates to null).
Simple Enemy Forge never has a hard assembly reference to Simple Item Forge. Instead, all editor code accesses SIF through a static bridge class:
| Component | Role |
|---|---|
ItemForgeDetector |
[InitializeOnLoad] class that probes for SIF types via
Type.GetType(). If found, populates SIFBridge with resolved types and
reflection-built delegates. If not found, clears everything. Also manages the
SIMPLE_ITEM_FORGE define symbol. |
SIFBridge |
Static class with properties: LootDataSourceType (System.Type),
ItemDataSourceType (System.Type), GetLootTableCodes (delegate that
returns string[] of loot table codes from a loot database SO), GetItemCodes
(delegate that returns string[] of item codes), and IsAvailable (bool).
All wizard and editor code checks SIFBridge.IsAvailable before showing loot table UI. |
This pattern means:
using SimpleItemForge; statements in the main editor codeThe SimpleEnemyDefinition struct always includes these loot-related fields,
regardless of whether SIF is installed:
| Field | Type | Purpose |
|---|---|---|
lootTableCode |
string |
The code identifying which loot table to use (e.g., "GOBLIN_LOOT").
Plain string — always compiles. |
lootTable |
ScriptableObject |
Direct reference to the SIF loot table asset. Stored as a generic
ScriptableObject — always compiles. |
Because both fields use plain Unity types (string and ScriptableObject),
the generated database class always compiles regardless of whether SIF is present. This is what
makes the install/remove cycle error-free.
enemy.lootTableCode
(a string) and enemy.lootTable (a ScriptableObject). If SIF is installed, you can cast
enemy.lootTable to ISimpleLootDataSource for full loot table API access.
If SIF is not installed, the string is still available for your own loot system to interpret.
Understanding how generated databases behave across SIF install/remove cycles:
When you generate an Enemy Database while SIF is available and loot tables are assigned:
lootTableCode is set to the selected code stringlootTable holds a reference to the SIF loot table .asset file.asset files still exist.asset files were removed
along with SIFIf you open the Enemy Forge wizard and regenerate without SIF installed:
.asset data is overwrittenPlayer Settings → Scripting Define Symbols and verify
SIMPLE_ITEM_FORGE is listed. If not, the detector may not have run yet.Assets → Reimport All or make a
trivial edit to any script to trigger compilation and a domain reload.SIMPLE_ITEM_FORGE is still in
Scripting Define Symbols after deleting SIF, remove it manually and recompile.This should never happen if Simple Enemy Forge is the only package using SIF. If you see errors:
SIMPLE_ITEM_FORGE was removed from Scripting Define SymbolsSimple Enemy Forge ships with one interactive demo — the Bestiary Demo, a 7-tab runtime data browser that showcases all forge outputs in a single scene. It exercises every generated database type and all 7 runtime helpers, giving you a hands-on preview of what your data looks like at runtime.
A 7-tab interactive data browser with search, filtering, spawn rolling, scaling preview, wave simulation, behavior evaluation, and faction relationship display — all driven by your generated databases.
The Bestiary Demo is generated via Window › Simple Enemy Forge › Generate Bestiary Demo
(menu priority 201). It creates a complete runtime scene with a full-screen Canvas UI using a
Black + Crimson Red (#C43A31) color scheme, matching Simple Enemy Forge's documentation theme.
The demo mirrors the pattern established by Simple Item Forge's ARPG Demo
(ARPGDemoGenerator.cs + ARPGDemo.cs), adapted for SEF's 7 forges
and runtime helpers.
Getting the Bestiary Demo running takes 3 steps:
Window › Simple Enemy Forge › Generate Bestiary Demo. This creates a new scene
with a Canvas, EventSystem, and the BestiaryDemo MonoBehaviour pre-configured.
Each tab corresponds to one of the 7 forges and demonstrates the runtime API for that forge:
| Tab | Forge | What to Try |
|---|---|---|
| Enemies | Enemy Forge | Browse enemies, search by name, filter by category, view icons and all dynamic properties (categories, flags, numerics, texts) |
| Squads | Squad Forge | View squad compositions with slot breakdowns showing enemy codes, count ranges, and level overrides |
| Spawn | Spawn Forge | Select a spawn table, adjust context sliders and toggles, click ROLL to see
SimpleSpawnRoller results with pool names and selected entries |
| Scaling | Scaling Forge | Select an enemy from the dropdown, drag the level slider, and see
SimpleScalingHelper preview showing base vs. scaled values for each numeric |
| Waves | Wave Forge | Select a wave sequence, click SIMULATE / PAUSE /
STOP / SKIP, and watch the SimpleWaveRunner
event log showing wave starts, spawn requests, completions, and loops |
| Behavior | Behavior Forge | Select a behavior profile, set context values using sliders and toggles, click
EVALUATE to see SimpleBehaviorEvaluator results showing
matching rules sorted by priority |
| Factions | Faction Forge | View the N×N relationship matrix with color-coded stance labels (Hostile in red, Unfriendly in orange, Neutral in gray, Friendly in green, Allied in blue) |
The Spawn and Behavior tabs build context controls dynamically from your database metadata.
For each context category, a dropdown is generated. For each context flag, a toggle is generated.
For each context numeric, a slider is generated. These controls map directly to
SimpleSpawnContext values used by the runtime helpers.
The demo targets 1920x1080 resolution using a CanvasScaler with
ScaleWithScreenSize mode. The UI uses split panels with a 32% list / 68% detail
ratio, matching the split-panel pattern used in the generated custom editors.
A Back button in the top-left corner destroys the demo canvas and returns to the scene's default state. There is no persistent inventory bar (unlike SIF's ARPG Demo) since SEF is a data browsing tool, not an inventory simulation.
The demo exercises all 6 runtime helper classes:
SimpleSpawnRoller — powers the Spawn tab's ROLL buttonSimpleSpawnContext — built from UI controls for Spawn and Behavior tabsSimpleSpawnEvaluator — used internally by the roller and behavior evaluatorSimpleScalingHelper — powers the Scaling tab's live previewSimpleWaveRunner — drives the Waves tab's simulation (called in Update())SimpleBehaviorEvaluator — powers the Behavior tab's EVALUATE buttonSimpleFactionHelper — used by the Factions tab for relationship queriesThe Bestiary Demo consists of 2 files:
| File | Assembly | Purpose |
|---|---|---|
Editor/Demo/BestiaryDemoGenerator.cs |
Editor | Static generator class that creates the demo scene, Canvas, UI hierarchy, and BestiaryDemo component via MenuItem (priority 201) |
Runtime/Demo/BestiaryDemo.cs |
Runtime | MonoBehaviour with 7 database arrays, cached interfaces, tab switching, search/filter, context building, and wave simulation in Update() |
Unity.TextMeshPro for
UI text rendering. The assembly definition references are already configured in the
.asmdef files.
All runtime types live in the SimpleEnemyForge namespace. Each forge wizard generates
a database ScriptableObject that implements a corresponding interface. You can access data either
by casting directly to the generated type, or by using the interface for generic code.
{Prefix} refers to the Class Prefix you set in each
wizard's Step 1 (Setup). The Enemy Forge uses the prefix alone (e.g., MyEnemyDatabase).
Other forges append a suffix: {Prefix}SquadDatabase, {Prefix}SpawnDatabase,
{Prefix}ScalingDatabase, {Prefix}WaveDatabase,
{Prefix}BehaviorDatabase, {Prefix}FactionDatabase.
Two access patterns are available for all databases:
Defines a single enemy with all its properties. Uses dynamic property arrays that correspond to the property definitions stored in the database.
| Field | Type | Description |
|---|---|---|
name | string | Display name shown to players (e.g., "Goblin Warrior") |
code | string | Unique UPPER_SNAKE_CASE identifier (e.g., "GOBLIN_WARRIOR") |
description | string | Enemy description/lore text |
icon | Sprite | Enemy portrait/sprite icon |
categoryValues | int[] | Category selections (index into each category's entries). For multi-select categories, this holds -1 as a placeholder. |
categoryMultiValues | SimpleIntArray[] | Multi-select category values. For single-select categories, the inner array is empty. |
flagValues | bool[] | Boolean flag values aligned with database flag definitions |
numericValues | float[] | Numeric property values. For range properties, this is the min value. |
numericRangeMaxValues | float[] | Max values for range numeric properties. For non-range properties, ignored (0). |
textValues | string[] | Text property values aligned with database text definitions |
modifiers | ScriptableObject[] | SAF modifier references (empty if SAF not installed) |
lootTableCode | string | SIF loot table code (empty if SIF not installed) |
lootTable | ScriptableObject | SIF loot table reference (null if SIF not installed) |
SimpleEnemyDefinition provides safe accessor methods that return defaults for out-of-range indices:
| Method | Returns | Description |
|---|---|---|
GetCategoryValue(int categoryIndex) | int | Selected entry index for a single-select category (default: 0) |
GetCategoryMultiValues(int categoryIndex) | int[] | Selected entry indices for a multi-select category |
GetFlagValue(int flagIndex) | bool | Flag value (default: false) |
GetNumericValue(int numericIndex) | float | Numeric value (default: 0f) |
GetNumericRange(int numericIndex) | (float min, float max) | Range numeric min/max values |
GetTextValue(int textIndex) | string | Text value (default: "") |
| Method / Property | Returns | Description |
|---|---|---|
EnemyCount | int | Total number of enemies in database |
GetEnemyDefinitions() | SimpleEnemyDefinition[] | Get all enemy definitions |
GetEnemyNames() | string[] | Get all enemy display names |
GetEnemyCodes() | string[] | Get all enemy codes |
GetEnemyByName(string name) | SimpleEnemyDefinition? | Get enemy by display name, or null |
GetEnemyByCode(string code) | SimpleEnemyDefinition? | Get enemy by code, or null |
HasEnemy(string nameOrCode) | bool | Check if enemy exists by name or code |
GetEnemyEnumType() | Type | Get the generated enum type for type-safe access |
| Method | Returns | Description |
|---|---|---|
GetCategoryLabels() | string[] | Category labels (e.g., "Enemy Type", "Rank") |
GetCategoryEntries(int categoryIndex) | string[] | Entries for a specific category |
GetFlagNames() | string[] | Flag names (e.g., "Is Boss", "Is Flying") |
GetNumericNames() | string[] | Numeric property names (e.g., "HP", "Attack Power") |
GetTextNames() | string[] | Text property names (e.g., "Lore", "Weakness Hints") |
| Method | Returns | Description |
|---|---|---|
GetEnemiesByCategory(int categoryIndex, int entryIndex) | SimpleEnemyDefinition[] | All enemies with specified category value |
GetEnemiesByFlag(int flagIndex, bool value) | SimpleEnemyDefinition[] | All enemies with specified flag value |
GetEnemiesByNumericRange(int numericIndex, float min, float max) | SimpleEnemyDefinition[] | All enemies with numeric in range |
| Field | Type | Description |
|---|---|---|
enemyCode | string | Enemy code referencing the enemy database |
countMin | int | Minimum spawn count for this slot |
countMax | int | Maximum spawn count (same as min for fixed count) |
level | int | Level override (-1 = use enemy default) |
| Field | Type | Description |
|---|---|---|
code | string | Unique code identifier (e.g., "GOBLIN_PATROL") |
name | string | Display name (e.g., "Goblin Patrol") |
description | string | Description of this squad |
slots | SimpleSquadSlot[] | Enemy slots in this squad |
categoryValues | int[] | Category values (aligned with database definitions) |
flagValues | bool[] | Flag values (aligned with database definitions) |
numericValues | float[] | Numeric values (aligned with database definitions) |
textValues | string[] | Text values (aligned with database definitions) |
| Method / Property | Returns | Description |
|---|---|---|
SquadCount | int | Total number of squads in database |
GetAllSquads() | SimpleSquadGroup[] | Get all squad groups |
GetSquadByCode(string code) | SimpleSquadGroup? | Get squad by code, or null |
GetSquadsContainingEnemy(string enemyCode) | SimpleSquadGroup[] | All squads containing a specific enemy |
GetCategoryLabels() | string[] | Category labels |
GetCategoryEntries(int categoryIndex) | string[] | Entries for a category |
GetFlagNames() | string[] | Flag names |
GetNumericNames() | string[] | Numeric property names |
GetTextNames() | string[] | Text property names |
Spawn tables use a 4-level hierarchy: Table › Pool › Entry › Condition.
| Field | Type | Description |
|---|---|---|
code | string | Unique code identifier (e.g., "FOREST_SPAWNS") |
name | string | Display name |
description | string | Description of this spawn table |
pools | SimpleSpawnPool[] | Pools in this spawn table |
| Field | Type | Description |
|---|---|---|
name | string | Display name (e.g., "Main Wave", "Reinforcements") |
rollCountMin | int | Minimum number of rolls from this pool |
rollCountMax | int | Maximum number of rolls from this pool |
rollChance | float | Chance (0-100) that this pool activates |
conditions | SimpleSpawnCondition[] | Conditions that must be met for this pool to activate |
entries | SimpleSpawnEntry[] | Entries in this pool |
| Field | Type | Description |
|---|---|---|
squadCode | string | Squad code (empty if single enemy) |
singleEnemyCode | string | Enemy code (empty if squad) |
weight | float | Base weight for selection (higher = more likely) |
conditions | SimpleSpawnCondition[] | Conditions for this entry to be included |
weightModifiers | SimpleSpawnWeightModifier[] | Modifiers that adjust weight based on context |
IsSquad | bool | True if this entry references a squad group |
| Field | Type | Description |
|---|---|---|
logic | SimpleSpawnConditionLogic | Logic operator for chaining (None for first condition) |
sourceType | SimpleSpawnConditionSourceType | What type of definition to check (Category, Flag, Numeric) |
sourceIndex | int | Index into the relevant definition array |
comparison | SimpleSpawnConditionComparison | How to compare the value |
value | float | Value to compare against |
| Enum | Values |
|---|---|
SimpleSpawnConditionSourceType | Category, Flag, Numeric |
SimpleSpawnConditionComparison | Equals, NotEquals, GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual |
SimpleSpawnConditionLogic | None, And, Or, Not |
| Field | Type | Description |
|---|---|---|
numericIndex | int | Which numeric definition affects weight |
modifierType | SimpleSpawnWeightModifierType | FlatPerPoint or PercentPerPoint |
value | float | Value per point (flat or percent) |
| Method / Property | Returns | Description |
|---|---|---|
SpawnTableCount | int | Total number of spawn tables |
GetSpawnTables() | SimpleSpawnTable[] | Get all spawn tables |
GetSpawnTable(string code) | SimpleSpawnTable? | Get spawn table by code, or null |
GetSpawnTableCodes() | string[] | Get all spawn table codes |
GetContextCategoryLabels() | string[] | Context category labels (e.g., "Biome", "Time of Day") |
GetContextCategoryEntries(int categoryIndex) | string[] | Entries for a context category |
GetContextFlagNames() | string[] | Context flag names (e.g., "Is Night", "Is Raining") |
GetContextNumericNames() | string[] | Context numeric names (e.g., "Player Level", "Difficulty") |
GetTablesContainingEnemy(string enemyCode) | SimpleSpawnTable[] | All tables that can spawn a specific enemy |
GetTablesContainingSquad(string squadCode) | SimpleSpawnTable[] | All tables that reference a specific squad |
| Value | Formula |
|---|---|
None | No scaling — use base value as-is |
Linear | base + (level - 1) * increment |
Percentage | base * (1 + (level - 1) * percentage / 100) |
Exponential | base * pow(exponentialBase, level - 1) |
Curve | base + curve.Evaluate(level / maxLevel) * curveMultiplier |
Step | Direct lookup table — at level X, value is Y |
| Field | Type | Description |
|---|---|---|
level | int | Level at which this value takes effect |
value | float | The value to use at this level (absolute, not additive) |
| Field | Type | Description |
|---|---|---|
numericIndex | int | Index into the enemy database's numeric definitions |
scalingType | SimpleScalingType | How this rule scales the value |
increment | float | Value added per level (Linear) |
percentage | float | Percent increase per level (Percentage) |
exponentialBase | float | Base of exponentiation (Exponential) |
curve | AnimationCurve | Curve mapping normalized level to a multiplier (Curve) |
curveMultiplier | float | Scales the curve output (Curve) |
curveMaxLevel | int | Maximum level for curve normalization (Curve) |
steps | SimpleScalingStep[] | Level-value lookup table (Step) |
| Field | Type | Description |
|---|---|---|
code | string | Unique code for this profile (often matches an enemy code) |
name | string | Display name |
description | string | Optional description |
rules | SimpleScalingRule[] | Scaling rules for individual numeric properties |
| Method / Property | Returns | Description |
|---|---|---|
ProfileCount | int | Total number of scaling profiles |
GetScalingProfiles() | SimpleScalingProfile[] | Get all scaling profiles |
GetScalingProfile(string code) | SimpleScalingProfile? | Get profile by code, or null |
GetProfileCodes() | string[] | Get all profile codes |
GetNumericNames() | string[] | Numeric property names (copied from linked enemy DB) |
GetScaledValue(string profileCode, int numericIndex, float baseValue, int level) | float | Compute scaled value for a specific profile, property, and level |
Wave data uses a 3-level hierarchy: Sequence › Wave › Entry.
| Field | Type | Description |
|---|---|---|
enemyCode | string | Single enemy code (empty if using squad) |
squadCode | string | Squad code (empty if using single enemy) |
count | int | How many to spawn from this entry |
spawnDelay | float | Delay in seconds between each individual spawn |
startTime | float | Time offset from the start of the wave |
IsSquad | bool | True if this entry references a squad group |
| Field | Type | Description |
|---|---|---|
name | string | Display name (e.g., "Wave 1", "Boss Wave") |
preDelay | float | Delay before this wave starts spawning |
postDelay | float | Delay after this wave finishes before the next begins |
entries | SimpleWaveEntry[] | Entries to spawn during this wave |
| Field | Type | Description |
|---|---|---|
code | string | Unique code identifier (e.g., "FOREST_WAVES") |
name | string | Display name |
description | string | Description of this wave sequence |
waves | SimpleWave[] | Ordered waves in this sequence |
loopAfterLast | bool | Whether to loop after the last wave |
difficultyScalePerLoop | float | Difficulty multiplier per loop (e.g., 1.2 = 20% harder) |
maxLoops | int | Maximum loop iterations (0 = infinite) |
categoryValues | int[] | Category values (aligned with database definitions) |
flagValues | bool[] | Flag values |
numericValues | float[] | Numeric values |
textValues | string[] | Text values |
| Method / Property | Returns | Description |
|---|---|---|
WaveSequenceCount | int | Total number of wave sequences |
GetWaveSequences() | SimpleWaveSequence[] | Get all wave sequences |
GetWaveSequence(string code) | SimpleWaveSequence? | Get sequence by code, or null |
GetWaveSequenceCodes() | string[] | Get all sequence codes |
GetCategoryLabels() | string[] | Category labels for wave sequences |
GetCategoryEntries(int categoryIndex) | string[] | Entries for a category |
GetFlagNames() | string[] | Flag names |
GetNumericNames() | string[] | Numeric property names |
GetTextNames() | string[] | Text property names |
GetSequencesContainingEnemy(string enemyCode) | SimpleWaveSequence[] | All sequences containing a specific enemy |
GetSequencesContainingSquad(string squadCode) | SimpleWaveSequence[] | All sequences containing a specific squad |
| Field | Type | Description |
|---|---|---|
conditions | SimpleSpawnCondition[] | Conditions that must be satisfied to trigger this rule (reuses spawn condition system) |
actionCode | string | User-defined action code (e.g., "HEAL", "FLEE", "AOE_ATTACK") |
priority | int | Priority for conflict resolution — higher wins |
cooldown | float | Minimum seconds between triggers of this rule |
| Field | Type | Description |
|---|---|---|
code | string | Unique code for this profile (e.g., "AGGRESSIVE_MELEE") |
name | string | Display name |
description | string | Description of this profile's behavior pattern |
rules | SimpleBehaviorRule[] | Behavior rules evaluated in priority order |
categoryValues | int[] | Category values (aligned with database definitions) |
flagValues | bool[] | Flag values |
numericValues | float[] | Numeric values |
textValues | string[] | Text values |
| Method / Property | Returns | Description |
|---|---|---|
ProfileCount | int | Total number of behavior profiles |
GetBehaviorProfiles() | SimpleBehaviorProfile[] | Get all behavior profiles |
GetBehaviorProfile(string code) | SimpleBehaviorProfile? | Get profile by code, or null |
GetProfileCodes() | string[] | Get all profile codes |
GetProfileNames() | string[] | Get all profile display names |
| Value | Int | Description |
|---|---|---|
Hostile | -2 | Factions are openly hostile — attack on sight |
Unfriendly | -1 | Factions are unfriendly — may attack under certain conditions |
Neutral | 0 | Factions are neutral — no inherent behavior |
Friendly | 1 | Factions are friendly — will not attack, may cooperate |
Allied | 2 | Factions are allied — fully cooperative |
| Field | Type | Description |
|---|---|---|
code | string | Unique code identifier (e.g., "UNDEAD") |
name | string | Display name (e.g., "The Undead") |
description | string | Optional description of the faction |
color | Color | Optional color for editor display |
categoryValues | int[] | Category values |
flagValues | bool[] | Flag values |
numericValues | float[] | Numeric values |
textValues | string[] | Text values |
| Method / Property | Returns | Description |
|---|---|---|
FactionCount | int | Total number of factions |
GetFactionNames() | string[] | Get all faction names |
GetFactionCodes() | string[] | Get all faction codes |
GetFactionDefinitions() | SimpleFactionDefinition[] | Get all faction definitions |
| Method | Returns | Description |
|---|---|---|
GetRelationship(string codeA, string codeB) | SimpleFactionStance | Get stance between two factions by code |
GetRelationship(int indexA, int indexB) | SimpleFactionStance | Get stance between two factions by index |
IsHostile(string codeA, string codeB) | bool | Check if stance is Hostile |
IsAllied(string codeA, string codeB) | bool | Check if stance is Allied |
IsFriendly(string codeA, string codeB) | bool | Check if stance is Friendly or higher |
GetHostileFactions(string factionCode) | string[] | All factions hostile to the specified faction |
GetAlliedFactions(string factionCode) | string[] | All factions allied with the specified faction |
| Method | Returns | Description |
|---|---|---|
GetCategoryLabels() | string[] | Category labels |
GetCategoryEntries(int categoryIndex) | string[] | Entries for a category |
GetFlagNames() | string[] | Flag names |
GetNumericNames() | string[] | Numeric property names |
GetTextNames() | string[] | Text property names |
SEF includes 7 static/instance helper classes for common runtime operations. These are ready to use out of the box — no code generation needed.
Rolls spawn tables using weighted random selection. Evaluates pool conditions, computes effective weights with modifiers, and performs with-replacement random selection.
| Method | Returns | Description |
|---|---|---|
Roll(SimpleSpawnTable table, SimpleSpawnContext context) | SpawnRollResult[] | Roll using Unity's Random |
Roll(SimpleSpawnTable table, SimpleSpawnContext context, System.Random rng) | SpawnRollResult[] | Roll with a specific RNG (for deterministic results) |
RollPool(SimpleSpawnPool pool, int poolIndex, SimpleSpawnContext context, System.Random rng) | SpawnRollResult[] | Roll a single pool |
SpawnRollResult struct fields: enemyCode, squadCode,
IsSquad, sourcePoolName, poolIndex, entryIndex.
Holds current game state values for evaluating spawn and behavior conditions. Includes a fluent builder pattern for easy construction.
| Method | Returns | Description |
|---|---|---|
SimpleSpawnContext.Create(int cats, int flags, int nums) | SimpleSpawnContextBuilder | Start building with fluent API |
builder.SetCategory(int index, int value) | SimpleSpawnContextBuilder | Set a category value |
builder.SetFlag(int index, bool value) | SimpleSpawnContextBuilder | Set a flag value |
builder.SetNumeric(int index, float value) | SimpleSpawnContextBuilder | Set a numeric value |
builder.Build() | SimpleSpawnContext | Build the final context |
Static building blocks for spawn condition evaluation and weight computation. Used internally
by SimpleSpawnRoller and available for custom spawn logic.
| Method | Returns | Description |
|---|---|---|
EvaluateSingleCondition(condition, context) | bool | Evaluate a single condition |
EvaluateConditions(conditions[], context) | bool | Evaluate a chain of conditions with And/Or/Not logic |
ComputeEffectiveWeight(entry, context) | float | Compute weight after conditions and modifiers (0 = excluded) |
IsPoolActive(pool, context) | bool | Check if a pool's conditions are met |
GetEligibleEntries(pool, context) | (int entryIndex, float weight)[] | Get all entries with weight > 0 |
Static utility for computing scaled values from scaling rules and profiles.
| Method | Returns | Description |
|---|---|---|
ComputeScaledValue(rule, baseValue, level) | float | Apply a single scaling rule to a base value |
ScaleAllNumerics(profile, baseValues[], level) | float[] | Scale an entire array of numeric values |
ScaleEnemy(enemy, profile, level) | float[] | Scale an enemy's numeric values by profile and level |
ScaleEnemy(enemy, profile, level, difficultyMultiplier) | float[] | Scale with additional difficulty multiplier |
Pure C# event-driven wave runner. Not a MonoBehaviour — you call Update(deltaTime)
from your own game loop. Drives a state machine through PreDelay, Spawning, PostDelay phases
with loop support.
| Event | Signature | Description |
|---|---|---|
OnWaveStarted | Action<int, string> | Fires when a wave begins spawning (waveIndex, waveName) |
OnSpawnRequested | Action<WaveSpawnRequest> | Fires once per individual spawn |
OnWaveCompleted | Action<int, string> | Fires when all entries in a wave are dispatched |
OnSequenceCompleted | Action | Fires when the entire sequence is done |
OnLoopStarted | Action<int, float> | Fires when the sequence loops (loopCount, difficultyScale) |
OnPaused | Action | Fires when the runner is paused |
OnResumed | Action | Fires when the runner is resumed |
| Method | Description |
|---|---|
Start(SimpleWaveSequence sequence) | Start running a wave sequence from the beginning |
Update(float deltaTime) | Drive the state machine forward (call each frame) |
Pause() | Pause the runner |
Resume() | Resume after pausing |
Stop() | Stop and reset to idle |
SkipToWave(int waveIndex) | Skip to a specific wave |
SkipCurrentWave() | Skip remaining spawns in current wave |
| Property | Type | Description |
|---|---|---|
Phase | WaveRunnerPhase | Current phase (Idle, PreDelay, Spawning, PostDelay, Complete) |
CurrentWaveIndex | int | Index of the current wave |
LoopCount | int | Completed loop iterations (0 on first play-through) |
CurrentDifficultyScale | float | Cumulative difficulty scale (starts at 1.0) |
ElapsedTime | float | Total elapsed time since Start (paused time excluded) |
IsPaused | bool | True if paused |
IsRunning | bool | True if actively running |
IsComplete | bool | True if sequence has completed |
Static methods for evaluating behavior profile rules against a spawn context. Uses the same condition system as spawn tables.
| Method | Returns | Description |
|---|---|---|
EvaluateProfile(profile, context) | SimpleBehaviorRule[] | All matching rules, sorted by priority descending |
EvaluateBestAction(profile, context) | SimpleBehaviorRule? | Highest-priority matching rule, or null |
GetMatchingActionCodes(profile, context) | string[] | Action codes for all matching rules |
Static convenience methods for faction relationship queries beyond what
ISimpleFactionDataSource provides directly.
| Method | Returns | Description |
|---|---|---|
IsHostileOrUnfriendly(db, codeA, codeB) | bool | True if stance is less than Neutral |
IsFriendlyOrAllied(db, codeA, codeB) | bool | True if stance is greater than Neutral |
GetStanceValue(db, codeA, codeB) | int | Numeric stance value (-2 to 2) |
GetNonHostileFactions(db, factionCode) | string[] | All faction codes that are NOT hostile |
GetFactionsWithStance(db, factionCode, stance) | string[] | All factions with the exact specified stance |
All runtime types in the SimpleEnemyForge namespace:
Solutions for common issues you may encounter when using Simple Enemy Forge.
Simple Enemy Forge uses a two-phase generation process. The first phase writes
C# scripts, triggers AssetDatabase.Refresh(), and waits for Unity's domain reload.
The second phase (triggered by [InitializeOnLoad]) creates the asset from a
temporary JSON file.
[Simple Enemy Forge]
prefix. If the first phase fails, the second phase never runs.SessionState flags
(e.g., SimpleEnemyForge_WaitingForCompilation). If Unity crashes during
compilation, the flag may be left in a stale state. Restarting Unity clears SessionState.using SimpleEnemyForge;. Make sure the Simple Enemy Forge Runtime assembly
definition is accessible from your generated code's location.{Prefix}Type.cs, {Prefix}Database.cs, and
{Prefix}DatabaseEditor.cs files, then generate again from the wizard.{Prefix}DatabaseEditor.cs
exists in the same folder as the database asset (or in an Editor folder).Reimport to force Unity to pick up the [CustomEditor] attribute."enemies" for Enemy Forge,
"squads" for Squad Forge). AI-generated JSON sometimes uses different field names.code
and name field. Items missing these are skipped."definitions" section that tells the AI which property names to use. If definitions
are missing, the AI may guess wrong field names (e.g., "name" instead of "label" for categories,
"options" instead of "entries").```json ... ```), remove the markdown markers before importing.true/false, not
0/1 or "yes"/"no".ISimpleEnemyDataSource. If you manually created
a ScriptableObject without implementing the interface, it won't be detected.{Prefix}DatabaseEditor.cs
must be in an Editor folder or an Editor assembly. If it was accidentally deleted, regenerate from
the wizard.For SAF-specific troubleshooting (detection issues, modifier references, safe round-trip), see the dedicated SAF Integration Troubleshooting section.
For SIF-specific troubleshooting (loot table linking, detection lifecycle, safe round-trip), see the dedicated SIF Integration Troubleshooting section.
All Simple Enemy Forge log messages are prefixed with [Simple Enemy Forge].
Filter the Console by this prefix to see only SEF-related messages, warnings, and errors.
Generation progress, import results, and detection events are all logged here.
Before making major changes to your property definitions or trying a new template, export your current schema as Full JSON. This gives you a complete backup that can be re-imported if something goes wrong. The JSON file contains all definitions and data.
Start with Enemy Forge alone. Once your enemy database is working, add Squad Forge, then Spawn Forge, and so on. This makes it easier to identify which forge is causing an issue. Each forge can be generated independently.
SEF includes 6 genre templates (RPG, Soulslike, Survival, Tower Defense, Sci-Fi, RTS) with pre-configured property definitions. Start with a template and customize from there rather than building from scratch. Templates populate all 7 forges at once.
Simple Enemy Forge is a Unity Editor tool for creating ScriptableObject-based enemy databases using a wizard-driven workflow. It follows the same architecture as Simple Item Forge — dynamic property systems, two-phase code generation, schema export/import, and optional integration bridges.
Define your enemies once in Enemy Forge, then reference them across all other forges. Squads group enemies into formations. Spawn tables select enemies by weight. Scaling profiles adjust enemy stats by level. Wave sequences orchestrate enemies over time. Behavior profiles define enemy AI rules. Factions organize enemies with relationship matrices. One enemy code threads through the entire system.
There are no built-in HP, ATK, DEF, or Speed fields. You define your own property schema using Categories, Flags, Numerics, and Texts. A survival game might have Hunger and Temperature. A tower defense game might have Lane Preference and Armor Type. Your game, your properties — nothing is assumed.
Generated databases are plain ScriptableObjects with struct arrays and enum types. No reflection at runtime, no dictionary lookups for basic access, no MonoBehaviour overhead. The runtime types are simple serializable structs that Unity handles natively. The only classes are the database ScriptableObjects themselves and the wave runner.
Every forge supports schema export in 3 formats (Full JSON, Light JSON, Markdown) with detailed instructions that AI models can follow to generate hundreds of enemies, squads, spawn tables, and more. Import the AI's output directly back into your wizard with validation and 3-choice conflict resolution.
Seven interconnected wizards, each generating a complete database system:
| Wizard | Steps | Purpose | Depends On |
|---|---|---|---|
| Enemy Forge | 5 | Define enemies with dynamic properties, icons, descriptions, and optional SAF/SIF links | None (foundation) |
| Squad Forge | 4 | Group enemies into named squad configurations with slots, counts, and level overrides | Enemy DB |
| Spawn Forge | 5 | Design spawn tables with weighted pools, conditions, and weight modifiers | Enemy DB, Squad DB (optional) |
| Scaling Forge | 3 | Create scaling profiles that adjust enemy numerics by level using 5 formula types | Enemy DB |
| Wave Forge | 5 | Build wave sequences that orchestrate enemies and squads with timing and loop settings | Enemy DB, Squad DB (optional) |
| Behavior Forge | 4 | Define behavior profiles with condition-action rules, priorities, and cooldowns | None (uses own context definitions) |
| Faction Forge | 4 | Organize factions with an N×N symmetric relationship matrix and dynamic properties | None (standalone) |
Instead of hardcoded fields, SEF uses four dynamic property types that you define in Step 2 (Definitions) of each wizard:
| Type | Definition | Storage | Example |
|---|---|---|---|
| Categories | Label, entries list, allow multiple selection | int[] categoryValues |
Enemy Type (Undead, Beast, Demon), Rank (Minion, Elite, Boss), Element (Fire, Ice, Lightning) |
| Flags | Name, default value | bool[] flagValues |
Is Boss, Is Flying, Is Undead, Has Ranged Attack, Is Immune to Stun |
| Numerics | Name, isRange, isInteger, min/max limits | float[] numericValues |
HP, Attack Power, Defense, Aggro Range, Movement Speed, XP Reward |
| Texts | Name, line count | string[] textValues |
Lore, Weakness Hints, Flavor Text, AI Notes, Death Dialogue |
Each forge generates 3 C# files and 1 asset file:
| File | Description |
|---|---|
{Prefix}Type.cs |
Enum with one entry per item code (e.g., MyEnemyType.GOBLIN_WARRIOR).
Provides compile-time safety and IDE autocomplete. |
{Prefix}Database.cs |
ScriptableObject class implementing the data source interface (e.g.,
ISimpleEnemyDataSource). Contains the data arrays, metadata arrays for
property definitions, and all interface method implementations. |
{Prefix}DatabaseEditor.cs |
Custom Inspector editor with split-panel layout, search, sort, filter, multi-select, bulk operations, and pagination. Generated in the Editor assembly. |
{Prefix}Database.asset |
The actual ScriptableObject asset file containing all your data. Created in phase 2 of the two-phase generation process. |
AssetDatabase.Refresh() to trigger Unity's domain reload. Phase 2 runs via
[InitializeOnLoad] after recompilation, reads the data from a temporary JSON file
stored via SessionState, and creates the .asset file.
Simple Enemy Forge integrates with two optional companion packages using reflection bridges — never conditional compilation or hard assembly references:
When SAF is detected in the project, enemy definitions gain a modifiers array
for attaching SAF attribute modifier ScriptableObjects. This is a modifier-only
integration (lighter than SIF's full attribute system). Useful for buffs, debuffs, auras, and
stat modifications that enemies apply during combat.
When SIF is detected, enemy definitions gain lootTableCode and
lootTable fields for linking to SIF loot tables. This lets enemies reference
drop tables so they can drop items on defeat. The bridge uses ISimpleLootDataSource
detection via reflection.
Every forge generates a professional custom Inspector editor alongside the database. These editors share a consistent feature set:
livingfailuregames@gmail.com