Skip to content

Latest commit

 

History

58 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gd-router

Editor-first hosted screen routing for Godot 4 app shells.

Use this addon when your project has a persistent main scene and wants to route between screen scenes inside a RouteHost. GdRouter owns navigation state, params, and history; RouteHost owns the mounted screen node.

Installation

Via gdam

gdam install @aviorstudio/gd-router

Manual

Copy addon/ into res://addons/@aviorstudio_gd-router/ and enable the plugin.

Quick Start

The plugin installs an autoload named GdRouter and adds editor types for RouteHost, RouteMap, RouteDefinition, RouteTransition, and RouteLink.

Create a main scene like this:

Main.tscn
  RouteHost

Create screens like this:

res://src/screens/home_screen/home_screen.tscn
res://src/screens/game_screen/game_screen.tscn

Select RouteHost in the editor and set:

  • initial_route: home
  • auto_discover: true
  • routes_dir: res://src/screens
  • route_dir_suffix: _screen

Navigate from code:

func _on_play_button_pressed() -> void:
	GdRouter.go_to("game", {"level": "level_01"})

Or add a RouteLink button and set its route_name in the Inspector.

For a production route map, use Godot's top menu:

Project > Tools > GD Router: Create Route Map From Screens

This scans res://src/screens/*_screen/*_screen.tscn and creates res://src/static/config/main_route_map.tres.

Recommended Project Shape

res://src/main/main.tscn
res://src/screens/home_screen/home_screen.tscn
res://src/screens/game_screen/game_screen.tscn
res://src/static/config/main_route_map.tres

main.tscn should stay persistent for app startup, autoload coordination, telemetry, audio, save systems, and other shell-level lifecycle. Routed screens should be mounted under a RouteHost child.

Navigation

var navigation = GdRouter.go_to("settings", {"tab": "audio"})
if navigation.is_pending():
	await navigation.completed
if navigation.is_success():
	print("settings mounted")

GdRouter.replace("home")
GdRouter.go_back()

Navigation is transactional and latest-wins. go_to, replace, and go_back return a RouteResult whose status is PENDING, SUCCEEDED, FAILED, or SUPERSEDED. Route, params, and history commit only after the matching generation mounts successfully. A newer valid navigation supersedes an older pending request; stale resource or transition completions cannot mount or commit. Immediate failures (unknown route, blocked guard, or no back history) are already settled when returned, so inspect status before awaiting completed.

What You Get

  • GdRouter: autoload navigation API, route table, params, and history.
  • RouteHost: scene-tree outlet that mounts the active screen as a child.
  • RouteMap: editor-visible route list resource for production projects.
  • RouteDefinition: route name, screen scene path, metadata, and optional guard.
  • RouteTransition: assignable transition resource.
  • InstantRouteTransition: no-animation transition.
  • CrossfadeRouteTransition: simple screen crossfade and slide transition.
  • RouteLink: button node that navigates to a route from Inspector data.

Auto Discovery

The router can auto-discover scenes that follow this convention:

res://src/screens/*_screen/*_screen.tscn

For example, res://src/screens/home_screen/home_screen.tscn becomes route home.

Auto-discovery is useful while prototyping. A committed RouteMap.tres is recommended for larger projects because routes become inspectable and reviewable in the Godot editor.

Route Maps

Create a RouteMap resource and assign it to RouteHost.route_map when you want explicit editor-authored routes. Each RouteDefinition can set:

  • route_name
  • scene_path
  • title
  • metadata
  • guard

When a RouteMap is assigned, RouteHost uses it instead of auto-discovery.

For production projects, prefer a committed route map over auto-discovery. Auto-discovery is excellent for early prototyping, but a RouteMap.tres gives designers and reviewers an explicit source of truth in the editor.

If RouteHost.initial_route is empty, the host uses RouteMap.initial_route.

Route Map Generation

The editor tool menu action creates a route map from the standard screen layout:

res://src/screens/home_screen/home_screen.tscn -> home
res://src/screens/game_screen/game_screen.tscn -> game

When updating an existing route map, the generator preserves route titles, metadata, and guards for matching route names while refreshing discovered scene paths. This keeps route maps editor-authored without making designers manually re-enter obvious paths.

Transitions

Assign a RouteTransition resource to RouteHost.transition.

Built-in transitions:

  • InstantRouteTransition: swaps screens without animation.
  • CrossfadeRouteTransition: fades between screens with a small slide-in.

Custom transitions should extend RouteTransition and emit finished when the host may free the previous screen.

Preset resources are included at:

res://addons/@aviorstudio_gd-router/presets/instant_route_transition.tres
res://addons/@aviorstudio_gd-router/presets/crossfade_route_transition.tres

Guards

Assign a RouteGuard resource to RouteDefinition.guard when a route needs to block entry.

extends RouteGuard

func can_enter(context: RouteContext) -> bool:
	return context.params.get("unlocked", false)

Guards run before the host loads the target scene. A blocked guard leaves the current route and history unchanged.

Route Links

RouteLink is a Button subclass for editor-authored navigation. Set its action in the Inspector:

  • GO_TO: calls GdRouter.go_to(route_name, params).
  • REPLACE: calls GdRouter.replace(route_name, params).
  • BACK: calls GdRouter.go_back().

Use RouteLink for simple menu buttons and keep direct GdRouter calls for screen-specific behavior that needs custom code.

Editor Warnings

RouteHost surfaces configuration warnings in the editor when:

  • no route map is assigned and auto-discovery is disabled
  • routes_dir does not exist
  • no routes are discovered
  • the initial route is missing
  • route map entries point at missing scenes
  • the transition resource does not implement RouteTransition

App Shell Model

gd-router is designed around a persistent app shell:

Main scene: app startup, autoload coordination, observability, layout shell
RouteHost: mounted active screen
Screens: authored destination scenes
Components: reusable parts inside screens

The router does not replace the whole SceneTree.current_scene by default. Whole-scene replacement is intentionally not the primary model because it makes global app lifecycle and editor-authored shells harder to manage.

Example

This repository includes a small app-shell example:

examples/app_shell/main.tscn
examples/app_shell/src/static/config/main_route_map.tres
examples/app_shell/src/screens/home_screen/home_screen.tscn
examples/app_shell/src/screens/game_screen/game_screen.tscn

It demonstrates RouteHost, RouteMap, and RouteLink together.

Local Addon Development

Use GDAM links to test unreleased addon changes in a game project:

gdam link @aviorstudio/gd-router /path/to/gd-router/addon
gdam install

Keep gdam.link.json local. If it lives under res://, exclude it from exports so local paths are never packed into builds.

Notes

  • Works in Godot 4.x native and web exports.
  • Game-specific guards, loading screens, and feature lifecycle should live in your game code.

Repository Layout

  • addon/: Godot plugin source packaged for GDAM and manual installation.
  • addon/plugin.cfg: plugin name, version, description, and entry script.
  • addon/src/core/: navigation state and request objects.
  • addon/src/editor/: editor automation helpers.
  • addon/src/nodes/: editor-visible routing nodes.
  • addon/src/resources/: editor-visible route, guard, and transition resources.
  • addon/src/discovery/: screen route discovery.
  • addon/presets/: built-in transition preset resources.
  • tests/: Godot test project/scripts for addon behavior.
  • .github/workflows/ci.yml: validates package shape and runs tests.
  • .github/workflows/release.yml: creates GitHub release ZIPs and publishes to GDAM.

Versioning And Releases

The version in addon/plugin.cfg is the addon package version. Releases are created from main with the manual release workflow and plain semver tags like v0.0.1. The workflow reruns the complete common gate, uploads the already-tested @aviorstudio_gd-router.zip without rebuilding it, records its SHA-256 and installed-tree digest, creates the GitHub Release, and publishes those bytes to GDAM.

Testing

Run locally with:

./tests/test.sh

CI and release both require Godot 4.7.2, run negative/restored runner controls, execute every *_test.gd with an assertion-reach sentinel and runtime-error/timeout checks, validate the closed release manifest, and test the exact ZIP through plugin enable, editor restart, smoke, disable, restart, and consumer-owned autoload preservation.

Every shipped GDScript must have a committed .gd.uid sidecar, even if both the sidecar and its manifest entry are accidentally removed. This is a script-only coverage rule, not a requirement to add sidecars to every Godot resource. The two legacy orphan sidecars remain explicitly declared; this check does not silently delete or migrate them.

The lifecycle gate compares every addon-relative file path and byte against the exact ZIP before startup and after each editor invocation, native smoke, and Web export. Generated UIDs are not ignored, and no reinstall masks import changes. Disposable contract tests exercise missing, mutated, and extra UID and non-UID files, reject changed archive paths and bytes, and restore the known-good fixtures. To run these checks separately:

python3 tests/package_contract_test.py
./scripts/package_addon.py
./tests/package_lifecycle_test.sh dist/@aviorstudio_gd-router.zip

Correction (fieldsofrevik#152): the earlier text said CI ran tests/test.sh “when available.” That conditional description overstated the gate: a missing suite could be skipped, runtime errors followed by exit zero were not rejected, releases rebuilt untested bytes, and editor/package lifecycle was not exercised. The suite and exact-package lifecycle are now mandatory in both CI and release.

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages