-
Notifications
You must be signed in to change notification settings - Fork 1
Tutorial Spawning Objects
Put something on the map that wasn't there when the map was built — anywhere the player happens to be standing.
place_object moves an object the map already carries. That works beautifully for a scripted scene on a map you designed: pre-place the rival at the door, hide him, show him when the moment comes.
It does nothing for anything the player can do anywhere. If you want a campsite the player can pitch on any patch of grass, you would need six invisible objects pre-placed on every patch of grass in your game.
spawn_object creates one.
as is the handle — the name you'll use to talk about this object afterwards. facing is optional and defaults to down.
From a script:
spawn_talker("camp_1", "SPRITE_MONSTER", player_x() + 1, player_y(), "left", "script:camp_chatter")
spawn_object("fire", "SPRITE_POKE_BALL", player_x(), player_y() + 1, "down")
Once spawned, your handle behaves like any object the map shipped with: face_object turns it, place_object moves it, hide_object / show_object toggle it. Spawning was the only missing verb.
Two ways, and the difference matters.
Inline — the spawn carries its own dialogue. text is a literal line; talk names a script:
{"cmd": "spawn_object", "as": "camp_1", "sprite": "SPRITE_MONSTER", "at": [7, 9],
"talk": "script:camp_chatter"}Keyed — an ordinary interact record:
{
"trigger": {"kind": "interact", "map": "map:Meadowbrook", "object": "camp_1"},
"commands": [{"cmd": "run_script", "script": "script:camp_chatter"}]
}Use inline for camp anywhere. Interact records are keyed by map and object, so a keyed record only reaches your handle on the maps you wrote records for — which is the designated-sites problem again. The inline dialogue travels with the spawn, so it works on any map the player stops on.
Use a keyed record when you want this specific place to be different. Where both exist, the keyed record wins — specific beats generic.
A talk script gets the full hatch surface run_script gets, so combine it with get_mon_field and per-individual fields and each body round your fire says something different depending on who it is, who else is in the party, and where you pitched:
let kind = get_mon_field(0, "type1")
if kind == "FIRE" {
say("It sits closer to the flames than the others.")
return true
}
say("It watches the sparks go up.")
return true
A talk script that errors or returns nothing rolls back its writes and falls through to text if you gave one — a half-written script is a visible no-op, not a silent success.
Spawned objects are transient by construction:
- A map change clears every one of them. The object list is rebuilt from the map's own, always.
- Nothing is written to your save file.
- Nothing enters the link identity, so a spawn can't desync a trade or a battle.
Which means a campfire re-places every time the player camps. Nothing persists except whatever flag you set yourself — and that is the behavior you want anyway.
A cap of 8 per map. Past it, spawn_object refuses loudly and stops the event. That second half is the point: a record looping over something unbounded halts instead of logging ninety errors and half-building a scene.
Re-spawning a handle replaces it. If the player camps twice on the same map without leaving, your record just runs again — the second spawn_object for camp_1 replaces the first rather than refusing. And despawn_object on something that isn't there is a quiet no-op, so "despawn everything, then spawn everything" is a safe opening move.
The map's own objects are off limits, both ways. You can't take over a map object's key with a handle, and despawn_object can only ever undo a spawn — there is no way to permanently delete an NPC the map shipped with.
Sprites come from your existing roster. No new asset path. An unknown sprite name refuses at --validate and at load, naming it.
The handle, the facing and the sprite are all authored literals, so all three refuse at load, naming the record — not mid-scene. --validate also collects every handle any record spawns, so a record addressing what it spawned is never reported as a dangling reference, while a despawn_object naming a handle nothing creates still is.
It isn't a follower system. An overworld companion that walks behind you everywhere is a much bigger feature — it needs walk cycles rather than idle poses, and it still wouldn't answer "put six of them round a fire". If followers ever ship, this stays useful; the campfire would just gain bodies that walked in rather than appeared.
Pairs with Overlay Projects and Set-Piece Battles & Narration. Gate: --mapobjtest.
Monworks: MIT code · CC0 samples · everything pictured is original content. The faithful Pokémon Red build derives from your own local pokered clone and is never distributed. Pokémon is © Nintendo/Creatures/GAME FREAK; this is an unaffiliated fan engineering project.
Play
Create
Tutorials
- Writing Text
- Battle Backdrops
- Colors and Palettes
- Display Filters and Shaders
- Widescreen and Scaling
- Quality-of-Life Knobs
- Overlays (Recipes)
- Abilities & Battle Hooks
- Modern Individuals
- Per-Individual Fields
- Reading a Party Member
- Item Field Scripts
- Spawning Objects
- Battle Animations by Mode
- Overlay Projects
- Field Effects
- The Modern Ruleset
- Per-Save Rulesets
- Six Stats
- The Clock
- Conditioned Bindings
- Day and Night
- Sprite Variants
- Overworld Spawns
- Language Packs
- Set-Piece Battles & Narration
- Run a Server
- Join a Server
Elsewhere
{"cmd": "spawn_object", "as": "camp_1", "sprite": "SPRITE_MONSTER", "at": [7, 9], "facing": "down", "talk": "script:camp_chatter"} {"cmd": "face_object", "object": "camp_1", "dir": "left"} {"cmd": "despawn_object", "object": "camp_1"}