In order to use conditions and mutations you'll need to have an understanding of how Dialogue Manager uses State.
You can use conditional blocks to further branch your dialogue. Start a condition line with "if" and then provide an expression. You can compare variables or function results.
Additional conditions use "elif" and you can use "else" to catch any other cases.
if SomeGlobal.some_property >= 10
Nathan: That property is greater than or equal to 10
elif SomeGlobal.some_other_property == "some value"
Nathan: Or we might be in here.
else
Nathan: If neither are true, I'll say this.
Note: To escape a condition line (i.e. if you wanted to start a dialogue line with "if"), you can prefix the condition keyword with a "".
Conditions can be joined with and/or and grouped with (,). For example:
if something == "a value" and (score < 0 or score > 100)
Nathan: That condition was true!
Responses can also have "if" conditions. Wrap these in [ and /] (sometimes referred to as "self-closing").
Nathan: What would you like?
- This one [if SomeGlobal.some_property == 0 or some_node_in_scope.some_other_property == false /]
Nathan: Ah, so you want this one?
- Another one [if SomeGlobal.some_method() /] => another_cue
- Nothing => END
If using a condition and a goto on a response line, make sure the goto is provided last.
Conditions can also be used inline in a dialogue line when wrapped with "[if condition]" and "[/if]".
Nathan: I have done this [if already_done]once again[/if]
For simple this-or-that conditions, you can write them like this:
Nathan: You have {{num_apples}} [if num_apples == 1]apple[else]apples[/if], nice!
Randomised lines and randomised jump lines can also have conditions. Conditions for randomised lines go in square brackets after the % and before the line's content:
% => some_cue
%2 => some_other_cue
% [if SomeGlobal.some_condition] => another_cue
To shortcut some if/elif/elif/elif chains you use a match line:
match SomeGlobal.some_property
when 1
Nathan: It is 1.
when > 5
Nathan: It is less than 5 (but not 1).
else
Nathan: It was something else.
You can also start a conditional block with "while". These blocks will loop as long as the condition is true.
while SomeGlobal.some_property < 10
Nathan: The property is still less than 10 - specifically, it is {{SomeGlobal.some_property}}.
$> SomeGlobal.some_property += 1
Nathan: Now, we can move on.
You can affect state with a line starting with $> (set and do are also supported but are considered deprecated).
if SomeGlobal.has_met_nathan == false
$> SomeGlobal.animate("Nathan", "Wave")
Nathan: Hi, I'm Nathan.
$> SomeGlobal.has_met_nathan = true
Nathan: What can I do for you?
- Tell me more about this dialogue editor
In the example above, the dialogue manager would expect a global called SomeGlobal to implement a method with the signature func animate(string, string) -> void.
Mutations can also be used inline. Inline mutations will be called as the typed out dialogue reaches that point in the text.
Nathan: I'm not sure we've met before [$> wave()]I'm Nathan.
Nathan: I can also emit signals[$> SomeGlobal.some_signal.emit()] inline.
Inline mutations that use await in their implementation will pause typing of dialogue until they resolve. To ignore awaiting, add a ">" after the "$>" directive - e.g. "[$>> something()]".
You can pass an array of nodes/objects/dictionaries as the extra_game_states parameter when requesting a line of dialogue which will also be checked for possible mutation methods. Classes should be instantiated, even if their contents are static. Objects are iterated through in the order provided looking for a matching property. Here is an example:
func pirate():
print("yarrr")
class GameStateClass:
var pirate_name = "phil"
func hello():
print("ahoy")
func _ready() -> void:
# GameStateClass.new(), not GameStateClass!
DialogueManager.show_example_dialogue_balloon(load("res://main.dialogue"), "start", [self, { "game" = GameStateClass.new() }])
On the dialogue end, you can call functions directly from objects passed in (for example: self.pirate() can be called as $> pirate()) or through dereference of member variables or dictionary keys (for example: $> game.hello()):
~ start
$> game.hello()
$> pirate()
$> game.pirate_name = "delilah"
$> debug(game.pirate_name)
=> END
Extra game states provide references to objects, nodes, or dictionaries that exist outside the dialogue system. Changes made to their properties will persist after the conversation ends. The main difference from locals is that extra game states are accessed directly by name (e.g., level), while variables created with $> locals.variable_name = value must always be prefixed with locals. (e.g., locals.counter).
Signals can be emitted similarly to how they are emitted in GDScript - by calling emit on them.
For example, if SomeGlobal has a signal called some_signal that has a single string parameter, you can emit it from dialogue like this:
$> SomeGlobal.some_signal.emit("some argument")
In some cases you might want to refer to properties of an object that may or may not be defined. This is where you can make use of null coalescing:
if some_node_reference?.name == "SomeNode"
Nathan: Notice the "?." syntax?
If some_node_reference is null then the whole left side of the comparison will be null and, therefore, not be equal to "SomeNode" and fail. If the null coalescing isn't used here and some_node_reference is null then the game will crash.
If you want to shorten your references to state from something like SomeGlobal.some_property to just some_property, there are two ways you can do this.
- If you use the same state in all of your dialogue, you can set up global state shortcuts in Settings.
- Or, if you want different shortcuts per dialogue file, you can add a
using SomeGlobalclause (for whatever autoload you're using) at the top of your dialogue file.
There are a couple of special built-in mutations you can use:
$> wait(float)- wait forfloatseconds (this has no effect when used inline).$> debug(...)- print something to the Output window.
There is also a special property self that you can use in dialogue to refer to the DialogueResource that is currently being run.