Level Editor/Scripting/Zones
A zone is a volume your script reacts to: a capture point, a spawn room, a deposit area, a hazard. Every area trigger in the map is a zone, and your script decides what each one means by its name.
Making a zone
- Build a brush of the shape you want with any shape tool. It can be carved and subtracted like any brush, so a zone can follow the shape of a room.
- With it selected, tick Is Area in the inspector. The brush is no longer solid or visible in the game; it only marks out space.
- Set Trigger Name to the name your script will match, for example
Capture Point. Left blank, the brush's own name is used. - Set Zone Team if the zone belongs to a team (0 means neutral).
Zone brushes draw translucent in the editor. Edit > Click Through Zone Brushes lets you click what is behind them, and Area Trigger Opacity in Settings changes how strongly they draw.
Names, keys and labels
Several zones may share a name: one "Spawn Room" per team is the natural way to build a map, told apart by their team. So a zone has two names:
- Its key, which the hooks hand you as
zone. Every key is unique: when two zones share a name, the second one's key gets a number added. - Its label, exactly the name you typed. api.zone_label
(zone)returns it.
Compare labels, not keys. zone == &"Capture Point" only ever matches the first zone with that name; api.zone_label(zone) == &"Capture Point" matches all of them. Tell same-named zones apart with api.zone_team, and find them all with api.zones_labeled. Names are matched exactly, including capitals and spaces.
Zone hooks
func _zone_entered(zone: StringName, entity_id: int) -> void: pass func _zone_exited(zone: StringName, entity_id: int) -> void: pass func _zone_tick(zone: StringName, occupants: Array, delta: float) -> void: pass
_zone_enteredruns when a player comes into a zone._zone_exitedruns when they go out of it, and also when they die or leave the match inside it. Do not assume an exit means they are alive somewhere else._zone_tickruns every server tick for every zone, including empty ones, so a capture meter can drain when nobody is on the point.occupantsis sorted.- Only living players count. What counts is the player's position: they are inside when the point they stand on is inside the volume.
_zone_usedruns when a player standing in a usable zone presses the use key. A zone is usable when it has a Use Prompt, which the HUD shows to anyone inside it. See _zone_used.
To ask at any other moment: api.zones, api.zone_occupants and api.zone_position. The hooks are the cheaper way; there is no need to check every zone yourself in _tick.
A zone can also act without a script: its Outputs in the inspector fire another entity's input (open a door, trigger a relay) when a player comes in or goes out. See Logic and outputs.
The world boundary
Place one World Boundary from the Functional menu, below everything a player can stand on. It is a flat plane through the marker: anybody whose position falls below it has left the world.
That does not arrive through _zone_entered. It calls _left_world(entity_id), which by default kills the player with nobody credited. Override it to do something else, such as putting them back:
func _left_world(entity_id: int) -> void: api.teleport(entity_id, _spawn_position(entity_id))
Do not name a zone WorldBoundary; that name belongs to the boundary.
Moving zones
A zone brush placed under a moving platform (Functional > Create Anchor Platform) moves with the platform, so a lift can carry a capture point. Nothing changes in the script: the zone arrives through the same hooks, and api.zone_position follows it.
A payload cart carries a zone of its own too; see Payload tracks.