Skip to content

03 Scripting

zhoeshin edited this page Aug 1, 2026 · 13 revisions

Basics

The plugin uses a domain-specific language for its core functionality. The basic building blocks are

# this sets the current dialog box. `someBoxScene` needs to be an initialized scene
[someBoxScene]

# this displays the output to the player
# `somesprite` is used for the portrait and `sometext` is the message, both can be expressions
# the dictionary after it is passed as `options`. if omitted, an empty dictionary is passed
somesprite: sometext {
    someparam = somevalue
}

# this creates an input for the player to respond to in accordance to the `someInputType` descriptor
# after that, `somevar` is assigned to the player's output
# the dictionary literal is passed as `options` to the dialog box
# `somevar` is optional and if omitted the result is not assigned
# the dictionary containing `options` is optional. an empty dictionary is passed if none present
somevar <- someInputType {
    someparam = somevalue
}
# matching by value after input is also optional
- somebranch1 # branch executed if player's output is same as `somebranch1`
    do1
- somebranch2 ? condition # this branch is only created if `condition` is true
    do2
- somebranch3 # this branch has no body. this is allowed for branches of an input statement but not for branches of a match statement
--
    dodefault

# matching uses the same syntax
somevalue:
- somematch1 # branch executed if `somevalue == somematch1`
    do1
# in this case, because `?` is an operator, the branch equals `somematch2` if `condition` is true and `null` otherwise
# if `somevalue` is `null`, it might still match this branch
- somematch2 ? conditon
    do2
--
    dodefault

# if statements are
? somecond1
    do1
# multiple conditional statements in a row get joined into if-else chains
? elsecond2
    doelse2
?! # the else branch terminates the chain of conditionals
    doelse

? cond1
    do1
. # the dot terminates the chain of conditionals
? cond2 # checked independantly of `cond1` branch
    do2
.

# any other line is treated as an expression
somevar = someval1 + someval2

Values

Literals

Numbers

Numbers can only be digits 0-9, with optional period. For example 12345, 12.34. .5 or 1. are not valid numerals. Different bases, such as hex 0xae, binary 0b1001 or octal 0x765 are not currently supported

Strings

Strings are quoted with either " or '. String interpolation is supported: a part of a string can be delimitered by {}. In which case, the contents are executed as an expression, for example

somevar = "a"
somevar2 = "b"
edwin: "Vars {a} {b} {a + b} {2 ** 4 + 3}" # outputs "Vars a b ab 19"
edwin: "No vars \{a} \{2 ** 4 + 3}" # backslash escapes interpolation, so output is "No vars {a} {2 ** 4 + 3}"

All strings are interpolated in the context they are used in.

Translated strings

Translation is a built-in functionality. Strings which are keys to be translated are prefixed with tr, for example tr"edwin.greet". Interpolation is also functional for translated strings. First, the key to be translated(the string literal in the script) is interpolated. Then, after translation is performed, the translated string is also interpolated. For example, tr"gameover.{deathcount}" will look up keys "gameover.1", "gameover.4", ...; after that, those messages get interpolated. For example, if key "gameover.1" returns "Come on, get up, {Global.playerName}", the output will be "Come on, get up, PlayerNameHere". For translating, Godot's built-in internalization functionality is used

Resources

Resources can be loaded using ^"res://path/to/resource" syntax. For example ^"res://sprites/portraits.edwin.png", ^"res://scripts/cutscene.gd", ^"res://dialogBoxes/default.tscn". If the loaded resource is a script or a scene, it is instantiated automatically.

This means, a script can be imported by binding = ^"res://path/to/script.gd" and setting a dialog box is possible via [^"res://path/to/dialogBox.tscn"].

Variables

For most scenarios, no special naming conventions or prefixes are required. Valid names are somevar, somevar123, _othervar. However, a local variable might shadow a global one. To force accessing a global variable, its name can be prefixed with %, for example, %Inventory will not look for a local variable named Inventory.

Built-ins

Most of Godot's built-in functions, objects and types are accessible by the same identifier with the DSL

Operators

Any expression written in the DSL can both access Godot's internal operators and use overloads. First, overloads are checked, and if any are present, that method is executed. Otherwise, execution falls back to Godot's built-in operators.

All operators in the DSL are:

Operator Overload Note
a in b _in
a () b _call
a [] b none Accessing a member
a . b none Accessing a member
a ** b _pow
a */ b _root Falls back to a ** (1.0 / b)
a * b _mul
a / b _div True division(not rounded) if both a and b are integer
a % b _mod
a // b _floor_div
a + b _add
a - b _sub
a > b _gt
a < b _lt
a >= b _ge
a <= b _le
a == b _eq
a != b _ne
a && b _land Can convert a and b to bool if they implement _to_bool method. Shortcircuits
a b
a ^^ b _lxor Can convert a and b to bool if they implement _to_bool method. Falls back to bool(a) != bool(b)
a & b _and
a b _xor
a ^ b _xor
a = b none
a ? b none can convert b to bool using _to_bool method. if b is falsey, returns a. b can reference a as _ *note
a ?! b none if a is null, returns b
a match b _match used to match a value to a branch in match statements and input branches. attempts to use a._match(b) then b._match_r(a), then a._eq(b), then b._eq_r(a), then built-in a==b
~a _inv
!a _not
+a _pos
-a _neg
tr a _tr if a has method _tr, calls it. otherwise, calls tr(str(a)) for translation

Any overloadable infix operator can have a secondary method prefixed with _r for when self is the right hand side. For example, for 5 + someCustomClass, int does not implement _add. If CustomClass impelemnts _add_r, that method is called with 5 as the argument. Otherwise, the interpreter falls back to using Godot's native addition

*note: For example, "Red" ? prevColor != _ will return null if prevColor == "Red". This is primarily used for branches in input statements(any branch matching a null is ignored). If the right hand size of the ? has a _, _ is substituted with the left hand side value. If no _ is present, left hand side is not evaluated unless right hand side is true.

Labels and Go-To's

The language supports scoped goto's. This means that it is possible to jump to any label within the current scope or any parent scope.

|-> label is used for declaring a label, |<- label is used for jumping to that label, for example

test: "Which?" {
	autoconfirm = true
}
flavor <- option
- "Cinnamon"
- "Chocolate"
- "Banana"
- "Plain"

repeating = false

test: "Topping?" {
	autoconfirm = true
}
|-> "Repeat topping" # declaring a label to jump to
topping <- option {
	noSelectionReset = repeating
}
- "Cinnamon" ? _ != flavor ?! "[color=gray]Cinnamon\n[font_size=16]already main flavor[/font_size][/color]"
- "Chocolate" ? _ != flavor ?! "[color=gray]Chocolate\n[font_size=16]already main flavor[/font_size][/color]"	
- "Banana" ? _ != flavor ?! "[color=gray]Banana\n[font_size=16]already main flavor[/font_size][/color]"	
- "Plain" ? _ != flavor ?! "[color=gray]Plain\n[font_size=16]already main flavor[/font_size][/color]"

? flavor in topping
	repeating = true
	|<- "Repeat topping" # jumping to force selecting a different option

test: "Selected {flavor} with \{{topping}}"

Note that labels are hoisted, meaning their definition is executed before any other code. Since the name of a label is also an expression, it is possible to use runtime values in the name, but not script-local variables, which don't exist at the time of label name evaluation

Clone this wiki locally