143 lines
No EOL
7.7 KiB
Markdown
143 lines
No EOL
7.7 KiB
Markdown
# Using dialogue in your game
|
|
|
|
A global called `DialogueManager` is available to provide lines of dialogue.
|
|
|
|
If you haven't specified your game states in [the editor](Settings.md) you can manually set them with something like:
|
|
|
|
```gdscript
|
|
# Game state objects are globals that have properties and methods used by your dialogue
|
|
DialogueManager.game_states = [GameState, SessionState]
|
|
```
|
|
|
|
Then you can get a line of dialogue by yielding to `get_next_dialogue_line` and providing a node title.
|
|
|
|
So, assuming your dialogue file was `res://assets/dialogue/example.tres` and you had a node title in there of `~ some_node_title` then you could do this to get the first printable dialogue:
|
|
|
|
```gdscript
|
|
var dialogue_resource = preload("res://assets/dialogue/example.tres")
|
|
var dialogue_line = yield(DialogueManager.get_next_dialogue_line("some_node_title", dialogue_resource), "completed")
|
|
```
|
|
|
|
You can also call `get_next_dialogue_line` on a resource directly (which is just a shortcut to calling the global):
|
|
|
|
```gdscript
|
|
var dialogue_resource = preload("res://assets/dialogue/example.tres")
|
|
var dialogue_line = yield(dialogue_resource.get_next_dialogue_line("some_node_title"), "completed")
|
|
```
|
|
|
|
This will find the line with the given title and then begin checking conditions and stepping over each line in the `next_id` sequence until we hit a line of dialogue that can be displayed (or the end of the conversation). Any mutations found along the way will be executed as well.
|
|
|
|
You need to `yield` this call because it can't guarantee an immediate return. If there are any mutations it will need to allow for them to run before finding the next line.
|
|
|
|
The returned line in dialogue is a `DialogueLine` and will have the following properties:
|
|
|
|
- **character**: String
|
|
- **dialogue**: String
|
|
- **translation_key**: String
|
|
- **replacements**: Array { expression, value_in_text } Dictionaries (expression is in AST format and can be manually resolved with `DialogueManager.replace_values()`)
|
|
- **pauses**: Dictionary of { index => time }
|
|
- **speeds**: Array of [index, speed]
|
|
- **inline_mutations**: Array of [index, expression] (expression is in AST format and which can be manually resolved with `DialogueManager.mutate()`)
|
|
- **next_id**: String
|
|
- **time**: null or String ("auto" or a float-like string)
|
|
- **responses**: Array of DialogueResponse:
|
|
- **character**: String
|
|
- **character_replacements**: Array { expression, value_in_text } Dictionaries (expression is in AST format and can be manually resolved with `DialogueManager.replace_values()`)
|
|
- **prompt**: String
|
|
- **is_allowed**: String
|
|
- **replacements**: Array { expression, value_in_text } Dictionaries (expression is in AST format and can be manually resolved with `DialogueManager.replace_values()`)
|
|
- **translation_key**: String
|
|
- **next_id**: String
|
|
|
|
Now that you have a line of dialogue you can find the next line after it with:
|
|
|
|
```gdscript
|
|
# If this line has no responses we can just use "next_id". If it does have responses you can use
|
|
# the "next_id" of whichever response was chosen
|
|
dialogue_line = yield(DialogueManager.get_next_dialogue_line(dialogue_line.next_id, dialogue_resource), "completed")
|
|
```
|
|
|
|
It's up to you to implement the actual dialogue rendering and input control but there are a couple of things included to get you started: `DialogueLabel` and the **Example Balloon**.
|
|
|
|
## DialogueLabel node
|
|
|
|
The addon provides a `DialogueLabel` node (an extension of the RichTextLabel node) which helps with rendering a line of dialogue text.
|
|
|
|
This node is given a `DialogueLine` object (mentioned above) and uses its properties to work out how to handling typing out the dialogue. It will automatically handle any `bb_code`, `wait`, `speed`, and `inline_mutation` references.
|
|
|
|
Use `type_out()` to start typing out the text. The label will emit a `finished` signal when it has finished typing.
|
|
|
|
The label will emit a `paused` signal (along with the duration of the pause) when there is a pause in the typing and a `spoke` signal (along with the letter typed and the current speed) when a letter was just typed.
|
|
|
|
## Example balloon
|
|
|
|
There is an example implementation of a dialogue balloon you can use to get started (it's the same balloon that gets used when you run the test scene from the dialogue editor).
|
|
|
|
Have a look at [/addons/dialogue_manager/example_balloon](../addons/dialogue_manager/example_balloon) to see how it's put together.
|
|
|
|
You can give the balloon a go in your game by doing something like this:
|
|
|
|
```gdscript
|
|
var dialogue_resource = preload("res://assets/dialogue/example.tres")
|
|
DialogueManager.show_example_dialogue_balloon("Some title", dialogue_resource)
|
|
```
|
|
|
|
This will add a CanvasLayer and some UI to the bottom of the screen for an interactive dialogue balloon. Input is mapped to `ui_up`, `ui_down`, and `ui_accept`.
|
|
|
|
![Example balloon instance](example-balloon.jpg)
|
|
|
|
Make a copy of the example balloon directory to start customising it to fit your own game.
|
|
|
|
Once you have your own balloon scene you can do something like this (This is what I have in my game):
|
|
|
|
```gdscript
|
|
# Start some dialogue from a title, then recursively step through further lines
|
|
func show_dialogue(title: String, resource: DialogueResource) -> void:
|
|
var dialogue = yield(DialogueManager.get_next_dialogue_line(title, resource), "completed")
|
|
if dialogue != null:
|
|
var balloon := DialogueBalloon.instance()
|
|
balloon.dialogue = dialogue
|
|
add_child(balloon)
|
|
# Dialogue might have response options so we have to wait and see
|
|
# what the player chose. "actioned" is emitted and passes the "next_id"
|
|
# once the player has made their choice.
|
|
show_dialogue(yield(balloon, "actioned"), resource)
|
|
```
|
|
|
|
![Real dialogue balloon example](real-example.jpg)
|
|
|
|
To achieve something similar to the above example (balloons that position themselves near characters) I'd suggest parenting your balloon control to a `Node2D` that you can then move to the character's `global_position`. Additionally, within each "talkable" character I have a `Position2D` node that is used to work out where the pin should be located.
|
|
|
|
## Conditions
|
|
|
|
Conditions let you optionally show dialogue or response options.
|
|
|
|
If you have a condition in the dialogue editor like `if some_variable == 1` or `if some_other_variable` then you need to have a matching property on one of the given `game_state`s or the current scene.
|
|
|
|
If you have a condition like `if has_item("rubber_chicken")` then you will need a method on one of the `game_state`s or the current scene that matches the signature `func has_item(thing: String) -> bool:` (where the argument `thing` can be called whatever you want, as long as the type matches or is untyped). The method will be given `"rubber_chicken"` as that argument).
|
|
|
|
## Mutations
|
|
|
|
Mutations are for updating game state or running sequences (or both).
|
|
|
|
If you have a mutation in the dialogue editor like `do some_variable = 1` then you will need a matching property on one of your `game_state`s or the current scene.
|
|
|
|
If you have a mutation like `do animate("Character", "cheer")` then you will need a method on one of the `game_state`s or the current scene that matches the signature `func animate(character: String, animation: String) -> void:`. The argument `character` will be given `"Character"` and `animation` will be given `"cheer"`.
|
|
|
|
## Generating Dialogue Resources at runtime
|
|
|
|
If you need to construct a `DialogueResource` at runtime you can use `get_resource_from_text(string)`:
|
|
|
|
```gdscript
|
|
var resource = DialogueManager.get_resource_from_text("~ title\nCharacter: Hello!)
|
|
```
|
|
|
|
This will run the given text through the parser.
|
|
|
|
If there were syntax errors they will be listed under `resource.errors`.
|
|
|
|
If there were no errors then you can use this ephemeral resource like normal:
|
|
|
|
```gdscript
|
|
var dialogue = yield(DialogueManager.get_next_dialogue_line("title", resource), "completed")
|
|
``` |