View and drive a running Godot game with no screenshare and no mouse.
A tiny dev-only autoload + a stdlib-only Python CLI that let an external process — your shell, a CI job, or an AI coding agent — control a live Godot game over a JSON file protocol: run commands, render frames to PNG, and evaluate GDScript, all while the game runs muted in a parked background window. Think Puppeteer, but for a running Godot game.
gdbridge start # launch the game with the bridge, parked + muted
gdbridge shot frame.png # render the current frame to a PNG you can open
gdbridge eval 'return get_tree().current_scene.name'
gdbridge call <your_command> k=v ... # any command your project registered
gdbridge stopBuilt for the loop where an agent edits code, renders a frame, and looks at it — no desktop automation, no flaky screen-grabs, no stolen mouse.
Verifying a Godot change usually means watching the window yourself, or driving the
OS with screenshot/mouse automation that hijacks your screen and breaks on HiDPI.
godot-dev-bridge replaces that with a clean, scriptable channel:
- Headless-feeling, but actually rendered. Frames come from the game's own
offscreen framebuffer (
get_viewport().get_texture()), so captures work while the window sits in a corner, unfocused — but unlike--headless, real rendering runs. - Background-stable. It decouples the loop from display vsync (
VSYNC_DISABLED+Engine.max_fps) and disables macOS App Nap, so it keeps responding while you work in other apps. - Zero cost when shipped. The autoload is inert unless the
GODOT_DEV_BRIDGEenv var is set (the CLI sets it). Your players never run it. - Game-agnostic core, project-specific commands. The published core knows nothing
about your game. Projects add their own verbs at runtime via a one-line registry
(
DevBridge.register_command(...)).
The addon (in your game):
- Copy
addons/dev_bridge/into your project, or install via the Godot Asset Library. - Enable Dev Bridge in Project Settings → Plugins. (This registers the
DevBridgeautoload for you.)
The CLI (on your machine):
# it's a single stdlib-only script — put it on your PATH
curl -o /usr/local/bin/gdbridge https://raw.githubusercontent.com/Joona-t/godot-dev-bridge/main/cli/gdbridge
chmod +x /usr/local/bin/gdbridgeRequires Python 3.8+ and a Godot 4 binary (found via $GODOT, your PATH, or the
usual install locations).
Run from anywhere inside your Godot project (it finds project.godot by walking up).
| Command | What it does |
|---|---|
start [--scene S] [--res WxH] [--no-park] [--godot PATH] |
kill old Godot, launch windowed with the bridge, wait until it's online |
stop |
quit the game, clean up |
ping |
health: project + Godot version, current scene, fps |
commands |
list built-in + project-registered commands |
shot [path] [--settle N] |
render the current frame to PNG (prints the path) |
eval "<gdscript>" [--shot P] |
run GDScript live (get_tree(), self, tree, scene available) |
scene <res://…|short> / reload |
change / reload the scene |
tree [--depth N] / node <path> |
dump the scene tree / a node's script vars |
key <code> [--up] / action <name> [--up] / click <x> <y> |
inject input |
call <name> [k=v …] [--shot P] |
call any command your project registered |
Most action commands accept -s/--shot PATH to also capture a frame.
The core ships with zero knowledge of your game. To expose game-specific verbs,
register Callables on the autoload — see docs/EXTENDING.md:
# my_bridge_commands.gd (autoload it AFTER DevBridge)
extends Node
func _ready() -> void:
var b := get_node_or_null("/root/DevBridge")
if b and b.is_enabled():
b.register_command("spawn", _spawn, "spawn N enemies")
func _spawn(args: Dictionary) -> Dictionary:
var n := int(args.get("n", 1))
# ... do the thing ...
return {"spawned": n}Then gdbridge call spawn n=5, or give it friendly ergonomics with a
.gdbridge.json alias → gdbridge spawn 5.
gdbridge is designed to be driven by coding agents. See AGENTS.md for
the agent contract, and integrations/claude-code/ for a
ready-made Claude Code skill.
cd demo && gdbridge start && gdbridge shot /tmp/cube.png && open /tmp/cube.png
gdbridge spin 90 -s /tmp/spun.png && gdbridge stopSee docs/PROTOCOL.md. In short: the CLI writes request.json
(monotonic seq) into a per-project temp dir; the autoload polls it each frame, runs
the command, optionally renders a PNG, and writes response.json. Single-writer-per-file
- atomic temp-then-rename = no races. A 5s watchdog prevents a stuck capture from wedging the bridge.
macOS is maintainer-tested. Linux and Windows code paths are present (stdlib process
control, per-OS Godot detection) and community-tested — issues/PRs welcome. Note:
some Linux/Windows compositors throttle fully-occluded windows; park-don't-minimize +
vsync-decouple mitigate this (a SubViewport capture path is planned for v1.1).
MIT © Joona Tyrninoksa. Contributions welcome.