For details about Clyde and how to write dialogues, check Clyde/LANGUAGE.md
This plugin exposes the interpreter as ClydeDialogue
.
This is ClydeDialogue
's interface:
extends Node
signal variable_changed(variable_name: String, value; Variant, previous_vale: Variant)
signal event_triggered(event_name; String)
# Load dialogue file
# file_name: path to the dialogue file.
# i.e 'my_dialogue', 'res://my_dialogue.clyde', res://my_dialogue.json
# block: block name to run. This allows keeping
# multiple dialogues in the same file.
func load_dialogue(file_name: String, block: String = "") -> Void
# Start or restart dialogue. Variables are not reset.
func start(block_name: String = "") -> void
# Get next dialogue content.
# The content may be a line, options or null.
# If null, it means the dialogue reached an end.
func get_content() -> Dictionary
# Choose one of the available options.
# option_index: index starting in 0.
func choose(option_index: int) -> void
# Set variable to be used in the dialogue
# name: variable name
# value: variable value
func set_variable(name: String, value: Variant) -> Variant
# Get current value of a variable inside the dialogue.
# name: variable name
func get_variable(name: String) -> Variant
# Set callback to be used when requesting external variables
func on_external_variable_fetch(callback: Callable) -> void
# Set callback to be used when an external variable is updated in the dialogue
func on_external_variable_update(callback: Callable) -> void
# Return all variables and internal variables. Useful for persisting the dialogue's internal
# data, such as options already choosen and random variations states.
func get_data(): Dictionary
# Load internal data
func load_data(data: Dictionary) -> void
# Clear all internal data
func clear_data() -> void
# Set optional settings for current interpreter. [br]
# Options:
# include_hidden_options (boolean, default false): Returns conditional options event when check resulted in false.
#
func configure(options: Dictionary) -> void
## Checks if a block with the given name exists.
func has_block(block_name: String) -> bool
You need to instantiate a ClydeDialogue
object.
var dialogue = ClydeDialogue.new()
The interpreter supports loading parsed JSON files, as well as .clyde
files imported in the project.
When only the file name is provided, the interpreter will look into the default folder defined on Project > Project Settings > Dialogue > Source Folder
.
dialogue.load_dialogue('my_dialogue')
# or
dialogue.load_dialogue('res://dialogues/my_dialogue.clyde')
# or
dialogue.load_dialogue('res://dialogues/my_dialogue.json')
As you can have more than one dialogue defined in a file through blocks, you can provide the block name to be used.
dialogue.load_dialogue('level_001', 'first_dialogue')
You can use dialogue.start()
at any time to restart a dialogue or start a different block.
# starts default dialogue
dialogue.start()
# starts a different block
dialogue.start('block_name')
Restarting a dialogue won't reset the variables already set.
You should use dialogue.get_content()
to get the next available content.
This method may return one of the following values:
A dialogue line (Dictionary
).
{
"type": "line",
"text": "Ahoy!",
"speaker": "Captain", # optional
"id": "123", # optional
"tags": ["happy"] # optional
}
Options list with options/topics the player may choose from (Dictionary
).
{
"type": "options",
"name": "What do you want to talk about?", # optional
"speaker": "NPC", # optional
"options": [
{
"label": "option display text",
"speaker": "NPC", # optional
"id": "abc", # optional
"tags": [ "some_tag" ], # optional
},
...
]
}
Returned when the dialogue reached its end. Any new subsequent call will return an end object.
{ "type": "end" }
You can listen to variable changes by observing the variable_changed
signal.
# ...
dialogue.connect('variable_changed', self, '_on_variable_changed')
func _on_variable_changed(variable_name, value, previous_vale):
if variable_name == 'hp' and value < previous_value:
print('damage taken')
You can listen to events triggered by the dialogue by observing the event_triggered
signal.
# ...
dialogue.connect('event_triggered', self, '_on_event_triggered')
func _on_event_triggered(event_name):
if event_name == 'self_destruction_activated':
_shake_screen()
_play_explosion()
To be able to use variations, single-use options and internal variables properly, you need to persist the dialogue data after each execution.
If you create a new ClydeDialogue
without doing it so, the interpreter will show the dialogue as if it was the first time it was run.
You can use dialogue.get_data()
to retrieve all internal data, and then later use dialogue.load_data(data)
to re-populate the internal memory.
Here is a simplified implementation:
var _dialogue_filename = 'first_dialogue'
var _dialogue
func _ready():
_dialogue = ClydeDialogue.new()
_dialogue.load_dialogue(_dialogue_filename)
_dialogue.load_data(persistence.dialogues[_dialogue_filename]) # load data
func _get_next_content():
var content = _dialogue.get_content()
# ...
if content == null:
_dialogue_ended()
func _dialogue_ended():
persistence.dialogues[_dialogue_filename] = _dialogue.get_data() # retrieve data for persistence
The example above assumes there is a global object called persistence
, which is persisted every time the game is saved.
When starting a new dialogue execution, the internal data is loaded from the persistence
object. When the dialogue ends, we update said object with the new values.
Note that the data is saved in in the dictionary under the dialogue filename key. The internal data should be used only in the same dialogue it was extracted from.
You should not change this object manually. If you want't to change a variable used in the previous execution, you should use dialogue.set_variable(name, value)
.
# ...
_dialogue = ClydeDialogue.new()
_dialogue.load_dialogue(_dialogue_filename)
_dialogue.load_data(persistence.dialogues[_dialogue_filename])
_dialogue.set_variable("health", character.health)
Variables set via set_variable
are included in the dialogue data object. This might not be ideal in cases where the data does not belong to the dialogue. For instance,
if you pass a health variable to the dialogue, all dialogues will contain a version of that value, which will increase your save file size. You can workaround this by using external variables.
External variables are accessed using the @
prefix. i.e. @health
. You need to define two callbacks to allow the dialogue to access and modify external variables:
# ...
_dialogue.on_external_variable_fetch(func (variable_name: String):
return persistence[variable_name]
)
_dialogue.on_external_variable_update(func (variable_name: String, value):
persistence[variable_name] = value
)
on_external_variable_fetch
will be called any time the dialogue tries to access an external variable. i.e. { @health > 10 }
.
on_external_variable_update
will be called any time the dialogue tries to update an external variable. i.e { set @health = 100 }
External variables are just a proxy to the game variables. No external data is stored as part of the dialogue.
Godot already comes with a localisation solution built-in.
The interpreter leverages this solution to translate its dialogues. Any dialogue line which contains an id defined will be translated to the current locale if a translation is available.
In case there is no translation for the id provided, the interpreter will return the default line.
By default, the interpreter will look for files under res://dialogues/
. In case you want to specify a different default folder, you need to change the configuration in Project > Project Settings > Dialogue > Source Folder
.
Alternatively, you can use the full path when loading dialogues:
var dialogue = ClydeDialogue.new()
dialogue.load_dialogue("res://samples/banana.clyde")
You can find usage examples on /example/ folder.