Repository navigation
Sector
Everything a Lua script can do with a sector.
| Where from | How |
|---|---|
| A script attached to the sector | the global sector
|
| A public field | public Sector door |
| A point in the map | Game.GetSectorAt(position) |
| An entity | entity:GetSector() |
| A raycast hit | hit.sector |
| A neighbour | sector:GetNeighbor(i) |
- Indices are 1-based: floors, vertices, walls, entities, neighbours and tags.
-
Map space. A
Vector2position is(x, z)on the map:.xis world x and.yis world z. Functions that take a position accept aVector3too and use only itsxandz. -
floorIndexpicks one floor/ceiling interval of a multi-storey sector. Most sectors have only one,1. When it's optional, it defaults to1. -
Bad arguments raise a Lua error. Examples: a floor index out of range, a speed of 0 or a
negative time. Wrap the call in
pcallif the values come from somewhere you don't control. -
Every sector handle stays safe. If the sector no longer exists,
isValidisfalseand any other call raises an error.
Game.GetSectorAt(position) -> Sector?| Parameter | Type | |
|---|---|---|
position |
Vector2 or Vector3
|
Map position. Height is ignored. |
Returns the sector containing position, or nil if the point is outside every sector. It uses
the same lookup that decides which sector an entity is in (see Occupancy).
local here = Game.GetSectorAt(entity.transform.position)
if here ~= nil and here:HasTag("water") then Debug.Print("splash") endentity:GetSector() -> Sector?Returns the sector the entity is standing in. It returns nil if the entity is outside the map
or has no Transform (UI entities, for example). This is the same membership ContainsEntity and
OnEntityEnter use.
A callback you define in a script attached to an entity. The engine calls it when that entity ends up in a different sector.
function OnSectorChange(sector) end| Parameter | Type | |
|---|---|---|
sector |
Sector? |
The sector the entity is in now, or nil if it left the map. |
- It fires at the end of the frame the entity crossed over, right after the sectors'
OnEntityExit/OnEntityEnter. - The sector the entity starts the level in does not count as a change.
- It only runs on entity scripts, and only while the script is enabled. On a sector script it
is ignored.
OnSectorChangeis a reserved name, so you can't use it for a public field.
function OnSectorChange(sector)
if sector ~= nil and sector:HasTag("water") then Debug.Print(entity.name .. " is swimming") end
end| Property | Type | Notes | |
|---|---|---|---|
id |
integer | read-only | Stable sector ID (the # number in the editor). |
isValid |
boolean | read-only |
false once the sector no longer exists. |
name |
string | read/write | Set in the sector inspector. Empty if unnamed. |
floorHeight |
number | read/write | Floor height of floor 1. Same as GetFloor(1).floorHeight. |
light |
Vector3 | read/write | Light colour, 0-255 per channel. Changes reach child sectors. Setting it cancels a running FadeLight. |
floorCount |
integer | read-only | Number of floor/ceiling intervals. |
vertexCount |
integer | read-only | |
wallCount |
integer | read-only | |
entityCount |
integer | read-only | Entities inside right now. Same as CountEntities(). |
neighborCount |
integer | read-only | |
tagCount |
integer | read-only | Tags are set in the editor; scripts can't add or remove them. |
- An entity is inside the sector that contains its
(x, z)position. Height is ignored, so on a multi-storey sector every storey counts as the same sector. - An entity is inside exactly one sector: the innermost one. An entity standing on a pillar or platform that is a child sector is inside the child, not the parent.
- Entities without a Transform are never inside a sector.
- Membership updates every frame for every entity that moved, whether a Rigidbody, the player controller or a script moved it.
sector:ContainsEntity(entity) -> boolean| Parameter | Type | |
|---|---|---|
entity |
Entity |
The entity to look for. |
Returns true if entity is inside this sector.
public Entity player = nil
function Update()
if sector:ContainsEntity(player) then sector:FadeLight(Vector3(255, 80, 80), 0.5) end
endsector:ContainsEntityWithTag(tag) -> boolean| Parameter | Type | |
|---|---|---|
tag |
string | Tag name, as created in Project Settings. |
Returns true if at least one entity inside this sector has tag. An unknown tag returns
false.
if not sector:ContainsEntityWithTag("enemy") then OpenExitDoor() endsector:GetEntities() -> Entity[]Every entity inside this sector, as a Lua list. The order isn't meaningful.
sector:GetEntitiesWithTag(tag) -> Entity[]Every entity inside this sector that has tag.
for _, e in ipairs(sector:GetEntitiesWithTag("burnable")) do
e:GetScript("Health"):TakeDamage(5)
endsector:CountEntities() -> integer
sector:CountEntities(tag) -> integer| Parameter | Type | |
|---|---|---|
tag |
string, optional | Count only entities with this tag. |
The number of entities inside, or only those with tag.
sector:IsEmpty() -> booleanReturns true if no entity is inside this sector.
sector:GetEntity(index) -> EntityThe index-th entity inside (1-based, up to entityCount). This is the old way to walk the list.
GetEntities() is usually simpler.
These are callbacks. You define them in a script attached to a sector and the engine calls them.
function OnEntityEnter(entity) end
function OnEntityExit(entity) end| Parameter | Type | |
|---|---|---|
entity |
Entity |
The entity that came in or went out. |
- They fire once, at the end of the frame the entity crossed the boundary, after all movement and physics for that frame.
- Entities already inside when the level starts do not trigger
OnEntityEnter. Usesector:GetEntities()inStartif you need them. - Moving from one sector to another fires the old sector's
OnEntityExitfirst, then the new sector'sOnEntityEnter. - An entity that is destroyed while inside, or that leaves the map entirely, also fires
OnEntityExit. After a destroy,entity.isValidisfalse, so check it before using the entity. - They only run on sector scripts, and only while the script is enabled. On an entity script they are ignored.
-
OnEntityEnterandOnEntityExitare reserved names: you can't use them for public fields.
-- Trap room: slam the door behind the player, reopen it when the room is clear.
public Sector door = nil
function OnEntityEnter(entity)
if entity.hasPlayerController then door:MoveCeilingToFloor(1, 200) end
end
function OnEntityExit(entity)
if sector:CountEntities("enemy") == 0 then door:MoveFloorToCeiling(1, 60) end
endsector:GetFloor(floorIndex) -> SectorFloorOne floor/ceiling interval. SectorFloor has floorHeight, ceilingHeight, floorColor,
ceilingColor, floorTexture and ceilingTexture (all read/write), plus index, isValid,
ClearFloorTexture() and ClearCeilingTexture(). Writing a height raises an error if the floor
would reach its ceiling or overlap another interval.
sector:GetFloorHeightAt(position) -> number
sector:GetFloorHeightAt(position, floorIndex) -> number| Parameter | Type | |
|---|---|---|
position |
Vector2 or Vector3
|
Map position (x, z). |
floorIndex |
integer, optional | Default 1. |
The floor height at position, including slope. On a flat floor this is just
GetFloor(i).floorHeight. On a sloped one, it's the height where something standing at
position would actually be. It uses the same slope math as physics and rendering. Points
outside the sector are extrapolated along the slope.
local p = Vector2(12, 4)
spawned.transform.position = Vector3(p.x, sector:GetFloorHeightAt(p), p.y)sector:GetCeilingHeightAt(position) -> number
sector:GetCeilingHeightAt(position, floorIndex) -> numberSame as GetFloorHeightAt, for the ceiling.
Every move below is fire-and-forget. You call it once and the engine moves the surface a bit
each frame until it arrives. No Update code is needed.
- Starting a new move on the same surface replaces the old one.
- A floor never passes its ceiling and a ceiling never passes its floor (they stop 0.01 apart). Neither passes into the interval above or below.
- Moves pass on to child sectors, the same as an edit in the inspector.
sector:MoveFloorToCeiling(floorIndex, speed)
sector:MoveFloorToCeiling(floorIndex, speed, gap)
sector:MoveCeilingToFloor(floorIndex, speed)
sector:MoveCeilingToFloor(floorIndex, speed, gap)| Parameter | Type | |
|---|---|---|
floorIndex |
integer | Which interval. |
speed |
number | Units per second, above 0. |
gap |
number, optional | Stop this far short of the opposite surface. Default 0 (really 0.01). Not negative. |
Moves the floor up to its ceiling, or the ceiling down to its floor. The target follows the opposite surface, so it still stops at the right place if that surface is moving too.
sector:MoveFloorToCeilingOverTime(floorIndex, seconds)
sector:MoveFloorToCeilingOverTime(floorIndex, seconds, gap)
sector:MoveCeilingToFloorOverTime(floorIndex, seconds)
sector:MoveCeilingToFloorOverTime(floorIndex, seconds, gap)The same moves, but arriving after seconds (not negative; 0 snaps next frame) instead of at a
set speed.
sector:MoveFloorTo(floorIndex, height, speed)
sector:MoveCeilingTo(floorIndex, height, speed)| Parameter | Type | |
|---|---|---|
floorIndex |
integer | Which interval. |
height |
number | Absolute world height to move to. Can be above or below the current height. |
speed |
number | Units per second, above 0. |
Moves the floor or ceiling to an exact height, up or down. Use these for multi-stop lifts,
crushers that stop partway and rising water. If height is past the opposite surface, the move
stops just short of it.
-- Three-stop lift.
local stops = {0, 64, 128}
local current = 1
function GoTo(stop)
current = stop
sector:MoveFloorTo(1, stops[stop], 40)
endsector:MoveFloorToOverTime(floorIndex, height, seconds)
sector:MoveCeilingToOverTime(floorIndex, height, seconds)MoveFloorTo / MoveCeilingTo, arriving after seconds (not negative).
sector:IsMoving(floorIndex) -> booleanReturns true while this interval's floor or ceiling is still moving.
sector:IsFloorMoving(floorIndex) -> boolean
sector:IsCeilingMoving(floorIndex) -> booleanThe same check for just one surface. For example, a door can wait for its ceiling to finish opening before starting its close timer:
local waiting = false
function Open()
sector:MoveCeilingTo(1, 128, 80)
waiting = true
end
function Update()
if waiting and not sector:IsCeilingMoving(1) then
waiting = false
-- fully open: start the close timer here
end
endsector:StopMoving(floorIndex)Stops this interval's floor and ceiling where they are.
sector:FadeLight(color, seconds)| Parameter | Type | |
|---|---|---|
color |
Vector3 |
Target light, 0-255 per channel. |
seconds |
number | Fade length. Not negative; 0 snaps next frame. |
Fades light from its current value to color over seconds, in a straight line. It runs by
itself each frame and child sectors follow along. Starting another fade replaces this one, and
setting sector.light directly cancels it.
-- Power cut
sector:FadeLight(Vector3(20, 20, 30), 2.0)sector:IsLightFading() -> booleanReturns true while a FadeLight is running.
All shape values are in map space. Child sectors cut out of this one (pillars, islands) are not part of its area.
sector:GetArea() -> numberFloor area in square units.
sector:GetCenter() -> Vector2The area-weighted centre, (x, z). For L-shaped or ring-shaped sectors this point can fall
outside the sector, like any centroid. Use RandomPointInside when you need a point that is
guaranteed to be inside.
sector:GetBounds() -> Vector2, Vector2Returns two values: the minimum and maximum corner of the sector's bounding rectangle,
(x, z).
local min, max = sector:GetBounds()
local width, depth = max.x - min.x, max.y - min.ysector:DistanceToSector(entity) -> number| Parameter | Type | |
|---|---|---|
entity |
Entity | Measured from its transform's x and z. Height is ignored. |
Distance from the entity to the nearest edge of the sector's outline. Returns 0 while the entity
is inside the sector. Child sectors cut out of this one count as outside, same as
ContainsEntity, and their edges count as part of the outline. Works for a sector of any shape.
Raises an error if the entity has no Transform.
-- Open a door when the player comes within 30 units of it.
if sector:DistanceToSector(player) <= 30 then
sector:MoveCeilingToFloor(1, 60, 40)
endsector:DistanceToSectorSquared(entity) -> numberDistanceToSector squared, without the square root. Compare it against a squared range when you
only need "is it within range":
if sector:DistanceToSectorSquared(player) <= range * range then
-- in range
endsector:RandomPointInside() -> Vector3
sector:RandomPointInside(floorIndex) -> Vector3| Parameter | Type | |
|---|---|---|
floorIndex |
integer, optional | Which floor to stand the point on. Default 1. |
Returns a random point inside the sector, evenly spread over its area and never inside a child
sector. y is the floor height at that spot, slope included, so the point is ready to use as an
entity position. It uses the same generator as mathT.Random*, so mathT.RandomSeed makes it
repeatable.
for i = 1, 5 do
local e = Game.FindEntity("Coin" .. i)
if e ~= nil then e.transform.position = sector:RandomPointInside() end
end| Function | Returns | |
|---|---|---|
GetVertex(index) |
Vector2 |
Outer boundary corner, 1-based. |
GetWall(index) |
Wall |
A wall on this sector's boundary, 1-based. |
GetNeighbor(index) |
Sector |
A sector sharing a wall with this one, 1-based. |
| Function | Returns | |
|---|---|---|
HasTag(tag) |
boolean |
true if this sector has tag. |
GetTag(index) |
string | The index-th tag, 1-based. |
Tags are assigned in the editor. Scripts can read them but not change them.