View Format: Multi-Page Single Page

Simple Enemy Forge

Generate Complete Enemy Databases, Squads, Spawn Tables, and More in Minutes.

Table of Contents

Installation

Prerequisites, importing from the Asset Store, folder structure, and menu items.

Quick Start

Create your first enemy database in under 5 minutes with a step-by-step walkthrough.

Enemy Forge

The core wizard — define dynamic properties, build enemies, generate databases.

Squad Forge

Group enemies into squads with slot assignments, count ranges, and level overrides.

Spawn Forge

Build weighted spawn tables with conditional pools and context-driven entry selection.

Scaling Forge

Define how enemy stats change with level or difficulty using 6 scaling types.

Wave Forge

Design multi-wave sequences with enemy/squad spawning, timing, and loop settings.

Behavior Forge

Create priority-based condition-action rules for enemy AI behavior profiles.

Faction Forge

Define factions with an N×N relationship matrix and 5 stance levels.

AI Workflow

Export schemas for AI-powered content generation. Import hundreds of entries in one click.

Templates

6 genre templates (RPG, Soulslike, Survival, Tower Defense, Sci-Fi, RTS) with pre-configured properties.

SAF Integration

Optional integration with Simple Attribute Forge for modifier references on enemies.

SIF Integration

Optional integration with Simple Item Forge for loot table linking on enemies.

Demos

Interactive 7-tab Bestiary Demo showcasing all forge outputs at runtime.

API Reference

Complete reference for all runtime types, interfaces, and helper classes.

Troubleshooting

Solutions for common issues with generation, schema import, wizards, and integrations.

About

Design philosophy, version history, and support information.


Installation

Get Simple Enemy Forge set up in your Unity project.

Prerequisites

Import from Unity Asset Store

1 Open Package Manager
In Unity, go to Window → Package Manager
2 Find Simple Enemy Forge
Switch to My Assets, search for "Simple Enemy Forge", and click Import
3 Verify Installation
Check that the menu items appear: Window → Simple Enemy Forge

Folder Structure

Assets/ └─ Simple Enemy Forge/ ├─ Runtime/ │ ├─ SimpleEnemyDefinition.cs │ ├─ ISimpleEnemyDataSource.cs │ ├─ SimpleEnemyEnums.cs │ ├─ SimpleSquadGroup.cs │ ├─ SimpleSpawnTable.cs │ ├─ SimpleScalingProfile.cs │ ├─ SimpleWaveDefinition.cs │ ├─ SimpleBehaviorProfile.cs │ ├─ SimpleFactionData.cs │ └─ ... ├─ Editor/ │ └─ Wizards/ │ ├─ EnemyDataWizard.cs │ ├─ SquadDataWizard.cs │ ├─ SpawnDataWizard.cs │ ├─ ScalingDataWizard.cs │ ├─ WaveDataWizard.cs │ ├─ BehaviorDataWizard.cs │ ├─ FactionDataWizard.cs │ └─ ... └─ Documentation/ └─ (you are here)

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

SAF Integration (Optional)

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.

SIF Integration (Optional)

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.

Next Steps

You're all set! Head to the Quick Start Guide to create your first enemy database in under 5 minutes.


Quick Start Guide

Create a complete enemy database in under 5 minutes. This walkthrough covers the Enemy Forge wizard — the foundation everything else builds on.

Prerequisites

5-Step Creation Process

1 Open the Enemy Forge Wizard
Navigate to Window → Simple Enemy Forge → Enemy Forge Wizard
2 Step 1 — Setup
Enter a Class Prefix (e.g., "DarkFantasy") and optionally select a genre template to pre-populate definitions. Templates include Generic RPG, Soulslike, Survival Horror, Tower Defense, Sci-Fi Shooter, and RTS/Strategy.
3 Step 2 — Definitions
Define your property system. Add Categories (dropdowns like Enemy Type, Rank), Flags (toggles like Is Boss, Is Flying), Numerics (numbers like HP, ATK, DEF, Aggro Range), and Texts (strings like Lore, Weakness Hints).
4 Step 3 — Enemies
Create enemies using the properties you defined. Each enemy has a name, code (auto-generated), description, icon, and values for all your categories, flags, numerics, and texts.
5 Step 4 — Settings
Choose output paths for generated scripts and the database asset.
6 Step 5 — Generate
Click Generate. The wizard creates your enum, database class, custom editor, and database asset automatically.

Example: Dark Fantasy Enemies

Step 1: Setup

Basic Settings

  • Class Prefix: "DarkFantasy"
  • Template: Generic RPG

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.

Step 3: Create Enemies

Goblin Warrior

  • Code: GOBLIN_WARRIOR
  • Enemy Type: Humanoid
  • Rank: Common
  • HP: 50
  • ATK: 8

Shadow Wraith

  • Code: SHADOW_WRAITH
  • Enemy Type: Undead
  • Rank: Elite
  • HP: 200
  • ATK: 25
  • Is Flying: true

Dragon Lord

  • Code: DRAGON_LORD
  • Enemy Type: Dragon
  • Rank: Boss
  • HP: 5000
  • ATK: 150

Step 5: Generate

After generation, your project will contain:

Assets/ └─ Generated/ ├─ Enemies/Scripts/ │ ├─ DarkFantasyType.cs (enum) │ ├─ DarkFantasyDatabase.cs (ScriptableObject) │ └─ Editor/ │ └─ DarkFantasyDatabaseEditor.cs (custom inspector) └─ Enemies/Data/ └─ DarkFantasyDatabase.asset (your data)

Using Your Enemies in Code

Access Your Data

The generated database ScriptableObject provides methods to access your enemies:

using UnityEngine; public class EnemySpawner : MonoBehaviour { [SerializeField] DarkFantasyDatabase enemyDatabase; void Start() { // Get enemy by code string var goblin = enemyDatabase.GetEnemyByCode("GOBLIN_WARRIOR"); // Get enemy by enum var dragon = enemyDatabase.GetEnemy(DarkFantasyType.DRAGON_LORD); // Access dynamic properties by index float hp = dragon.numericValues[0]; string lore = dragon.textValues[0]; } }

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].

Interface-Based (Generic Code)

// Works with any enemy database through the interface ISimpleEnemyDataSource source = enemyDatabase; // Filter enemies by category var undead = source.GetEnemiesByCategory(0, 1); // Category 0 (Enemy Type), Entry 1 (Undead) // Filter by flag var bosses = source.GetEnemiesByFlag(2, true); // Flag 2 (Is Boss) = true // Filter by numeric range var tanky = source.GetEnemiesByNumericRange(0, 500f, 99999f); // Numeric 0 (HP) >= 500

The Generated Custom Editor

Your database asset gets a professional custom Inspector with:

No more "Element 0", "Element 1" in the Inspector. Every property shows its real name.

Next Steps

AI-Powered Content

Export a schema, paste it into ChatGPT or Claude, and import hundreds of enemies in one click.

AI Workflow →

Build Squads

Group enemies into squads with slot assignments and formation data.

Squad Forge →

Design Spawn Tables

Create weighted spawn pools with conditions and context-sensitive logic.

Spawn Forge →

Add Scaling

Define how enemy stats scale with level, difficulty, or any custom parameter.

Scaling Forge →
Congratulations! You now have a fully functional enemy database with type-safe enum access and a generated custom Inspector editor. The enemies you define here are the foundation for squads, spawn tables, scaling profiles, wave sequences, behavior rules, and factions.

Enemy Forge Wizard

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.

The 5 Steps: Setup → Definitions → Enemies → Settings → Generate. You can move freely between steps using the tab bar at the top of the wizard window. Each step validates its input and shows errors or warnings in real time.

The Dynamic Property System

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.

How it works at runtime: The generated database includes metadata arrays (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.

Genre Templates

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.

Switching templates clears existing definitions. When you select a new template, a confirmation dialog appears before any definitions are replaced. If you select "None (Start Fresh)" while definitions exist, you will be asked "Clear Definitions?" before proceeding. If you have already customized your properties, consider exporting your schema first as a backup before switching templates.

Step 1 — Setup

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.

Database Configuration

1 Database Name
A unique name for your enemy database (e.g., "GameEnemies", "DungeonMonsters"). This must be a valid C# identifier — letters, numbers, and underscores only, and it cannot start with a number. The database name is used as part of the generated asset file name.
2 Namespace (Optional)
The C# namespace for all generated scripts (e.g., "MyGame.Enemies", "RPG.Monsters"). If provided, all generated classes and enums are wrapped in this namespace, which is useful for avoiding naming collisions in larger projects. Leave this empty to generate code in the global namespace.
3 Class Prefix
A short identifier (e.g., "Dungeon", "Fantasy", "SciFi") used to name all generated files. This prefix is prepended to every generated class name using PascalCase. For example, a prefix of Dungeon generates DungeonType.cs, DungeonDatabase.cs, Editor/DungeonDatabaseEditor.cs, and DungeonDatabase.asset. The prefix must be a valid C# identifier if provided.
4 Append Suffix
A toggle that inserts a configurable suffix between the class prefix and the type name. When enabled, a text field appears where you can type the suffix (e.g., "Enemy"). With prefix 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.

Template Selection

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.

Simple Attribute Forge Integration

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).

SAF integration is lighter for enemies than for items. Unlike Simple Item Forge (which uses SAF for requirements, stat bonuses, and modifiers), Simple Enemy Forge only uses SAF for modifier references. Enemies do not have attribute requirements or stat bonuses — they have their own dynamic numeric properties for stats like HP, Attack, and Defense. For full details on the detection lifecycle and what happens when SAF is installed or removed, see the SAF Integration page.

Simple Item Forge Integration

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.

SIF is optional. The Enemy Forge works perfectly without Simple Item Forge installed. You can add or remove SIF at any time with zero compilation errors. The loot table fields are always present in the runtime struct but only populated when SIF is linked. For full details, see the SIF Integration page.

Generated Names Preview

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.

Validation

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.

Step 2 — Definitions

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

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:

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

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:

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.

Numerics

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:

Texts

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:

Schema Export / Import

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.

Schema version: The current schema version is 1.0. The schema includes a 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).

Step 3 — Enemies

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.

Left Panel — Enemy List (280px)

The left panel contains the master enemy list with several tools above it:

Right Panel — Enemy Editor

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:

Identity Section (Always Visible)

Categories Section (Collapsible)

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.

Flags Section (Collapsible)

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.

Numerics Section (Collapsible)

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)").

Texts Section (Collapsible)

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)").

Placeholders in the generated editor: In the generated custom editor (the Inspector for the ScriptableObject), all four property sections display a placeholder message when their definition count is zero: "No categories defined.", "No flags defined.", "No numeric properties defined.", and "No text properties defined." In the wizard, only the Flags section shows a placeholder.

SAF Modifiers Section (Collapsible, Conditional in Wizard)

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.

SIF Loot Table Section (Collapsible, Conditional in Wizard)

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:

Duplicate Enemy Button

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.

Inline Validation

The editor validates enemies in real time and shows colored messages directly in the enemy editor:

Step 4 — Settings

Configure where generated files are saved and which files to generate. This step gives you full control over your project's file organization.

Output Paths

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.

Code Generation Options

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).
What is {GP}? GP stands for "Generated Prefix." It is derived from the Class Prefix you set in Step 1. For example, a class prefix of "Dungeon" produces GP = "Dungeon". Files generated: DungeonType.cs, DungeonDatabase.cs, DungeonDatabase.asset, Editor/DungeonDatabaseEditor.cs.

File Preview

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.

Existing files will be overwritten. If files with the same names already exist at the target paths, the validation panel warns you. Generation always overwrites — it does not merge. If you have manually edited a generated file, consider backing it up before regenerating.

Step 5 — Generate

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.

Generation Summary

A read-only summary panel at the top shows all key metrics at a glance:

Files to Generate

A list of every file that will be created, with full paths. This is your final chance to review before committing.

Phase 1 — Script Generation

When you click Generate, the wizard writes all C# files to disk:

Scripts Path (e.g., Assets/Generated/Enemies/Scripts)/ ├── {GP}Type.cs — Enum of all enemy codes ├── {GP}Database.cs — ScriptableObject with all data └── Editor/ └── {GP}DatabaseEditor.cs — Custom Inspector editor Asset Path (e.g., Assets/Generated/Enemies/Data)/ └── {GP}Database.asset — Your data

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.

Phase 2 — Asset Creation (Automatic)

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.

Generate clicked → Write C# scripts to disk → Save wizard data to Temp/SimpleEnemyForge_PendingAssetData.json → Set SessionState flag → AssetDatabase.Refresh() → Unity compiles new scripts (domain reload — static state lost) → [InitializeOnLoad] detects flag + temp file → EditorApplication.delayCall (wait for Unity to be ready) → Retry loop checks if generated type is loaded (up to 10 retries) → CreateDatabaseAsset() — populate SO with all data → Cleanup: delete temp file, clear SessionState flag

Post-Generation Actions

After successful generation, the wizard displays action buttons:

Do not close Unity during generation. The two-phase process requires a domain reload to complete. The wizard persists its state to a temp JSON file and uses SessionState flags to survive the reload, but closing Unity mid-process will abort asset creation. The generated scripts will still be valid — you can manually create the ScriptableObject asset later.

What Gets Generated

The 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.

{GP}Type.cs — Enum

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.

public enum DungeonType { GoblinWarrior, SkeletonArcher, ShadowDrake, AncientLich, // ... one entry per enemy (PascalCase) }

{GP}Database.cs — ScriptableObject

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.).

Accessing Your Database in Code

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:

// Reference the database via a serialized field [SerializeField] private DungeonDatabase dungeonDatabase; // Access enemies by code string var goblin = dungeonDatabase.GetEnemyByCode("GOBLIN_WARRIOR"); Debug.Log(goblin.Value.name); // "Goblin Warrior" Debug.Log(goblin.Value.description); // "A ferocious goblin..." // Filter by category (e.g., category 0 = Enemy Type, entry 2 = Undead) var undead = dungeonDatabase.GetEnemiesByCategory(0, 2); // Filter by flag (e.g., flag 0 = Is Boss) var bosses = dungeonDatabase.GetEnemiesByFlag(0, true); // Filter by numeric range (e.g., numeric 0 = HP, range 500-2000) var toughEnemies = dungeonDatabase.GetEnemiesByNumericRange(0, 500f, 2000f); // Metadata: get property names string[] categoryLabels = dungeonDatabase.GetCategoryLabels(); string[] flagNames = dungeonDatabase.GetFlagNames(); string[] numericNames = dungeonDatabase.GetNumericNames();

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:

ISimpleEnemyDataSource db = dungeonDatabase; var allEnemies = db.GetEnemyDefinitions(); var codes = db.GetEnemyCodes(); bool hasEnemy = db.HasEnemy("SHADOW_DRAKE");

{GP}DatabaseEditor.cs — Custom Inspector

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.

Split Panel Layout

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, Sort & Filter

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.

Multi-Select & Bulk Operations

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).

Pagination

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.

All Properties By Name

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.

SAF & SIF Sections

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.

{GP}Database.asset — Data File

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.

Runtime Data Structures

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.

SimpleEnemyDefinition

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.

public struct SimpleEnemyDefinition { // Identity (always present) public string name; public string code; public string description; public Sprite icon; // Dynamic property arrays (indices match Step 2 definitions) public int[] categoryValues; // Single-select category indices public SimpleIntArray[] categoryMultiValues; // Multi-select category indices public bool[] flagValues; public float[] numericValues; // Single values (or min for ranges) public float[] numericRangeMaxValues; // Max values for range numerics public string[] textValues; // Simple Attribute Forge integration (always compiled; populated when SAF is linked) public ScriptableObject[] modifiers; // Simple Item Forge integration (always compiled; populated when SIF is linked) public string lootTableCode; public ScriptableObject lootTable; // Convenience accessors public int GetCategoryValue(int categoryIndex); public int[] GetCategoryMultiValues(int categoryIndex); public bool GetFlagValue(int flagIndex); public float GetNumericValue(int numericIndex); public (float min, float max) GetNumericRange(int numericIndex); public string GetTextValue(int textIndex); }

SimpleIntArray

A wrapper struct for serializing arrays of int, since Unity cannot serialize jagged arrays directly. Used by categoryMultiValues for multi-select categories.

public struct SimpleIntArray { public int[] values; public int Length => values?.Length ?? 0; public int this[int index] { get; } }

ISimpleEnemyDataSource

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.

Tips & Best Practices

Start With a Template

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.

Use AI for Bulk Content

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.

Auto-Generate Codes

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.

Design for the Ecosystem

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.

Use Flags for Combat Properties

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.

Keep Codes Unique and Descriptive

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.

Batch Icon Assignment

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.

Regenerate Freely

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).

The Enemy Forge is the foundation. 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 other six forges all link to your Enemy Database and validate enemy codes against it. Define your enemies once, use them everywhere.

Squad Forge

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.

Overview

Squad Forge is a 4-step wizard that walks you through the entire process:

Step 1 — Setup

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.

Step 2 — Definitions

Define custom properties for your squads — Categories, Flags, Numerics, and Texts. Export and import schemas for AI-assisted content generation.

Step 3 — Squads

Build squad compositions in a split-panel editor. Add enemy slots with codes, counts, and level overrides. Set dynamic property values per squad.

Step 4 — Generate

Generate the database ScriptableObject, type-safe enum, and a custom Inspector editor with search, sort, pagination, and bulk operations.

Step 1 — Setup

Link Enemy Databases

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:

Output Configuration

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

Step 2 — Definitions

Define custom properties that apply to squads (not individual enemies). These are separate from and independent of the properties defined in Enemy Forge.

Property Types

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.

Schema Export/Import

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.

Step 3 — Squads

The main builder step uses a split-panel layout:

Left Panel — Squad List

Right Panel — Squad Editor

Select a squad from the list to edit its details:

Identity

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

Slots

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)

Dynamic Properties

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.

Duplicating Squads

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.

Step 4 — Generate

Generation uses a two-phase process to handle Unity's domain reload:

1 Phase 1 — Generate Scripts
Writes the C# source files, then calls AssetDatabase.Refresh() to trigger compilation and domain reload.
2 Phase 2 — Create Asset
After domain reload, an [InitializeOnLoad] handler fires via delayCall, reads squad data from a temporary JSON file, and creates the ScriptableObject asset.

Generated Files

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

Runtime Data Structures

SimpleSquadSlot

public struct SimpleSquadSlot { public string enemyCode; // Enemy code referencing the enemy database public int countMin; // Minimum spawn count public int countMax; // Maximum spawn count (same as min for fixed) public int level; // Level override (-1 = use enemy default) }

SimpleSquadGroup

public struct SimpleSquadGroup { public string code; // Unique identifier (e.g., "GOBLIN_PATROL") public string name; // Display name public string description; // Optional description public SimpleSquadSlot[] slots; // Enemy slots in this squad // Dynamic properties (aligned with database metadata) public int[] categoryValues; public bool[] flagValues; public float[] numericValues; public string[] textValues; }

Interface Reference — ISimpleSquadDataSource

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

Usage Example

// Access squad data at runtime ISimpleSquadDataSource squadDB = mySquadDatabase as ISimpleSquadDataSource; // Get a specific squad SimpleSquadGroup? patrol = squadDB.GetSquadByCode("GOBLIN_PATROL"); if (patrol.HasValue) { Debug.Log($"Squad: {patrol.Value.name} has {patrol.Value.slots.Length} slots"); foreach (var slot in patrol.Value.slots) { int count = Random.Range(slot.countMin, slot.countMax + 1); Debug.Log($" Spawn {count}x {slot.enemyCode}"); } } // Find all squads containing a specific enemy SimpleSquadGroup[] withGoblin = squadDB.GetSquadsContainingEnemy("GOBLIN_WARRIOR"); Debug.Log($"Found {withGoblin.Length} squads containing Goblin Warrior");

Tips

Design Varied Compositions

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.

Use Count Ranges

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.

Reference Enemy Codes Carefully

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.

AI-Generated Squads

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

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.

Understanding the Spawn Table Hierarchy

Spawn tables use a three-level hierarchy. Each level adds its own logic:

Spawn Table: "Forest Enemies" | +-- Pool: "Common" (2-4 rolls, 100% chance) | +-- Entry: Goblin Warrior (weight: 40) | +-- Entry: Forest Spider (weight: 30) | +-- Entry: Wolf Pack [squad] (weight: 30) | +-- Pool: "Elite" (1 roll, 20% chance) | +-- Entry: Orc Chieftain (weight: 60) | +-- Entry: Dark Treant (weight: 40) | +-- Pool: "Boss" (1 roll, 5% chance, condition: PlayerLevel >= 10) +-- Entry: Forest Dragon (weight: 100)

How a Roll Works

1 Evaluate pool conditions against the current SimpleSpawnContext. If conditions fail, skip the entire pool.
2 Check rollChance (0-100%). If the roll fails, skip the pool.
3 Determine roll count — random between rollCountMin and rollCountMax.
4 For each roll, filter entries by their conditions, apply weight modifiers to eligible entries, then perform weighted random selection.

The Condition System

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.

SimpleSpawnCondition Fields

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)

Pool Conditions vs Entry Conditions

Condition Chaining

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:

// Example: Player Level >= 10 AND Biome == Forest Condition 1: logic=None, sourceType=Numeric, sourceIndex=0, comparison=GreaterThanOrEqual, value=10 Condition 2: logic=And, sourceType=Category, sourceIndex=0, comparison=Equals, value=2 // Example: Is Night OR Difficulty > 5 Condition 1: logic=None, sourceType=Flag, sourceIndex=0, comparison=Equals, value=1 Condition 2: logic=Or, sourceType=Numeric, sourceIndex=1, comparison=GreaterThan, value=5

Weight Modifier System

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.

Modifier Types

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).

Step 1 — Setup

Link Databases

Spawn Forge accepts two types of linked databases:

Output Configuration

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

Step 2 — Definitions

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.

Context Property Types

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.

Schema Export/Import

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.

Step 3 — Spawn Tables

The main builder step uses a split-panel layout with nested ReorderableLists:

Left Panel — Table List

Right Panel — Table Editor

Select a table from the list to edit:

Identity

Name, Code (with Auto toggle), and Description — same pattern as all forges.

Pools

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)

Entries

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

Step 4 — Settings

Configure generation output settings — output folder path and any additional generation options. This step also shows a summary of what will be generated.

Step 5 — Generate

Same two-phase generation as all forges:

1 Phase 1 — Generate Scripts
Writes C# source files, triggers AssetDatabase.Refresh() and domain reload.
2 Phase 2 — Create Asset
[InitializeOnLoad] handler creates the ScriptableObject from temporary JSON data.

Generated Files

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

Runtime Helpers

Spawn Forge includes three runtime helper classes for evaluating and rolling spawn tables in your game code:

SimpleSpawnContext

A struct that holds current game state values, aligned with your context definitions:

// Create a context matching your spawn database's definitions var context = new SimpleSpawnContext( categoryCount: 2, // e.g., Biome, TimeOfDay flagCount: 1, // e.g., IsNight numericCount: 2 // e.g., PlayerLevel, Difficulty ); // Set current game state context.categoryValues[0] = 1; // Biome = Desert context.categoryValues[1] = 3; // TimeOfDay = Night context.flagValues[0] = true; // IsNight = true context.numericValues[0] = 15f; // PlayerLevel = 15 context.numericValues[1] = 3f; // Difficulty = 3

SimpleSpawnRoller

Rolls a spawn table against a context, returning an array of results:

// Get your spawn database ISimpleSpawnDataSource spawnDB = mySpawnDatabase as ISimpleSpawnDataSource; SimpleSpawnTable? table = spawnDB.GetSpawnTable("FOREST_SPAWNS"); if (table.HasValue) { // Roll the table with current game context SpawnRollResult[] results = SimpleSpawnRoller.Roll(table.Value, context); foreach (var result in results) { if (result.IsSquad) Debug.Log($"Spawn squad: {result.squadCode} (from pool: {result.sourcePoolName})"); else Debug.Log($"Spawn enemy: {result.enemyCode} (from pool: {result.sourcePoolName})"); } } // For deterministic results (e.g., seeded runs), pass a System.Random: var rng = new System.Random(42); SpawnRollResult[] seededResults = SimpleSpawnRoller.Roll(table.Value, context, rng);

SimpleSpawnEvaluator

Evaluates conditions without performing a roll — useful for checking whether a pool or entry would be active under a given context:

// Check if a specific condition set passes bool passes = SimpleSpawnEvaluator.EvaluateConditions(conditions, context);

Runtime Data Structures

SimpleSpawnCondition

public struct SimpleSpawnCondition { public SimpleSpawnConditionLogic logic; // None, And, Or, Not public SimpleSpawnConditionSourceType sourceType; // Category, Flag, Numeric public int sourceIndex; // Index into context array public SimpleSpawnConditionComparison comparison; // Equals, NotEquals, GT, GTE, LT, LTE public float value; // Value to compare against }

SimpleSpawnWeightModifier

public struct SimpleSpawnWeightModifier { public int numericIndex; // Which context numeric affects weight public SimpleSpawnWeightModifierType modifierType; // FlatPerPoint or PercentPerPoint public float value; // Value per point }

SimpleSpawnEntry

public struct SimpleSpawnEntry { public string squadCode; // Squad code (empty if single enemy) public string singleEnemyCode; // Enemy code (empty if squad) public float weight; // Base selection weight public SimpleSpawnCondition[] conditions; // Entry-level conditions public SimpleSpawnWeightModifier[] weightModifiers; // Dynamic weight adjustments public bool IsSquad => !string.IsNullOrEmpty(squadCode); }

SimpleSpawnPool

public struct SimpleSpawnPool { public string name; // Pool display name public int rollCountMin; // Minimum rolls public int rollCountMax; // Maximum rolls public float rollChance; // Activation chance (0-100) public SimpleSpawnCondition[] conditions; // Pool-level conditions public SimpleSpawnEntry[] entries; // Entries in this pool }

SimpleSpawnTable

public struct SimpleSpawnTable { public string code; // Unique identifier (e.g., "FOREST_SPAWNS") public string name; // Display name public string description; // Optional description public SimpleSpawnPool[] pools; // Pools in this table }

Interface Reference — ISimpleSpawnDataSource

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

Tips

Layer Your Pools

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.

Use Context Numerics for Scaling

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.

Combine Conditions and Weight Modifiers

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").

AI-Generated Spawn Tables

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

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.

The 6 Scaling Types

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.

Formula Examples

Linear

// HP scaling: base=100, increment=25 // Level 1: 100 + (1-1) * 25 = 100 // Level 5: 100 + (5-1) * 25 = 200 // Level 10: 100 + (10-1) * 25 = 325

Percentage

// ATK scaling: base=50, percentage=15 // Level 1: 50 * (1 + 0 * 0.15) = 50 // Level 5: 50 * (1 + 4 * 0.15) = 80 // Level 10: 50 * (1 + 9 * 0.15) = 117.5

Exponential

// Gold reward scaling: base=10, exponentialBase=1.2 // Level 1: 10 * pow(1.2, 0) = 10 // Level 5: 10 * pow(1.2, 4) = 20.74 // Level 10: 10 * pow(1.2, 9) = 51.60

Step

// Defense tier scaling: manual breakpoints // Steps: (1, 10), (5, 25), (10, 50), (20, 100) // Level 1: 10 // Level 3: 10 (no step at 3, uses level 1 value) // Level 7: 25 (uses level 5 step) // Level 15: 50 (uses level 10 step) // Level 20: 100

Step 1 — Setup

Link Enemy Database

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.

Output Configuration

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

Step 2 — Scaling Rules

The main editing step uses a split-panel layout:

Left Panel — Profile List

Right Panel — Profile Editor

Select a profile from the list to edit:

Identity

Name, Code (with Auto toggle), and Description.

Rules

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

Formula HelpBox

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:

// Example HelpBox contents: Linear: base + (level - 1) * 25.0 Percentage: base * (1 + (level - 1) * 15.0 / 100) Exponential: base * pow(1.2, level - 1) Step: lookup table with 4 entries

Slider Preview

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.

Step 3 — Generate

Same two-phase generation as all forges:

1 Phase 1 — Generate Scripts
Writes C# source files, triggers AssetDatabase.Refresh() and domain reload.
2 Phase 2 — Create Asset
[InitializeOnLoad] handler creates the ScriptableObject from temporary JSON data.

Generated Files

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

Runtime Helper — SimpleScalingHelper

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.

ComputeScaledValue

// Apply a single scaling rule to a base value float scaledHP = SimpleScalingHelper.ComputeScaledValue(rule, baseHP, level);

ScaleAllNumerics

// Scale an entire array of numeric values using a profile // Returns a NEW array - never mutates the input float[] baseValues = new float[] { 100f, 50f, 30f }; // HP, ATK, DEF float[] scaledValues = SimpleScalingHelper.ScaleAllNumerics(profile, baseValues, level);

ScaleEnemy

// Scale an enemy's numeric values using a scaling profile. // Returns a new float array of scaled numerics — does not modify the enemy struct. float[] scaled = SimpleScalingHelper.ScaleEnemy(enemy, profile, level); // Overload with difficulty multiplier (useful for wave loop scaling). // All scaled values are multiplied by the difficulty multiplier after scaling. float[] scaledHard = SimpleScalingHelper.ScaleEnemy(enemy, profile, level, 1.2f);

Using the Generated Database Directly

// The generated database also implements GetScaledValue directly ISimpleScalingDataSource scalingDB = myScalingDatabase as ISimpleScalingDataSource; // Get a scaled value for a specific profile, property, and level float scaledHP = scalingDB.GetScaledValue("GOBLIN_WARRIOR", 0, baseHP, level); // Parameters: profileCode, numericIndex, baseValue, level // Get the numeric property names to find the right index string[] numericNames = scalingDB.GetNumericNames(); int hpIndex = System.Array.IndexOf(numericNames, "HP"); float scaledHP2 = scalingDB.GetScaledValue("GOBLIN_WARRIOR", hpIndex, baseHP, level);

Runtime Data Structures

SimpleScalingStep

public struct SimpleScalingStep { public int level; // Level at which this value takes effect public float value; // The value to use (absolute, not additive) }

SimpleScalingRule

public struct SimpleScalingRule { public int numericIndex; // Index into enemy database's numeric definitions public SimpleScalingType scalingType; // None, Linear, Percentage, Exponential, Curve, Step // Linear parameters public float increment; // Value added per level // Percentage parameters public float percentage; // Percent increase per level // Exponential parameters public float exponentialBase; // Base of exponentiation // Curve parameters public AnimationCurve curve; // Mapping normalized level (0-1) to multiplier public float curveMultiplier; // Scales the curve output public int curveMaxLevel; // Maximum level for normalization // Step parameters public SimpleScalingStep[] steps; // Level-value lookup table }

SimpleScalingProfile

public struct SimpleScalingProfile { public string code; // Unique identifier (e.g., "GOBLIN_WARRIOR") public string name; // Display name public string description; // Optional description public SimpleScalingRule[] rules; // Rules for individual numeric properties }

Interface Reference — ISimpleScalingDataSource

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

Tips

Start with Linear

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.

Use Step for Manual Control

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.

Preview with the Slider

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.

Test at Extremes

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

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.

Understanding the Wave Hierarchy

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.

Wave Sequence: "Forest Assault" | +-- Wave 1: "Scout Wave" | +-- Entry: Goblin Scout (count: 3, delay: 0s, start: 0s) | +-- Entry: Wolf (count: 2, delay: 1s, start: 2s) | +-- Wave 2: "Main Force" | +-- Entry: Orc Warrior (count: 5, delay: 0.5s, start: 0s) | +-- Entry: ORC_SQUAD (squad, count: 1, start: 5s) | +-- Wave 3: "Boss Wave" +-- Entry: Forest Troll (count: 1, delay: 0s, start: 0s)

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.

Entry Structure

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.

Loop Settings

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.

Step 1 — Setup

Configure the basics and link your databases.

1 Class Prefix
Enter a prefix for generated types (e.g., "ForestWaves" generates ForestWavesType.cs, ForestWavesDatabase.cs, etc.)
2 Enemy Databases (Required)
Link one or more Enemy Database ScriptableObjects. These provide the enemy codes that entries can reference. At least one enemy database is required.
3 Squad Databases (Optional)
Link Squad Database ScriptableObjects if you want entries that can spawn entire squad groups. When linked, entries gain an enemy/squad toggle.

Multiple databases are supported for both types. When duplicates exist across databases, the first database's version wins.

Step 2 — Definitions

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.

Step 3 — Sequences

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.

Step 4 — Settings

Choose output paths for the generated scripts and database asset.

Step 5 — Generate

Click Generate to create all output files. Wave Forge uses the same two-phase generation as all other forges:

1 Phase 1: Scripts
Generates {Prefix}Type.cs (enum), {Prefix}Database.cs (ScriptableObject), and {Prefix}DatabaseEditor.cs (custom editor). Triggers AssetDatabase.Refresh() and domain reload.
2 Phase 2: Asset
After domain reload, an [InitializeOnLoad] handler detects a SessionState flag and creates the {Prefix}Database.asset from temporary JSON data via delayCall.

After generation, your project will contain:

Assets/ └─ Generated/ ├─ Waves/Scripts/ │ ├─ ForestWavesType.cs (enum) │ ├─ ForestWavesDatabase.cs (ScriptableObject) │ └─ Editor/ │ └─ ForestWavesDatabaseEditor.cs (custom inspector) └─ Waves/Data/ └─ ForestWavesDatabase.asset (your data)

Runtime Helper: SimpleWaveRunner

SimpleWaveRunner is a pure C# event-driven wave runner with no MonoBehaviour dependency. You drive it by calling Update(deltaTime) each frame.

using SimpleEnemyForge; using UnityEngine; public class WaveController : MonoBehaviour { [SerializeField] ScriptableObject waveDatabase; SimpleWaveRunner runner = new SimpleWaveRunner(); void Start() { // Subscribe to events runner.OnWaveStarted += (index, name) => Debug.Log($"Wave {index + 1}: {name}"); runner.OnSpawnRequested += (request) => { if (request.IsSquad) SpawnSquad(request.squadCode, request.difficultyScale); else SpawnEnemy(request.enemyCode, request.difficultyScale); }; runner.OnWaveCompleted += (index, name) => Debug.Log($"Wave {index + 1} complete!"); runner.OnSequenceCompleted += () => ShowVictoryScreen(); runner.OnLoopStarted += (loopCount, scale) => Debug.Log($"Loop {loopCount}, difficulty: {scale:F1}x"); // Start a sequence var db = waveDatabase as ISimpleWaveDataSource; var sequence = db.GetWaveSequence("FOREST_ASSAULT"); if (sequence.HasValue) runner.Start(sequence.Value); } void Update() { runner.Update(Time.deltaTime); } }

Runner Controls

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.

Runner State

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.

Events

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.

Runtime Data Structures

SimpleWaveSequence

A complete wave sequence with identity, waves, loop settings, and dynamic properties.

public struct SimpleWaveSequence { public string code; // Unique identifier (e.g., "FOREST_ASSAULT") public string name; // Display name public string description; // Description public SimpleWave[] waves; // Ordered waves in this sequence // Loop settings public bool loopAfterLast; // Whether to loop after the last wave public float difficultyScalePerLoop; // Multiplier applied per loop (e.g., 1.2) public int maxLoops; // Max loops (0 = infinite) // Dynamic properties public int[] categoryValues; public bool[] flagValues; public float[] numericValues; public string[] textValues; }

SimpleWave

A single wave within a sequence, containing timed spawn entries.

public struct SimpleWave { public string name; // Display name (e.g., "Scout Wave") public float preDelay; // Seconds before spawning begins public float postDelay; // Seconds after spawning before next wave public SimpleWaveEntry[] entries; // Entries to spawn during this wave }

SimpleWaveEntry

A single spawn entry within a wave — either an enemy or a squad.

public struct SimpleWaveEntry { public string enemyCode; // Enemy code (empty if squad) public string squadCode; // Squad code (empty if enemy) public int count; // How many to spawn public float spawnDelay; // Delay between each individual spawn public float startTime; // Offset from wave start public bool IsSquad => !string.IsNullOrEmpty(squadCode); }

ISimpleWaveDataSource

Interface implemented by generated wave databases.

public interface ISimpleWaveDataSource { int WaveSequenceCount { get; } SimpleWaveSequence[] GetWaveSequences(); SimpleWaveSequence? GetWaveSequence(string code); string[] GetWaveSequenceCodes(); // Property metadata string[] GetCategoryLabels(); string[] GetCategoryEntries(int categoryIndex); string[] GetFlagNames(); string[] GetNumericNames(); string[] GetTextNames(); // Utility queries SimpleWaveSequence[] GetSequencesContainingEnemy(string enemyCode); SimpleWaveSequence[] GetSequencesContainingSquad(string squadCode); }

Tips

Use preDelay and postDelay

Give players a breather between waves. A 3-5 second postDelay lets players prepare, while a short preDelay can display a "Wave incoming!" warning.

Mix Enemies and Squads

Combine individual enemy spawns with squad groups in the same wave. Use squads for coordinated groups and individual entries for stragglers or bosses.

Stagger with startTime

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.

Loop for Endless Modes

Enable loopAfterLast with a difficultyScalePerLoop of 1.1-1.3 for escalating endless modes. Use maxLoops = 0 for truly infinite play.


Behavior Forge

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.

How Behavior Profiles Work

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.

Behavior Profile: "Goblin Warrior AI" | +-- Rule 1: Priority 10, Cooldown 5s | Conditions: IF Health < 25% | Action: FLEE | +-- Rule 2: Priority 8, Cooldown 3s | Conditions: IF Ally Count < 2 AND Health < 50% | Action: CALL_REINFORCEMENTS | +-- Rule 3: Priority 5, Cooldown 0s | Conditions: IF Target Distance <= 2 | Action: MELEE_ATTACK | +-- Rule 4: Priority 3, Cooldown 0s | (No conditions — fallback) | Action: APPROACH_TARGET

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.

Priority-Based Resolution

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.

Cooldown System

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).

Step 1 — Setup

Configure the basics for your behavior database.

1 Class Prefix
Enter a prefix for generated types (e.g., "GoblinBehavior" generates 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.

Step 2 — Definitions

Define the dynamic property system for your behavior profiles and the context properties that conditions check against.

Profile Properties

These properties are stored on each behavior profile:

Context Properties

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.

Step 3 — Behavior Rules

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:

Step 4 — Generate

Click Generate to create all output files using the standard two-phase process.

After generation, your project will contain:

Assets/ └─ Generated/ ├─ Behaviors/Scripts/ │ ├─ GoblinBehaviorType.cs (enum) │ ├─ GoblinBehaviorDatabase.cs (ScriptableObject) │ └─ Editor/ │ └─ GoblinBehaviorDatabaseEditor.cs (custom inspector) └─ Behaviors/Data/ └─ GoblinBehaviorDatabase.asset (your data)

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.

Runtime Helper: SimpleBehaviorEvaluator

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.

using SimpleEnemyForge; // Build a context with the current game state var context = new SimpleSpawnContext(); context.SetNumeric(0, healthPercent); // Context property 0: Health % context.SetNumeric(1, allyCount); // Context property 1: Ally Count context.SetNumeric(2, targetDistance); // Context property 2: Target Distance // Get the database and profile var db = behaviorDatabase as ISimpleBehaviorDataSource; var profile = db.GetBehaviorProfile("GOBLIN_WARRIOR_AI"); if (profile.HasValue) { // Get the best (highest priority) matching action SimpleBehaviorRule? best = SimpleBehaviorEvaluator.EvaluateBestAction( profile.Value, context); if (best.HasValue) { string action = best.Value.actionCode; // e.g., "FLEE" ExecuteAction(action); } // Or get ALL matching action codes, sorted by priority string[] actions = SimpleBehaviorEvaluator.GetMatchingActionCodes( profile.Value, context); // e.g., ["FLEE", "CALL_REINFORCEMENTS", "APPROACH_TARGET"] // Or get all matching rules with full detail SimpleBehaviorRule[] rules = SimpleBehaviorEvaluator.EvaluateProfile( profile.Value, context); }

Evaluator Methods

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).

Runtime Data Structures

SimpleBehaviorProfile

A named behavior profile containing condition-action rules and dynamic properties.

public struct SimpleBehaviorProfile { public string code; // Unique identifier (e.g., "AGGRESSIVE_MELEE") public string name; // Display name public string description; // Description public SimpleBehaviorRule[] rules; // Rules evaluated in priority order // Dynamic properties public int[] categoryValues; public bool[] flagValues; public float[] numericValues; public string[] textValues; }

SimpleBehaviorRule

A single rule that maps conditions to an action code.

public struct SimpleBehaviorRule { public SimpleSpawnCondition[] conditions; // Conditions that must pass public string actionCode; // Action to trigger (e.g., "HEAL", "FLEE") public int priority; // Higher wins when multiple rules match public float cooldown; // Minimum seconds between triggers }

ISimpleBehaviorDataSource

Interface implemented by generated behavior databases.

public interface ISimpleBehaviorDataSource { int ProfileCount { get; } SimpleBehaviorProfile[] GetBehaviorProfiles(); SimpleBehaviorProfile? GetBehaviorProfile(string code); string[] GetProfileCodes(); string[] GetProfileNames(); }

Tips

Always Include a Fallback

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.

Use Priorities Wisely

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.

Keep Cooldowns Reasonable

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).

Test with the Evaluator

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

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

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.

Humans Elves Orcs Undead Humans Allied Friendly Hostile Hostile Elves Friendly Allied Hostile Hostile Orcs Hostile Hostile Allied Neutral Undead Hostile Hostile Neutral Allied

Key properties of the matrix:

The 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.

The 5 Stances

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.

Step 1 — Setup

Configure the basics and optionally link enemy databases.

1 Class Prefix
Enter a prefix for generated types (e.g., "WarFactions" generates WarFactionsType.cs, WarFactionsDatabase.cs, etc.)
2 Enemy Databases (Optional)
Link Enemy Database ScriptableObjects if you want to reference enemy codes within faction data. This is optional — factions work independently.

Step 2 — Definitions

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.

Step 3 — Factions

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.

Identity

Each faction has a Name, Code (auto-generated from name), and Description.

Dynamic Properties

All four property sections (Categories, Flags, Numerics, Texts) are always visible with values for each property defined in Step 2.

Relationships

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).

Relationships for "Humans": Elves [Friendly v] Orcs [Hostile v] Undead [Hostile v] Dwarves [Allied v]

Adding or removing a faction automatically expands or shrinks the relationship matrix for all existing factions.

Step 4 — Generate

Click Generate to create all output files using the standard two-phase process.

After generation, your project will contain:

Assets/ └─ Generated/ ├─ Factions/Scripts/ │ ├─ WarFactionsType.cs (enum) │ ├─ WarFactionsDatabase.cs (ScriptableObject) │ └─ Editor/ │ └─ WarFactionsDatabaseEditor.cs (custom inspector) └─ Factions/Data/ └─ WarFactionsDatabase.asset (your data)

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.

Runtime Helper: SimpleFactionHelper

SimpleFactionHelper is a static helper class that provides convenience methods for querying faction relationships beyond what ISimpleFactionDataSource offers directly.

Using the Interface Directly

using SimpleEnemyForge; var db = factionDatabase as ISimpleFactionDataSource; // Get the stance between two factions SimpleFactionStance stance = db.GetRelationship("HUMANS", "ORCS"); // stance = SimpleFactionStance.Hostile (-2) // Check specific relationships bool hostile = db.IsHostile("HUMANS", "ORCS"); // true bool allied = db.IsAllied("HUMANS", "ELVES"); // false bool friendly = db.IsFriendly("HUMANS", "ELVES"); // true (Friendly or Allied) // Get all factions hostile to Humans string[] enemies = db.GetHostileFactions("HUMANS"); // ["ORCS", "UNDEAD"] // Get all factions allied with Humans string[] allies = db.GetAlliedFactions("HUMANS"); // ["DWARVES"]

Using SimpleFactionHelper

using SimpleEnemyForge; var db = factionDatabase as ISimpleFactionDataSource; // Check if hostile or unfriendly (stance < Neutral) bool isEnemy = SimpleFactionHelper.IsHostileOrUnfriendly(db, "HUMANS", "ORCS"); // true // Check if friendly or allied (stance > Neutral) bool isFriend = SimpleFactionHelper.IsFriendlyOrAllied(db, "HUMANS", "ELVES"); // true // Get the numeric stance value (-2 to 2) int stanceValue = SimpleFactionHelper.GetStanceValue(db, "HUMANS", "ORCS"); // -2 // Get all factions with a specific stance toward a faction string[] hostiles = SimpleFactionHelper.GetFactionsWithStance( db, "HUMANS", SimpleFactionStance.Hostile); // ["ORCS", "UNDEAD"] // Get all non-hostile factions (Unfriendly, Neutral, Friendly, or Allied) string[] nonHostile = SimpleFactionHelper.GetNonHostileFactions(db, "HUMANS"); // ["ELVES", "DWARVES"]

Helper Methods

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).

Runtime Data Structures

SimpleFactionStance (Enum)

The stance enum with integer values for easy comparison.

public enum SimpleFactionStance { Hostile = -2, // Attack on sight Unfriendly = -1, // May attack under conditions Neutral = 0, // Indifferent Friendly = 1, // Won't attack, may cooperate Allied = 2 // Full cooperation }

SimpleFactionDefinition

A single faction with identity, color, and dynamic properties.

public struct SimpleFactionDefinition { public string code; // Unique identifier (e.g., "UNDEAD") public string name; // Display name (e.g., "The Undead") public string description; // Description public Color color; // Editor display color // Dynamic properties public int[] categoryValues; public bool[] flagValues; public float[] numericValues; public string[] textValues; }

Relationship Matrix

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:

// Row-major indexing int stanceValue = relationshipMatrix[factionIndexA * factionCount + factionIndexB]; SimpleFactionStance stance = (SimpleFactionStance)stanceValue;

In practice, you should use the ISimpleFactionDataSource interface methods rather than indexing the matrix directly.

ISimpleFactionDataSource

Interface implemented by generated faction databases.

public interface ISimpleFactionDataSource { // Faction access int FactionCount { get; } string[] GetFactionNames(); string[] GetFactionCodes(); SimpleFactionDefinition[] GetFactionDefinitions(); // Relationship queries SimpleFactionStance GetRelationship(string codeA, string codeB); SimpleFactionStance GetRelationship(int indexA, int indexB); // Convenience helpers bool IsHostile(string codeA, string codeB); bool IsAllied(string codeA, string codeB); bool IsFriendly(string codeA, string codeB); string[] GetHostileFactions(string factionCode); string[] GetAlliedFactions(string factionCode); // Metadata access string[] GetCategoryLabels(); string[] GetCategoryEntries(int categoryIndex); string[] GetFlagNames(); string[] GetNumericNames(); string[] GetTextNames(); }

Tips

Start with 3-4 Factions

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.

Leverage Symmetric Writes

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.

Use Dynamic Properties for Traits

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.

Matrix Grows Quadratically

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.


AI-Powered Content Generation

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.

The Workflow

1 Define Your Properties (Step 2)
Set up your categories, flags, numerics, and texts in the wizard. These define the schema that AI will follow.
2 Export a Schema
Click one of the 3 export buttons in Step 2. The schema includes your exact property definitions, comprehensive AI instructions, format examples, and guidance.
3 Paste into AI
Open ChatGPT, Claude, or any AI chat. Paste the exported schema. The AI receives your exact property system with instructions on how to generate content.
4 AI Generates Content
The AI creates enemies, squads, spawn tables, or other content matching your exact property definitions. It outputs a JSON file ready for import.
5 Import the JSON
Click the Import button in the wizard. All generated content is validated, mapped to your definitions, and added to the wizard. A result popup shows what was imported and any issues.

Three Export Modes

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

What the Schema Contains

Exported schemas are comprehensive and designed for AI consumption:

Schema Info

Name, version, description, linked databases, template tracking (was template modified?)

Your Definitions

Every category with its entries, every flag with defaults, every numeric with constraints, every text with lineCount. AI sees your exact property system.

AI Instructions

10+ sections of guidance: what AI can/cannot do, property type explanations, balance guidance, format examples, ecosystem overview, step-by-step workflow.

Existing Data

Already-created enemies, squads, or other content serve as examples. AI sees your naming patterns and property usage.

AI Instruction Sections (Enemy Forge)

The Enemy Forge schema includes the following AI instruction sections. Other forges follow the same pattern with content adapted to their domain:

Ecosystem-Aware Generation

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.

Import Validation

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.

Schema Export per Wizard

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

Tips for Best Results

Use Full JSON for First Generation

Full mode gives AI all the context it needs. Use Light mode for follow-up generations when the AI already knows your game.

Create a Few Examples First

Add 3-5 enemies manually before exporting. AI learns from your naming patterns, stat ranges, and category usage.

Use Templates as Starting Points

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.

Iterate in Batches

Generate 20-50 enemies at a time, review, then ask for more. The existing data in subsequent exports helps AI maintain consistency.

Real-world tested: Hundreds of enemies, squads, spawn tables, and other content have been successfully generated by AI and imported in single batches using this workflow. The entire process from schema export to populated database takes just minutes.

Genre Templates

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.

Templates only affect definitions. Applying a template populates Step 2 (Definitions) and optionally adds example enemies to Step 3 (Enemies). It does not lock you into any structure — add, remove, or rename any property after applying.

Template Overview

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

Generic RPG

Standard RPG enemy system with types, ranks, elements, combat stats, and reward values. Works for most traditional RPGs, JRPGs, action RPGs, and MMOs.

Categories (4)

Flags (6)

Numerics (10)

Texts (2)

Example Enemies

Soulslike

Soulslike enemy system with poise, stagger thresholds, damage types, weakness multipliers, and multi-phase boss support. Designed for punishing, pattern-based combat.

Categories (4)

Flags (5)

Numerics (15)

Texts (3)

Example Enemies

Survival / Horror

Survival horror enemy system with detection ranges, aggro behavior, sensory systems, infection/contamination mechanics, and fear factor. For horror, zombie, and survival games.

Categories (5)

Flags (8)

Numerics (12)

Texts (2)

Example Enemies

Tower Defense

Tower defense enemy system with movement types, armor categories, resistances, special abilities, and wave/economy values. For TD, strategy, and base defense games.

Categories (4)

Flags (6)

Numerics (10)

Texts (1)

Example Enemies

Sci-Fi / Shooter

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.

Categories (5)

Flags (5)

Numerics (14)

Texts (2)

Example Enemies

RTS / Strategy

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.

Categories (6)

Flags (8)

Numerics (16)

Texts (2)

Example Enemies

Generated Custom Editors

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.

Tips

Templates Are Starting Points

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.

Modify Before Generating

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.

Export Schema After Applying

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.

Mix and Match

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 Attribute Forge Integration

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.

Completely optional. Simple Enemy Forge works perfectly on its own. The SAF integration adds modifier reference capabilities, but all core functionality (enemies, squads, spawn tables, scaling, waves, behaviors, factions, schema export, custom editors) works without SAF installed. You can add or remove SAF at any time with zero compilation errors.
Modifier-only integration. Unlike Simple Item Forge’s SAF integration (which includes attribute requirements, stat bonuses, and modifiers), Simple Enemy Forge’s SAF integration is lighter. Enemies only hold modifier references — ScriptableObject references to SAF modifier assets that represent buffs, debuffs, damage-over-time, or other effects an enemy can apply. Enemy stats are handled entirely through the dynamic numerics system (HP, ATK, DEF, etc.), so attribute requirements and stat bonuses are not needed.

Features Enabled by SAF

When SAF is detected, one additional capability appears in the Enemy Forge wizard and generated editors:

Modifier References

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.

Where SAF Features Appear

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.

Detection Lifecycle

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.

Installing SAF

1 Import the SAF package
Import Simple Attribute Forge into your project (Asset Store or .unitypackage). Unity detects new scripts and triggers compilation.
2 Domain reload → Detector fires
After compilation, Unity performs a domain reload. The [InitializeOnLoad] attribute on AttributeForgeDetector causes it to run automatically. It calls Type.GetType("SimpleForgeFramework.SimpleModifierAssetBase, SimpleModifierSystem.Runtime") to probe for SAF’s modifier types.
3 Define symbol added
SAF types are found. The detector adds SIMPLE_ATTRIBUTE_FORGE to Player Settings → Scripting Define Symbols. It also populates SAFBridge with the resolved ModifierAssetBaseType and builds a GetModifierMetadata delegate via reflection.
4 Recompilation → SAF features appear
The new define triggers another compilation. Editor code gated behind SAFBridge.IsAvailable checks now activates. The Modifiers section appears in the Enemy Forge wizard, and generated editors gain modifier ObjectFields.

Removing SAF

1 Delete the SAF package
Remove Simple Attribute Forge from your project. Unity detects the missing scripts and triggers compilation.
2 Domain reload → Detector fires again
The same [InitializeOnLoad] detector runs. This time, Type.GetType() returns null — SAF types are no longer present.
3 Define symbol removed
The detector removes SIMPLE_ATTRIBUTE_FORGE from the scripting defines. It also clears all SAFBridge properties (sets ModifierAssetBaseType and GetModifierMetadata to null).
4 Recompilation → Zero errors
The removed define causes another compilation. All SAF-gated editor code is excluded. The Modifiers sections disappear from the wizard. No compilation errors occur because modifiers are stored as plain ScriptableObject[] in runtime types (see below).

The SAFBridge Pattern

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:

Why not conditional assemblies? A separate assembly with defineConstraints 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.

Runtime vs. Editor Behavior

SAF integration is split into two layers: unconditional runtime types that always compile, and conditional editor code that only activates when SAF is present.

Always Compiled (Unconditional)

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.

Conditional (Editor Only)

The following editor features are gated behind SAFBridge.IsAvailable runtime checks and only activate when SAF is installed:

When SAF is removed, these sections simply disappear. The wizard reverts to its base feature set with no visible errors or broken UI.

Generated Database Behavior

Understanding how generated databases behave across SAF install/remove cycles:

Generated With SAF Installed

When you generate an Enemy Database while SAF is available and modifiers are assigned:

After Removing SAF

Regenerating Without SAF

If you open the Enemy Forge wizard and regenerate without SAF installed:

Safe round-trip: Install SAF → generate with modifiers → remove SAF → everything compiles → reinstall SAF → data still in the asset. As long as you don’t regenerate while SAF is absent, your modifier data survives the round-trip.

Troubleshooting

SAF features not appearing after import

SAF features not disappearing after removal

Modifier references lost after regeneration

Compilation errors after removing SAF

This should never happen if Simple Enemy Forge is the only package using SAF. If you see errors:


Simple Item Forge Integration

Simple 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.

Completely optional. Simple Enemy Forge works perfectly on its own. The SIF integration adds loot table references, but all core functionality (enemies, squads, spawn tables, scaling, waves, behaviors, factions, schema export, custom editors) works without SIF installed. You can add or remove SIF at any time with zero compilation errors.
Loot tables belong to SIF. Simple Item Forge defines loot tables via its Loot Forge wizard. Simple Enemy Forge does not create loot tables — it only references them by code. Each enemy can be linked to a SIF loot table code so your game logic knows which drop table to roll when the enemy is defeated.

Features Enabled by SIF

When SIF is detected, two additional fields appear on each enemy:

Loot Table Code

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.

Loot Table Reference

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.

Where SIF Features Appear

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.

Detection Lifecycle

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.

Installing SIF

1 Import the SIF package
Import Simple Item Forge into your project (Asset Store or .unitypackage). Unity detects new scripts and triggers compilation.
2 Domain reload → Detector fires
After compilation, Unity performs a domain reload. The [InitializeOnLoad] attribute on ItemForgeDetector causes it to run automatically. It calls Type.GetType("SimpleItemForge.ISimpleLootDataSource, SimpleItemForge.Runtime") to probe for SIF’s loot types.
3 Define symbol added
SIF types are found. The detector adds 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.
4 Recompilation → SIF features appear
The new define triggers another compilation. Editor code gated behind SIFBridge.IsAvailable checks now activates. The loot table fields appear in the Enemy Forge wizard and generated editors.

Removing SIF

1 Delete the SIF package
Remove Simple Item Forge from your project. Unity detects the missing scripts and triggers compilation.
2 Domain reload → Detector fires again
The same [InitializeOnLoad] detector runs. This time, Type.GetType() returns null — SIF types are no longer present.
3 Define symbol removed
The detector removes SIMPLE_ITEM_FORGE from the scripting defines. It also clears all SIFBridge properties (sets types and delegates to null).
4 Recompilation → Zero errors
The removed define causes another compilation. All SIF-gated editor code is excluded. The loot table sections disappear from the wizard. No compilation errors occur because the runtime fields use plain types (see below).

The SIFBridge Pattern

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:

Runtime Fields

The 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.

Runtime access: Your game code can always read 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.

Generated Database Behavior

Understanding how generated databases behave across SIF install/remove cycles:

Generated With SIF Installed

When you generate an Enemy Database while SIF is available and loot tables are assigned:

After Removing SIF

Regenerating Without SIF

If you open the Enemy Forge wizard and regenerate without SIF installed:

Safe round-trip: Install SIF → link loot tables → remove SIF → everything compiles → reinstall SIF → data still in the asset. As long as you don’t regenerate while SIF is absent, your loot table data survives the round-trip.

Troubleshooting

SIF features not appearing after import

Loot table dropdown is empty

SIF features not disappearing after removal

Compilation errors after removing SIF

This should never happen if Simple Enemy Forge is the only package using SIF. If you see errors:


Demos Overview

Simple 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.

Bestiary Demo

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.

Bestiary Demo

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.

Setup

Getting the Bestiary Demo running takes 3 steps:

1 Generate the Scene
Go to Window › Simple Enemy Forge › Generate Bestiary Demo. This creates a new scene with a Canvas, EventSystem, and the BestiaryDemo MonoBehaviour pre-configured.
2 Assign the 7 Database Arrays
Select the BestiaryDemo GameObject in the Hierarchy. In the Inspector, assign your generated database assets to the 7 array fields:
  • Enemy Databases — your generated enemy database ScriptableObjects
  • Squad Databases — your generated squad database ScriptableObjects
  • Spawn Databases — your generated spawn table database ScriptableObjects
  • Scaling Databases — your generated scaling profile database ScriptableObjects
  • Wave Databases — your generated wave sequence database ScriptableObjects
  • Behavior Databases — your generated behavior profile database ScriptableObjects
  • Faction Databases — your generated faction database ScriptableObjects
3 Enter Play Mode
Press Play. The Bestiary Demo canvas will appear with 7 tabs along the top.
Tip: You can assign multiple databases per slot. The demo merges them using the same first-DB-wins deduplication that the wizards use. This lets you combine databases from different templates or projects.

The 7 Tabs

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)

Key Features

Dynamic Context Controls

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.

Layout

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.

Navigation

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.

Runtime Helpers in Action

The demo exercises all 6 runtime helper classes:

Technical Reference

The 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()
TextMeshPro Required: Both files reference Unity.TextMeshPro for UI text rendering. The assembly definition references are already configured in the .asmdef files.

Overview

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.

Convention: {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:

// Pattern 1: Direct cast (type-safe, requires knowing generated class name) var db = enemyDatabaseAsset as MyEnemyDatabase; var goblin = db.GetEnemyByCode("GOBLIN_WARRIOR"); // Pattern 2: Interface cast (generic, works with any enemy database) var source = enemyDatabaseAsset as ISimpleEnemyDataSource; var goblin = source.GetEnemyByCode("GOBLIN_WARRIOR");

Enemy Database

SimpleEnemyDefinition Struct

Defines a single enemy with all its properties. Uses dynamic property arrays that correspond to the property definitions stored in the database.

FieldTypeDescription
namestringDisplay name shown to players (e.g., "Goblin Warrior")
codestringUnique UPPER_SNAKE_CASE identifier (e.g., "GOBLIN_WARRIOR")
descriptionstringEnemy description/lore text
iconSpriteEnemy portrait/sprite icon
categoryValuesint[]Category selections (index into each category's entries). For multi-select categories, this holds -1 as a placeholder.
categoryMultiValuesSimpleIntArray[]Multi-select category values. For single-select categories, the inner array is empty.
flagValuesbool[]Boolean flag values aligned with database flag definitions
numericValuesfloat[]Numeric property values. For range properties, this is the min value.
numericRangeMaxValuesfloat[]Max values for range numeric properties. For non-range properties, ignored (0).
textValuesstring[]Text property values aligned with database text definitions
modifiersScriptableObject[]SAF modifier references (empty if SAF not installed)
lootTableCodestringSIF loot table code (empty if SIF not installed)
lootTableScriptableObjectSIF loot table reference (null if SIF not installed)

Convenience Accessors

SimpleEnemyDefinition provides safe accessor methods that return defaults for out-of-range indices:

MethodReturnsDescription
GetCategoryValue(int categoryIndex)intSelected entry index for a single-select category (default: 0)
GetCategoryMultiValues(int categoryIndex)int[]Selected entry indices for a multi-select category
GetFlagValue(int flagIndex)boolFlag value (default: false)
GetNumericValue(int numericIndex)floatNumeric value (default: 0f)
GetNumericRange(int numericIndex)(float min, float max)Range numeric min/max values
GetTextValue(int textIndex)stringText value (default: "")

ISimpleEnemyDataSource Interface

Enemy Access

Method / PropertyReturnsDescription
EnemyCountintTotal 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)boolCheck if enemy exists by name or code
GetEnemyEnumType()TypeGet the generated enum type for type-safe access

Property Definitions

MethodReturnsDescription
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")

Dynamic Filtering

MethodReturnsDescription
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

Code Example

using SimpleEnemyForge; // Get database via interface var db = myDatabaseAsset as ISimpleEnemyDataSource; // Look up an enemy var enemy = db.GetEnemyByCode("SHADOW_WRAITH"); if (enemy.HasValue) { Debug.Log($"Name: {enemy.Value.name}"); Debug.Log($"HP: {enemy.Value.GetNumericValue(0)}"); Debug.Log($"Is Boss: {enemy.Value.GetFlagValue(0)}"); } // Filter enemies by category var bosses = db.GetEnemiesByFlag(0, true); // flag 0 = "Is Boss" var undead = db.GetEnemiesByCategory(0, 2); // category 0 entry 2 = "Undead"

Squad Database

SimpleSquadSlot Struct

FieldTypeDescription
enemyCodestringEnemy code referencing the enemy database
countMinintMinimum spawn count for this slot
countMaxintMaximum spawn count (same as min for fixed count)
levelintLevel override (-1 = use enemy default)

SimpleSquadGroup Struct

FieldTypeDescription
codestringUnique code identifier (e.g., "GOBLIN_PATROL")
namestringDisplay name (e.g., "Goblin Patrol")
descriptionstringDescription of this squad
slotsSimpleSquadSlot[]Enemy slots in this squad
categoryValuesint[]Category values (aligned with database definitions)
flagValuesbool[]Flag values (aligned with database definitions)
numericValuesfloat[]Numeric values (aligned with database definitions)
textValuesstring[]Text values (aligned with database definitions)

ISimpleSquadDataSource Interface

Method / PropertyReturnsDescription
SquadCountintTotal 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

Code Example

var squadDb = mySquadAsset as ISimpleSquadDataSource; var patrol = squadDb.GetSquadByCode("GOBLIN_PATROL"); if (patrol.HasValue) { foreach (var slot in patrol.Value.slots) { int count = Random.Range(slot.countMin, slot.countMax + 1); Debug.Log($"Spawn {count}x {slot.enemyCode}"); } }

Spawn Database

Data Hierarchy

Spawn tables use a 4-level hierarchy: Table › Pool › Entry › Condition.

SimpleSpawnTable Struct

FieldTypeDescription
codestringUnique code identifier (e.g., "FOREST_SPAWNS")
namestringDisplay name
descriptionstringDescription of this spawn table
poolsSimpleSpawnPool[]Pools in this spawn table

SimpleSpawnPool Struct

FieldTypeDescription
namestringDisplay name (e.g., "Main Wave", "Reinforcements")
rollCountMinintMinimum number of rolls from this pool
rollCountMaxintMaximum number of rolls from this pool
rollChancefloatChance (0-100) that this pool activates
conditionsSimpleSpawnCondition[]Conditions that must be met for this pool to activate
entriesSimpleSpawnEntry[]Entries in this pool

SimpleSpawnEntry Struct

FieldTypeDescription
squadCodestringSquad code (empty if single enemy)
singleEnemyCodestringEnemy code (empty if squad)
weightfloatBase weight for selection (higher = more likely)
conditionsSimpleSpawnCondition[]Conditions for this entry to be included
weightModifiersSimpleSpawnWeightModifier[]Modifiers that adjust weight based on context
IsSquadboolTrue if this entry references a squad group

SimpleSpawnCondition Struct

FieldTypeDescription
logicSimpleSpawnConditionLogicLogic operator for chaining (None for first condition)
sourceTypeSimpleSpawnConditionSourceTypeWhat type of definition to check (Category, Flag, Numeric)
sourceIndexintIndex into the relevant definition array
comparisonSimpleSpawnConditionComparisonHow to compare the value
valuefloatValue to compare against

Condition Enums

EnumValues
SimpleSpawnConditionSourceTypeCategory, Flag, Numeric
SimpleSpawnConditionComparisonEquals, NotEquals, GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual
SimpleSpawnConditionLogicNone, And, Or, Not

SimpleSpawnWeightModifier Struct

FieldTypeDescription
numericIndexintWhich numeric definition affects weight
modifierTypeSimpleSpawnWeightModifierTypeFlatPerPoint or PercentPerPoint
valuefloatValue per point (flat or percent)

ISimpleSpawnDataSource Interface

Method / PropertyReturnsDescription
SpawnTableCountintTotal 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

Scaling Database

SimpleScalingType Enum

ValueFormula
NoneNo scaling — use base value as-is
Linearbase + (level - 1) * increment
Percentagebase * (1 + (level - 1) * percentage / 100)
Exponentialbase * pow(exponentialBase, level - 1)
Curvebase + curve.Evaluate(level / maxLevel) * curveMultiplier
StepDirect lookup table — at level X, value is Y

SimpleScalingStep Struct

FieldTypeDescription
levelintLevel at which this value takes effect
valuefloatThe value to use at this level (absolute, not additive)

SimpleScalingRule Struct

FieldTypeDescription
numericIndexintIndex into the enemy database's numeric definitions
scalingTypeSimpleScalingTypeHow this rule scales the value
incrementfloatValue added per level (Linear)
percentagefloatPercent increase per level (Percentage)
exponentialBasefloatBase of exponentiation (Exponential)
curveAnimationCurveCurve mapping normalized level to a multiplier (Curve)
curveMultiplierfloatScales the curve output (Curve)
curveMaxLevelintMaximum level for curve normalization (Curve)
stepsSimpleScalingStep[]Level-value lookup table (Step)

SimpleScalingProfile Struct

FieldTypeDescription
codestringUnique code for this profile (often matches an enemy code)
namestringDisplay name
descriptionstringOptional description
rulesSimpleScalingRule[]Scaling rules for individual numeric properties

ISimpleScalingDataSource Interface

Method / PropertyReturnsDescription
ProfileCountintTotal 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)floatCompute scaled value for a specific profile, property, and level

Wave Database

Data Hierarchy

Wave data uses a 3-level hierarchy: Sequence › Wave › Entry.

SimpleWaveEntry Struct

FieldTypeDescription
enemyCodestringSingle enemy code (empty if using squad)
squadCodestringSquad code (empty if using single enemy)
countintHow many to spawn from this entry
spawnDelayfloatDelay in seconds between each individual spawn
startTimefloatTime offset from the start of the wave
IsSquadboolTrue if this entry references a squad group

SimpleWave Struct

FieldTypeDescription
namestringDisplay name (e.g., "Wave 1", "Boss Wave")
preDelayfloatDelay before this wave starts spawning
postDelayfloatDelay after this wave finishes before the next begins
entriesSimpleWaveEntry[]Entries to spawn during this wave

SimpleWaveSequence Struct

FieldTypeDescription
codestringUnique code identifier (e.g., "FOREST_WAVES")
namestringDisplay name
descriptionstringDescription of this wave sequence
wavesSimpleWave[]Ordered waves in this sequence
loopAfterLastboolWhether to loop after the last wave
difficultyScalePerLoopfloatDifficulty multiplier per loop (e.g., 1.2 = 20% harder)
maxLoopsintMaximum loop iterations (0 = infinite)
categoryValuesint[]Category values (aligned with database definitions)
flagValuesbool[]Flag values
numericValuesfloat[]Numeric values
textValuesstring[]Text values

ISimpleWaveDataSource Interface

Method / PropertyReturnsDescription
WaveSequenceCountintTotal 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

Behavior Database

SimpleBehaviorRule Struct

FieldTypeDescription
conditionsSimpleSpawnCondition[]Conditions that must be satisfied to trigger this rule (reuses spawn condition system)
actionCodestringUser-defined action code (e.g., "HEAL", "FLEE", "AOE_ATTACK")
priorityintPriority for conflict resolution — higher wins
cooldownfloatMinimum seconds between triggers of this rule

SimpleBehaviorProfile Struct

FieldTypeDescription
codestringUnique code for this profile (e.g., "AGGRESSIVE_MELEE")
namestringDisplay name
descriptionstringDescription of this profile's behavior pattern
rulesSimpleBehaviorRule[]Behavior rules evaluated in priority order
categoryValuesint[]Category values (aligned with database definitions)
flagValuesbool[]Flag values
numericValuesfloat[]Numeric values
textValuesstring[]Text values

ISimpleBehaviorDataSource Interface

Method / PropertyReturnsDescription
ProfileCountintTotal 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

Faction Database

SimpleFactionStance Enum

ValueIntDescription
Hostile-2Factions are openly hostile — attack on sight
Unfriendly-1Factions are unfriendly — may attack under certain conditions
Neutral0Factions are neutral — no inherent behavior
Friendly1Factions are friendly — will not attack, may cooperate
Allied2Factions are allied — fully cooperative

SimpleFactionDefinition Struct

FieldTypeDescription
codestringUnique code identifier (e.g., "UNDEAD")
namestringDisplay name (e.g., "The Undead")
descriptionstringOptional description of the faction
colorColorOptional color for editor display
categoryValuesint[]Category values
flagValuesbool[]Flag values
numericValuesfloat[]Numeric values
textValuesstring[]Text values

ISimpleFactionDataSource Interface

Faction Access

Method / PropertyReturnsDescription
FactionCountintTotal number of factions
GetFactionNames()string[]Get all faction names
GetFactionCodes()string[]Get all faction codes
GetFactionDefinitions()SimpleFactionDefinition[]Get all faction definitions

Relationship Queries

MethodReturnsDescription
GetRelationship(string codeA, string codeB)SimpleFactionStanceGet stance between two factions by code
GetRelationship(int indexA, int indexB)SimpleFactionStanceGet stance between two factions by index
IsHostile(string codeA, string codeB)boolCheck if stance is Hostile
IsAllied(string codeA, string codeB)boolCheck if stance is Allied
IsFriendly(string codeA, string codeB)boolCheck 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

Metadata Access

MethodReturnsDescription
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

Runtime Helpers

SEF includes 7 static/instance helper classes for common runtime operations. These are ready to use out of the box — no code generation needed.

1. SimpleSpawnRoller

Rolls spawn tables using weighted random selection. Evaluates pool conditions, computes effective weights with modifiers, and performs with-replacement random selection.

MethodReturnsDescription
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.

var context = SimpleSpawnContext.Create(1, 1, 2) .SetCategory(0, 1) // Biome = Forest .SetFlag(0, true) // Is Night = true .SetNumeric(0, 15f) // Player Level = 15 .SetNumeric(1, 3f) // Difficulty = 3 .Build(); var results = SimpleSpawnRoller.Roll(spawnTable, context); foreach (var r in results) { if (r.IsSquad) Debug.Log($"Spawn squad: {r.squadCode} from pool '{r.sourcePoolName}'"); else Debug.Log($"Spawn enemy: {r.enemyCode} from pool '{r.sourcePoolName}'"); }

2. SimpleSpawnContext

Holds current game state values for evaluating spawn and behavior conditions. Includes a fluent builder pattern for easy construction.

MethodReturnsDescription
SimpleSpawnContext.Create(int cats, int flags, int nums)SimpleSpawnContextBuilderStart building with fluent API
builder.SetCategory(int index, int value)SimpleSpawnContextBuilderSet a category value
builder.SetFlag(int index, bool value)SimpleSpawnContextBuilderSet a flag value
builder.SetNumeric(int index, float value)SimpleSpawnContextBuilderSet a numeric value
builder.Build()SimpleSpawnContextBuild the final context

3. SimpleSpawnEvaluator

Static building blocks for spawn condition evaluation and weight computation. Used internally by SimpleSpawnRoller and available for custom spawn logic.

MethodReturnsDescription
EvaluateSingleCondition(condition, context)boolEvaluate a single condition
EvaluateConditions(conditions[], context)boolEvaluate a chain of conditions with And/Or/Not logic
ComputeEffectiveWeight(entry, context)floatCompute weight after conditions and modifiers (0 = excluded)
IsPoolActive(pool, context)boolCheck if a pool's conditions are met
GetEligibleEntries(pool, context)(int entryIndex, float weight)[]Get all entries with weight > 0

4. SimpleScalingHelper

Static utility for computing scaled values from scaling rules and profiles.

MethodReturnsDescription
ComputeScaledValue(rule, baseValue, level)floatApply 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
// Scale an enemy to level 10 var enemy = enemyDb.GetEnemyByCode("GOBLIN_WARRIOR").Value; var profile = scalingDb.GetScalingProfile("GOBLIN_WARRIOR").Value; float[] scaled = SimpleScalingHelper.ScaleEnemy(enemy, profile, 10); // With difficulty multiplier (e.g., wave loop scaling) float[] hardMode = SimpleScalingHelper.ScaleEnemy(enemy, profile, 10, 1.5f);

5. SimpleWaveRunner

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.

Events

EventSignatureDescription
OnWaveStartedAction<int, string>Fires when a wave begins spawning (waveIndex, waveName)
OnSpawnRequestedAction<WaveSpawnRequest>Fires once per individual spawn
OnWaveCompletedAction<int, string>Fires when all entries in a wave are dispatched
OnSequenceCompletedActionFires when the entire sequence is done
OnLoopStartedAction<int, float>Fires when the sequence loops (loopCount, difficultyScale)
OnPausedActionFires when the runner is paused
OnResumedActionFires when the runner is resumed

Control Methods

MethodDescription
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

Read-Only State

PropertyTypeDescription
PhaseWaveRunnerPhaseCurrent phase (Idle, PreDelay, Spawning, PostDelay, Complete)
CurrentWaveIndexintIndex of the current wave
LoopCountintCompleted loop iterations (0 on first play-through)
CurrentDifficultyScalefloatCumulative difficulty scale (starts at 1.0)
ElapsedTimefloatTotal elapsed time since Start (paused time excluded)
IsPausedboolTrue if paused
IsRunningboolTrue if actively running
IsCompleteboolTrue if sequence has completed
var runner = new SimpleWaveRunner(); runner.OnWaveStarted += (index, name) => Debug.Log($"Wave {index}: {name} started"); runner.OnSpawnRequested += (req) => { if (req.IsSquad) SpawnSquad(req.squadCode, req.difficultyScale); else SpawnEnemy(req.enemyCode, req.difficultyScale); }; runner.OnSequenceCompleted += () => Debug.Log("All waves complete!"); var sequence = waveDb.GetWaveSequence("FOREST_WAVES").Value; runner.Start(sequence); // In your Update loop: void Update() { runner.Update(Time.deltaTime); }

6. SimpleBehaviorEvaluator

Static methods for evaluating behavior profile rules against a spawn context. Uses the same condition system as spawn tables.

MethodReturnsDescription
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
var profile = behaviorDb.GetBehaviorProfile("AGGRESSIVE_MELEE").Value; var context = SimpleSpawnContext.Create(0, 1, 2) .SetFlag(0, true) // In Combat = true .SetNumeric(0, 0.3f) // HP Percent = 30% .SetNumeric(1, 5f) // Distance to Target = 5 .Build(); var best = SimpleBehaviorEvaluator.EvaluateBestAction(profile, context); if (best.HasValue) Debug.Log($"Best action: {best.Value.actionCode} (priority {best.Value.priority})"); // Or get all matching actions string[] actions = SimpleBehaviorEvaluator.GetMatchingActionCodes(profile, context); // e.g., ["HEAL", "FLEE", "MELEE_ATTACK"] sorted by priority

7. SimpleFactionHelper

Static convenience methods for faction relationship queries beyond what ISimpleFactionDataSource provides directly.

MethodReturnsDescription
IsHostileOrUnfriendly(db, codeA, codeB)boolTrue if stance is less than Neutral
IsFriendlyOrAllied(db, codeA, codeB)boolTrue if stance is greater than Neutral
GetStanceValue(db, codeA, codeB)intNumeric 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
var factionDb = myFactionAsset as ISimpleFactionDataSource; // Check relationships bool hostile = factionDb.IsHostile("UNDEAD", "HUMANS"); bool allied = factionDb.IsAllied("ELVES", "HUMANS"); // Get all enemies of a faction string[] enemies = factionDb.GetHostileFactions("HUMANS"); // Extended queries via helper bool threat = SimpleFactionHelper.IsHostileOrUnfriendly(factionDb, "UNDEAD", "HUMANS"); string[] nonHostile = SimpleFactionHelper.GetNonHostileFactions(factionDb, "HUMANS"); int stanceVal = SimpleFactionHelper.GetStanceValue(factionDb, "ELVES", "DWARVES"); // -2 to 2

Namespace Reference

All runtime types in the SimpleEnemyForge namespace:

SimpleEnemyForge (namespace) ├─ SimpleEnemyDefinition.cs │ ├─ SimpleIntArray (struct) │ └─ SimpleEnemyDefinition (struct) ├─ ISimpleEnemyDataSource.cs │ └─ ISimpleEnemyDataSource (interface) ├─ SimpleEnemyEnums.cs │ ├─ SimpleSpawnConditionSourceType (enum) │ ├─ SimpleSpawnConditionComparison (enum) │ ├─ SimpleSpawnConditionLogic (enum) │ └─ SimpleSpawnWeightModifierType (enum) ├─ SimpleSquadGroup.cs │ ├─ SimpleSquadSlot (struct) │ └─ SimpleSquadGroup (struct) ├─ ISimpleSquadDataSource.cs │ └─ ISimpleSquadDataSource (interface) ├─ SimpleSpawnTable.cs │ ├─ StringArray (class) │ ├─ SimpleSpawnCondition (struct) │ ├─ SimpleSpawnWeightModifier (struct) │ ├─ SimpleSpawnEntry (struct) │ ├─ SimpleSpawnPool (struct) │ └─ SimpleSpawnTable (struct) ├─ ISimpleSpawnDataSource.cs │ └─ ISimpleSpawnDataSource (interface) ├─ SimpleScalingProfile.cs │ ├─ SimpleScalingType (enum) │ ├─ SimpleScalingStep (struct) │ ├─ SimpleScalingRule (struct) │ └─ SimpleScalingProfile (struct) ├─ ISimpleScalingDataSource.cs │ └─ ISimpleScalingDataSource (interface) ├─ SimpleWaveDefinition.cs │ ├─ SimpleWaveEntry (struct) │ ├─ SimpleWave (struct) │ └─ SimpleWaveSequence (struct) ├─ ISimpleWaveDataSource.cs │ └─ ISimpleWaveDataSource (interface) ├─ SimpleBehaviorProfile.cs │ ├─ SimpleBehaviorRule (struct) │ └─ SimpleBehaviorProfile (struct) ├─ ISimpleBehaviorDataSource.cs │ └─ ISimpleBehaviorDataSource (interface) ├─ SimpleBehaviorEvaluator.cs │ └─ SimpleBehaviorEvaluator (static class) ├─ SimpleFactionData.cs │ ├─ SimpleFactionStance (enum) │ └─ SimpleFactionDefinition (struct) ├─ ISimpleFactionDataSource.cs │ └─ ISimpleFactionDataSource (interface) ├─ SimpleSpawnContext.cs │ ├─ SimpleSpawnContext (struct) │ └─ SimpleSpawnContextBuilder (struct) ├─ SimpleSpawnEvaluator.cs │ └─ SimpleSpawnEvaluator (static class) ├─ SimpleSpawnRoller.cs │ ├─ SpawnRollResult (struct) │ └─ SimpleSpawnRoller (static class) ├─ SimpleScalingHelper.cs │ └─ SimpleScalingHelper (static class) ├─ SimpleWaveRunner.cs │ ├─ WaveSpawnRequest (struct) │ ├─ WaveRunnerPhase (enum) │ └─ SimpleWaveRunner (class) ├─ SimpleFactionHelper.cs │ └─ SimpleFactionHelper (static class) └─ Demo/BestiaryDemo.cs └─ BestiaryDemo (MonoBehaviour)

Troubleshooting

Solutions for common issues you may encounter when using Simple Enemy Forge.

Generation Issues

Database asset not created after generation

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.

Compilation errors after generation

Custom editor not showing in Inspector

Schema Import Issues

Import says "0 items imported"

AI-generated JSON won't import

Values not mapping correctly

Wizard Issues

Can't proceed to next step

Linked database shows 0 enemies

Data lost after domain reload

Custom Editor Issues

Inspector shows "Element 0", "Element 1" instead of custom editor

Editor performance slow with many entries

Browse button not showing enemy/squad references

SAF Integration

For SAF-specific troubleshooting (detection issues, modifier references, safe round-trip), see the dedicated SAF Integration Troubleshooting section.

SIF Integration

For SIF-specific troubleshooting (loot table linking, detection lifecycle, safe round-trip), see the dedicated SIF Integration Troubleshooting section.

General Tips

Check the Console

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.

Export Before Experimenting

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.

Generate Incrementally

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.

Use Templates

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.


About Simple Enemy Forge

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.

Design Philosophy

Define Once, Use Everywhere

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.

Dynamic, Not Hardcoded

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.

Zero Runtime Cost

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.

AI-First Content Pipeline

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.

The Forge Ecosystem

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)

The Dynamic Property System

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
All sections always visible. In both the wizard and the generated custom editor, Categories, Flags, Numerics, and Texts foldouts are always shown, even when 0 definitions exist. Empty sections display a "No X defined." placeholder rather than hiding the section.

What Gets Generated

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.
Two-Phase Generation: Phase 1 writes the three .cs files, then calls 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.

Integration

Simple Enemy Forge integrates with two optional companion packages using reflection bridges — never conditional compilation or hard assembly references:

Simple Attribute Forge (SAF) — Modifier 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.

Full details →

Simple Item Forge (SIF) — Loot Table Linking

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.

Full details →

Safe Round-Trip: Both integrations are designed for zero-error round-trips. Install the companion package, generate, link data, remove the companion package — zero compilation errors. Reinstall — links are preserved. The reflection bridge pattern ensures no hard dependencies.

Generated Custom Editors

Every forge generates a professional custom Inspector editor alongside the database. These editors share a consistent feature set:

Version History

v1.0 — Initial Release

Support

Self-Help Resources

Get in Touch