Skip to content

Map file serving

When your server uses Tiled or LDtk maps, clients need the raw map files to render levels. golem-engine can serve those files over HTTP alongside the WebSocket endpoint, with no separate file server process needed.

For a small map, omit url_prefix and the generated world value carries bytes tile_data. The complete wrapped world frame may be at most 32,000 bytes, so this approach is best for compact map data.

For larger maps, or whenever browser caching matters, set url_prefix. The generated value contains a string map_url, and the client fetches the file over HTTP. Both approaches use the same source: block; see World schemas for the full YAML reference.

Set MapDir in ServerConfig. The server registers a /maps/ handler using http.FileServer at startup:

srv := golem.NewServer(golem.ServerConfig{
Addr: ":8080",
StaticDir: "public/", // optional, for other static assets
MapDir: "maps/", // serves everything in maps/ at /maps/
})

With this configuration, maps/level1.tmj is reachable at http://yourserver:8080/maps/level1.tmj.

The world schema then uses url_prefix: "/maps/":

schemas/world/level1.yaml
world: Level1
source:
format: tiled
file: maps/level1.tmj
url_prefix: "/maps/"

After golem-bake, LoadLevel1Data(path) sets MapUrl = "/maps/level1.tmj". The client reads that value from the received WorldUpdate and fetches the file:

// JS client (example)
worldManager.onLevel1Update((d) => {
fetch(d.mapUrl)
.then(r => r.arrayBuffer())
.then(buf => renderer.loadTiledMap(buf));
});

Load and send a new map value when one player crosses into another zone:

level, err := synced.LoadLevel1Data("maps/route-2.tmj")
if err != nil {
return err
}
if err := srv.SendWorldData(sessionID, level); err != nil {
return err
}

The targeted update does not replace shared data in srv.World or affect other sessions. Configure WorldSnapshotExclude: []string{"Level1"} when each session should receive its initial level on demand rather than during the automatic connect snapshot. Exclusions use the exact, case-sensitive world: name. See World schemas for the complete server setup.

When you omit Addr and mount the WebSocket handler on your own http.Server, use MapFileHandler to get an http.Handler for the same directory:

srv := golem.NewServer(golem.ServerConfig{TickRate: 20})
mux := http.NewServeMux()
mux.Handle("/ws", srv.Handler())
mux.Handle("/maps/", http.StripPrefix("/maps/", srv.MapFileHandler("maps/")))
go http.ListenAndServe(":8080", mux)
if err := srv.Run(ctx); err != nil && err != context.Canceled {
log.Fatal(err)
}

MapFileHandler(dir) returns an http.Handler you mount at the prefix of your choice.

http.FileServer sets standard caching headers based on Last-Modified. Map files change only when you redeploy, so clients that have already fetched a file will validate with a 304 Not Modified response on subsequent connects. No additional configuration is needed for basic caching behavior.

If you need stronger caching guarantees (immutable cache headers, content hashing), serve map files through a CDN or your own static asset pipeline and put the CDN URL in url_prefix instead of a server-relative path.

MapDir and StaticDir are independent:

  • StaticDir serves an arbitrary directory at / as a fallback for all non-WebSocket requests, useful for a built JS client.
  • MapDir serves one directory specifically at /maps/, intended for Tiled/LDtk files.

You can set both at once. They register on different URL prefixes and do not conflict.

See also World schemas for the source: block and url_prefix reference.