Skip to main content
The live game

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 RandomPosition query, no bypass.
  • Arenas should regenerate per match: a declared Seed reproduces 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

ActorOn this page
schema-authordeclares maps, obstacle primitives, destructibles, layers and their rules
room-ownerbinds a map to a room; requests spawn positions; raycasts
operatorplaces or removes obstacles and layers from the panel

At a glance

The 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 asset
Coming soon — Go

Attribute-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
Unity C# is the same C# API here — the same attributes compile in Unity, on the 2021.3 baseline — no C# 12 syntax here
[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

Scatter 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 repeating
var 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),
))
Coming soon — Go

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.
Unity C# is the same C# API here — a master-client build runs the same query; a plain client is spawned at the resulting position
var spawn = map.RandomPosition(r =>
{
    r.Layer("ground");
    r.AwayFrom(players, minDistance: 12);
    r.NoRepeat(lastN: 3);
});

The model

static layer — the published map versionone map instancea query — RandomPosition, a raycastan answer, true for a named momentdynamic obstacles brought by entities shared, immutable geometrythis instance only
A query reads the published static layer plus the dynamic obstacles of one instance; the answer holds for a named moment

Two layers, declared by different people.

LayerWhat it holds, and who declares it
staticterrain with height, obstacle primitives, bounds and locations — authored content
dynamicobstacles 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.

DeclaresWhat it is
key and versiona 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
terraina 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
obstaclesa 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 obstacleimpassable · 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
boundsthe volume outside which a position is inadmissible
world stratadeclared spatial strata inside a map — ground, underground, air. These are geometry and addressing declarations
locationsnamed places or areas — a spawn point, a capture zone, a corridor. A location answers where, never what happens: it carries no game logic
placement generatoroptionally, 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 stratuma declaration inside the map — ground, underground, air
map instancean 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.

AlwaysWhat it is
one geometric canonall geometry is in the platform's declared coordinate canon, and the precision of every geometric field is declared on the field
the world modelis 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 momenta 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 valuesare managed, not seed: geometry isn't daily tuning, so an edit from the admin console is refused rather than silently kept
movement and contactare 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.