1 unstable release
| 0.1.0 | Oct 1, 2025 |
|---|
#714 in Game dev
49KB
743 lines
spacetime_tiled
Load Tiled maps into SpacetimeDB for multiplayer games.
This library parses TMX files and stores them in SpacetimeDB tables. Since SpacetimeDB modules run in WASM without filesystem access, it includes an in-memory XML parser that works with embedded map data or content sent from clients.
Quick Start
Add to your server module's Cargo.toml:
[dependencies]
spacetimedb = "1.4.0"
spacetime_tiled = "0.1"
[lib]
crate-type = ["cdylib"]
In your server code:
use spacetimedb::{reducer, ReducerContext};
pub use spacetime_tiled::*;
#[reducer]
pub fn load_map(ctx: &ReducerContext) -> Result<(), String> {
// Embed the TMX file at compile time
const MAP_DATA: &str = include_str!("../assets/map.tmx");
// Parse and store in database
load_tmx_map_from_str(ctx, "level1", MAP_DATA)?;
Ok(())
}
Then call the reducer after publishing:
spacetime build
spacetime publish my-game
spacetime call my-game load_map
Why This Exists
SpacetimeDB modules run in WASM sandboxes with no filesystem access. You can't just std::fs::read_to_string("map.tmx") in a reducer - it'll fail with "operation not supported on this platform".
This library provides two solutions:
load_tmx_map_from_str()- Parses TMX XML in-memory usingquick-xml. Works in WASM. Use this.load_tmx_map()- Uses thetiledcrate's file loader. Doesn't work in WASM. Only useful for testing outside SpacetimeDB.
What Gets Stored
The library defines six tables:
- tiled_map - Map dimensions, tile size, orientation
- tiled_layer - Layer names, types, visibility, opacity
- tiled_tile - Individual tiles with position, GID, and flip flags
- tiled_tileset - Tileset metadata (names, dimensions, tile counts)
- tiled_object - Objects from object layers (positions, sizes, shapes)
- tiled_property - Custom properties on any element
All tables are indexed for querying by map_id or layer_id.
Usage Patterns
Server-Side Map Loading
Option 1: Embedded maps (recommended)
#[reducer]
pub fn init(ctx: &ReducerContext) -> Result<(), String> {
const OVERWORLD: &str = include_str!("../maps/overworld.tmx");
const DUNGEON: &str = include_str!("../maps/dungeon.tmx");
load_tmx_map_from_str(ctx, "overworld", OVERWORLD)?;
load_tmx_map_from_str(ctx, "dungeon", DUNGEON)?;
Ok(())
}
Option 2: Client-uploaded maps
#[reducer]
pub fn upload_map(ctx: &ReducerContext, name: String, tmx: String) -> Result<(), String> {
load_tmx_map_from_str(ctx, &name, &tmx)?;
Ok(())
}
Then from your client: spacetime call my-game upload_map '{"name": "custom", "tmx": "<?xml version..."}'
Querying Map Data
use spacetimedb::{reducer, ReducerContext, Table};
#[reducer]
pub fn check_collision(ctx: &ReducerContext, x: u32, y: u32) -> Result<bool, String> {
// Get collision layer (assuming it's layer 1)
let tile = ctx.db.tiled_tile()
.iter()
.find(|t| t.layer_id == 1 && t.x == x && t.y == y);
// Non-zero GID = collision tile
Ok(tile.map_or(false, |t| t.gid != 0))
}
#[reducer]
pub fn get_spawn_points(ctx: &ReducerContext) -> Result<(), String> {
let spawns: Vec<_> = ctx.db.tiled_object()
.iter()
.filter(|obj| obj.obj_type == "spawn")
.collect();
for spawn in spawns {
log::info!("Spawn at ({}, {}): {}", spawn.x, spawn.y, spawn.name);
}
Ok(())
}
Client Setup
The Rust client SDK requires an event loop to process messages. Without it, subscriptions never apply and you won't get any data.
use spacetimedb_sdk::{DbContext, Table};
use module_bindings::*;
fn main() {
let conn = DbConnection::builder()
.with_uri("http://localhost:3000")
.with_module_name("my-game")
.build()
.unwrap();
conn.subscription_builder()
.on_applied(on_sub_applied)
.subscribe([
"SELECT * FROM tiled_map",
"SELECT * FROM tiled_layer",
"SELECT * FROM tiled_tile",
]);
// THIS IS REQUIRED - starts processing messages
conn.run_threaded();
// Wait a moment for subscription to apply
std::thread::sleep(Duration::from_millis(500));
// Now you can query data
let maps: Vec<_> = conn.db.tiled_map().iter().collect();
println!("Loaded {} maps", maps.len());
}
fn on_sub_applied(ctx: &module_bindings::SubscriptionEventContext) {
println!("Subscription applied!");
// Initial data is available here
}
Important: You must import the table accessor traits:
use module_bindings::{
tiled_map_table::TiledMapTableAccess,
tiled_layer_table::TiledLayerTableAccess,
tiled_tile_table::TiledTileTableAccess,
// ... etc
};
Examples
Check out examples/simple_game/ for a complete working example with:
- Server module that loads an embedded demo map
- Interactive Rust client that queries map data
- Sample TMX file with layers, tiles, and objects
To run it:
cd examples/simple_game/server
spacetime build
spacetime publish simple-game
spacetime call simple-game load_demo_map
cd ../client
spacetime generate --lang rust --out-dir src/module_bindings --project-path ../server
cargo run
Common Issues
"No maps loaded" in client
You forgot to call conn.run_threaded(). The SDK doesn't process messages automatically - you have to start an event loop.
"operation not supported on this platform"
You're trying to use load_tmx_map() in a WASM module. Use load_tmx_map_from_str() instead with include_str!().
"unresolved import" or "method not found" errors in client
Missing trait imports. The generated bindings define traits like TiledMapTableAccess that you need to import to use .tiled_map() methods.
use module_bindings::tiled_map_table::TiledMapTableAccess;
Build fails with "clang: program not found"
The zstd-sys dependency needs LLVM/clang for WASM compilation. Install LLVM and add it to your PATH, or use WSL/Linux.
Supported Tiled Features
- ✅ Orthogonal, isometric, staggered, and hexagonal maps
- ✅ Tile layers (finite and infinite)
- ✅ Object layers with rectangles, ellipses, and points
- ✅ Custom properties (string, int, float, bool, color, file)
- ✅ Tile flipping (horizontal, vertical, diagonal)
- ✅ Multiple tilesets per map
- ✅ CSV tile data encoding
- ❌ Base64/gzip tile encoding (not yet)
- ❌ Polygon/polyline vertices (shape type only)
- ❌ Tile animations
- ❌ Wang sets
License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.
Contributing
Contributions are welcome! See CONTRIBUTING.md for guidelines.
Areas that need help:
- Base64/gzip tile encoding support
- Polygon/polyline vertex storage
- Tile animation support
- More examples
Support
Dependencies
~14MB
~254K SLoC