Skip to content

Colliders

The server drives the per-tick collision step, but it does not know which entities should participate—that is game code’s responsibility. Register a shape when an entity spawns and unregister it when the entity is removed. The cleanest place is the OnSpawn / OnRemove hooks on the entity struct.

This page covers the 2D collision backends. For 3D projects using CollisionSphere or CollisionAABB3D, see 3D collision.

Set up your collision matrix once at startup. Call Bind to attach the backend, Define to name your layers, and SetCollides to declare which pairs interact — the rule is always symmetric, so you cannot accidentally create a one-sided filter:

var layers = golem.NewCollisionLayers().
Bind(backend).
Define("Player", "Enemy", "Wall", "Projectile").
SetCollides("Player", "Wall").
SetCollides("Enemy", "Wall").
SetCollides("Projectile", "Enemy").
SetCollides("Projectile", "Wall")

SetCollides("Player", "Wall") records that Player shapes test against Wall and Wall shapes test against Player. The layer bit and mask are derived automatically when you register or replace a shape — you never write raw uint32 values.

Up to 32 layers can be defined. Define, SetCollides, Add, Set, and Remove all panic on misuse (unknown name, duplicate name, over-32, unbound backend) so mistakes surface at startup rather than as silent filtering bugs.

Call layers.Add in OnSpawn and layers.Remove in OnRemove. The layer name is the only thing you pass — the correct bit and mask come from the matrix you configured above:

type Player struct {
*synced.SyncedPlayer
}
func (p *Player) OnSpawn() {
layers.Add(p.EntityID(), golem.CollisionCircle{R: 0.5}, "Player", false)
}
func (p *Player) OnRemove() {
layers.Remove(p.EntityID())
}

The fourth argument is trigger. When true, the shape reports overlaps but does not request push-out (contact Depth is always 0). Use triggers for overlap zones like item pickups or damage fields.

TypeFieldsDescription
golem.CollisionCircleR float64Circle centred on the entity’s position
golem.CollisionAABBW, H float64Axis-aligned bounding box centred on the entity’s position

To replace an entity’s shape or trigger flag without removing it from the game, use layers.Set:

// Shrink the player's hitbox while crouching.
func (p *Player) OnCrouchStart() {
layers.Set(p.EntityID(), golem.CollisionAABB{W: 0.6, H: 0.9}, "Player", false)
}
func (p *Player) OnCrouchEnd() {
layers.Set(p.EntityID(), golem.CollisionAABB{W: 0.6, H: 1.8}, "Player", false)
}

Set preserves the entity’s current position. If the entity is not registered when Set is called, it is a no-op; call Add first.

Layer, Mask, and MaskFor are still available when you need raw bitmask values for spatial queries:

ids := srv.OverlapCircle(x, y, 200, layers.MaskFor("Enemy"))
hit, ok := srv.Raycast(x1, y1, x2, y2, layers.MaskFor("Wall", "Enemy"))

Raw bitmasks: if you prefer not to use CollisionLayers, plain uint32 constants work fine — the underlying filter is just (A.layer & B.mask) != 0. CollisionLayers is a helper that derives and cross-checks those values for you.

Previous: Collision & physics. Next: Contacts & events, 3D collision.