


⚠️ Alpha. This is early software: the core spawn/despawn/AI loop is solid and well-tested, but you will hit rough edges (see Known issues below) — roughly a quarter of the creature roster doesn't spawn yet, co-op is single-player-only for now, and there's an occasional cosmetic terrain glitch. Back up saves you care about before experimenting.
Press F11, pick a creature, spawn it beside you.
SpawnKit gives you a real vanilla hostile — not a dummy or a stand-in. It wanders, it aggros, it fights, it drops its normal loot, and it dies like the wild version, because it is the wild version. Spawn a pack of hyenas to train on. Drop a Tuanosaur next to a bandit camp and watch them fight each other. Restock a region you've already cleared out.
Spawns are ephemeral: they never enter your save file, and they vanish when you leave the area.
Requires: BepInEx 5 (Outward's Mono branch — see Compatibility), plus CompanionKit and ForgeKit. If you install through a mod manager these come along automatically.
Install from Thunderstore and the dependencies come with it. Installing by hand: drop all three
folders — ForgeKit/, CompanionKit/, SpawnKit/ — side by side into BepInEx/plugins/. If
BepInEx logs a dependency error and refuses to load SpawnKit, one of the other two is missing.
Launch the game, load a save, and press F11 for a draggable window listing every creature you can spawn.
The key is rebindable ([Menu] MenuKey; Backslash is the classic debug-menu choice), and the
menu closes on Esc, on F11 again, or when you open a vanilla menu.
BepInEx/config/cobalt.spawnkit.cfg, created on first launch:
| Setting | Default | What it does |
|---|---|---|
[Spawner] Enabled |
true |
Master kill-switch. Off = no new spawns (cleanup still works). |
MaxActiveSpawns |
8 |
Cap on live spawns at once. |
SpawnDistance |
5 |
How far from you they appear, in meters. |
MaxCachedTemplates |
12 |
How many creature bodies stay in memory. Higher = more instant spawns, more RAM. |
DefaultCorpsePolicy |
Vanilla |
Vanilla leaves the corpse and its loot. NoBody deletes the corpse (and its loot) after death. |
[Menu] EnableMenu |
true |
The F11 menu. |
Everything is also driven by writing a line into BepInEx/config/SpawnKit_cmd.txt — it runs on the
next poll, even while the game is paused. Useful for scripting or if a keybind conflicts.
| Verb | What it does |
|---|---|
spawn <species> [count] |
Spawn 1 or more beside you. Names may contain spaces: spawn Armored Hyena 3. |
despawnall [kill] |
Remove every spawn. kill gives them a real death (animation + loot) instead of deleting them. |
spawnex <species> [dist=N] [life=N] [faction=Name] [owner=tag] [body=vanilla|none] [linger=N] [keepquest] |
One spawn with the full option surface. |
spawnlist / spawndump |
List spawnable creatures / show what's live right now. |
spawnprewarm <species> / spawnprep <scene> |
Pay the body-fetch cost now so later spawns are instant. |
spawnclearcache |
Drop cached bodies (frees memory; next spawn re-fetches). |
selftest |
Sanity-check the install; look for [SELFTEST] … DONE in the log. |
Some creatures don't work. Roughly a quarter of the rows in the list fail to spawn — their body can't be found in the scene the table expects. You'll get a clear message in the log rather than anything harmful. This is being chipped away at.
Single-player / host-only. In co-op, only the host can spawn. Clients are refused cleanly (you'll see why in the log), and creatures the host spawns are not synced to clients. Treat this as a single-player mod for now.
A rare visual glitch. Very occasionally, after a lot of spawning in one session, a patch of ground can go invisible (you can still walk on it) — changing zone and coming back repairs it.
Outward has no way to create a creature from nothing — every creature is baked into the scene it lives in. So SpawnKit borrows one: it quietly loads the scene a species lives in, copies a live creature out of it, gives the copy a fresh identity, drops it on solid ground near you, and unloads the scene again. That's the 1–6 second pause on a cold spawn, and it's why the result behaves perfectly — it's the same creature the game itself would have spawned, not an imitation.
The creature roster comes from CompanionKit, which owns the table mapping each species to the
scenes it can be found in. spawnlist prints it. You can add or fix an entry without rebuilding
anything by dropping a DonorScenes.txt into BepInEx/config/:
Hyena=Emercar_Dungeon5,ChersoneseDungeonsSmall
Alpha Tuanosaur=Hallowed_Dungeon3,HallowedMarshNewTerrain
Region-only creatures need an extra step. Open-world regions and towns are too big to load in the
background — trying it crashes the game, so SpawnKit refuses to. Creatures that live only out in a
region (Pearlbird, Torcrab, Coralhorn and friends) therefore can't be fetched the usual way; their
rows are labelled (expedition-only) and a cold spawn fails with a clear message. To stock them,
write expedition <species> into BepInEx/config/ck_cmd.txt: CompanionKit takes you through the
loading screen and back, and from then on that creature spawns like any other.
| Symptom | What it means | What to do |
|---|---|---|
No spawnable species matches '<x>' |
Not in the table | Check spawnlist; add a line to BepInEx/config/DonorScenes.txt |
has NO viable donor (all candidates oversized) |
A region-only creature, not stocked yet | Run expedition <species> (see above) |
| First spawn of a creature pauses, later ones don't | Normal — that's the body fetch | Use spawnprewarm to pay it early |
| No mods load at all, no crash log | Wrong Steam branch | See Compatibility |
Outward must be on its Mono Steam beta branch, not the default IL2CPP build (Properties → Betas →
select mono). If the game runs but no BepInEx mods load and there's no crash log, this is almost
always why.
SpawnKit is also a library. Reference it and declare the dependency:
[BepInDependency(SpawnKit.Plugin.GUID)]
Then spawning is one line:
SpawnHandle h = Spawner.Spawn("Hyena");
Everything else is optional — per-spawn customization rides a SpawnOptions, and the returned
SpawnHandle is your window onto the creature's lifecycle:
Spawner.Spawn("Alpha Tuanosaur",
new SpawnOptions
{
Faction = Character.Factions.Tuanosaurs, // null = the donor's own faction
Distance = 12f, // ring-probe radius (clamped 1–50m)
// Position = somewhereExact, // skip the ring probe entirely
LifetimeSeconds = 120f, // auto-despawn clock (null = forever)
Corpse = Core.CorpsePolicy.NoBody, // destroy the corpse after death (null = the
// [Spawner] DefaultCorpsePolicy config;
// NoBody takes the LOOT with it — see traps)
CorpseLingerSeconds = 8f, // death-anim time before the NoBody destroy
OwnerTag = "mymod", // namespace your spawns (see DespawnAll)
// StripQuestEvents = false, // default true — leave it unless you KNOW
},
onReady: handle =>
{
if (!handle.IsAlive) { /* handle.FailReason says why */ return; }
handle.OnDied += _ => DropReward();
handle.OnDespawned += _ => CleanupMarker();
});
The full surface (SpawnKit.Spawner, all static):
| Call | What it does |
|---|---|
Spawn(species, options?, onReady?) |
Spawn ONE creature; returns a SpawnHandle immediately (Pending → Alive/Failed; sync refusals return already-Failed with a FailReason). One call = one creature — loop for more; a cold-species loop still costs one fetch (in-flight acquires dedup). |
Despawn(handle, kill=false) |
Remove one spawn: kill = real damage pipeline (death anim + loot, → Died); silent = destroy (→ Despawned). Cancels a still-Pending handle. |
DespawnAll(ownerTag=null, kill=false) |
Sweep by owner tag. null = EVERYTHING — that's the operator verb; mods should pass their own tag. |
Prewarm(species, onDone?) / IsPrewarmed(species) |
Pay/check the body fetch off your hot path. |
IsExpeditionOnly(species) |
True when a species can only be stocked by an expedition (see How it works) — check before promising a boss fight. |
Active(ownerTag=null) |
Snapshot of tracked handles (Pending + Alive). |
SpeciesKeys() |
The spawnable species roster. |
| Trap | Rule |
|---|---|
handle.Character can go Unity-null under you (scene unload, despawn) |
Check handle.IsAlive before every use; never cache the Character across scene loads. |
| Spawns are ephemeral by design | Scene unload = OnDespawned, and saves skip SK_ UIDs both directions. Don't build persistence on top. |
A cold species blocks ~1–6s before onReady |
Prewarm on your init path if the spawn must feel instant. |
MaxActiveSpawns is global across all consumers (pending spawns count too) |
Tag your spawns (OwnerTag) and never call DespawnAll(null) from a mod — you'd sweep other mods' spawns. |
| Callbacks run on the main thread inside the watch tick | Keep them cheap. A throwing callback is isolated (swallowed + logged) — it won't break SpawnKit, but you won't get a second chance either. |
Co-op clients always get Failed/NotMaster |
Design your feature so only the host drives spawning. |
StripQuestEvents = false on a quest-flagged creature fires real quest events on death |
The default (true) exists because live donors really do carry QuestEventOnDeath — leaving it on is what keeps spawns from mutating quest state. |
CorpsePolicy.NoBody destroys the loot bag with the body |
The corpse GameObject IS the loot container — never combine NoBody with a drop-loot expectation. |
| Ring geometry (step angles, elevation band, navmesh snap) is not configurable | By design. If you need exact placement, pass SpawnOptions.Position and own the spot yourself. |
| The species roster and creature supply are CompanionKit's, not SpawnKit's | An expedition-only species fails cold spawns until an expedition stocks it. |
Apache License 2.0 — see LICENSE. You may use, modify, and redistribute this kit (including in
commercial mods) provided you keep the copyright/license notice; see the license text for the full
terms.