Region Population System
This pair of features—RegionPopulationController (component) and RegionSpawnLibrary (ScriptableObject)—adds designer-friendly, macro control over local/“tile” spawns on top of the Runtime Spawner package. You get global knobs for density and encounter mix without hand-editing every LocalAreaSpawner
.
RegionPopulationController (component)
What it does
A scene-level controller that:
Scales each active
LocalAreaSpawner
’s desired min/max population based on a step (e.g., fromSpawnDirector
) plus an authorable density curve.Enables or disables regions dynamically (e.g., quiet during “calm” steps) using an enable threshold.
Builds each region’s spawn list from a global RegionSpawnLibrary (tags + step ranges), optionally replacing or appending to what’s authored on the region.
Pre-warms pools for all resolved entries to keep hitches down.
It does not spawn by itself; it orchestrates inputs to the existing spawn loops.
How it works (in one picture)
SpawnDirector (step 0..N) ─┐
│
density curve │ LocalAreaSpawner (per tile)
+ enable threshold│ - Min/Max (base)
├─► RegionPopulationController
│ - Computes per-tile min/max override
RegionSpawnLibrary (rules) ─┘ - Resolves per-tile spawn list (tags + step)
- Prewarms pools
Key concepts
Base vs Override: Each
LocalAreaSpawner
still has MinObjectCount / MaxObjectCount. The controller computes a scaled “DesiredRangeOverride” at runtime (per region). The region spawn loop honors the override if present; otherwise it uses the base values.Enable Threshold: If the effective density for a region falls below
enableThreshold
, the controller quietly unregisters that region fromRuntimeSpawner
. When density rises again, it re-registers.Tags: The controller asks the region for semantic tags to match rules:
Implement
RegionPopulationController.IRegionTagProvider
on any component under the region root and return strings (e.g.,"industrial"
,"residential"
).If none is provided, the region’s Unity Tag (if not “Untagged”) is used.
If neither exists, the region has no tags (only rules with no tag filters will match).
Step input: By default, the controller listens to
SpawnDirector.OnStepChanged
. If you drive difficulty elsewhere, callApplyStepFromDirector(step, stepsCount)
yourself.
Inspector (fields you’ll see)
References
RuntimeSpawner
(required)SpawnDirector
(optional; used to auto-receive step changes)RegionSpawnLibrary
(global rules asset)
Discovery
autoDiscoverAtStart
: also picks up anyLocalAreaSpawner
already in the scene (useful for static scenes).
Scaling
enableThreshold
(0–1): regions under this effective density are disabled.localDensityByStep
(Curve): x = normalized step (0..1) → multiplier (e.g., 0.8 → 1.7).useJitter
: adds ±10% deterministic per-region variety.
Diagnostics
logChanges
: logs enables/disables & list refreshes.
Setup (quick start)
Add a
RegionPopulationController
to your gameplay scene.Assign your scene’s
RuntimeSpawner
.(Optional) Assign your
SpawnDirector
(or callApplyStepFromDirector
yourself).Create a
RegionSpawnLibrary
asset and assign it.Author your tiles with
LocalAreaSpawner
(BoxCollider isTrigger). Set base Min/Max.(Optional) Tag tiles via Unity Tag or an
IRegionTagProvider
component.Press Play—step changes will scale density and rebuild per-tile spawn lists automatically.
Runtime API (for advanced control)
ApplyStepFromDirector(int step, int stepsCount, float localDensityOverride = -1f)
Apply a new step; passlocalDensityOverride ≥ 0
to force a density multiplier for this pass.RegisterRegion(LocalAreaSpawner)
/UnregisterRegion(LocalAreaSpawner)
Usually handled viaRuntimeSpawner
events; exposed for procedural flows.SetLibrary(RegionSpawnLibrary)
Swap libraries at runtime.
Troubleshooting
“My region never spawns.”
Check the enableThreshold: if density is below threshold, the controller unregisters the region.
Verify your RegionSpawnLibrary has matching rules or fallback entries.
Confirm the region has a BoxCollider (isTrigger) and is being registered by
RuntimeSpawner
.
“New enemy didn’t show up across tiles.”
Add a Rule for it (see below), confirm tags/step range, and ensure weight > 0.
Pools are pre-warmed, but the Global/Region caps in
RuntimeSpawner
and onSpawnEntry.maxPopulation
still apply.
“Design wants a quiet step.”
Lower the density curve at that step so regions fall below
enableThreshold
(they’ll disable cleanly).
RegionSpawnLibrary (asset)
What it is
A global, designer-authored rule table that says:
“When the director is at step X and a region has tags Y/Z, include SpawnEntry ‘Grunt’ with weight W,” …optionally replacing the tile’s authoring instead of appending.
This lets you add (or remove) enemies once and have all matching tiles update automatically.
Create one
Assets → Create → Runtime Spawner → Region Spawn Library
Assign it to the RegionPopulationController
.
Anatomy of a Rule
Each Rule contains:
Name: For readability in inspectors/logs.
Entry: A
SpawnEntry
(what to spawn).Filters:
Required Tags (ALL): every tag must be present on the region.
Any Tags (ANY): at least one must be present.
Min/Max Step: inclusive step range for the rule to be active.
Weights:
Weight by Step (Curve): x = normalized step (0..1). If empty, weight = 1.
When multiple rules match a region, higher weight sorts first; ties break by name.
Behavior:
Replace Instead of Append: if any matched rule sets this, the final list replaces the tile’s authored entries (rather than appending to them).
Caps (optional):
Per-Region Cap: extra hard cap for this entry in this region (0 = off). (Note: the core loops always respect
SpawnEntry.maxPopulation
. This field is a hook for custom policies if you choose to enforce it.)
Fallback Entries
If no rules match, the library’s Fallback Entries list is used (or nothing, if empty).
Authoring patterns
Add an enemy across “industrial street” tiles at mid/late steps
Rule:
Entry:
Enemy_Grunt
Required Tags:
industrial
Any Tags:
street
Min Step:
2
, Max Step:4
Weight by Step: curve that rises from 0.5 at 0.5 to 1.0 at 1.0
Replace Instead of Append: off (keeps tile flavor)
Force a bespoke palette for “fortress” tiles at high threat
Rule A:
Entry:
Enemy_EliteGuard
Required Tags:
fortress
Min Step:
3
, Max Step:4
Replace Instead of Append: on
(Add more Rules with the same flag; replacement is global for the resolved set.)
Ensure there’s always something
Fallback Entries:
Enemy_Scavenger
,Enemy_Wolf
Tagging guidelines
Prefer stable, semantic tags:
industrial
,residential
,forest
,river
,outskirts
.Use Unity Tag for quick prototypes; move to
IRegionTagProvider
when tiles need multiple tags.Keep tag vocabulary short; rules are easier to reason about.
Designer workflow (recommended)
Place
LocalAreaSpawner
volumes per tile. Set Min/Max for that tile’s “typical” density.Add
RegionPopulationController
to the scene and assign aRegionSpawnLibrary
.Model your pacing in
SpawnDirector
(steps), then set the controller’s density curve:Low steps below
enableThreshold
to quiet down.Mid steps around
1.0×
.High steps up to
1.5–1.8×
(watch your global caps).
Author global rules in the RegionSpawnLibrary:
Tag tiles (Unity Tag or provider).
Add rules for each enemy or set.
Use Replace for curated, step-gated takeovers; otherwise append.
Provide Fallback for safety.
Playtest:
Watch the controller’s inspector runtime panel for each region:
Effective DesiredRangeOverride (min/max)
DensityMult
Resolved entries (via the region’s inspector)
Tweak density curve, enable threshold, and rule weights.
Performance notes
The controller pre-warms pools for all resolved entries per region.
Rebuilds happen on step changes or when new regions register (procedural tiles).
The per-step recompute is lightweight: list filtering + small sorts.
FAQs
Q: Do I still need to set Min/Max on LocalAreaSpawner
?
A: Yes—those are your base values. The controller computes a runtime override (scaled) that the region loop honors first.
Q: What if I don’t use SpawnDirector
?
A: Call ApplyStepFromDirector(step, stepsCount)
yourself (e.g., from your own difficulty system).
Q: What if a tile has no tags? A: Only rules with no tag filters (or your Fallback) will apply.
Q: I added a rule with Replace, but the old entries still show. A: Replacement applies to the resolved set the controller writes at runtime. Make sure:
The region matches the rule’s tags and step range.
The rule’s weight > 0 at the current step.
You’re inspecting during play (the runtime list is not serialized).
Versioning / Compatibility
Requires Runtime Spawner 1.4.0 or higher with region loop support for
LocalAreaSpawner.DesiredRangeOverride.
RuntimeSpawner
must raise region registration events. If you’re on an older version, update the package or bind regions manually withRegisterRegion/UnregisterRegion
.
One-minute checklist
Last updated