godot-journey
godot-journey copied to clipboard
Implement a Dialogue system.
Similar to Task system. Speaking strictly from a backend position, these will be stored in a cyclic graph as nodes. If a dialogue option gives an opportunity to move to another line of dialogue this generates a connection between those nodes. Should be able to encapsulate bundled nodes which refer to a subset of a graph (a node can be 1 line of dialogue or represent multiple lines of dialogue). Need to be sure to avoid infinite cycles here.
Multidialogue nodes will automatically portray themselves as composite nodes which maintain transitive connections to relevant other nodes, i.e. these will be fake connections. If you ask for the connections associated with a composite node, it will simply loop through the internal nodes and compile a list of connections before returning references to those connections. It doesn't actually store new connections in the data.
Dialogue nodes' statements can be connected to Expression nodes. All expression nodes are tracked such that one can form a tabular list of all expressions and keep tabs on which Properties, Content, Tasks, or other StoryItems they reference.
Action nodes allow user-configurable actions to take place which are triggered by the dialogue system. These are likewise tracked.
Need to leave open the possibility of executing multiple lines of dialogue at the same time. Even if a web of dialogue tracks exist, one may have multiple Conversation instances that are actively traveling through the web's data. This is to support scenarios where a player walks through an environment and multiple NPCs in the background are talking with scripted sequences, based on conditions, even while the player is engaged in other scripted conversations/cutscenes.
Lines of dialogue may have an arbitrary number of speakers. Maybe there is no speaker, maybe there are several. Users should be able to bind labels, properties, and actions to any single dialogue node. Some minor level of scheduling of actions in relation to a dialogue node should be available (not restricted to only "execute action when the dialogue begins").
This is not a GUI for a dialogue system. It is a backend that could very well support a variety of GUI and text-based systems. We should provide some as part of Journey, but those will be other tasks to complete down the road.
Wow, ambitious.
"This is not a GUI for a dialogue system. It is a backend that could very well support a variety of GUI and text-based systems. We should provide some as part of Journey, but those will be other tasks to complete down the road." - you just can send people to Rakugo when it will switch to Journey as backend.
So I this is what system like this need based on my Rakugo/Ren'Py experience (we can change names of this components):
- Character - describes character which talk
- Statement
- the base class of "line" of dialog,
- it waits for right signal from Journey to be executed,
- if
importantwill be add to history - that player can check, - "Lines of dialogue may have an arbitrary number of speakers. Maybe there is no speaker, maybe there are several. Users should be able to bind labels, properties, and actions to any single dialogue node. Some minor level of scheduling of actions in relation to a dialogue node should be available (not restricted to only "execute action when the dialogue begins")." - so it should gets this arguments as one dictionary, if there is no speaker it use narrator as speaker
- Say - standard line/s of dialog - just have arguments:
label,text,who: [array, of, characters] - Ask - Question form speaker that waits for player keyboard input, base on Say
- Choice - Question form speaker that waits for player to choose form given options, base on Say, add one argument "choices": dictionary
{"text_of_choice" : dialog_label_to_go} - Notify - just to do something like "Sam will remember this" - I don't know if we need this one
@jebedaia360
Wow, ambitious.
Thanks!
you just can send people to Rakugo when it will switch to Journey as backend.
That's the idea. I'd plan for the README to maintain a list of projects which offer frontends to Journey.
- Character - describes character which talk
- Statement
- the base class of "line" of dialog,
- it waits for right signal from Journey to be executed,
Yeah, sounds good.
- if
importantwill be add to history - that player can check,
Would probably want to leave this as a configurable value, but yes, provide options for...
- record the sequence of every line of dialogue
- record only user-defined "important" lines of dialogue
- do not record any dialogue
- "Lines of dialogue may have an arbitrary number of speakers. Maybe there is no speaker, maybe there are several. Users should be able to bind labels, properties, and actions to any single dialogue node. Some minor level of scheduling of actions in relation to a dialogue node should be available (not restricted to only "execute action when the dialogue begins")." - so it should gets this arguments as one dictionary, if there is no speaker it use narrator as speaker
I'm thinking there should be a dedicated class for this, that way if people want to extend methods to redefine where data is sourced from, they are free to do so. So, there would really be some Reference data structure with, perhaps, a property for a Dictionary<CharacterDisplayedName, CharacterNameForStoryItem>. So, if I have a StoryItem and its "name" property is "BobOnFifthStreet", but I want the actual display name of the character to be "Bob", then I can do...
var statement = DialogueStatement.new()
data.speakers = { "Bob": "BobOnFifthStreet" }
- Say - standard line/s of dialog - just have arguments:
label,text,who: [array, of, characters]
What is 'label' in this context?
var sequence = DialogueSequence.new([
Say.new("Hello.", { "Bob": "BobOnFifthStreet" })
]
- Ask - Question form speaker that waits for player keyboard input, base on Say
var sequence = DialogueSequence.new()
var ask = DialogueAsk.new()
ask.text = "What's your name?"
# add configurable support to implicitly convert values to bindings
# so "Professor Oak" might become {"Professor Oak": "professorOak"} or
# {"Professor Oak": "professor_oak"}, etc.
ask.speakers = "Professor Oak"
sequence.add_statement(ask)
...
ask.input = "RED" # triggers an `input_changed` signal
- Choice - Question form speaker that waits for player to choose form given options, base on Say, add one argument "choices": dictionary
{"text_of_choice" : dialog_label_to_go}
This is where things get really complicated. I want to have the option of supporting a C++ implementation of the Ink runtime (because Ink is an extremely powerful narrative scripting language that is used by many professional game studios), and the breadth and depth of options it gives users will more than cover the vast majority of features that most other systems need - so we should try to make it possible to support all of its API imo.
As far as Ink is concerned, there are a plethora of features:
- you can do inlined conditional expressions with endless sub-choices.
- some choices get "consumed" by being chosen, so they don't show up again.
- others have a visitation count where they are consumed once visited a number of times and, with each visit, individual string segments rotate between a pool of strings.
- Each choice can redirect the user to a particularly labeled paragraph which can even be namespaced, hence my desire to support namespaced "global" variables and references to vertices/arcs.
For detailed examples on all of this stuff, see their writer's documentation. It's really impressive stuff. The "say" and "ask", if they even support that, are all really just simplified versions of an underlying, highly complex and powerful Choice API
- Notify - just to do something like "Sam will remember this" - I don't know if we need this one
This is basically just going to be us having a single signal to emit for a dialogue sequence or for Journey as a whole or something. And where applicable, someone will be able to emit a signal with a Dictionary of user-defined data. The user will have to be the one to decide what information gets emitted though, and when. Will have to figure out a clean API for that.
What is 'label' in this context? You wrote this before: Users should be able to bind labels, properties, and actions to any single dialogue node.
O, this Ink is advanced.
So if we want have similar advanced choices I think that we need Choice class.
example:
var ch := Choice.new()
ch.label = "text to show player"
ch.dialog = place_to_jump_in_dialog
ch.display = [
"text to display if ch.dialog is null",
"if ch.rotate this will sometimes displayed"
]
ch.disable_after_chosen = false
ch.rotate = false
Then it will be choose([array, of, choices]).
@jebedaia360
What is 'label' in this context?
This was in reference to the underlying graph data structure that has, according to the Core API, Labels which tag any given StoryItem (0+) or Relationship (1). So, if a line of dialogue mentions a character in the story database, I want the database to pick up on that and keep track of that line of dialogue (so I can later go, "what is all the dialogue related to this item?" or, "what items does this line of dialogue reference?"
O, this Ink is advanced. So if we want have similar advanced choices I think that we need Choice class.
We will need some kind of Builder pattern to piece together a Statement from scattered segments since all sentences could potentially be pieced together from segments using conditional logic, nest branching structures, user text input, user choice selections, etc. Say would just be a simplified version of the same underlying class as Choice but which simply doesn't take advantage of its extra features.
It may also be a good idea to examine the C# runtime API for Ink to get an idea of how it actually parses and builds up its backend data.
O, know C# good I think - I must use unity at my work. I will try look it too it today. Can we some how use it this project - godot support C#, but how with calling it from GDScript?