Skip to content

Godot Export Pipeline

The @world-forge/export-godot package converts a WorldProject into a structured content pack with .tscn scene generation for Godot 4.

  1. ValidatevalidateProject() runs structural checks. Invalid projects fail with detailed error context.
  2. Convert zonesZone[] becomes Godot spatial resources with 2D coordinate transform (grid → Godot 2D).
  3. Convert content — entities, items, connections, dialogues, loot tables, spawn markers, and transitions.
  4. Convert tiles + interiorsTileLayer[]TileMapLayer + TileSet (baked tile_map_data for image tilesets), non-walkable cells → wall StaticBody2D collision, and props → a Node2D container.
  5. Convert town — markets + crafting stations, and buildings / hubs / strongholds (convert-economy, convert-structures).
  6. Convert world modeling — vertical strata + links (convert-strata), typed hazards (convert-hazards), and zone entry gates (convert-gates).
  7. Build scenebuildWorldScene() generates a single playable .tscn from all of the above.
  8. Build asset bindings — sprites, portraits, and backgrounds mapped to Godot resource paths.
  9. Fidelity report — structured tracking of lossless, approximated, and dropped data.

The export produces a GodotContentPack containing:

  • Per-zone resources with spatial data
  • Entity manifest with placement coordinates
  • Navigation links between zones
  • Transition nodes for scene changes
  • Loot tables and spawn markers
  • Dialogue resources
  • District groupings with faction/economy data
  • Asset binding manifest
  • A world .tscn scene (optional, controlled by export options)

buildWorldScene() emits a single .tscn that opens navigable in the Godot 4 editor — not a metadata graph. The root Node2D (y-sort enabled) contains:

  • A framed Camera2D so the scene is visible the moment it opens.
  • Per zone: a Node2D with a StaticBody2D collision hull, a NavigationRegion2D navmesh, y_sort_enabled, and a z_index derived from its stratum band (+ elevation).
  • TileMapLayer nodes per tile layer (image tilesets bake tile_map_data cells; color-only layers carry a TileSet scaffold + metadata), each with per-cell wall StaticBody2D collision for non-walkable tiles.
  • A Props container (Node2D placements).
  • Town: Markets / CraftingStations containers, plus Buildings (StaticBody2D footprints with a CollisionShape2D), Hubs, and Strongholds.
  • Strata + StratumLinks containers (metadata; zones carry stratum_id and a z_index band so surface layers sort over the cellar).
  • Hazards — one Area2D per (zone, hazard) with an inline CollisionShape2D region; the hazard’s effects ride as metadata, read on body_entered.
  • Gated zones carry entry_gate / entry_gate_mode / entry_gate_reason metadata for the runtime to evaluate against party state on entry.

Every node is a textureless, self-contained engine primitive — the export loads clean in real Godot 4.7 headless (the dogfood smoke asserts 36 facts about the generated scene, from zone collision to the cellar’s underground z_index band).

Terminal window
npx world-forge-export-godot project.json --out ./GodotPack
npx world-forge-export-godot project.json --validate-only
npx world-forge-export-godot project.json --out ./GodotPack --no-world-tscn

--out writes a loadable Godot 4 project root (File → Open Project):

Path What
project.godot config_version=5, features 4.x, run/main_scene="res://world.tscn"
world.tscn playable scene; ExtResource entries point at .tres under res://world_data/
world_data/ stamped .tres bodies (zones, items, dialogues, …)
assets/ copied authored textures (tilesets / sprites / props); URI paths warn and are skipped
scripts/player.gd CharacterBody2D move script for the player pawn
pack.json / fidelity.json data pack + lossless / approximated / dropped report

--out requires a path that does not start with -. Exit 1 on validation or write failure, with path + message + a fix hint.

import { exportToGodot } from '@world-forge/export-godot';
const result = exportToGodot(project);
if (!result.success) {
// GodotExportError — validation or conversion failed
console.error(result.errors);
} else {
// GodotExportResult
const pack = result.contentPack; // GodotContentPack
const scene = pack.worldSceneTscn; // single playable .tscn, not per-zone
const { warnings, fidelity } = result;
}

When exporting from the editor’s Export modal, the Godot 4 target options panel provides:

Option Default Effect
Entity scene prefix res://entities/ Resource path prefix for entity scenes
Transition scene prefix res://transitions/ Resource path prefix for transition scenes
Include world .tscn ✓ enabled Whether to generate the top-level world scene
Asset binding mode manifest How assets are referenced: manifest (centralized) or manual (inline paths)

These settings are embedded in the exported bundle’s exportSettings field for downstream tooling to consume.

World Forge grid coordinates are converted to Godot 2D coordinates:

  • Grid position → Godot Vector2 in pixels
  • Zone dimensions map to scene node bounds
  • Entity placements become positioned child nodes

A 2.5D world is described by three grids at once, and the exporter converts between none of them. Mixing them is how a harbour ends up with a person drawn in a room the simulation says they are not in.

Grid Unit Who owns it Hashed?
Sim occupancy zone id the engine’s WorldState yes — authoritative
Dimetric cell 256x128 diamond, span 3 the client’s isometric view, via presentation never
Forge cartesian gridX / gridY in tiles the editor and this .tscn never

The Godot export writes the cartesian grid: zone origins, tile cells, and the CharacterBody2D pawn all live there, and a sandbox scale may multiply the whole thing. The optional WorldProject.presentation block holds the dimetric grid — zone anchor cells, floor plates, and an occupancy row per actor — and is carried through the export untouched, never scaled and never recomputed from gridX/gridY. The zone id is the only thing the two share, and it is what a client joins on.

exportToGodot pushes presentationAdvisories() onto warnings[], so a block whose cells fall outside a zone’s span, or whose actor stands in a room the sim places elsewhere, is reported at export time rather than discovered on screen. The stage-fixture lane (dogfood/export-stage-fixture.ts) can promote those advisories to a hard failure with --strict.

Every export produces a structured fidelity report tracking what was preserved, approximated, or dropped:

  • Lossless — zones, entities, items, connections, dialogues, spawn points, tile cells (image tilesets), wall collision
  • Approximated — town economy + structures, vertical strata, hazards, and entry gates (emitted as nodes + metadata the runtime drives); parallax (no ParallaxBackground node emitted yet); position-default fallbacks
  • Dropped — references with no matching definition (e.g. a zone hazardRef with no HazardDefinition), reported with the offending id

Fidelity is grouped by domain (zones, tiles, props, economy, structures, navigation, …) so you can see exactly what each subsystem contributed.

GODOT_PACK_FORMAT_VERSION — currently 1.1.0.

Bump rules (keep in sync with packages/export-godot/src/migrations.ts):

  • Major — required field added/removed, or field semantics change in a way a loader must see.
  • Minor — optional field added. Old loaders ignore it; new loaders may read it.
  • Patch — clarifications, doc-only changes.

migrateGodotPack() walks that chain. 1.1.0 added files (each stamped resourcePath.tres body) and zoneGates on the JSON pack so a data-driven loader does not need to parse the .tscn.