Skip to content

Python

A dependency-free SDK (stdlib only) with the runtime bundled in the wheel.

Install

bash
pip install godot-ui-sdk

Example

python
import threading
from godot_ui import GodotClient

client = GodotClient()
client.connect()                           # starts the runtime, opens a window

root = client.get_root()
root.set("title", "Counter")

box = client.instantiate("VBoxContainer")
label = client.instantiate("Label")
button = client.instantiate("Button")

label.set("text", "0")
button.set("text", "Increment")
root.add_child(box)
box.add_child(label)
box.add_child(button)

n = 0
def pressed():
    global n
    n += 1
    label.set("text", str(n))
button.on("pressed", pressed)

done = threading.Event()
client.on_window_close_requested = lambda oid: done.set()
done.wait()
client.disconnect()

Run with python app.py. Writes (set, add_child) are queued and batched; get and call round-trip and return values.

Scenes from the Godot editor

Load screens designed in the editor and address their nodes by name (background: Custom scenes & assets):

python
import os

client.request("loadPack", {"path": os.path.abspath("app.pck")})
ui = client.instantiate_scene("res://main.tscn")
client.get_root().add_child(ui)

save = ui.call("get_node", "%SaveButton")
status = ui.call("get_node", "%Status")
save.on("pressed", lambda: status.set("text", "Saved."))

During development, instantiate_scene("/abs/path/main.tscn") loads a scene file directly, without a pack.

Assets

python
# Theme (prebuilt Adwaita/Breeze/Fluent/macOS themes are included):
root.set("theme", client.load(os.path.abspath("themes/adwaita_dark.tres")))

# Image loaded at runtime (png/jpg/webp/svg):
img = client.instantiate("Image")
img.call("load", "/home/me/avatar.png")
tex = client.instantiate("ImageTexture")
tex.call("set_image", img)
avatar_rect.set("texture", tex)
img.free()

# Font:
font = client.instantiate("FontFile")
font.call("load_dynamic_font", "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf")
title.set("theme_override_fonts/font", font)

Threading

The SDK reads engine messages on a background thread, and signal callbacks run there. Calling SDK methods from a callback is safe (sends are thread-safe). Touching another framework's objects is not; marshal first (loop.call_soon_threadsafe for asyncio, after() for Tk, signals for Qt).

In-process mode

Runs the engine inside the Python process instead of spawning it:

python
from godot_ui import GodotClient, InprocTransport

client = GodotClient()
client.connect(transport=InprocTransport(args=["--no-project"]))

A background thread runs the engine's main loop; the FFI call releases the GIL, so rendering and Python code run concurrently. One engine per process; it cannot be restarted after close. The default spawned mode is preferable unless a single process is required.

Notes

  • client.sync() is the barrier for read-after-write; see Core patterns. Reads of locally written values are served from the SDK cache.
  • obj.on(signal, cb, throttle_ms=..., once=...); the returned subscription has .off(). Use throttle_ms for high-frequency signals.
  • client.instantiate_as(Button, "Button") returns generated typed wrappers. client.request("classInfo", {"className": "Button"}) reports what the connected runtime contains.
  • For CI, pass --headless: client.connect(stdio={"args": ["--no-project", "--headless"]}).
  • Object handles are opaque strings.
  • Paths in messages are resolved by the runtime; send absolute paths (os.path.abspath) or res://.
  • Call disconnect() at exit so queued writes are flushed.

Packaging

Depend on godot-ui-sdk; the runtime is inside that wheel. For PyInstaller-style single files, include the SDK's _runtime/libguido.so as a data file. See Shipping your app.