Go client
Go client
Section titled “Go client”Use the Go client when your game runtime is a native Go process instead of a browser or Unity project. golem-bake emits typed Go entity state, managers, command builders, and protobuf codecs for your schemas, and golem-go-client opens the Golem transport connection and feeds updates into those generated managers.
Generate the client
Section titled “Generate the client”Add go-client to golem.yaml alongside your server integration:
integrations: go-server: out: internal/synced/ package: synced go-client: out: internal/client/ package: clientRun golem-bake. The go-client output includes generated SyncedPlayer-style state holders, EntityManager, optional WorldManager and EventManager files, command builders, and protobuf codec files. The generated code imports golem-engine/golem-go-client by default; set golem_import if your project depends on Golem through a different module path.
Connect from Go
Section titled “Connect from Go”The generated CreateClient() function wires your managers and codecs into a golem-go-client.GameClient:
package main
import ( "context" "log"
"example.com/mygame/internal/client" golemclient "golem-engine/golem-go-client")
func main() { ctx := context.Background() c := client.CreateClient()
c.OnConnect(func() { log.Println("connected") }) c.OnDisconnect(func(info golemclient.DisconnectInfo) { log.Println("disconnected:", info.Err) })
if err := c.Connect(ctx, golemclient.ConnectOptions{ Transport: golemclient.TransportWebSocket, URL: "ws://localhost:8080/ws", }); err != nil { log.Fatal(err) }}For WebSocket servers, configure the server with TransportWebSocket and StateUpdateLaneStream as shown in Channels and transports.
For WebTransport servers, fetch the server’s realtime config and convert it into ConnectOptions. The Go client decodes certificate hashes from the config and uses them to pin development self-signed WebTransport certificates:
cfg, err := golemclient.FetchRealtimeConfig(ctx, "http://localhost:8080/api/realtime-config", nil)if err != nil { log.Fatal(err)}
options, err := golemclient.ConnectOptionsFromRealtimeConfig( cfg, golemclient.WithQueryParam("token", sessionToken),)if err != nil { log.Fatal(err)}
if err := c.Connect(ctx, options); err != nil { log.Fatal(err)}Use WithQueryParam or WithQuery for game-specific auth values such as session tokens, room IDs, or selected character IDs. If your deployment uses its own TLS policy, pass WithTLSClientConfig; explicit TLS config takes precedence over certificate hashes from realtime config.
Read synced state
Section titled “Read synced state”c.Entities is the generated entity manager. It stores one generated state holder per visible entity and rejects stale revisions for state, delta, and removal frames:
entity := c.Entities.GetSynced(playerID)player, ok := entity.(*client.SyncedPlayer)if ok { log.Println(player.PosX(), player.PosY(), player.Hp())}For custom behavior, register a factory before connecting. Your type embeds the generated state holder and can implement lifecycle hooks:
type PlayerViewModel struct { *client.SyncedPlayer}
func NewPlayerViewModel(id int64) client.PlayerClientEntity { return &PlayerViewModel{SyncedPlayer: client.NewSyncedPlayer(id)}}
func (p *PlayerViewModel) OnSpawn() { log.Println("spawned:", p.EntityID())}
func (p *PlayerViewModel) OnRemove() { log.Println("removed:", p.EntityID())}
c.Entities.RegisterPlayer(NewPlayerViewModel)Player, Hp, and PlayerClientEntity are examples; names come from your entity YAML.
Send commands
Section titled “Send commands”For each command schema, the generated client exposes a BuildXxxCommand helper. Send the built command over the reliable stream:
cmd := client.BuildMoveCommand(playerID, dx, dy)if err := c.Send(cmd); err != nil { log.Println("send command:", err)}The reliable path encodes the command as a ClientMessage, wraps one or more messages in a ClientPacket, and keeps each reliable packet within the 32000-byte message cap. On the server, use the generated router’s packet entrypoint:
rt.Commands.BindStreamHandler(func(sess *golem.Session, err error) { log.Println("command:", err)})On WebTransport, generated helpers are also available for reliable datagram command lanes:
if err := client.SendMoveReliableUnordered(c.GameClient, playerID, dx, dy); err != nil { log.Println("datagram command:", err)}Use the reliable stream first. Reach for reliable unordered or ordered datagrams when a small command should avoid waiting behind unrelated stream traffic; see Reliable Datagram Commands.
World data and events
Section titled “World data and events”If your schemas include world data, c.World stores the latest generated world values and fires typed callbacks:
c.World.OnZoneUpdate(func(zone *client.ZoneData) { log.Println("zone:", zone.Name)})If your schemas include events, c.Events dispatches global and session events through generated callbacks. Entity-targeted events are delivered to the entity instance when it implements the generated receiver interface:
func (p *PlayerViewModel) OnBuffApplied(e *client.BuffAppliedEvent) { log.Println("buff:", e.BuffId)}Register event and world callbacks before connecting so the initial frames sent during the connection handshake are not missed.
State update lanes
Section titled “State update lanes”The Go client supports both public state update modes:
StateUpdateLaneStreamsends incremental entity updates on the reliable stream. Use this with WebSocket.StateUpdateLaneDatagramsends compact incremental state over WebTransport datagrams while keeping spawns, removals, world data, and events on the reliable stream.
The generated Go EntityManager handles both paths automatically. Your game code reads the same generated state holders either way.
See also Channels and transports, State updates, and Client commands.