Skip to content

Chipmunk (cp) backend

Swap the import and create a CpBackend instead:

import collisioncp "golem.collision/cp"
backend := collisioncp.New()
srv.SetCollisionBackend(backend)

By default all entities are registered as kinematic bodies: your code controls their velocity and position, and the engine reports contacts. For advanced use cases you can access the underlying Chipmunk space to add static geometry (walls, terrain) or switch specific bodies to dynamic mode:

space := backend.Space()
// add a static segment wall
wall := cp.NewSegment(space.StaticBody, cp.Vector{X: 0, Y: 0}, cp.Vector{X: 100, Y: 0}, 0)
wall.SetFilter(cp.NewShapeFilter(cp.NO_GROUP, uint(layerWall), maskAll))
space.AddShape(wall)

Position corrections from the physics engine are written back to entity structs automatically via SetPosition each tick—clients receive the updated positions without any extra work.

When two entities should physically stop each other—a ball bouncing off a wall, crates that block movement—you want dynamic bodies. Unlike kinematic bodies where your code sets position each tick, dynamic bodies are owned by the cp physics engine: it integrates their velocity, applies collision-response impulses when they contact solid shapes, and moves them on its own.

Register a dynamic body in OnSpawn using AddDynamic instead of Add:

import (
"golem"
collisioncp "golem.collision/cp"
cp "github.com/jakecoffman/cp/v2"
)
// Keep the concrete *CpBackend—AddDynamic and SetVelocity are not on
// golem.CollisionBackend. Pass cpBackend to srv.SetCollisionBackend.
var cpBackend *collisioncp.CpBackend
func (b *Ball) OnSpawn() {
x, y := b.Position()
mass := 1.0
moment := cp.MomentForCircle(mass, 0, 0.5, cp.Vector{})
cpBackend.AddDynamic(b.EntityID(), golem.CollisionCircle{R: 0.5}, layerBall, maskAll, false, mass, moment, x, y)
}
func (b *Ball) OnRemove() {
cpBackend.Remove(b.EntityID())
}

Use Chipmunk’s moment helpers (cp.MomentForCircle, cp.MomentForBox) for physically sensible values rather than guessing raw moment numbers.

Dynamic bodies are moved by giving them a velocity, not by setting their position directly. Call SetVelocity in your OnTick callback:

srv.OnTick(func(dt float64, s *golem.Server) {
for _, e := range s.All() {
if ball, ok := e.(*Ball); ok {
cpBackend.SetVelocity(ball.EntityID(), ball.VX(), ball.VY())
}
}
})

SetVelocity is on *CpBackend only, not on golem.CollisionBackend. Keep the concrete type when you create the backend:

cpBackend = collisioncp.New() // *collisioncp.CpBackend
srv.SetCollisionBackend(cpBackend) // SetCollisionBackend accepts the interface

Tick order: the loop runs TickAll → OnTick → collision (Update → Step → ReadBack). Calling SetVelocity in OnTick is the natural place—velocity is in effect when Step runs that tick. Setting velocity inside a per-entity Ticker.Tick (called during TickAll, before OnTick) also works because Update is a no-op for dynamic bodies and does not overwrite it.

After Step, cp has moved every dynamic body. The server loop calls SetPosition (via ReadBack) on any entity that implements PositionWriter (SetPosition(x, y float32)). All generated Synced* types implement it. Custom entity types must add SetPosition to receive physics-corrected positions—otherwise the position correction has no effect on them.

AddDynamic accepts trigger: true to register a sensor shape. Overlaps are detected and reported through the same contact pipeline as other shapes (per-entity contact events, and an optional central OnContact if you use it), but cp does not apply collision-response impulses to sensors. Use this for overlap zones (pickups, damage fields) attached to physics-simulated entities.

Global damping and gravity can be set on the space directly:

cpBackend.Space().SetGravity(cp.Vector{X: 0, Y: -9.8})
cpBackend.Space().SetDamping(0.9) // velocity multiplied each second

Per-shape restitution and friction are configured on each *cp.Shape you create. For static geometry built via Space(), keep the shape reference and call SetElasticity / SetFriction before adding it to the space. For entity bodies managed by the backend, you can iterate shapes via cpBackend.Space().EachShape(...).

Kinematic and dynamic bodies in the same space

Section titled “Kinematic and dynamic bodies in the same space”

Kinematic (Add) and dynamic (AddDynamic) bodies can coexist in the same space and will detect contacts with each other. Don’t use both for the same entity—choose one registration path per entity type and keep it consistent.

Previous: Contacts & events. Next: Overlap & cast queries.