Your first entity
A booted server is empty until you put entities in it. Entities are your game objects (players, projectiles, pickups): the server tracks them, ticks them, and replicates their changing state to every client each tick. You describe an entity’s replicated fields in YAML, golem-bake generates a Synced<Name> type that stores those fields and serializes what changed, and you attach gameplay by embedding that type in your own struct.
Describe the entity in YAML
Section titled “Describe the entity in YAML”entity: Player
vars: health: { tag: 1, type: double, sync: tick } display_name: { tag: 2, type: string, sync: once }sync: tick fields replicate whenever they change; sync: once fields are sent a single time when a client first sees the entity. Run golem-bake and you get a SyncedPlayer type with Health() / SetHealth, DisplayName(), Position() / SetPosition, and the serialization the loop needs. The full field reference is in Entity schemas.
Wrap the generated type
Section titled “Wrap the generated type”Embed the generated type in your own struct. Embedding promotes its getters and setters, so your wrapper is a Player to your own code and a valid entity to the engine:
type Player struct { *synced.SyncedPlayer}Put game-only fields (state you never replicate, like AI timers or input buffers) on this struct alongside the embedded synced state.
Run logic every tick
Section titled “Run logic every tick”Implement Tick(dt) and the registry calls it once per tick for every Player, before your OnTick closure runs:
func (p *Player) Tick(dt float64) { p.SetHealth(p.Health() + 5*dt) // simple health regen}dt is the fixed timestep in seconds. Mutating a sync: tick field through its setter (here SetHealth) marks it changed, and the loop replicates the new value after the tick. See Game loop for the full tick order and how per-entity Tick relates to the server-wide OnTick.
Spawn and remove entities
Section titled “Spawn and remove entities”Construct an entity and hand it to the server. CreateEntity takes the entity plus an optional owner session ID, passing sess.ID ties the entity to that player so ownership-checked commands work:
players := map[int64]int64{} // sessionID -> entityID
srv.OnConnect(func(sess *golem.Session) { p := &Player{SyncedPlayer: synced.NewSyncedPlayer(0, 0, 100, "")} if err := srv.CreateEntity(p, sess.ID); err != nil { log.Printf("spawn player: %v", err) return } players[sess.ID] = p.EntityID()})
srv.OnDisconnect(func(sess *golem.Session) { if id, ok := players[sess.ID]; ok { srv.DeleteEntity(id) delete(players, sess.ID) }})The generated constructor takes the starting position, then each vars field in tag order, then an optional ID: NewSyncedPlayer(posX, posY, health, displayName). Leave the ID off and CreateEntity assigns one, readable afterward with EntityID(). OnConnect and OnDisconnect both run on the tick goroutine, so the plain players map needs no locking. For per-entity setup or teardown, implement OnSpawn() / OnRemove() on the wrapper; see Creation and destruction.
Handle a command on the entity
Section titled “Handle a command on the entity”Commands that target an entity can be handled on the entity itself. Declare the command:
command: Movetarget: entityentity_type: Player
fields: dx: float dy: floatThen implement the generated receiver interface on your wrapper. golem-bake emits an On<Command>Command method for each entity-targeted command. The router resolves the target entity, checks that the sender owns it, and calls your method only when the check passes:
func (p *Player) OnMoveCommand(senderID int64, cmd *synced.MoveCommand) { x, y := p.Position() p.SetPosition(x+cmd.Dx, y+cmd.Dy)}No central registration is required: with rt.Commands.BindAllHandlers wired in main (see Minimal server wiring), owned Move commands reach this method. Prefer a central rt.Commands.OnMove(...) handler when the logic spans several systems rather than one entity. Both styles are covered in Client commands.
Where to go next
Section titled “Where to go next”- Field types, sync modes, and collections: Entity schemas
- Tick order and per-entity vs system logic: Game loop
- Spawn/despawn hooks and ownership: Creation and destruction
- Command routing and session-scoped commands: Client commands