Skip to content

Backends & wiring

When entities can bump into walls, block each other, or trigger events on contact, you need to know which of them are overlapping each tick. golem provides a collision backend system that slots directly into the tick loop: you supply a backend, register shapes when entities spawn, and the server delivers overlap information after each simulation step—without changing any other part of your server.

Two 2D backends ship out of the box:

  • golem.collision/resolv — detection-only. Reports overlapping pairs and the push-out vector but does not physically move anything. Good for most 2D games where your own code handles movement.
  • golem.collision/cp — full physics simulation via Chipmunk2D. By default your simulation stays authoritative over movement while the engine detects contacts; you can also register entities so Chipmunk integrates and resolves them as proper physics bodies. See Chipmunk (cp) backend.

Both implement the same golem.CollisionBackend interface, so you can swap them without touching the rest of your code.

For 3D projects, golem.NewCollisionSimple3DBackend() provides a pure-Go detection-only MVP with spheres, axis-aligned boxes, overlap queries, raycasts, and OnContact3D. It does not simulate rigid bodies, constraints, rotations, or character controllers. Use it when the server needs authoritative 3D overlap/contact checks without a native physics dependency. See 3D collision for the full setup.

backend := golem.NewCollisionSimple3DBackend()
srv.SetCollision3DBackend(backend)
backend.Add(playerID, golem.CollisionSphere{R: 0.5}, playerLayer, worldMask, false)
srv.OnContact3D(func(contacts []golem.CollisionContact3D) {
// contacts include Normal as a golem.CollisionVec3 and penetration Depth
})

Pick a backend, create it, and hand it to the server. Turn on contact events once, then implement the contact interfaces you need on your entity types (for example trigger enter/exit or solid collision enter):

import (
"golem"
collisionresolv "golem.collision/resolv"
)
backend := collisionresolv.New()
srv.SetCollisionBackend(backend)
srv.EnableContactEvents()
// On your entity types: OnTriggerEnter, OnCollisionEnter, … — see Contacts & events.

SetCollisionBackend activates the per-tick collision step. EnableContactEvents turns on per-entity enter/stay/exit delivery; without it, those entity methods never run (overlaps are still computed for the step, ReadBack, and spatial queries). You can still register an optional OnContact callback without EnableContactEvents if you want a single global list—most games should prefer entity events instead. Details are on Contacts & events.

Each tick, after your OnTick callback and before the entity flush, the server runs the collision step:

  1. Update — every live entity’s current position is fed into the backend.
  2. Step — the backend detects overlaps (and advances physics for the cp backend). Returns a list of contacts.
  3. ReadBack — for physics backends (cp), any position corrections are written back to the entity via SetPosition. Because this happens before the flush, the corrected position is included in the delta sent to clients that tick automatically.
  4. Contact delivery — if you called EnableContactEvents, the server dispatches per-entity enter/stay/exit callbacks from that contact list. You can also register an optional OnContact hook if you want one place to see every pair (logging, prototypes, global rules); see Contacts & events.

You do not need to call any of these manually—SetCollisionBackend is the only thing that activates them.

backend := collisionresolv.New()
srv.SetCollisionBackend(backend)
srv.EnableContactEvents()
srv.SetRemovalSerializer(synced.MarshalEntityRemoved)
srv.OnTick(func(dt float64, s *golem.Server) {
// move entities — SetPosition here; collision step runs after this
})
// Implement golem.TriggerEnter / golem.CollisionEnter / … on your entity types — see Contacts & events.
srv.OnConnect(func(sess *golem.Session) {
p := &Player{synced.NewSyncedPlayer(nextID(), 0, 0)}
_ = srv.CreateEntity(p, sess.ID)
// OnSpawn calls backend.Add automatically
})
srv.OnDisconnect(func(sess *golem.Session) {
srv.DeleteEntity(playerID(sess.ID))
// OnRemove calls backend.Remove automatically
})
if err := srv.Run(ctx); err != nil && err != context.Canceled {
log.Fatal(err)
}

Next: Colliders (register 2D shapes and layers), Contacts & events (2D contact interfaces and optional OnContact), and 3D collision. For full 2D simulation with Chipmunk, see Chipmunk (cp) backend. For one-shot 2D overlap and cast queries without persistent shapes, see Overlap & cast queries.

See also: Game loop, Map file serving, Interest management.