Ruzta documentation
Start with the language basics, then move through control flow, typing, editor helpers, and follow-up learning resources.
Setup
Download and enable Ruzta
Ruzta scripts live in `.rz` files, but the language itself is delivered as a Godot GDExtension. The first setup job is getting the extension bundle into a project where Godot can load it.
extends Nodefunc _ready() -> void: print("Hello from Ruzta")
- Download the current build archive, then unzip it before opening the project in Godot.
- Keep `ruzta.gdextension` together with the shipped `lib/` binaries so the manifest can resolve the correct platform library.
- A Ruzta source file uses the `.rz` extension; script resources can also be exported as `.rzc` token streams later in a pipeline.
- If Godot does not recognize the language immediately, reopen the project so the extension reloads cleanly.
- Treat the archive like an addon bundle: copy the whole runtime payload, not just the script files.
First `.rz` script
If you already know Godot scenes, the quickest proof that your setup works is to attach a tiny `.rz` script to a node and print from `_ready()`.
class_name Playerextends CharacterBody2D@export var speed := 220.0func _ready() -> void: print("Player ready")func _process(delta: float) -> void: position.x += speed * delta
- Create a new script file with the `.rz` extension.
- Use `extends` exactly the way you would in GDScript.
- Attach the script to a node, run the scene, and check the output panel for the printed line.
- From there, add `@export` properties and callbacks such as `_process()` or `_input()` the same way you would in a regular Godot workflow.
Learn more
Ruzta is intentionally close to GDScript, so the fastest way to level up is to combine this site with the official Godot scripting references and a couple of practice-oriented guides.
- Use the official GDScript docs as the default syntax reference when you need exact statement or type behavior.
- Keep this Ruzta guide open for the repo-specific differences: `.rz` files, language packaging, and how this port is positioned.
- If you want a guided path instead of a reference manual, start with GDQuest's beginner material and cross-check syntax here.
Ruzta vs GDScript
What changes in practice
The biggest differences are packaging and cadence, not the everyday syntax. Ruzta scripts use `.rz`, and this project ships the language runtime as a GDExtension rather than baking it into the editor build.
- Use `.rz` instead of `.gd` for source files.
- When you share a project, you need the Ruzta extension bundle available alongside the project, not only the scripts.
- Official Godot pages often talk about GDScript by name; for syntax and most patterns, those pages are still relevant to Ruzta.
- For edge cases, prefer testing against the actual Ruzta runtime because language ports can move on a different release cadence than upstream Godot.
- If a guide mentions a GDScript builtin or annotation, assume it is conceptually relevant, then confirm the exact behavior in Ruzta when it matters.
Variables and Constants
Declaring values
Use `var` for mutable state and `const` for values that must not change. Class variables live on the script object, while local variables live inside a function body.
const DEFAULT_SPEED := 240.0const ENEMY_SCENE = preload("res://enemy.tscn")var health := 5var title: String = "Scout"var active := true
- Use `=` when you want a simple assignment and `:=` when you want inference from the initializer.
- Constants are best for shared configuration, cached preloads, and enum-like numeric values that should not be reassigned.
- Local declarations work the same way inside functions, loops, and match branches.
- Use clear defaults so the editor and your teammates can read intent before the scene ever runs.
Scope and static state
Ruzta also supports `static var` and `static func` for data or helpers that belong to the class itself instead of an instance.
static var spawned_count := 0var nickname := "unit"func _init() -> void: spawned_count += 1static func get_spawned_count() -> int: return spawned_count
- Instance variables are unique per object; static variables are shared by all instances of that script class.
- Use static state sparingly for counters, registries, or caches that really are global to the script type.
- A local variable shadows an outer name in the usual way, so keep names distinct when possible.
Primitive Types
Core value types
Ruzta works with the same core Godot value model you already know: booleans, integers, floats, strings, string names, node paths, engine math types, objects, and `Variant` when something may be dynamic.
var retries: int = 3var cooldown: float = 0.35var label: String = "Ruzta"var enemy_name: StringName = &"Enemy"var camera_path: NodePath = ^"Player/Camera2D"var position_2d := Vector2(16, 32)
- `bool`, `int`, and `float` cover most gameplay flags, counters, timers, and movement values.
- `String` is the everyday text type; `StringName` is useful for identifiers, property names, and other interned keys.
- `NodePath` is the typed representation behind scene paths, and math/value structs such as `Vector2`, `Vector3`, `Color`, and `Transform3D` work the same way as in GDScript.
- `null` is valid for `Variant` and object-like references where an empty value makes sense.
Casts and type checks
Use `is` when you want to test a value's runtime type and `as` when you want to cast it into a more specific type.
func describe(value: Variant) -> void: if value is int: print("int:", value) elif value is String: print("string:", value) var amount := value as int
- Use `is` in guards before touching members on dynamic values.
- Use `as` when the conversion is intentional and should produce a typed result.
- Typed arrays and dictionaries can also participate in type tests such as `value is Array[int]`.
- Prefer explicit casts in API boundaries so the next reader can see when narrowing is intentional.
Aggregate Types
Array
Arrays are ordered collections. Use them for sequences, inventories, waypoints, batched events, or any other list-shaped data.
var checkpoints: Array[Vector2] = [Vector2.ZERO, Vector2(64, 0)]checkpoints.append(Vector2(128, 0))for point in checkpoints: print(point)
- Untyped arrays are flexible and useful for quick gameplay scripting or dynamic data.
- Typed arrays such as `Array[int]` or `Array[Enemy]` give stronger editor help and earlier errors.
- Loop arrays directly with `for item in items:` or by index when you need positional access.
- Many builtins and engine APIs accept arrays, so they are the default collection type for batched values.
Dictionary
Dictionaries store key-value pairs. Use them for named stats, lookup tables, metadata blobs, and other record-like structures where labels matter more than order.
var stats: Dictionary[String, int] = { "hp": 8, "mp": 3,}stats["hp"] += 1for key: String in stats: print("%s = %d" % [key, stats[key]])
- Untyped dictionaries are convenient for quick configuration data and deserialized content.
- Typed dictionaries such as `Dictionary[String, int]` or `Dictionary[int, LootDrop]` are better when a shape is expected.
- Iterating a dictionary yields keys; use those keys to read or update the stored values.
- Literal dictionaries are concise, so they work well for local tables and compact state maps.
Functions, Lambdas, and Setters/Getters
Functions and return values
Define behavior with `func`. Parameters go in parentheses, defaults keep call sites short, and `->` documents the return type when a function produces a value.
func heal(amount: int = 1) -> void: health += amountfunc is_alive() -> bool: return health > 0
- Use return types on public helpers and shared gameplay APIs so call sites are easier to understand.
- Default values are good for optional tuning parameters and convenience overloads.
- Scene callbacks are regular functions, so everything you learn here applies to `_ready()`, `_process()`, `_input()`, and friends.
Lambdas and callables
Lambdas are inline functions created with `func`. They are useful for short callbacks, custom sort logic, deferred work, and signal handlers that do not deserve a named method.
var announce := func(message: String) -> void: print("announce:", message)var double := func(value: int) -> int: return value * 2announce.call("ready")print(double.call(4))
- Store lambdas in variables or pass them directly where a `Callable` is expected.
- A lambda can declare parameter types and a return type just like a named function.
- Call lambdas with `.call(...)`.
Setters and getters
Properties can expose a computed interface instead of a raw backing field. Inline `get:` and `set(value):` blocks are the clearest form when the logic is small.
var _speed := 200.0var speed: float: get: return _speed set(value): _speed = maxf(0.0, value)
- Use a private backing variable when the property should validate or normalize writes.
- For larger property logic, you can also route through named getter and setter functions.
- Typed properties work the same way as untyped ones.
Variadic arguments
Ruzta supports variadic parameters with `...args: Array`. Use them when the function really accepts a flexible tail of values.
func log_event(name: String, level: int = 0, ...args: Array) -> void: prints(name, level, args)var collector := func(prefix: String, ...args: Array) -> void: prints(prefix, args)log_event("spawn")log_event("damage", 2, "orc", 15)collector.call("values", 1, 2, 3)
- Keep required parameters first, optional defaults next, and the variadic tail last.
- The collected extra arguments arrive as an array.
- Variadics also work in lambdas, so short forwarding helpers can stay inline.
Enum
Named and unnamed enums
Enums group related integer constants under a readable name. Use named enums for most public APIs and unnamed enums when you want a few file-local constants without an extra type name.
enum Direction { LEFT = -1, RIGHT = 1 }enum { STARTING_LIVES = 3 }var facing: Direction = Direction.RIGHTvar lives := STARTING_LIVES
- A named enum is accessed through its type, such as `Direction.LEFT`.
- Unnamed enum entries are introduced directly into the surrounding scope.
- You can assign explicit numeric values when you need stable save data, wire formats, or editor-facing identifiers.
Using enums in typed code
Enums behave like integers at runtime, but they still carry useful structure in typed code and make state transitions much easier to read.
enum State { IDLE, RUN, HIT, DOWN }func set_state(state: State) -> void: match state: State.IDLE: print("idle") State.RUN: print("run") _: print("other")
- Use enum types for variables, parameters, return values, and dictionary keys when a state machine or finite set is involved.
- Enums work well in `match` expressions because every branch reads like a named state instead of a magic number.
- If you inherit or preload scripts, enum members can also be accessed through those script types.
Control Flow
Conditionals
Use `if`, `elif`, and `else` for straightforward branching. Ruzta also keeps the inline ternary form for compact value selection.
if health <= 0: state = "down"elif sprinting: state = "run"else: state = "idle"var banner = "danger" if health < 3 else "safe"
- Reach for `if` blocks when each branch performs multiple actions.
- Use the inline `a if condition else b` form when you only need to pick one value.
- Keep conditions readable; extract helper functions instead of stacking several unclear expressions together.
Pattern matching
Use `match` when the branching logic is state-driven or pattern-shaped. It reads better than a long `if` chain once values, destructuring, or guards are involved.
match state: "idle": print("standing") "run": print("moving") var current when current.begins_with("attack"): print("combat") _: print("unknown")
- Use `_` as the fallback pattern.
- Pattern guards with `when` let you refine a matching branch without leaving the `match` block.
- Arrays and other structured values can be matched destructively, including variable binds inside the pattern.
Loops
Use `for` when iterating a range or iterable value and `while` when the stop condition depends on state that changes inside the loop.
for index in range(3): print(index)for action in ["jump", "dash", "roll"]: print(action)while energy > 0: energy -= 1
- Loop arrays, strings, dictionaries, and custom iterables with `for item in value:`.
- Use `range(start, end, step)` when you need index-style iteration.
- A dictionary loop yields keys, not key-value tuples.
- Keep `while` loops tight and make the exit condition obvious so they do not turn into hidden infinite loops.
Operators
Arithmetic, comparison, and assignment
The core operator set is the familiar one: arithmetic, comparisons, boolean logic, and compound assignments.
score += 10ammo -= 1var can_dash = stamina > 0 and not exhaustedvar same_lane = lane_a == lane_bvar wrapped = turn % 4
- Use `+`, `-`, `*`, `/`, and `%` for numeric work.
- Use `==`, `!=`, `<`, `<=`, `>`, and `>=` for comparisons.
- Use `and`, `or`, and `not` for boolean logic.
- Compound assignments such as `+=`, `-=`, `*=`, `/=`, and `%=` keep mutations compact.
- Ruzta does not use `++` or `--`; write the increment or decrement explicitly.
Type and membership operators
A few operators matter especially often in script code: `is`, `as`, and `in`.
if target is Node2D: print(target.position)var named_target := target as Nodeif "dash" in abilities: print("dash ready")
- Use `is` for runtime type checks before touching object members or narrowing a dynamic value.
- Use `as` when a typed conversion is intentional and should be visible in the code.
- Use `in` to test whether a value exists in an array, string, or other container-like type.
Printing and String Formatting
Console output and formatted strings
Ruzta keeps the common Godot output helpers such as `print`, `prints`, and `printerr`, and it supports percent-style string formatting for compact status text and debug lines.
print("ready:", player_name)prints("spawn", wave, position)printerr("Missing save file")var hp_label = "HP %03d / %03d" % [health, max_health]var time_label = "Time %.02f" % elapsedprint(hp_label)print(time_label)
- Use `print` for general logging and `printerr` when you want the message to stand out as a problem.
- Use `prints` when you want several values separated cleanly without building a string first.
- Use the `%` formatter for width, padding, decimal precision, and multi-value templating.
- Prefer readable format strings over long chains of concatenation when building debug messages.
Signals and Concurrency
Signals
Signals decouple systems cleanly. Declare them with `signal`, connect listeners with `connect()`, and emit them from the producer when something interesting happens.
signal collected(item_name, amount)func _ready() -> void: collected.connect(_on_collected)func pickup(name: String, amount: int) -> void: collected.emit(name, amount)func _on_collected(item_name: String, amount: int) -> void: print(item_name, amount)
- Custom signals are great for UI updates, combat events, scene transitions, and gameplay milestones.
- A signal can declare parameters, which makes the payload contract explicit.
- You can connect a named method or an inline lambda depending on how much logic the handler needs.
Await and asynchronous flow
Use `await` to suspend a function until a signal or coroutine result is ready. This keeps async scene logic readable without manually threading state through callbacks.
signal finished(result)func _ready() -> void: call_deferred("emit_finished") var result = await finished print(result)func emit_finished() -> void: finished.emit("done")
- Await a signal directly with `await some_signal`.
- A signal with no parameters resumes with `null`, one parameter resumes with that value, and multiple parameters resume with an array payload.
- Timers and other signal-emitting engine APIs compose naturally with `await`.
- Mark code structure clearly after an `await`, because execution resumes later, not immediately.
Class and Global
Unnamed scripts and `class_name`
Every `.rz` file defines a script class, even if it does not declare `class_name`. Adding `class_name` simply gives the class a global identifier you can reference directly.
class_name Projectileextends Area2Dconst EnemyScript = preload("res://enemy.rz")func spawn_enemy() -> void: var enemy = EnemyScript.new() add_child(enemy)
- Use `class_name` when the script should be easy to instantiate or reference across the project.
- Unnamed scripts still work fine; load or preload them and instantiate them through the returned script resource.
- Use `extends` at the top level to bind the script to a native or script base class.
Inner classes
A Ruzta script can also declare inner classes. They are useful when a helper type belongs tightly to one script and does not need its own global file or `class_name`.
class Entry: var id: String var count: intfunc make_entry(id: String, count: int) -> Entry: var entry := Entry.new() entry.id = id entry.count = count return entry
- Inner classes help keep small data carriers and helper objects local to the script that owns them.
- They can extend other classes and participate in typed code just like top-level classes.
- Use them when splitting files further would make the code harder, not easier, to navigate.
Globals and singletons
Autoload singletons are the usual Godot answer for project-wide services and state. Once registered in Project Settings, they are available by name from any Ruzta script.
func _ready() -> void: if SaveGame.has_profile(): print(SaveGame.current_slot) SaveGame.mark_seen("intro")
- Use an autoload for save systems, settings, audio routers, quest state, or other cross-scene services.
- Keep singleton APIs narrow and explicit so they do not turn into a hidden dumping ground.
- A singleton name behaves like a global entry point, but the underlying implementation is still just a script or node you own.
Constructors
Instantiating with `new()`
Use `.new()` to create instances from native classes, script classes, inner classes, and preloaded script resources. This remains the standard constructor form in Ruzta.
class Projectile: var speed := 300.0func test() -> void: var projectile := Projectile.new() var marker := Node2D.new() print(projectile.speed) marker.free()
- Call `TypeName.new()` for native engine classes such as `Node`, `Label`, or `Timer`.
- Call `MyScript.new()` for top-level script classes and `Outer.Inner.new()` for inner classes.
- Use constructor arguments exactly where `_init()` expects them; if `_init()` requires values, `.new()` must supply them.
Using `builder constructor`
`builder constructor` is the builder-style constructor form in this branch. It constructs the instance first, then applies receiver-relative assignments, calls, and nested child builders inside a `{ ... }` block.
func make_pause_menu() -> VBoxContainer: return VBoxContainer { name = "PauseMenu" alignment = BoxContainer.ALIGNMENT_CENTER Label { text = "Paused" horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER } Button { text = "Resume" pressed.connect(on_resume_pressed) } }
- Use `TypePath { ... }` when the target can be constructed without arguments.
- Use `TypePath(arg1, arg2) { ... }` when `_init()` needs constructor arguments before the builder body runs.
- Nested builder_constructor children call `add_child(child)` automatically when the current receiver exposes a compatible `add_child()` parameter.
Releasing objects with `free()`
When you manually construct engine objects and they are not being kept alive by scene ownership or reference counting, release them explicitly with `free()`.
func test() -> void: var node := Node.new() node.name = "Temporary" node.free()
- Use `free()` on objects such as `Node` instances you created yourself and no longer need.
- Do not keep using an object after calling `free()` on it; the instance is gone immediately.
- Ref-counted types usually die when references disappear, but `Object` and scene objects often need explicit lifetime handling.
Annotations
Common annotations you will use first
Annotations start with `@` and modify the next declaration. In day-to-day gameplay scripts, the most common ones are `@export`, `@onready`, and grouping annotations for the Inspector.
@toolextends Node2D@export_group("Movement")@export var speed := 240.0@export var jump_force := 420.0@onready var sprite = $Sprite2D
- Use `@export` to make a property editable in the Inspector.
- Use `@export_group`, `@export_subgroup`, or `@export_category` to keep Inspector-heavy scripts readable.
- Use `@onready` for node lookups or values that should initialize after the node enters the scene tree.
- Use `@tool` when a script should also run in the editor.
Warnings and advanced annotations
Ruzta also supports annotations that affect warnings or class behavior. These are useful once you are tuning editor feedback or expressing more specialized intent.
@warning_ignore("unused_parameter")func _process(_delta: float) -> void: pass
- Use `@warning_ignore(...)` when a specific warning is noisy and you are intentionally keeping the code as written.
- Use warning ignores narrowly; they should document a conscious exception, not hide sloppy code.
- Other annotations such as `@abstract` or `@static_unload` are more specialized and should be introduced only when their behavior is needed.
Style Reference
Formatting and naming
Ruzta reads best when it follows the same clear, conservative style that Godot recommends for GDScript: strong naming, one clear statement per line, and indentation that makes control flow obvious.
const MAX_SPEED = 400.0@export var move_speed := 220.0func apply_damage(amount: int) -> void: if amount <= 0: return health -= amount
- Use tabs for indentation and keep block depth visually clean.
- Use `snake_case` for variables and functions.
- Use `PascalCase` for classes and `CONSTANT_CASE` for constants.
- Prefer short functions with explicit names over very clever multi-purpose ones.
- Add types where the API matters most, especially on exported members and public helpers.
- Keep node lookups near `@onready` declarations instead of scattering `$Node` calls everywhere.
Builtin Functions
Everyday builtins
Ruzta scripts have access to the usual Godot globals plus script-level helpers. In practice, that means you already have a large toolkit available before writing any utility class of your own.
func _ready() -> void: assert(len("ruzta") == 5) print(typeof(3.5)) print(char(65)) print(ord("A"))
- Use `print`, `prints`, and `printerr` for output.
- Use `len`, `str`, `int`, `float`, and `typeof` for basic conversions and inspection.
- Use `range` constantly in loops and `assert` when you want a development-time correctness check.
- Use `char` and `ord` when you need character/code point conversions.
Loading, debugging, and runtime helpers
A second group of builtins covers resource loading, stack inspection, and dynamic type checks. These are the helpers you reach for when wiring projects together or debugging script behavior.
const HUD_SCENE = preload("res://ui/hud.tscn")func spawn_hud(scene_path: String) -> void: var hud_scene = load(scene_path) print_debug(hud_scene) print(is_instance_of(HUD_SCENE, TYPE_OBJECT))
- Use `preload` for constant asset references known at parse time and `load` for dynamic paths chosen at runtime.
- Use `print_debug`, `print_stack`, and `get_stack` when a regular `print` is not enough.
- Use `is_instance_of` when the type you want to compare against is itself dynamic.
- Remember that many engine-wide constants and helpers come from `@GlobalScope`, not only the script helper set.