Skip to content

3D collision

Use the 3D collision backend when a project has simulation.dimensions: 3 and the server needs authoritative volume checks: spheres around pickups, boxes around hazards, raycasts for line of sight, or a central contact list for gameplay rules. The built-in 3D backend is pure Go and detection-only; it does not simulate rigid bodies or move entities for you.

3D collision expects generated entities to expose Position3D. Enable 3D positions and declare the named collision matrix in golem.yaml:

simulation:
dimensions: 3
collision:
layers: [Player, Enemy, World]
collides:
- [Player, Enemy]
- [Player, World]

After golem-bake, generated Go entities have SetPosition3D(x, y, z) and clients receive posZ / PosZ alongside X/Y. Interest management still uses the XY projection; see Interest management.

Choose one 3D shape in each collidable entity schema:

entity: Enemy
collider:
aabb3d: [1.0, 2.0, 1.0]
layer: Enemy
vars:
health: { tag: 1, type: int32 }

Use sphere: radius for spherical volumes or aabb3d: [width, height, depth] for boxes. AABB values are full extents, and every size must be positive. Set trigger: true for overlap-only volumes.

Available 3D shapes:

TypeFieldsDescription
golem.CollisionSphereR float64Sphere centered on the entity’s Position3D
golem.CollisionAABB3DW, H, D float64Axis-aligned box centered on the entity’s Position3D

The trigger argument follows the 2D convention: trigger contacts are reported with Depth == 0.

Create the pure-Go backend and enable it through the generated runtime before restoring or spawning collidable entities:

srv, rt := generated.NewServer(golem.ServerConfig{TickRate: 20})
backend := golem.NewCollisionSimple3DBackend()
rt.EnableCollision3D(backend)

The generated setup exposes the named matrix as rt.Layers3D. The server registers schema shapes before OnSpawn and removes them before OnRemove. Each tick it reads Position3D() from live entities, updates the backend, runs collision detection, and calls OnContact3D if any pairs overlap.

For runtime-selected shapes, override a generated entity’s Collider3D() method or call rt.Layers3D.Add / Set / Remove manually.

Register a central callback when one place should handle every 3D overlap:

srv.OnContact3D(func(contacts []golem.CollisionContact3D) {
for _, c := range contacts {
if c.Depth == 0 {
handleTrigger3D(c.A, c.B)
continue
}
handleSolid3D(c.A, c.B, c.Normal, c.Depth)
}
})

CollisionContact3D.Normal is a golem.CollisionVec3 push direction for entity A, pointing away from entity B. Depth is the penetration distance for solid contacts.

Per-entity enter/stay/exit interfaces are currently 2D-only. For 3D collision, use OnContact3D and track transitions in game code if you need enter/exit semantics.

Use 3D overlap queries for volume checks:

nearby := srv.OverlapSphere(px, py, pz, 8, rt.Layers3D.MaskFor("Enemy"))
insideRoom := srv.OverlapBox3D(cx, cy, cz, 5, 3, 5, rt.Layers3D.MaskFor("Player"))

Use raycasts for ordered line checks:

hit, ok := srv.Raycast3D(
golem.CollisionVec3{X: eyeX, Y: eyeY, Z: eyeZ},
golem.CollisionVec3{X: targetX, Y: targetY, Z: targetZ},
rt.Layers3D.MaskFor("World", "Enemy"),
)
if ok {
log.Println("hit entity", hit.EntityID, "at", hit.Point)
}

RaycastAll3D returns every hit sorted by Fraction, closest first.

The built-in 3D backend is intentionally small:

  • It detects sphere/sphere, sphere/AABB, and AABB/AABB overlaps.
  • It supports OverlapSphere, OverlapBox3D, Raycast3D, and RaycastAll3D.
  • It does not integrate velocity, apply impulses, solve constraints, rotate boxes, or provide character controllers.
  • It uses a simple pairwise pass, so very large collider counts should move to a future broadphase or native physics backend.

For 2D physics simulation, see Chipmunk (cp) backend. For 2D overlap and cast queries, see Overlap & cast queries.