docs: community mapmaking documentation (7-file SDK guide)
- docs/mapmaking/00-index.md — SDK overview with workflow diagram - docs/mapmaking/01-getting-started.md — template setup & first map - docs/mapmaking/02-building-geometry.md — CSG guide & prefab reference - docs/mapmaking/03-lighting-and-env.md — LightmapGI baking guide - docs/mapmaking/04-validation.md — validator CLI & CI/CD usage - docs/mapmaking/05-packaging-and-shipping.md — .pck pipeline - docs/mapmaking/06-faq-and-troubleshooting.md — 30+ common issues - README.md — add mapmaking SDK link to features list
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
# Packaging & Shipping
|
||||
|
||||
Once your map is built, validated, and baked, it's time to publish it to the
|
||||
world. Tactical Shooter uses the **PCK** (Godot Resource Pack) format — the map
|
||||
is packaged as a `.pck` file, uploaded to a registry server, and clients
|
||||
download it automatically when they join a server running that map.
|
||||
|
||||
## Pipeline Overview
|
||||
|
||||
```
|
||||
┌──────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────────┐
|
||||
│ pack_map │ → │ .pck + .json │ → │ Map Registry │ → │ Client │
|
||||
│ .gd │ │ files │ → │ Server │ → │ Downloader │
|
||||
└──────────┘ └──────────────┘ └──────────────┘ └────────────┘
|
||||
(editor (artifacts) (HTTP serving) (autoload)
|
||||
tool)
|
||||
```
|
||||
|
||||
## Step 1: Package the Map
|
||||
|
||||
The `pack_map.gd` script (at `scripts/map_packaging/pack_map.gd`) is a Godot
|
||||
editor tool that exports a `.tscn` scene as a standalone `.pck` resource pack.
|
||||
|
||||
### Usage
|
||||
|
||||
From the **map template project** (not the main game project):
|
||||
|
||||
```bash
|
||||
cd maps/my_first_map/
|
||||
|
||||
godot --headless --script /path/to/scripts/map_packaging/pack_map.gd \
|
||||
--map=res://de_my_map.tscn
|
||||
```
|
||||
|
||||
> Replace `/path/to/` with the actual path to the tactical-shooter repository
|
||||
> root, or copy `pack_map.gd` into your map project's `scripts/` directory.
|
||||
|
||||
### What It Does
|
||||
|
||||
1. Loads the specified `.tscn` scene
|
||||
2. Recursively collects all dependencies (materials, textures, meshes, etc.)
|
||||
3. Exports them to `user://packed_maps/de_my_map.pck`
|
||||
4. Writes a metadata file `user://packed_maps/de_my_map.json`
|
||||
|
||||
### Output Files
|
||||
|
||||
```
|
||||
user://packed_maps/
|
||||
├── de_my_map.pck # Binary resource pack (~1–10 MB for a typical map)
|
||||
└── de_my_map.json # JSON metadata (machine-readable)
|
||||
```
|
||||
|
||||
**Metadata example** (`de_my_map.json`):
|
||||
```json
|
||||
{
|
||||
"map_name": "de_my_map",
|
||||
"source_scene": "res://de_my_map.tscn",
|
||||
"godot_version": {
|
||||
"major": 4,
|
||||
"minor": 2,
|
||||
"string": "4.2"
|
||||
},
|
||||
"packed_at": "2026-06-25T14:30:00"
|
||||
}
|
||||
```
|
||||
|
||||
### Requirements
|
||||
|
||||
- The map scene must use **local resources** only (relative `res://` paths)
|
||||
- External dependencies (game shared assets like weapon models, player
|
||||
models) should stay in the base game — the `.pck` is **additive** content
|
||||
- The `.pck` contains only the map's unique assets, not the entire game
|
||||
|
||||
### From the Editor
|
||||
|
||||
You can also run `pack_map.gd` from the Godot editor:
|
||||
|
||||
1. Open your map project in Godot
|
||||
2. Open your map scene (`de_my_map.tscn`)
|
||||
3. **Project → Tools → Pack Current Map** (if configured)
|
||||
4. The script detects the active scene and exports it
|
||||
|
||||
## Step 2: Set Up the Map Registry Server
|
||||
|
||||
The registry server is a lightweight Python HTTP server
|
||||
(`scripts/map_packaging/map_registry_server.py`) that:
|
||||
- Serves `.pck` files for download
|
||||
- Provides a JSON `/maps` endpoint listing available maps
|
||||
- Auto-scans the maps directory every 5 seconds
|
||||
- Computes and serves SHA-256 checksums for integrity verification
|
||||
|
||||
### Starting the Server
|
||||
|
||||
```bash
|
||||
cd /path/to/tactical-shooter
|
||||
|
||||
# Default: port 8090, maps from ./packed_maps/
|
||||
python3 scripts/map_packaging/map_registry_server.py
|
||||
|
||||
# Custom configuration
|
||||
python3 scripts/map_packaging/map_registry_server.py \
|
||||
--port 8080 \
|
||||
--maps-dir /data/tactical-shooter-maps \
|
||||
--verbose
|
||||
```
|
||||
|
||||
**Environment variables:**
|
||||
| Variable | Overrides | Default |
|
||||
|----------|-----------|---------|
|
||||
| `MAP_REGISTRY_PORT` | `--port` / `-p` | 8090 |
|
||||
| `MAP_REGISTRY_MAPS` | `--maps-dir` / `-d` | `./packed_maps/` |
|
||||
|
||||
### API Endpoints
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `GET /` | — | Server info and endpoint documentation |
|
||||
| `GET /maps` | — | JSON list of all available maps with metadata |
|
||||
| `GET /maps/<name>.pck` | — | Download map `.pck` binary |
|
||||
| `GET /maps/<name>.json` | — | Download per-map metadata |
|
||||
| `OPTIONS /maps` | — | CORS preflight |
|
||||
|
||||
**Example responses:**
|
||||
|
||||
```
|
||||
GET /maps
|
||||
```
|
||||
```json
|
||||
{
|
||||
"maps": [
|
||||
{
|
||||
"name": "de_dust2",
|
||||
"size": 5432100,
|
||||
"version": 1,
|
||||
"description": "Classic competitive map",
|
||||
"checksum_sha256": "a1b2c3d4...",
|
||||
"packed_at": "2026-06-25T14:30:00"
|
||||
}
|
||||
],
|
||||
"server_name": "Tactical Shooter Map Registry",
|
||||
"server_version": "1.0.0",
|
||||
"map_count": 1
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
GET /maps/de_dust2.pck
|
||||
```
|
||||
→ Returns the `.pck` binary file as an octet-stream download.
|
||||
|
||||
### Deployment Options
|
||||
|
||||
- **Local dev:** Run on the same machine as the game (default `127.0.0.1:8090`)
|
||||
- **Server host:** Run on a public server alongside the master server
|
||||
- **Docker:** Wrap in a Docker container for easy deployment
|
||||
- **Systemd service:** Create a service file for production hosting
|
||||
|
||||
**Systemd service example:**
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Tactical Shooter Map Registry
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=tactical
|
||||
WorkingDirectory=/opt/tactical-shooter
|
||||
ExecStart=/usr/bin/python3 scripts/map_packaging/map_registry_server.py \
|
||||
--port 8090 --maps-dir /data/tactical-maps
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
## Step 3: Upload Your Map
|
||||
|
||||
1. Copy the `.pck` and `.json` files to the registry server's maps directory:
|
||||
```bash
|
||||
cp user://packed_maps/de_my_map.pck /data/tactical-maps/
|
||||
cp user://packed_maps/de_my_map.json /data/tactical-maps/
|
||||
```
|
||||
2. The server auto-detects new files within 5 seconds
|
||||
3. Verify via `curl http://your-server:8090/maps`
|
||||
|
||||
## Step 4: Client Downloads
|
||||
|
||||
The client uses `MapDownloader` (autoload at `client/scripts/map_downloader.gd`)
|
||||
to fetch and load maps at runtime.
|
||||
|
||||
### How It Works
|
||||
|
||||
1. Player joins a server that specifies a map name
|
||||
2. The client checks its local cache (`user://maps/manifest.json`)
|
||||
3. If the map is not cached, `MapDownloader.fetch_map_list()` polls
|
||||
the registry server to verify the map exists
|
||||
4. `MapDownloader.download_map(name)` downloads the `.pck` to `user://maps/`
|
||||
5. `ProjectSettings.load_resource_pack()` loads it into the game
|
||||
6. The server changes to the new level
|
||||
|
||||
### Map Caching
|
||||
|
||||
```
|
||||
user://maps/
|
||||
├── de_dust2.pck # Cached map binary
|
||||
├── de_inferno.pck # Another cached map
|
||||
└── manifest.json # Cache index
|
||||
```
|
||||
|
||||
**Cache behaviour:**
|
||||
- Maps are cached to disk after download (survives restarts)
|
||||
- The manifest tracks version numbers for staleness checks
|
||||
- `MapDownloader.remove_map(name)` clears a specific map
|
||||
- `MapDownloader.clear_cache()` removes all cached maps
|
||||
|
||||
### Configuration
|
||||
|
||||
The `MapDownloader` autoload can be configured in-game:
|
||||
|
||||
```gdscript
|
||||
# Set the registry server URL (override default http://127.0.0.1:8090)
|
||||
MapDownloader.registry_url = "http://maps.myserver.com:8090"
|
||||
```
|
||||
|
||||
The URL can also be set via the `MAP_REGISTRY_URL` environment variable,
|
||||
which is read in `_ready()`.
|
||||
|
||||
### Signals
|
||||
|
||||
```gdscript
|
||||
# Register for map lifecycle events
|
||||
MapDownloader.map_list_loaded.connect(_on_map_list)
|
||||
MapDownloader.map_download_progress.connect(_on_download_progress)
|
||||
MapDownloader.map_download_complete.connect(_on_download_done)
|
||||
MapDownloader.map_loaded.connect(_on_map_loaded)
|
||||
```
|
||||
|
||||
## Full Pipeline Example
|
||||
|
||||
```bash
|
||||
# 1. Package from the template project
|
||||
cd maps/my_first_map
|
||||
godot --headless --script ../../scripts/map_packaging/pack_map.gd \
|
||||
--map=res://de_my_map.tscn
|
||||
|
||||
# 2. Start registry server
|
||||
cd ../..
|
||||
python3 scripts/map_packaging/map_registry_server.py --port 8090 &
|
||||
|
||||
# 3. Deploy map
|
||||
cp ~/.local/share/godot/app_userdata/MapTemplate/packed_maps/de_my_map.pck \
|
||||
scripts/map_packaging/packed_maps/
|
||||
cp ~/.local/share/godot/app_userdata/MapTemplate/packed_maps/de_my_map.json \
|
||||
scripts/map_packaging/packed_maps/
|
||||
|
||||
# 4. Verify
|
||||
curl -s http://localhost:8090/maps | python3 -m json.tool
|
||||
|
||||
# 5. In-game: connect to server and play the map
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [FAQ & Troubleshooting](06-faq-and-troubleshooting.md) — common issues
|
||||
Reference in New Issue
Block a user