RapidGameFramework
Reusable Godot managers for data-driven small games
Loading...
Searching...
No Matches
Level JSON Schema

a

Shipped shared level files live under data/levels/. Editor-authored custom levels live beside the owning game data at data/games/<game_id>/custom_levels/, for example data/games/platformer_demo/custom_levels/ or data/games/project_alchemy/custom_levels/.

The current schema is backward-compatible with the older tiles and spawns fields, but new files should prefer layers and objects.

Core Fields

  • schema_version: current value is 1.
  • id: stable level id.
  • name: display name.
  • tile_size: [width, height] in pixels.
  • metadata: game/editor metadata such as game_type, theme, background, music, camera settings, biome, weather, or palette.
  • legend: map from tile character to metadata, such as type, sprite, walkable, damage, one_way, or tags.
  • layers: ordered tile layers.
  • objects: placed gameplay objects.
  • triggers: optional data-driven trigger definitions.

Layers

Each layer is a dictionary:

{
"id": "terrain",
"type": "tiles",
"collision": true,
"visible": true,
"tiles": [
"........................",
"########################"
]
}

Layer rows should be rectangular. Multiple layers can represent background, terrain, collision, decoration, hazards, foreground, or editor-only annotation layers. The editor supports selecting the active paint layer and adding non-colliding decoration layers, which is important for metroidvania-style rooms where collision, foreground art, secrets, and door markers often need separate authoring passes.

Non-square or irregular rooms should still use a rectangular backing grid. Mark cells outside the playable room shape as void/null terrain instead of shrinking the map around each room. A void tile is a normal legend entry with type: "void" and walkable: false; games should render it as black/outside space, block movement through it, and avoid drawing normal grid details over it. This lets one dungeon level contain several differently shaped rooms and halls inside one map file:

{
"legend": {
" ": { "type": "void", "terrain": "void", "walkable": false, "render": "black", "draw_grid": false },
".": { "type": "empty", "terrain": "stone_floor", "walkable": true },
"#": { "type": "solid", "terrain": "wall", "walkable": false }
},
"layers": [{
"id": "terrain",
"type": "tiles",
"collision": true,
"visible": true,
"tiles": [
" ####### ",
" #.....# ",
"#######.....##### ",
"#...............# ",
"#######.....##### ",
" #.....# ",
" ####### "
]
}]
}

Objects

Objects are placed by tile coordinate:

{
"id": "player_start",
"kind": "player",
"tile": [2, 7],
"facing": "right"
}

Objects that should not be locked to the tile grid can use position instead of tile. position is stored in tile units, so [2.5, 3.25] means two and a half tiles from the left and three and a quarter tiles from the top. This is useful for Putt-Putt course devices, bumpers, decorative hazards, large cosmetic props, and future arcade/metroidvania objects whose collision anchor or art overhang should not be forced to a grid cell. If an object uses position, the Godot scene bridge preserves position on export instead of snapping it back to tile.

Common kind values:

  • player
  • enemy
  • pickup
  • upgrade
  • checkpoint
  • exit
  • door
  • hazard
  • npc
  • resource
  • obstacle
  • trigger_volume
  • secret
  • tee
  • cup
  • bumper
  • course_device

Objects can include arbitrary game-specific metadata such as archetype, sprite_id, target, reward, patrol, script, device, collision_shape, entry_sensitive, force, cycle_time, or radius.

Putt-Putt Course Modules

Putt-Putt Roguelike uses the same editor palette format as other games, plus a course-module catalog at data/games/putt_putt_roguelike/course_modules.json. Normal generated holes are built from editable course pieces arranged in a 3x3 grid:

  • Bottom row: starting pad, normally centered and facing up.
  • Middle row: generated putting green pieces.
  • Top row: cup/hole piece, normally centered.

Each piece declares role, size, tiles, optional objects, and a connections array such as ["up", "down", "left", "right"]. During generation, non-void adjacent pieces must expose matching navigable sides: a piece connected to its right neighbor needs right, and the neighbor needs left. This allows individual pieces to be edited in JSON or level-editor tools while still giving the course generator enough information to avoid dead-ended or mismatched rooms.

The older start/middle/finish module fields remain valid for experiments, but new normal holes should prefer the 3x3 pieces and piece_generation format so the same authored pieces can be reused, reskinned, and validated. puttPuttCourseManager.validate_catalog() validates the piece catalog before generation. It checks piece sizes, rectangular tile rows, unknown tile characters, connection names, object bounds, and whether each non-void normal_roles slot has at least one enabled piece with the connections required by its neighbors.

Putt-Putt run length and boss placement live in data/games/putt_putt_roguelike/run_modes.json. These are roguelike run modes, not separate level campaigns:

  • tutorial_3: 3 holes, unlocked by default, mini-boss on hole 3.
  • standard_9: 9 holes, unlocked by completing Tutorial Run, mini-boss on hole 9.
  • full_18: 18 holes, unlocked by completing 9-Hole Run, mini-boss on hole 9 and final boss on hole 18.

Boss entries are stamped into the run sequence as encounter metadata so the normal hole generator can continue to build individual hole courses while later systems replace boss encounters with fog-room exploration and ball-based minigames.

Ball-minigame adapter definitions live in data/games/putt_putt_roguelike/minigames.json. Each definition should include an id, display name, type, preview, target_score, and step_score. Run-mode boss stages reference these ids through minigame_pool or script. The active encounter stores only save-friendly adapter state such as score, turn count, completion state, seed, and the selected definition metadata.

Course tiles may define collision metadata such as:

  • collision_shape: "rect" for standard walls.
  • collision_shape: "slope_45_ne" or slope_45_nw for 45-degree bank walls.
  • collision_shape: "slope_30_ne" or slope_30_nw for shallower angle walls.
  • reskin_group: "wall" so the same collision shape can be drawn as brick, wood, castle stone, carnival plastic, or another theme later.

Course objects should use kind: "course_device" when the ball interaction is defined by a device id instead of a normal pickup or exit. Initial device ids include loop, windmill, bounce_pad, water_wheel, fan, and portal. Devices can mark entry_sensitive: true when behavior depends on the side or angle where the ball enters the object.

Triggers

Triggers are reserved for data-driven interactions:

{
"id": "exit_opened",
"type": "objective_complete",
"conditions": { "collected_at_least": { "coin": 3 } },
"actions": [{ "type": "unlock_exit", "id": "exit_01" }]
}

The trigger condition language should converge with conditionEvaluator as the editor and scene builder mature.

Tooling

  • levelManager.load_level(path) loads either the new schema or legacy tiles/spawns files.
  • levelManager.validate_level(data) returns issue dictionaries suitable for smoke tests and editor display.
  • levelManager.make_template_level(id, width, height) generates starter level JSON.
  • levelEditorManager owns reusable editor mutations for schema v1 data: history, resize, paint, erase, flood fill, eyedropper pick, active-tool cycling, palette visibility, object removal, and compact input badge descriptors.
  • customLevelLibrary resolves, saves, and discovers custom levels from data/games/<game_id>/custom_levels/ so games can load editor-authored levels without editing shipped indexes.
  • data/games/<game_id>/editor_palette.json defines the game-specific visual tile and object palette used by the editor. Tile entries provide tile characters, sprites, and legend metadata; object entries provide kind, sprite, and default placement fields such as enemy archetype, resource id, or exit target. A metroidvania palette can define one-way platforms, breakable blocks, doors, save points, upgrades, boss rooms, and ability gates without changing editor code.
  • scenes/level_editor.tscn opens the Godot level editor for loading, creating, painting, validating, and saving schema v1 JSON levels. It includes a visual tile canvas, legend-backed tile palette, object placement for common gameplay kinds, a game selector that chooses the target custom-level folder, controller-friendly cursor movement, palette cycling, layer cycling, and a raw JSON editor for advanced/manual changes. The visible palette is now scoped to the active tool: tile mode shows only tiles, object mode shows only objects, and erase mode hides the palette entirely. Non-visual operations are routed through levelEditorManager so future platformer, tactical, metroidvania, and room editors can share the same editing behavior.
  • addons/rgf_level_tools/ provides an optional Godot editor plugin. Enable RGF Level Tools in Project Settings -> Plugins to add an RGF Levels dock inside the Godot IDE. The dock browses data/games/<game_id>/custom_levels/, creates starter template levels, validates selected JSON with levelManager, previews raw JSON, reveals files on disk, opens the existing runtime level editor scene, builds editable Godot 2D scenes from selected JSON, and exports the active Godot scene back to JSON. This makes the Godot editor a first-class authoring surface without replacing the portable JSON level format.

Godot IDE Workflow

Use this workflow when developing levels inside the Godot editor:

  1. Enable Project -> Project Settings -> Plugins -> RGF Level Tools.
  2. Open the RGF Levels dock.
  3. Select the target game, such as Platformer Demo, Project Alchemy, or Putt-Putt Pieces.
  4. Select an existing level or click New. Most games create a schema v1 JSON template under the selected game's custom_levels folder. Putt-Putt creates a new 16x16 course piece inside data/games/putt_putt_roguelike/course_modules.json. Built-in levels and Putt-Putt pieces are shown in the list and can be edited in place. When Putt-Putt Pieces is selected, the dock exposes role, connection, and disabled controls. These fields determine whether the procedural generator can use the piece for start, green, finish, or void slots.
  5. Use Validate after hand-editing or external-tool edits.
  6. Use Build 2D Scene to generate an editable .tscn bridge from the selected JSON level. The generated scene contains normal Node2D children for layers, tile sprites, and placed objects, so you can use Godot's native 2D viewport, node tree, transform tools, duplication, and inspector.
  7. Use the dock's Library tab to browse the selected game's editor_palette.json. Tiles and objects appear in one shared list, so level authors can select an entry without switching modes. Enable Paint in 2D View to place the selected entry by clicking directly in Godot's 2D viewport. If reusable definitions were changed in RGF Pieces, click Reload Palette to refresh this Library list without reopening the editor. The X/Y fields and Add Selected button remain available for exact grid-cell placement. Move, duplicate, or inspect the created node normally in the 2D editor.
  8. Save or edit the scene normally in Godot.
  9. Use Export Active Scene to write the currently open RGF scene back to the selected level JSON.

The plugin is intentionally a bridge, not a competing level format. JSON remains the portable runtime/source format. Generated .tscn files are editable Godot authoring views over that data, with RGFLevelRoot, RGFLevelTile, and RGFLevelObject marker scripts preserving the metadata needed to round-trip back to JSON. Game runtime code, the in-game editor, the Godot editor plugin, and future external tools should all load and save the same schema v1 JSON files.

When editing through the native 2D viewport, each non-empty tile becomes a sprite-backed RGFLevelTile node and each placed object becomes an RGFLevelObject node. Moving a node changes its tile coordinate on export. Deleting a node removes it from the exported JSON. Adding new marker-scripted nodes manually is supported, but the fastest workflow is usually to duplicate an existing tile or object and then change its exported properties in the Inspector.

The Library tab is powered by data/games/<game_id>/editor_palette.json. Adding a tile, enemy, pickup, exit, door, NPC, resource, or future metroidvania ability gate to that file makes it available to the Godot plugin without changing plugin code. A future plugin pass should add direct click-to-paint drag painting, eraser mode, and rectangle brushes; the current pass provides a resizable palette tab with data-driven click placement, exact-cell add/place, and normal Godot 2D editing.

Reusable piece definitions can also be edited through the separate RGF Pieces Godot plugin. Enable RGF Piece Tools in Project Settings -> Plugins to add an RGF Pieces dock. That dock edits the same editor_palette.json files that RGF Levels reads, but it works at the catalog/default-definition layer instead of the placement layer:

  • Tile definitions: id, label, tile character, sprite id, class/type, collision_shape, bounce, friction, and metadata.
  • Object definitions: id, label, kind, sprite id, collision/bounce/friction defaults, and arbitrary default placement data. Object defaults can set placement: "grid" or placement: "free". Grid objects are placed by tile coordinate; free objects are placed at the exact clicked position in tile units and export with position.
  • Use Validate after editing reusable definitions. The RGF Pieces validator catches blank or duplicate ids, duplicate tile symbols, invalid object placement modes, non-dictionary metadata/default blocks, and sprite ids that are not present in the loaded sprite manifests.

Use RGF Pieces when adding or maintaining the library of reusable things a game can place, such as Putt-Putt slope walls and course devices, Platformer coins and enemies, or Project Alchemy resources and exits. Use RGF Levels when placing those things into a specific level, course piece, room, or adventure stage. After a bridge scene is built, Godot's Inspector remains the best place to fine-tune a single placed node; exporting the scene writes those instance-level values back to JSON.

The two docks also cooperate with the normal Godot selection model. In a bridge scene, select an RGFLevelTile or RGFLevelObject node, then use Read Selected Node in RGF Pieces to copy that node's exported metadata into the definition editor. This is useful when a good one-off Inspector tweak should become a reusable palette default. Conversely, edit or select a palette definition in RGF Pieces, then use Apply To Selected Node to stamp that definition's tile character, object kind, sprite, collision shape, bounce, friction, and metadata onto the selected scene node. The Inspector can still override individual placed nodes after that; the palette remains the reusable starting point and the scene remains the per-level authored result.

Games consume the exported JSON from the same source path the plugin writes. Custom levels are written to data/games/<game_id>/custom_levels/. Platformer built-in levels are standalone schema v1 files under data/levels/, so exports overwrite those files directly. Putt-Putt course pieces are edited as temporary schema v1 bridge scenes, then exported back into the matching pieces[] entry in data/games/putt_putt_roguelike/course_modules.json. The bridge uses piece_generation.piece_size as the authoring grid, currently 16x16 cells, with a 32x32 pixel tile size. Older smaller prototype pieces are expanded into that authoring grid when opened, and exports preserve the full grid instead of shrinking to the edited node bounds. New or edited built-in pieces are picked up automatically by runtime course generation because the game loads course_modules.json and selects from pieces[] by role, connections, and disabled state. Project Alchemy adventure stages are embedded inside adventure JSON; the plugin converts a stage into editable schema v1 scene data, then folds the exported layout, objects, enemies, exits, player spawn, and scripts back into the original adventure JSON level entry.

Editor Controls

The editor is intended to work like a lightweight maker tool rather than a text form. The on-screen control strip uses compact button/icon badges so controller, keyboard, and mouse users can understand the current mappings without a long instruction panel.

  • D-pad / arrow keys: move the editor cursor by one tile.
  • A / Enter: place the selected tile or object at the cursor.
  • X / 2: erase at the cursor.
  • Y / 3: cycle Tile, Object, and Erase tools.
  • LB/RB / PageUp/PageDown: cycle the active tile/object palette item.
  • Left Stick button / 5: cycle the active layer.
  • B / 4: open save confirmation.
  • F: flood fill matching connected tiles on the active layer.
  • Q: pick the tile under the cursor and select it in the palette.
  • Ctrl+Z / Ctrl+Y: undo and redo editor changes.

The controller cursor auto-scrolls the canvas so large platformer and metroidvania rooms can be authored without relying on the mouse.

Editor Capability Roadmap

The editor is intentionally moving toward proven tile-map authoring patterns:

  • Layer controls: add lock, hide/show, opacity, tint, and drag reorder for tile, collision, foreground, annotation, and object layers.
  • Object templates: define reusable enemy, pickup, trigger, door, checkpoint, and NPC presets with custom properties.
  • Selection tools: add rectangle selection, copy/paste, stamps, brush sizes, pattern brushes, and object multi-select.
  • Trigger editing: draw rectangular or polygon trigger volumes and bind them to schema triggers.
  • Validation overlays: highlight missing player starts, unreachable exits, invalid object metadata, and unsupported tiles for the selected game.
  • Playtest loop: launch the selected level in its target game directly from the editor.
  • Future sprite support: use game sprite mappings to preview animated tiles, enemies, pickups, portals, and hazards in-editor.