Skip to main content
The live game

Not translated yet — showing English.

Collision

Bind a transform to the obstacle map; declare what contact does. Collision runs inside the platform's simulation. You declare bodies, layers and responses, and subscribe to contacts.

When to use it

  • Moving entities must resolve contacts server-side (slide, stop, bounce) without a hand-written deflection routine.
  • Gameplay reacts to touch: pickups collect on overlap, trigger volumes fire an entity state machine.
  • Locomotion and projectiles need swept resolution against the map's obstacle set.
  • Placement previews or targeting need "would this fit here?" and volume-overlap queries.
  • Skip it when nothing physically meets. Request/response gameplay over records is plain Data.

Who does what

ActorOn this page
room-ownerdeclares bodies, layers and responses; queries overlaps and contacts

Which rooms this applies to

This module runs where the platform steps the simulation: rooms declared Host = "Backend". If your own game server owns the simulation (PlayServ as the metaserver), movement, collision and prediction stay engine-side, and this page describes the platform-hosted alternative rather than a requirement.

At a glance

Shape, layer and what contact does all sit on the body itself — nothing declares layer pairs from a distance:

A capsule Body on the tank: vehicles layer — sliding off walls, passing through pickups, crates decided per contact
[Entity("tank")]
public class Tank
{
    [Sync] public Vector3 Position;

    [Body(Shape.Capsule, Radius = 0.6f, Layer = "vehicles")]
    [CollidesWith("walls", Response.Slide)]
    [CollidesWith("pickups", Response.Pass)]        // reported, motion passes through
    public Body Body;
}
@Entity('tank')
export class Tank {
  @Sync() position!: Vector3;

  @Body({ shape: 'capsule', radius: 0.6, layer: 'vehicles' })
  @CollidesWith('walls', Response.Slide)
  @CollidesWith('pickups', Response.Pass)        // reported, motion passes through
  body: Body;
}
@entity("tank")
class Tank:
    position: Vector3 = sync()

    body = collision.body(shape="capsule", radius=0.6, layer="vehicles",
                           collides_with=[
                               ("walls", Response.SLIDE),
                               ("pickups", Response.PASS),   # reported, motion passes through
                           ])
Coming soon — Go

Attribute-style declaration in Go is still open (O-1) — the samples land when the carrier is decided.

// the declaration rides inside the engine's own reflection macros, in the specifier position —
// UHT reads it from the header text, and the member is a reflected property at the same time
UCLASS(PSEntity = "tank")
class UTank : public UObject
{
    GENERATED_BODY()

    UPROPERTY(PSSync)
    FVector3f Position;

    // walls slide, pickups report the contact and let motion pass through —
    // multi-entry values are one quoted list (a specifier value is a single token)
    UPROPERTY(PSBody = (Shape = "Capsule", Radius = "0.6", Layer = "vehicles"),
              PSCollidesWith = "walls:Slide, pickups:Pass")
    FPSBody Body;
};
// 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
[Entity("tank")]
public class Tank
{
    [Sync] public Vector3 Position;

    [Body(Shape.Capsule, Radius = 0.6f, Layer = "vehicles")]
    [CollidesWith("walls", Response.Slide)]
    [CollidesWith("pickups", Response.Pass)]        // reported, motion passes through
    public Body Body;
}

A contact is an event, and modules subscribe to it. There is no hook on a contact: by the time one exists the step has already resolved it, so there is nothing left to reject. Where a studio needs different rules, it overrides the admissibility and path checks as an implementation — see Extensibility — and the response stays declared.

The model

What a body declares.

DeclaresWhat it is
shapea primitive from a closed set — sphere, capsule, box — with declared dimensions. An arbitrary mesh is not on offer, the same constraint and the same reason as the server's world model in map
where it liveson an aspect, together with the transform: that is the unit of policy, and a body shares one fate with its transform
how its path is checkedstepwise: the step's final position is checked; fast. swept: the segment between positions is checked; more expensive, and tunnelling is excluded within a step. Declared, never chosen by an implementation from the speed
areas it participates inwhich volumes it is counted inside
its relation to the art modelnone is required: a body is a simplification, and divergence from the art model is admissible within declared bounds

A fast stepwise body passes through thin obstacles

stepwise checks only where a step ends, so a fast body can pass through a thin obstacle. swept checks the segment between positions and excludes tunnelling within a step. Whether a projectile can fly through a wall is a property of your game, and you declare it.

The response is declared on a pair — an obstacle's passability kind × a body type — and it comes from a closed set:

ResponseWhat it means
stopmovement ceases at the last admissible position
slidemovement continues along the obstacle by whatever component is admissible
bouncethe direction is reflected and the speed multiplied by a declared coefficient
dampmovement continues with the speed multiplied by a declared fraction
passthe obstacle does not affect movement, but the contact is still observable
cease to existthe entity ends — a projectile against a wall

The coefficients are declared values rather than computed from masses and materials — there are neither in this contract.

What is true of every check.

AlwaysWhat it is
every pairhas a response: a missing pair is a declaration defect, refused at deploy rather than met in combat
the response tableis readable by the client: the same table the authority computes by, so a client and a server given one declaration answer one contact the same way
reproducible within one authority, not across platformsthe same input in the same order gives the same result within one process and one build. Bit-identical results on different platforms and builds are not promised
simultaneityis declared: when two moving bodies collide inside one step the order of resolution is declared and deterministic. The storage traversal order, the order input arrived and randomness may not be the ground for it
one contact, one facta contact between two bodies is observable by both sides as a single fact with a single identifier, not as two independent events
extension points sit on the step, not on a contactbefore the step the transform may be changed, after it there is observation. An override of the admissibility and path checks must also be available to the client
the modulemoves nothing itself: it answers whether a position is admissible and what the response is; applying that is locomotion's
a contact is an eventwhich is why modules subscribe rather than couple: a trap's state machine binds a transition to a trigger-volume entry, drops collects on overlap, and projectiles resolve hits through this module's sweep

Errors

  • "Inadmissible" is an answer, not an error, and it names which of three reasons: outside the bounds, occupied by a static obstacle, or occupied by another entity's body. A client reacts to the three differently: turn round, go round, or wait.
  • Declaration failures land at deploy, never on the first contact: a body shaped outside the closed set, a body on an aspect with no transform, and a pair with no declared response are all refused at deploy.
  • The entity or the location is not found answers not found, and repeating is pointless.
  • The check rate exceeded answers in the rate-limit category, with the deadline before which a retry is pointless.

Limits

The numbers arrive with the platform limits chapter; what happens at each ceiling is fixed now.

  • Bodies in a room: declaring another is refused as a conflict; existing bodies are never removed to make room.
  • Contacts per step: the excess is never discarded silently. Either the step is refused, or the order of cut-off is declared.
  • A body's size against the map's grid step: refused at deploy, because a body smaller than the height field's step falls through the terrain.
  • Areas one body may be inside: an excess is refused at deploy.
  • The check rate per actor: a rate limit with a deadline.
  • Bodies in a "who is in this area" answer: truncated by a declared order, and the truncation flag is obligatory.