-
Notifications
You must be signed in to change notification settings - Fork 86
Dialogue manual
[[TOC]]
The plugin contains a dialogue backend system with proper editor support for unreal engine. We tried to make it as general as possible, but the main design decisions were made based on the needs of our own game. The source code is public though - even if your needs are different you may find it a good starting point.
This document describes both the integration and the usage of the system, however it expects some general knowledge about the engine from the reader.
Using the plugin you will create Dialogue Assets in the content browser - they contain the static data with some logical expressions (they are similar to behavior trees, but not that complex). In those you will reference Dialogue Participants - they are objects implementing the proper interface functions.
In order to start a dialogue runtime you will need to provide an array of Dialogue Participants matching the ones referenced inside the dialogue. If the dialogue is started successfully you will get a Dialogue Context which can be used to control the dialogue and get information from its current state.
The Dialogue Memory will contain the visited nodes for each dialogue - this can be used for some of the conditions.
Both UI and serialization are outside of the scope of this plugin - we found them way too game specific to be included.
You can create a Dialogue Asset in content browser using Right Click -> Dialogue System -> Dialogue. Doing this will also create a text file in the same folder by default - it contains exactly the same data as the .uasset. You can change the format of the text file or disable it completely in the dialogue editor settings.
Create and open a Dialogue to open the graph editor.
The dialogue graph is constructed from different nodes and the edges between them. Some node contains text or texts, others are only there to build up more complicated logical behavior. The edges can represent player choices or in some case they are simply there to modify the flow of the dialogue.
Just like in behavior tree, the children of a node are sorted based on the horizontal location of their visual representation: the one in the left is the first, and the one in the right is the last.
Each node has a number in the top right corner: that is the index of the node.
Defines the entry point of the Dialogue - its children are evaluated at the beginning of the dialogue and the first successful child defines the first line of the dialogue. It does not contain any text, does not have any conditions, does not fire any event, and it is the only node without a node owner. The start node is already placed and can not be removed.
A standard dialogue line - it has an owner (speaker) and a text. Optionally it can also contain a Sound Wave or a Dialogue Wave - you can select which to use (or hide both) globally in the dialogue dialogue settings.
The node has a bool property IsVirtualParent. If it is set the Node will list the children of its first satisfied child as its own - it is useful when you want to give the user the same player choices after different NPC lines. Virtual Parent dialogue speech nodes are displayed with gray background.
You can think of this node type as a helper/shortcut node.
Example:
- With virtual parent node
- Without virtual parent node
The purpose of this node is to make the graph more organized - a single speech sequence node contains the data of multiple speech nodes and can be used instead of those in linear cases. You can extract a speech sequence node to a series of speech nodes via Right Click on the node and choosing Convert to speech nodes in the context menu. You can also convert more speech nodes into a sequence node, but only if they are connected with each other without any branches. Simply select them, right click on empty space in the grid and select Convert… from the context menu.
This node type does not contain any text to display and the dialogue is not supposed to stop reaching these. When the node is entered its children are evaluated and one of them is selected immediately. Depending on the Selector Type the selected node is either the first satisfied child or a randomly picked one.
The dialogue is terminated when the execution flow reaches an end node. This node type does not have any text attached, but it can have conditions and it can trigger events.
Each edge can contain conditions and a text. The ones coming from a Selector node are evaluated and stepped over automatically. In other cases the game can get the data from the actual outgoing edges from the current node to provide them as choices to the player.
Edges that are part of the shortest way from start node to the target node are called Primary Edges, the rest are called Secondary edges. This categorization is purely for visual clarity: they can be displayed with different colors and each category can be hidden.
Base operations:
Add node: right click in the grid and select the desired node type under Dialogue Node category in the context menu.
Add edge: press and hold left mouse button on the (black) border area of the source node, then drag the mouse to the border of the target node.
Retarget edge: hold Ctrl and drag the edge via left mouse button to change its target node.
The graph editor also supports undo/redo and copy/paste.
To edit a graph or an edge simply select it via left click, then you can use the details panel to change its properties.
Edges and nodes can contain enter conditions - a node is only reachable from an edge if the conditions belonging to the edge and to the node are all satisfied.
You can add a condition in the details panel of the edge or node:
If a node or edge has a condition an icon is displayed in the graph:
The conditions are stored in arrays and the content of those arrays are evaluated together. The Strength of a condition can be strong or weak. A condition array is satisfied if it only contains satisfied strong conditions, and it has at least one satisfied weak condition, or none at all. In other words the condition array fails (and the node cannot be entered) if there are any failed strong conditions or all the weak conditions are failed ones (or both).
The Condition Type defines the behaviour of the condition
Some condition asks for a variable from the owner participant and performs a logical operation on it. The supported variable types are Integer, Float, Boolean and Name. (Managing these variables are outside of the scope of the plugin, it simply uses an interface to request those values from the participants).
The Check named condition expects the participant to execute a more complicated logical expression identified by the Condition Name.
The Was node already visited condition checks if the node with the given index was already visited or not.
If the Long Term Memory is checked the previous dialogues using the same Dialogue Asset are taken into account as well, otherwise the check is limited to the actual dialogue context.
Using random in any condition is not supported and can lead to undefined behavior. The dialogue system can call the conditions multiple time in an update and it expects to get the same results.
For check class variables please check the Reflection Variables section.
Nodes can have enter events - these are executed each time the node is reached in the active dialogue context. Nodes with events are also marked with an icon (see image).
Similarly to Conditions, events can either modify (or set) the value of a supported variable type, or it can simply execute a named event the participant have to handle.
For modify class variables please check the Reflection Variables section.
To start a dialogue some of your objects (e.g. your player character class/blueprint) need to implement the DlgDialogueParticipant
interface. It supports both C++ and blueprint. If you
work with C++ check DlgDialogueParticipant.h
. In Blueprint you will get the functions you have to implement if you added the interface to the class using the Class Settings.
The only function you have to implement in all case is the GetParticipantName
- this should return a Name variable which has to be unique inside any Dialogue Asset - the participant is identified with this inside the Dialogue Editor.
The other functions are optional. The variable getters and the CheckCondition
function are used by the conditions, the variable modifiers and the OnDialogueEvent
function are used by the events. The rest (GetParticipantDisplayName
, GetParticipantIco
n, etc.) can be used to get those extra informations from the active participant from the dialogue context.
To start a dialogue call the UDlgManager::StartDialogue
static function. You will need to provide a dialogue asset and an array of objects (implementing the participant interface).
It returns with the DialogueContext
which can be used to control the ongoing dialogue.
After the dialogue is started it is already executed to the point where the first player choice is required - selecting the only available option is also considered to be a choice in this case.
You can get the active node data and the currently available options from the context using the following DlgContext
functions: GetOptionNum
, GetOptionText
, GetActiveNodeText
, GetActiveParticipant
.
If it is possible that the result of the conditions can change during the dialogue after a node became available but before the user picked an option the active options can be refreshed via the ReevaluateChildren
function.
Once the player selected an option you can call ChooseChild()
with the selected option index to proceed the dialogue. If the function returns with false the dialogue is ended and the context must be dropped.
If you use the long term memory of the dialogue system (for conditions checking if a certain dialogue node was visited or not during the whole game) you will have to serialize the dialogue history.
When saving your game simply call UDlgManager::GetDialogueHistory
, and save the returned data structure. On load you can use the loaded value via UDlgManager::SetDialogueHistory
.
When a new game is started (e.g. with a different player profile) you should also clear the dialogue memory via calling the setter function with an empty map, otherwise the system may use the previously used one.
To clear the memory you can use the helper method UDlgManager::ClearDialogueHistory
in the StartPlay/BeginPlay
of your GameMode.
But you don't need to do this by default because the memory is already cleared automatically by the plugin whenever a new world is loaded, change this setting in the dialogue settings under Clear Dialogue History Automatically name.
To enable commands you have to manually register them using UDlgManager::RegisterDialogueModuleConsoleCommands
inside your
StartPlay/BeginPlay
of your GameMode and unregister them in EndPlay
using UDlgManager::UnRegisterDialogueModuleConsoleCommands
Example:
Speaker states can be used to add an extra FName
to your nodes and/or edges. The Speaker State of the active node is passed to the GetParticipantIcon()
function if it is called via UDlgContext::GetActiveParticipantIcon()
. It can be used e.g. to attach emotions to nodes like Happy
, Sad
, etc. - you can use different images for the different states in your UMG widget.
The SpeakerState of an edge can be requested via UDlgContext::GetOptionSpeakerState()
.
To display SpeakerStates in the dialogue editor you have to change the SpeakerState Visibility
property in the dialogue settings.
SpeakerStates also have custom UI picker, but the used values are stored in a global cache.
A Blueprint Select on SpeakerState
node is also provided - it lists all used SpeakerState values as wildcard option pins.
This feature is useful if you want to directly modify variables inside your Blueprint instead of using your own data storage structures.
For example for the following case:
The dialogue system will modify the variable Stamina
to 20
inside the MrCube
participant.
Where MrCube
has the following variables defined:
Because the dialogue editor can't know what variables a participant might have you need to set the participant class for it to work.
Click on an empty canvas inside the dialogue editor and on the left side (by default) you should the property DlgParticipantClasses
.
The plugin contains some custom blueprint nodes to help out with the implementation of the condition and event related functions.
They copy the behaviour of the Switch and Select nodes, but they get the participant name of the owner blueprint using the IDlgParticipant interface and gather all the associated values from all dialogues.
Click on Window -> Dialogue Browser to open it. The purpose of this window is to gather and visualize dialogue related data.
You can see all your dialogue participants here, check on the dialogues they are referenced in and see the list of associated events and variables.
The events/variables/conditions are also organized by dialogues, and the containing nodes or edges are also listed. Clicking on those will open the dialogue with the associated node/edge being selected.
You can use the search window to search in the dialogue texts.
If you want to search inside a single dialogue open the search window via pressing ctrl+f in the dialogue editor.
If you want to search globally use the ones under Window -> Find in dialogues.
You can use the Dialogue Data Display window runtime to list the active participants, their variables/events. It is also possible to modify the values, check conditions or fire events.
To open in editor you can use Window -> Dialogue Data Display
Alternative way to run is the console command Dlg.DlgDataDisplay
. (This also works in non-editor builds.)
Before you can use the DlgDataDisplay
Window you need to register the console commands of the Plugin. An example is provided in the section Dialogue Console Commands or in the C++ project here.
NOTE: Previous to version
5.0
of the plugin the settings where located here Edit -> Editor Preferences -> Content Editors -> Dialogue Editor.
You can open the Dialogue editor settings via Edit -> Project Settings -> Editor -> Dialogue. Here you can change the layout and the behaviour of the dialogue system.