What’s new? (Upgrade Guide)
What's new, what's changed
Upgrade Guide: 1.5.1 → 1.6.0
This guide covers upgrading Runtime Spawner from 1.5.1 to 1.6.0.
The most important changes are:
WaveTrigger is now API-driven (activation state is no longer settable externally).
LocalAreaSpawner now has a canonical driver API and no longer relies on
OnTriggerStayspam to signal occupancy.Both components now support ActivationMode (
UnityPhysicsvsExternal) to enable deterministic networking drivers.Both components now expose UnityEvents for designers plus static C# events for systems.
1) WaveTrigger Changes (Breaking Change)
What changed
In 1.5.1, WaveTrigger activation was driven by Unity triggers and a writable boolean:
IsActivatedhad a public setter.OnTriggerEntersetisActivated = trueand invokedonWaveTriggerActivated.Reset was a coroutine that flipped
isActivatedback to false and invokedonWaveTriggerReset.RuntimeSpawnertypically listened toWaveTrigger.onWaveTriggerActivated.
In 1.6.0, WaveTrigger is an offline-first component with a public activation API:
IsActivatedis read-only externally (private set).Activation is routed through:
StartWave()/StopWave()ExternalActivate(...)/ExternalReset()SetTriggerInside(bool)(occupancy replication)
Trigger behavior is explicitly separated into:
Occupancy (“inside”) state (
IsInside) with trigger UnityEventsWave activation lifecycle with wave UnityEvents
Old → New API Mapping
wt.IsActivated = true;
wt.StartWave();
wt.IsActivated = false;
wt.StopWave(); (or ExternalReset() depending on intent)
Rely on OnTriggerEnter for activation
Keep UnityPhysics mode, or use External mode with a driver (see Networking Integrations)
ResetTrigger() always starts coroutine
ResetTrigger() is still available, plus deterministic ExternalReset()
No occupancy concept
IsInside + SetTriggerInside(bool) + trigger UnityEvents
Required code changes (common)
If you had UI/tools/drivers doing this:
Change to:
Networking setup (recommended)
For networked projects, set WaveTrigger to:
activationMode = ActivationMode.External
And add the corresponding Network Driver for your component.
Then the networking driver will call:
Replicate enter/exit →
wt.SetTriggerInside(bool)Activate from authority →
wt.ExternalActivate(scheduleLocalReset:false)orwt.StartWave()Reset deterministically →
wt.ExternalReset()
New Events (WaveTrigger)
In 1.6.0, WaveTrigger adds UnityEvents:
Trigger occupancy (generic):
OnTriggerEnterEventOnTriggerStayEventOnTriggerExitEventOnTriggerStateChanged(bool inside)
Wave lifecycle (domain specific):
OnWaveActivatedOnWaveCompletedOnWaveDeactivatedOnWaveReset
Static C# events still exist:
WaveTrigger.onWaveTriggerActivatedWaveTrigger.onWaveTriggerReset
2) LocalAreaSpawner Changes (Breaking + Behavior)
What changed
In 1.5.1, LocalAreaSpawner signaled “player inside” like this:
OnTriggerStayfired every physics tick while inside.Every
OnTriggerStayinvoked:onPlayerIsInRegion(this, true)
OnTriggerExitinvoked:onPlayerIsInRegion(this, false)
This creates two problems:
Event spam: enter state is broadcast repeatedly, not only when it changes.
Networking mismatch: “stay” does not map cleanly to replicated state.
In 1.6.0, LocalAreaSpawner has a canonical, edge-triggered API:
State is stored as
isActiveSpawnerand exposed as:IsInside(property)IsActiveSpawner()(back-compat helper)
Unity trigger messages call
EnterRegion/ExitRegion(UnityPhysics mode).Networking drivers call
SetPlayerInRegion(bool)(External mode).Events fire only when state changes.
Old → New API Mapping
region.IsActiveSpawner()
region.IsInside (preferred) or IsActiveSpawner() (still supported)
OnTriggerStay used to signal inside
Inside state changes fire once; stay is optional UnityEvent
No explicit API to set inside
region.SetPlayerInRegion(bool)
No activation mode
activationMode = UnityPhysics or External
Only static event
Static event + UnityEvents for trigger + region lifecycle
Required behavior awareness (important)
If you had systems that accidentally depended on the spammy nature of 1.5.1 OnTriggerStay, you must update them.
In 1.5.1 you might have been doing something like:
“While inside, keep refreshing a timer / keep region alive”
“Every OnTriggerStay, re-add this region to active list”
In 1.6.0, the region will emit:
onPlayerIsInRegion(region, true)once when enteringonPlayerIsInRegion(region, false)once when exiting
If you truly need “continuous inside” behavior, use:
OnTriggerStayEventUnityEvent (invoked every frame while inside), orPoll
region.IsInsidefrom your own Update loop.
New Events (LocalAreaSpawner)
Static C# event (canonical system hook):
LocalAreaSpawner.onPlayerIsInRegion(LocalAreaSpawner region, bool inside)
UnityEvents added:
Trigger occupancy (generic):
OnTriggerEnterEventOnTriggerStayEvent(invoked every frame while inside)OnTriggerExitEventOnTriggerStateChanged(bool inside)
Region lifecycle (domain):
OnRegionEnteredOnRegionExitedOnRegionStateChanged(bool inside)
Networking setup (recommended)
For networked projects, set LocalAreaSpawner to:
activationMode = ActivationMode.External
Then your driver should replicate inside state and call:
3) ActivationMode: UnityPhysics vs External
Both WaveTrigger and LocalAreaSpawner now have:
ActivationMode.UnityPhysicsUses Unity physics callbacks (
OnTriggerEnter/Exit/Stay)Good for offline, editor testing, single player
ActivationMode.ExternalDesigned for Fusion/PUN drivers (deterministic replication)
Driver calls public API methods to set state and activate
4) Upgrade Steps Checklist
Step A — Update WaveTrigger callsites
Search for:
IsActivated =direct invocation of wave activation/reset events
spawner notify patterns
Replace with:
StartWave()/StopWave()ExternalActivate(...)/ExternalReset()for networking drivers
Step B — Update LocalAreaSpawner assumptions
Search for:
systems that relied on
OnTriggerStay-driven spamlogic that treats “enter” as a per-frame signal
Adjust:
Use state change events (
onPlayerIsInRegion) for enter/exitUse
OnTriggerStayEventif you need continuous inside tickOr poll
IsInsidewhen necessary
Step C — Networking projects
Set triggers/regions to
ActivationMode.ExternalAdd the new Fusion/PUN drivers
Ensure authority drives activation and replicated state:
Fusion: StateAuthority
PUN: MasterClient (or your chosen authority)
5) Quick “Before / After” Snippets
WaveTrigger manual activation
Before (1.5.1):
After (1.6.0):
LocalAreaSpawner “keep alive while inside”
Before (1.5.1): implicitly happened via OnTriggerStay spam.
After (1.6.0):
Use
OnTriggerStayEventif you want continuous callbacks, orPoll
region.IsInsideand run your own loop.
Upgrading from 1.4.x → 1.5.0
Version 1.5.0 introduces full networking support for both Photon Fusion and Photon PUN 2, plus lifecycle controls that make spawners robust in session-based or multiplayer contexts.
It’s fully backward-compatible - no setup changes are required for single-player projects.
What changed
🌐 Networking Integrations (new)
Built-in adapters for Photon Fusion 2.0 and Photon PUN 2.
New
FusionFactoryAssetandPUNFactoryAssetimplementIObjectFactoryfor seamless integration with Runtime Spawner.
🧭 Lifecycle Control (new)
RuntimeSpawnerAPI now include:Pause()- temporarily halts all spawn and update activity.Resume()- resumes spawning and timers.End()- performs clean teardown (useful for session transitions or disconnects).
Lifecycle calls automatically coordinate with network factories to ensure clean despawns across clients.
📘 Documentation & XML Comments
All new public APIs include full XML documentation for IDE IntelliSense.
Inspector tooltips and help boxes updated to explain Fusion/PUN setup flow.
Migration steps
Update to 1.5.0
The package is backward-compatible; existing non-network projects continue to work normally.
For Photon Fusion users
Create a
FusionFactoryAssetand assign it to your RuntimeSpawner.Ensure your
NetworkRunnerusesFusionPoolObjectProvider.Verify the prefab pool includes all spawnable prefabs.
For Photon PUN 2 users
Create a
PUNFactoryAssetand assign it to your RuntimeSpawner.Confirm your prefabs are registered in PUN’s
PhotonNetwork.PrefabPool(automatic if using Resources).
Add Lifecycle Control (optional)
Use
SpawnDirector.Pause(),Resume(), orEnd()when transitioning game states (e.g., from lobby → mission, or during pause menus).If using Fusion or PUN, ensure these calls occur on the authoritative instance.
Test
In Play Mode, validate that networked spawns replicate correctly across clients.
Use the Fusion Bootstrap sample scene to confirm setup.
Notes
The 1.5.0 networking layer builds directly on existing factory abstractions - your spawn profiles and entries remain unchanged.
Lifecycle controls improve robustness for multiplayer lobbies, mission restarts, and session-based gameplay.
All features are opt-in; standalone projects are unaffected.
Troubleshooting
Objects spawn locally but not on other clients
The prefab isn’t registered in Fusion/PUN pools
Check factory asset and prefab registration
Despawns not synchronized
Using Destroy() instead of factory despawn
Always despawn via the RuntimeSpawner or Runner.Despawn()
Scene transitions leave ghost objects
Missing End() call
Call SpawnDirector.End() during teardown or scene unload
FAQ
Do I need to use Photon Fusion or PUN now? No. These are optional adapters; local projects still use the default object factory.
Is this compatible with my existing SpawnProfiles? Yes. The only change is the factory asset reference in your spawner.
Can I mix Fusion and non-networked spawners in the same scene? Yes, but it’s recommended to separate them logically - each spawner should have one active factory type.
Where do I find the sample? There are 2 samples available in the Unity Package Manager.
Samples->Runtime Spawner PUN and Samples->Runtime Spawner Fusion
Each demonstrates host/client startup and object replication setup including a bootstrap / startup class you can use as a starting point as well as basic implementations of network character controller and spawner movement controllers for each networking library.
See their corresponding pages in the manual for more details.
Upgrading from 1.3.x → 1.4.0
Version 1.4.0 adds designer-friendly macro control for local (“tile”) spawns, deterministic global seeding, and editor polish. It’s backward compatible - if you don’t opt in, your 1.3 setups keep working.
What changed
🗺️ Region-scale population control (new)
RegionPopulationController (scene component) scales each active
LocalAreaSpawnervia a step-driven density curve, can enable/disable sparse regions, resolves each region’s spawn list from a global RegionSpawnLibrary, and pre-warms pools.RegionSpawnLibrary (ScriptableObject) defines rule-based
SpawnEntrymixes per region using tags, director step ranges, and optional weights. Rules can append or replace a region’s authored list; a fallback list covers “no match” cases.SpawnDirector integration so region density & mixes track your intensity steps automatically.
🎲 Deterministic global seeding (new)
RuntimeSpawner now supports a Global Seed with a Use Deterministic Seed toggle.
Spawns use a reproducible PCG32 stream derived by a seed router from:
Global Seed
The
SpawnEntry’s stable identityContext hints (e.g., SourceName such as wave/table name, region name)
Works across Global, Region, and Wave flows; group sizes/order are stable per seed.
API:
SetGlobalSeed(int seed)and eventRuntimeSpawner.onSeedChanged. (Call before any spawns to lock a run.)
🧩 LocalAreaSpawner runtime knobs (non-destructive)
DesiredRangeOverride (min,max)+DensityMultare runtime-only. Region loop honors the override; authored Min/Max remain your baseline.
🔄 Region loop awareness
RegionSpawnLoopnow respectsDesiredRangeOverrideif set, else uses the authored Min/Max.
🧰 Editor/Inspector updates
New inspectors for RegionPopulationController and RegionSpawnLibrary.
LocalAreaSpawner inspector shows runtime overrides/readouts for debugging.
Unified package header (Docs / API / Support) in inspectors.
Migration steps
Update to 1.4.0. No breaking changes; existing scenes behave like 1.3 unless you opt in to new features.
(Optional) Enable deterministic runs
In RuntimeSpawner, enable Use Deterministic Seed and set Global Seed (Inspector), or
Call
SetGlobalSeed(yourSeed)from your game’s RNG/bootstrap beforeStartSpawners(). Tip: listen toRuntimeSpawner.onSeedChangedif you mirror the seed elsewhere.
(Optional) Adopt region-scale control
Add RegionPopulationController to your scene and assign RuntimeSpawner.
(Optional) Assign SpawnDirector so step changes drive density/mixes automatically.
Create a RegionSpawnLibrary (
Create → Runtime Spawner → Region Spawn Library) and assign it.
Tag your regions
Basic: set a Unity Tag on each
LocalAreaSpawnerGameObject.Multi-tag tiles: implement
RegionPopulationController.IRegionTagProvideron a component to return string[].
Author rules in the RegionSpawnLibrary
Use Required (ALL) and Any tag filters, Min/Max Step, and optional Weight by Step.
Check Replace Instead of Append for rules that should override a tile’s authored list.
Add Fallback Entries to avoid empty tiles when nothing matches.
Tune density
In RegionPopulationController, adjust the density curve (x=step 0..1 → multiplier) and the Enable Threshold (regions below this are temporarily unregistered).
Playtest
Use the LocalAreaSpawner inspector’s runtime panel to confirm DesiredRangeOverride, Density, and resolved entries.
Validate global/region caps and each
SpawnEntry.maxPopulation.
Notes
Back-compat: Without RegionPopulationController, 1.4 behaves like 1.3 (authored Min/Max & lists).
Determinism scope: With the same Global Seed, scene content, and timing, spawn choice/order/grouping are reproducible. External nondeterminism (e.g., physics side-effects, asynchronous NavMesh baking, time-based triggers) can still change outcomes.
Procedural tiles: Controller auto-tracks regions as they register/unregister via
RuntimeSpawner.
Troubleshooting
Tile is silent: Density fell below Enable Threshold → controller unregistered that region. Raise threshold or increase curve at that step.
New enemy didn’t appear where expected: Check region tags, rule step ranges/weights, and that caps (
SpawnEntry.maxPopulation, region/global caps) allow it.Run isn’t deterministic: Ensure Use Deterministic Seed is ON and
SetGlobalSeed(...)is called before any spawns; avoid frame-time dependent randomness in your own scripts.
FAQ
Do I have to use the new controller/library? No - opt-in. Existing region setups continue to work.
Will the controller overwrite my authored Min/Max? No. It writes a runtime override; authored values remain your baseline.
What drives “step”?
Typically SpawnDirector (intensity profile). If you have your own system, call ApplyStepFromDirector(step, stepsCount) on the controller.
How do I seed the run for reproducible testing?
Set Use Deterministic Seed and either set the Global Seed in the inspector or call SetGlobalSeed(seed) during bootstrap (before StartSpawners()).
Can I give a tile multiple semantic tags?
Yes - implement IRegionTagProvider on a component and return a string[].
Upgrading from 1.2.x → 1.3.0
Version 1.3.0 introduces two big features and a few quality-of-life updates:
NavMesh Placement Policy per Spawn Entry (Require / Prefer / Ignore)
Spawn Hints workflow (scene anchors + a settings asset)
Inspector polish, new editor menu actions, and clearer debug/warnings
Internal: deferred placement queue for “Require” policy, tag-aware wave spawning
What changed
🚦 NavMesh Placement Policy (per Spawn Entry)
Require – Only spawn if the position can be projected to the NavMesh now. If not, the request is deferred and retried until success or a retry cap.
Prefer (default) – Spawn immediately; if there’s no NavMesh yet, a helper will enable the
NavMeshAgentwhen the mesh appears.Ignore – Skip NavMesh projection entirely (useful for non-NavMesh actors, flying units, FX).
Spawn Hints system
EnemySpawnHintPoint (component): place these in the scene to steer where units appear.
Per-hint rules:
minPlayerDist,maxPlayerDist,denyLineOfSight,requireNavMesh, optionaloverrideAreaMask, tags.
SpawnHintSettings (ScriptableObject): global knobs for search radius, LOS mask, reservation cooldown, etc.
Includes Hint-Only mode (skip fallback sampling when no hint qualifies).
Includes Ignore Global Min Distance (let close-range hints be used even if spawner’s global min is large).
Integrated across Global, Region, and Wave flows.
WaveSpawnPoint → Anchor Tags: tags can bias hints (e.g., “Flank”, “Ambush”) during waves.
Editor/Inspector improvements
Runtime Spawner inspector: Spawn Hints section with Create & auto-assign settings.
SpawnHintSettings inspector: presets, utilities, and warnings.
WaveSpawnPoint inspector: simple range + anchor tags UI.
Menu:
GameObject/Runtime Spawner/Create Wave Spawn PointGameObject/Runtime Spawner/Add Spawn Hint(drops a hint under current selection).
Migration steps
Update the package to 1.3.0.
Update the Runtime Spawner Samples (from Package Manager)
Review your Spawn Entries
Open each
SpawnEntryand set Placement Policy:Most existing setups should use Prefer (matches prior behavior).
If you need deterministic “only on NavMesh” placement at time of spawn, choose Require.
If the prefab doesn’t use a
NavMeshAgent, choose Ignore.
If using Require, optionally set Max Deferred Retries.
(Optional) Adopt Spawn Hints
In Runtime Spawner: enable Use Spawn Hints and assign a SpawnHintSettings asset (create from the button if needed).
Place a few EnemySpawnHintPoint per tile / encounter space (menu: Add Spawn Hint).
For waves, add WaveSpawnPoint children under your WaveTrigger and fill Anchor Tags to bias hint selection.
Check distances
Make sure spawner Min Spawn Range doesn’t conflict with your hint distances.
If you want very close hints, enable Ignore Global Min Range in SpawnHintSettings.
Test
Enable Debug Logs (settings) while tuning.
Watch for messages like:
“HintOnly: no hint → refusing … spawn.”
“No suitable hint found; falling back to sampler.” (off in Hint-Only)
Min/max distance conflict warnings.
Notes
Performance: Spawn hints use a spatial registry (cell size in settings). Smaller cells = tighter queries but more buckets.
Reservation: After a hint is used, it’s soft-reserved for a short cooldown to avoid dog-piling.
Deferred queue: With Require, spawns that can’t yet snap to the NavMesh are retried with backoff (configurable retry cap).
Waves + Tags: If a
WaveSpawnPointprovides tags, matching hints are preferred first.
Before & After
Before (≤1.2.x)
All spawns tried to land on the NavMesh; timing quirks sometimes left agents disabled/inactive until mesh appeared.
No concept of authored “good spots” - global/region sampling only.
Now (1.3.0+)
Clear Require / Prefer / Ignore placement contracts per entry.
Author intent with Spawn Hints; optionally Hint-Only to prevent uncontrolled spawns.
Wave flows can target hint tags via
WaveSpawnPointanchor tags.
Troubleshooting
Nothing spawns with Hint-Only Likely no hint qualifies. Check:
Player distance vs.
minPlayerDist/maxPlayerDistdenyLineOfSightvs. your LOS Block MaskrequireNavMesh+ NavMesh Sample Max Dist too smallGlobal Min Spawn Range > your hint distances (enable Ignore Global Min Range if needed)
Agents never enable with Require Ensure your NavMesh is present/updated at runtime, increase Max Deferred Retries, and verify Area Mask (hint or spawner) includes baked areas.
Hints are used too often in one spot Increase Reservation Seconds in SpawnHintSettings.
FAQ
Q: Do I need Spawn Hints? No. They’re optional. Use them where placement really matters (flanks, ambushes), keep sampler fallback for the rest - or enable Hint-Only for fully curated placement.
Q: What does “Prefer scoped first” mean? Hints inside the immediate scope (e.g., wave range or region bounds) are preferred before searching the broader/global radius.
Last updated