Not translated yet — showing English.
Map
The static world: bounds, terrain, obstacles, and "where can things go?". The physical model is deliberately far simpler than the visual one: primitives with a footprint and a height, layers with rules, and one valid-position query every other module reuses.
When to use it
- You need a static world (bounds, terrain, obstacles) the server can query, not just render.
- Spawns, drops and decorations must land in legal spots: one rule-based
RandomPositionquery, no bypass. - Arenas should regenerate per match: a declared
Seedreproduces the same map in a bug report. - Crates and walls break and come back: destructibles with HP and respawn timers.
- Bots and projectiles need raycast and line-of-sight answers against the obstacle set.
- Skip it when the world is purely visual and no server code asks where things can go.
Who does what
| Actor | On this page |
|---|---|
schema-author | declares maps, obstacle primitives, destructibles, layers and their rules |
room-owner | binds a map to a room; requests spawn positions; raycasts |
operator | places or removes obstacles and layers from the panel |
At a glance
arena layout declared: seed and bounds, terrain, rocks, respawning crates, a rules layer[Map("arena", Seed = 42, Bounds = "160x160")]
public static class Arena
{
[Terrain(HeightNoise = 0.3f)] public static Terrain Height; // 3D height field
[Scatter("rock", Count = 40, MinSpacing = 6)] public static ObstacleSet Rocks;
[Destructible("crate", Count = 12, Hp = 100, RespawnAfter = "30s")] public static ObstacleSet Crates;
[Layer("ground", NotInside = "water")] public static Layer Ground;
}
// or: Maps.Named("arena-caves-v3") — authored in the panel or loaded from an asset@Map('arena', { seed: 42, bounds: '160x160' })
export class Arena {
@Terrain({ heightNoise: 0.3 }) height: Terrain; // 3D height field
@Scatter('rock', { count: 40, minSpacing: 6 }) rocks: ObstacleSet;
@Destructible('crate', { count: 12, hp: 100, respawnAfter: '30s' }) crates: ObstacleSet;
@Layer('ground', { notInside: 'water' }) ground: Layer;
}
// or: Maps.named('arena-caves-v3') — authored in the panel or loaded from an asset@Map("arena", seed=42, bounds="160x160")
class Arena:
height = terrain(height_noise=0.3) # 3D height field
rocks = scatter("rock", count=40, min_spacing=6)
crates = destructible("crate", count=12, hp=100, respawn_after="30s")
ground = layer("ground", not_inside="water")
# or: maps.named("arena-caves-v3") — authored in the panel or loaded from an assetAttribute-style declaration in Go is still open (O-1) — the samples land when the carrier is decided.
USTRUCT(PSMap = (Name = "arena", Seed = 42, Bounds = "160x160"))
struct FArena
{
GENERATED_BODY()
UPROPERTY(PSTerrain = (HeightNoise = "0.3")) FPSTerrain Height; // 3D height field
UPROPERTY(PSScatter = (Obstacle = "rock", Count = 40, MinSpacing = 6)) FPSObstacleSet Rocks;
UPROPERTY(PSDestructible = (Obstacle = "crate", Count = 12, Hp = 100,
RespawnAfter = "30s")) FPSObstacleSet Crates;
UPROPERTY(PSStratum = (Name = "ground", NotInside = "water")) FPSStratum Ground;
};
// or: PS::Maps::Named(TEXT("arena-caves-v3")) — authored in the panel or loaded from an asset
// declarations compile into the same pushed model — playserv push from the UE project or CI[Map("arena", Seed = 42, Bounds = "160x160")]
public static class Arena
{
[Terrain(HeightNoise = 0.3f)] public static Terrain Height; // 3D height field
[Scatter("rock", Count = 40, MinSpacing = 6)] public static ObstacleSet Rocks;
[Destructible("crate", Count = 12, Hp = 100, RespawnAfter = "30s")] public static ObstacleSet Crates;
[Layer("ground", NotInside = "water")] public static Layer Ground;
}
// or: Maps.Named("arena-caves-v3") — authored in the panel or loaded from an assetScatter and Destructible are placement generators, not runtime rolls. A generator resolves when the map version is published: the forty rocks become forty declared primitives, and the published version carries the primitives, not the rule. The same Seed therefore gives the same forty rocks in the match, in the replay and in the bug report — and the geometry limits are checked once, on that resolved set, before the version reaches an environment.
The query everything else asks:
RandomPosition: a fair spawn on ground, away from players, never repeatingvar spawn = map.RandomPosition(r =>
{
r.Layer("ground");
r.AwayFrom(players, minDistance: 12);
r.NoRepeat(lastN: 3);
});const spawn = map.randomPosition((r) => {
r.layer('ground');
r.awayFrom(players, { minDistance: 12 });
r.noRepeat({ lastN: 3 });
});spawn = map.random_position(rules=lambda r: (
r.layer("ground"),
r.away_from(players, min_distance=12),
r.no_repeat(last_n=3),
))Attribute-style declaration in Go is still open (O-1) — the samples land when the carrier is decided.
// Dedicated-server host: place a spawn through the same rule-based query
Map->Positions->GetRandom({ .Stratum = PSKeys::Strata::Ground,
.AwayFrom = Players,
.MinDistance = 12.f,
.NoRepeatLastN = 3 },
TPSOnResult<FVector>::CreateLambda([](const TPSResult<FVector>& Result)
{
if (!Result.HasValue()) { return; }
PlaceSpawn(Result.Value());
}));
// co_await support ships later: a built-in SDK coroutine wrapper that respects Unreal's GC and threading.var spawn = map.RandomPosition(r =>
{
r.Layer("ground");
r.AwayFrom(players, minDistance: 12);
r.NoRepeat(lastN: 3);
});The model
Two layers, declared by different people.
| Layer | What it holds, and who declares it |
|---|---|
static | terrain with height, obstacle primitives, bounds and locations — authored content |
dynamic | obstacles brought by entities at runtime: doors, destructibles, platforms. A destructible is therefore an entity with a lifecycle, states and an owner, and it becomes map only in the part where it brings an obstacle — a static rock is declared in the map, a door is an entity bringing one. These have no lifecycle of their own here: it belongs to the entity |
What a map declares.
| Declares | What it is |
|---|---|
key and version | a map is authored content: declared in code, addressed by key, and versioned — the version is part of what a room references. Changing the geometry of a released version is forbidden; an edit is a new version |
terrain | a height field — a regular grid with a declared step, and the step is a declared precision limit, so a height query answers by it rather than exactly. Terrain may be absent: an arena in the void is lawful |
obstacles | a closed set of primitives — box, sphere, capsule, convex hull with a declared vertex limit. An arbitrary triangle mesh is not accepted, which is the condition of a server-side check being possible at all |
passability kind per obstacle | impassable · passable for a declared class · blocks the line of sight only. One primitive serves as a wall and as a bush, and the difference is declared rather than modelled twice |
bounds | the volume outside which a position is inadmissible |
world strata | declared spatial strata inside a map — ground, underground, air. These are geometry and addressing declarations |
locations | named places or areas — a spawn point, a capture zone, a corridor. A location answers where, never what happens: it carries no game logic |
placement generator | optionally, a rule producing primitives — a count, a minimum spacing, an area, a seed. It is resolved when a version is published, deterministically by the seed, and thereafter the map holds primitives rather than a rule |
A world stratum and a map instance are never aliases.
| What it is | |
|---|---|
world stratum | a declaration inside the map — ground, underground, air |
map instance | an independent runtime copy of the published map. Instances share the immutable published geometry and have independent dynamic obstacles and independent entity rosters. A room occupies an instance and may select strata inside it |
What is true of every query.
| Always | What it is |
|---|---|
one geometric canon | all geometry is in the platform's declared coordinate canon, and the precision of every geometric field is declared on the field |
the world model | is a simplification: the server's geometry is not the art model, and it is not obliged to be |
an answer names its instance and its moment | a query is answered from the map's static layer plus the dynamic obstacles of the instance it asked about, and it declares the moment it is true for — dynamic obstacles change, so the answer is a snapshot |
the values | are managed, not seed: geometry isn't daily tuning, so an edit from the admin console is refused rather than silently kept |
movement and contact | are not resolved here: it answers what the space is; whether a position is admissible and what the response is belongs to collision, and applying it to locomotion |
Errors
- A map, a version or an instance that is not found answers not found, and so does a withdrawn version — repeating is pointless.
- Publishing changed geometry under an existing version is a conflict: make a new version.
- Publication failures land at declaration, at deploy, never at runtime — a map beyond the primitive limit, a convex hull beyond its vertex limit, and an arbitrary mesh as an obstacle are all validation refusals before anything ships.
- A height query outside the bounds is not an error — it is the declared answer "outside the bounds", and it is distinguishable from "inside an obstacle", because a client turns round in one case and goes round in the other.
- The query rate exceeded answers in the rate-limit category with a deadline.
Limits
The numbers arrive with the platform limits chapter; what happens at each ceiling is fixed now.
- Obstacle primitives per map, convex-hull vertices, height-field resolution, the size of the bounds, locations per map: every one of these is refused at publication, not at query time.
- Map instances per map: creating another is refused as a conflict; existing instances are never released to make room.
- Versions kept: the oldest deprecating version is withdrawn, and a version under a live room never is.
- The rate of queries to space: a rate limit with a deadline.