Procedural Quest Forge Wizard
The Procedural Quest Forge lets you design quest templates with
variable slots that are resolved at runtime to generate unlimited unique quests.
Instead of hand-authoring hundreds of quests, you author a handful of templates and let the
SimpleProceduralQuestGenerator produce concrete quest instances on the fly.
Open the wizard via Window › Simple Quest Forge › Procedural Quest Forge Wizard.
How It Works
The procedural system uses a token replacement approach. You write template strings
like "Hunt {COUNT} {ENEMY}s", define variables that control how {COUNT} and
{ENEMY} are resolved, and at runtime the generator picks random values from each
variable's pool and substitutes them into the template to produce a concrete quest.
Create named variable slots with source types. Example:
ENEMY as a TextList with options ["Wolf", "Bear", "Bandit"],
COUNT as a NumericRange from 5 to 20.
Each variable has a key (the token name used in template strings) and a
source type that determines how values are picked at runtime.
Use
{VARIABLE_KEY} tokens in the quest name template, description template, objective
templates, and reward templates. For example, a name template of "Hunt {COUNT} {ENEMY}s"
with an objective that targets {ENEMY} with a count of {COUNT}.
Call
SimpleProceduralQuestGenerator.Generate() which resolves all variables and
produces a SimpleGeneratedQuest struct with real values filled in. The generated quest
contains fully resolved SimpleQuestObjective[] and SimpleQuestReward[]
arrays ready for use with your game systems.
Variable System
Each variable has a key (the token name) and a sourceType (a plain string, not an enum) that determines how values are resolved at runtime. The three supported source types are:
| Source Type | Parameters | Resolution | Example |
|---|---|---|---|
| NumericRange | rangeMin, rangeMax, isInteger |
Random number between min and max. When isInteger is true, produces whole numbers;
otherwise produces decimal values rounded to one decimal place. |
COUNT: min=5, max=20, isInteger=true → resolves to "12" |
| TextList | options[] (string array) |
Random pick from the options list. Options can be manually entered or populated from linked databases (enemy codes, item codes, etc.). | ENEMY: ["Wolf", "Bear", "Bandit"] → resolves to "Wolf" |
| Custom | (none — resolved externally) | The generator leaves the token unresolved unless you provide an external variable pool
via the variablePools parameter. Use this for values that depend on game state
at runtime (player level, current region, time of day, etc.). |
PLAYER_LEVEL: resolved from game state at runtime via external pool |
ENEMY can pull its
options from your SEF enemy database codes, ensuring generated quests always reference valid enemies
that actually exist in your game. At runtime, you can also pass external pools via the
variablePools parameter on Generate() to override or supplement
a variable's built-in options.
Variable Fields
Each SimpleProceduralVariable struct contains:
| Field | Type | Description |
|---|---|---|
key |
string |
Token name used in template strings (e.g., "ENEMY", "COUNT"). This is what you wrap in
curly braces: {ENEMY}. |
sourceType |
string |
One of "NumericRange", "TextList", or "Custom". Note: this is a plain string, not an enum. |
displayName |
string |
Human-readable label for the variable (e.g., "Target Enemy"). Used in the wizard UI. |
rangeMin |
float |
Minimum value for NumericRange type. |
rangeMax |
float |
Maximum value for NumericRange type. |
isInteger |
bool |
When true, NumericRange produces whole numbers. When false, produces decimals (1 decimal place). |
options |
string[] |
Available text options for TextList type. The generator picks one at random. |
Template Strings
Template strings use {VARIABLE_KEY} token syntax. During generation, every occurrence
of {KEY} in a template string is replaced with that variable's resolved value.
Tokens can appear in any text field within a procedural quest template:
- Name template —
"Hunt {COUNT} {ENEMY}s" - Description template —
"The village needs you to eliminate {COUNT} {ENEMY}s from the {REGION}." - Objective code template —
"KILL_{ENEMY}" - Objective description template —
"Kill {COUNT} {ENEMY}" - Reward code template —
"GOLD_{AMOUNT}" - Reward description template —
"{GOLD_AMOUNT} Gold"
Note that objective target codes and counts are resolved differently from template strings.
Instead of embedding tokens in a string, you specify a variable key that the
generator looks up directly. For example, an objective's targetVariable might be
"ENEMY" and its countVariable might be "COUNT" — the
generator resolves these variables and assigns the results to the generated objective's
targetCode and targetCount fields.
Step 1 — Setup
Configure your procedural quest database identity and optionally link companion databases for variable pool population.
Naming
Enter your Database Name, optional Namespace, and Class Prefix. The wizard previews the generated type names:
{Prefix}Type— Enum with one entry per template code{Prefix}Database— ScriptableObject implementingISimpleProceduralQuestDataSource{Prefix}DatabaseEditor— Custom Inspector editor
Template Selection
Choose from 6 genre templates (Generic RPG, Open World, MMO, Survival, Narrative RPG, Roguelike) or start from scratch. Templates pre-populate property definitions and include example procedural templates with variables showing how to use the token replacement system effectively. See the Templates page for details on each genre.
Linked Databases (Optional)
Link up to 5 database types from companion packages for variable pool population. Each database type has its own ReorderableList with drag-and-drop support:
| Database Type | Bridge | Provides | Example Use |
|---|---|---|---|
| Enemy Databases | SEFBridge | Enemy codes | Populate an ENEMY TextList variable with actual enemy codes from your game |
| Faction Databases | SEFBridge | Faction codes | Populate a FACTION TextList with faction codes for reputation templates |
| Item Databases | SIFBridge | Item codes | Populate a REWARD_ITEM TextList with item codes for reward templates |
| Loot Databases | SIFBridge | Loot table codes | Populate a LOOT_TABLE TextList with loot table codes |
| Attribute Databases | SAFBridge | Attribute names | Populate a STAT TextList with attribute names for stat-based templates |
These linked databases are only visible when their respective companion package is installed. If SEF is not installed, the Enemy and Faction database lists won't appear (and a message explains why). This is purely optional — you can always type variable options manually.
Step 2 — Definitions
Define single-level dynamic properties for procedural quest templates. Unlike the Quest Forge which has two-level properties (quest + objective), the Procedural Quest Forge uses only template-level properties. These properties describe the template itself, not the generated quests.
The Four Property Types
| Type | Storage | Example Properties | Example Values |
|---|---|---|---|
| Categories | int[] |
Template Type | Kill Bounty, Fetch Quest, Escort, Delivery, Defense |
| Flags | bool[] |
Is Repeatable, Has Timer, Scales With Level | true / false |
| Numerics | float[] |
Base XP, Base Gold, Min Level, Max Level | 100, 50, 1, 60 |
| Texts | string[] |
Template Notes, Design Intent | "Daily bounty board quest", "Scales enemy count with player level" |
These properties are useful for filtering and categorizing templates at runtime. For example,
you might query GetProceduralQuestsByCategory(0, 2) to get all "Escort" templates,
or GetProceduralQuestsByFlag(0, true) to get all repeatable templates.
Schema Export / Import
Four buttons at the top of this step provide schema export/import:
- Export Full JSON — Complete schema with definitions, existing templates, AI instructions, and variable documentation
- Export Light JSON — Definitions and instructions only (no template data)
- Export Markdown — Human-readable format for pasting into AI chat
- Import Schema (JSON) — Import templates from a JSON file with 3-choice dialog
See the AI Workflow page for the full export/import workflow.
Step 3 — Templates
Create procedural templates in a split-panel layout. The left panel shows a list of all templates with search, sort, and pagination controls. The right panel shows the detail editor for the selected template.
Identity Section
Each template has basic identity fields:
- Code — Unique template identifier (e.g.,
KILL_BOUNTY_TEMPLATE). Must be unique across all templates in this database. - Name — Display name for the template itself (e.g., "Monster Bounty"). This is the template's name, not the generated quest's name.
- Name Template — Template string for the generated quest name
(e.g.,
"{ENEMY} Bounty"). This is resolved at runtime. - Description Template — Template string for the generated quest description
(e.g.,
"Adventurers needed to hunt {COUNT} {ENEMY}s in the {REGION}."). - Icon — Optional Sprite icon for the template.
Variables Section (Nested ReorderableList)
Define the variable slots for this template. Each variable has the fields described in the Variable System section above: key, sourceType, displayName, range parameters (for NumericRange), and options (for TextList).
You can add as many variables as needed. Common patterns include 2-5 variables per template. Variable keys must be unique within a template.
Objective Templates (Nested ReorderableList)
Each objective template defines how a generated objective will look. See Understanding Objective Templates below for the full field reference.
Reward Templates (Nested ReorderableList)
Each reward template defines how a generated reward will look. See Understanding Reward Templates below for the full field reference.
Template-Level Properties
The dynamic properties defined in Step 2 appear here for categorizing each template. Set category values, toggle flags, enter numeric values, and write text properties.
Understanding Objective Templates
Each SimpleProceduralObjective defines a blueprint for generating a concrete
SimpleQuestObjective at runtime. The key difference from regular objectives is that
instead of fixed values, you specify variable keys that are resolved during
generation.
| Field | Type | Description |
|---|---|---|
codeTemplate |
string |
Template string for the generated objective's code field.
Example: "KILL_{ENEMY}" → resolves to "KILL_WOLF" |
descriptionTemplate |
string |
Template string for the generated objective's description field.
Example: "Kill {COUNT} {ENEMY}" → resolves to "Kill 12 Wolf" |
isOptional |
bool |
Whether this objective is optional (copied directly to the generated objective). |
targetVariable |
string |
Variable key for target code resolution. The generator looks up this variable's resolved
value and assigns it to objective.targetCode.
Example: "ENEMY" → objective.targetCode = "Wolf" |
countVariable |
string |
Variable key for target count resolution. Leave empty to use fixedCount.
Example: "COUNT" → objective.targetCount = 12 |
fixedCount |
int |
Fixed target count used when countVariable is empty. Defaults to 1 if set to 0. |
timeLimitVariable |
string |
Variable key for time limit resolution. Leave empty to use fixedTimeLimit. |
fixedTimeLimit |
float |
Fixed time limit in seconds (0 = no limit). Used when timeLimitVariable is empty. |
sortOrder |
int |
Sort order within the generated quest (copied directly to the generated objective). |
codeTemplate and
descriptionTemplate use full template string resolution (all {KEY}
tokens are replaced). targetVariable, countVariable, and
timeLimitVariable are single variable key lookups — you specify just the
variable name (e.g., "ENEMY"), not a template string (not "{ENEMY}").
Understanding Reward Templates
Each SimpleProceduralReward defines a blueprint for generating a concrete
SimpleQuestReward at runtime.
| Field | Type | Description |
|---|---|---|
codeTemplate |
string |
Template string for the generated reward's code field.
Example: "GOLD_REWARD" |
descriptionTemplate |
string |
Template string for the generated reward's description field.
Example: "{GOLD_AMOUNT} Gold" → "500 Gold" |
targetVariable |
string |
Variable key for target code resolution. Resolves to reward.targetCode.
Example: "REWARD_ITEM" → reward.targetCode = "HEALTH_POTION" |
amountVariable |
string |
Variable key for amount resolution. Leave empty to use fixedAmount.
Example: "GOLD_AMOUNT" → reward.amount = 500 |
fixedAmount |
float |
Fixed reward amount used when amountVariable is empty. |
isChoice |
bool |
Whether this is a choice reward (copied to generated reward). |
choiceGroup |
int |
Choice group ID. Rewards with the same choiceGroup and isChoice=true
form a "pick one" group in the generated quest. |
Step 4 — Settings
Configure output paths for the generated scripts and data asset. You can choose separate folders
for scripts and the database asset, or use the default Assets/Generated/ProceduralQuests
location.
- Scripts Folder — Where to write the generated
.csfiles (enum, database, editor) - Data Folder — Where to create the
.assetfile
Step 5 — Generate
Click Generate to create your procedural quest database. This uses the same two-phase generation process as all SQF wizards:
The wizard writes 3 C# files (
{Prefix}Type.cs, {Prefix}Database.cs,
{Prefix}DatabaseEditor.cs) and calls AssetDatabase.Refresh() to trigger
Unity's script compilation.
After the domain reload completes, an
[InitializeOnLoad] handler reads wizard data
from a temporary JSON file and creates the {Prefix}Database.asset ScriptableObject
with all your template data populated.
The SessionState flag SimpleQuestForge_ProceduralQuest_WaitingForCompilation tracks
the generation state across the domain reload.
Runtime Generation
Use SimpleProceduralQuestGenerator to generate concrete quests at runtime. The
generator is a static class with two main methods:
Generate a Single Quest
Generate() takes a SimpleProceduralQuest
struct, NOT a database reference. You must first retrieve the template from your database using
GetProceduralQuestByCode() or GetProceduralQuests(), then pass the
struct to Generate(). The return type is SimpleGeneratedQuest, not
SimpleQuestDefinition.
Generate with External Variable Pools
Generate with Deterministic Seeding
Generate a Batch of Quests
Resolve Template Strings Manually
Runtime Data Structures
SimpleProceduralQuest (the template)
This is the template struct stored in your generated database. It contains all the information needed to generate a concrete quest at runtime.
SimpleGeneratedQuest (the output)
This is the struct returned by Generate(). It contains the fully resolved quest
with all variable tokens replaced by concrete values.
SimpleQuestObjective
and SimpleQuestReward structs as hand-authored quests. This means generated quests
are fully compatible with SimpleQuestTracker, SimpleQuestHelper, and
all other runtime APIs. You can treat a generated quest exactly like a regular quest.
SimpleProceduralVariable
ISimpleProceduralQuestDataSource
The interface implemented by generated procedural quest databases. Use this for type-safe database access without depending on the generated class name.
| Method | Returns | Description |
|---|---|---|
ProceduralQuestCount |
int |
Total number of procedural quest templates |
GetProceduralQuests() |
SimpleProceduralQuest[] |
Get all procedural quest templates |
GetProceduralQuestByCode(string) |
SimpleProceduralQuest? |
Get a template by code (nullable — returns null if not found) |
GetProceduralQuestCodes() |
string[] |
Get all template codes |
GetProceduralQuestNames() |
string[] |
Get all template display names |
HasProceduralQuest(string) |
bool |
Check if a template exists by code |
GetProceduralQuestEnumType() |
Type |
Get the generated enum type for type-safe access |
GetCategoryLabels() |
string[] |
Category labels from definitions |
GetCategoryEntries(int) |
string[] |
Entries for a specific category |
GetFlagNames() |
string[] |
Flag names from definitions |
GetNumericNames() |
string[] |
Numeric property names from definitions |
GetTextNames() |
string[] |
Text property names from definitions |
GetProceduralQuestsByCategory(int, int) |
SimpleProceduralQuest[] |
Filter templates by category value |
GetProceduralQuestsByFlag(int, bool) |
SimpleProceduralQuest[] |
Filter templates by flag value |
GetProceduralQuestsByNumericRange(int, float, float) |
SimpleProceduralQuest[] |
Filter templates by numeric range |
Designing Good Templates
Tips for creating procedural quest templates that generate diverse, interesting quests:
Use Enough Variables
Templates with only 1-2 variables produce repetitive quests. Aim for 3-5 variables per template: a target (ENEMY/ITEM), a quantity (COUNT), a location (REGION), and optionally a modifier (ADJECTIVE) and a reward scaler (REWARD_MULTIPLIER).
Wide Numeric Ranges
A COUNT range of 10-12 produces nearly identical quests. Use wider ranges like 5-25 for noticeable variety. Consider using isInteger=false for decimal values like gold amounts (100.0-500.0) when precision doesn't matter.
Link Real Databases
When possible, link your SEF/SIF databases so TextList options are populated from actual game data. This ensures generated quests always reference valid enemy codes, item codes, or faction codes that exist in your game.
Use External Pools for Context
Pass variablePools to Generate() to inject context-aware values.
Filter enemies by the player's current zone, scale reward amounts by player level, or limit
items to what the current vendor stocks. This is especially powerful for Custom-type variables.
Template Families
Create multiple templates of the same type with different flavors. Instead of one "Kill Bounty" template, create "Monster Bounty", "Pest Control", "Mercenary Contract", and "Arena Challenge" — all kill-type but with different descriptions, reward scales, and variable pools.
Use Properties for Filtering
Set template-level properties thoughtfully. Tag templates as "Daily" or "Weekly" with flags,
set min/max level ranges with numerics, and categorize by type. At runtime, use
GetProceduralQuestsByCategory/Flag/NumericRange to select appropriate templates
for the current game context.