Usage

In the sections bellow, it's described in detail each step to run the dialogue.

Here is a simple implementation for reference:


var _dialogue

# this represents your persistence logic. In this example, it keeps it in memory
var _external_persistence = {}
var _dialogue_internal_data_persistence = {}

func _ready():
  # Setting up your dialogue
  _dialogue = ClydeDialogue.new()
  dialogue.load_dialogue('my_dialogue_file')

  # listen to events to react to them
  _dialogue.event_triggered.connect(_on_event_triggered)

  # load any previous data. This object is opaque.
  # it holds internal dialogue data, like access memory and random selections
  dialogue.load_data(_dialogue_internal_data_persistence);

  # configure the data access so your dialogue
  # can access and update external data
  _dialogue.on_external_variable_fetch(_on_external_variable_fetch)
  _dialogue.on_external_variable_update(_on_external_variable_update)


# this method will be triggered by a user interaction (button, click, key press)
func _get_next_dialogue_line():
  var content = _dialogue.get_content()
  if content.type == "end":
    # persisting dialogue access data
    _dialogue_internal_data_persistence = _dialogue.get_data()
    # do other stuff like hiding the interface, resuming inputs, ...
    return

  if content.type == 'line':
    # Show dialogue line to player
    # ...

  if content.type == 'options':
    # display options for player to choose from
    # ...


# this will be called when player selects an option
func _on_option_selected(index):
  _dialogue.choose(index)
  _get_next_dialogue_line()


func _on_event_triggered(event_name, parameters):
  print("Event received: %s params: %s " % [event_name, parameters])


func _on_restart_pressed():
  _dialogue.start()
  _get_next_dialogue_line()


# this is an example on how to provide access to external variables.
# For example, if the dialogue tries to access { @health }, this method will be called and return the value from
# _external_persistence["health"]
func _on_external_variable_fetch(variable_name: String):
  return _external_persistence[variable_name]


# This method is called when the dialogue tries to set an external variable. i.e { set @health = 10 }
func _on_external_variable_update(variable_name: String, value: Variant):
  _external_persistence[variable_name] = value

You can find and execute the complete example on the examples folder.

Creating an object

You need to instantiate a ClydeDialogue object.

var dialogue = ClydeDialogue.new()

Loading dialogues

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')

Starting / Restarting a 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.

Getting next content

You should use dialogue.get_content() to get the next available content.

This method may return one of the following values:

Line

A dialogue line (Dictionary).

{
  "type": "line",
  "text": "Ahoy!",
  "speaker": "Captain", # optional
  "id": "123", # optional
  "tags": ["happy"] # optional
}

Options

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
    },
    ...
  ]
}

End

Returned when the dialogue reached its end. Any new subsequent call will return an end object.

{ "type": "end" }

Listening to variable changes

You can listen to variable changes by observing the variable_changed signal.

  # ...

  dialogue.variable_changed.connect(func (variable_name, value, previous_value):
    if variable_name == 'hp' and value < previous_value:
      print('damage taken')
  )

Listening to events

You can listen to events triggered by the dialogue by observing the event_triggered signal.

  # ...

  dialogue.event_triggered.connect(func (event_name, parameters):
    if event_name == 'self_destruction_activated':
      _shake_screen()
      _play_explosion()
  )

Data persistence

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.type == "end":
        _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

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.

Translations / Localisation

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.

Dialogue folder and organisation

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")

More examples

You can find more usage examples on the examples folder.