Open ThePat02 opened 3 weeks ago
Hey @ThePat02 . Not sure what's your game's structure, but here is how I went about loading game data in my game.
I haven't played Disco Elysium yet so I'm not sure exactly how the flow goes. In my game I initialize the dialogue in two steps: first I load the persisted internal dialogue data, and then I load all relevant global variables.
I'd start like this for the internal data:
var dialogue = ClydeDialogue.new()
dialogue.load_dialogue('my_dialogue')
dialogue.load_data(persisted_dialogue_data)
Then load the globals like this:
# game data is a dictionary that has the variables/flags
for v in game_data:
dialogue.set_external_variable(v, game_data[v])
After that I started the dialogue normally dialogue.start()
. However, set_external_variable
can be called at any time, so in some cases I'd set variables based on events. For example, inside my dialogue I could have an event like: { trigger job_accepted }
. And then in my event callback I'd do:
func _on_dialogue_event_triggered(event_name: String):
if event_name == "job_accepted":
# here I load any other information I need. For example, maybe accepting
# the job generates some random prize and you want to share that:
var reward = randi_range(10, 100) # this would be persisted somewhere
dialogue.set_external_variable("reward", reward)
Again, this will vary depending on how you structure your game, but if you always want your dialogues to start with all the global variables available in your game, your best bet is creating a wrapper around it that initializes like I did in the first two blocks.
So you use the same persisted_dialogue_data
resource for all your dialogues?
I haven't played Disco Elysium yet
In Disco Elyisum there are a few different type of dialogue lines that can appear:
For example this line
Kim Kitsuragi - Please don't kick this trash can again.
will appear if you have kicked the trash can before. This is probably done by checking a flag trash_can_kicked
or something similar. This other line can appear depending on how you built your character as
Logic [Hard: Failure] - You cannot determine any structure behind the action of the murderer.
or
Logic [Hard: Success] - The murderer is operating with a pattern. Logic - He uses the git reset command as a weapon to kill programmers. Logic - You should watch out too!
What would be the best approach to built similar dynamic systems with Clyde?
You can do this in few different ways. Not sure what's the best for your game as I don't know how you are structuring stuff, but here is how I think I'd go about it.
So you use the same persisted_dialogue_data resource for all your dialogues?
No, I keep one per .clyde
file. This data contains internal information like options accessed and variations so these features remember previous choices. They are dialogue file specific.
For the internal dialogue data, I'd keep a dictionary that is updated when the dialogue ends. This is what I do in my helpers example (https://github.com/viniciusgerevini/godot-clyde-dialogue/blob/godot_4/addons/clyde/examples/examples_with_helpers/fixed_bubble/clyde_dialogue_config.gd), but using the original modules, here is an example:
Wherever you start you dialoguer you do this:
var dialogue = ClydeDialogue.new()
dialogue.load_dialogue(dialogue_name)
dialogue.load_data(persistence.dialogue_data[dialogue_name])
When the dialogue ends you do this:
var content = dialogue.get_content()
if content.type == "end":
persistence.dialogue_data[dialogue_name] = dialogue.get_data()
In this example I'm assuming you have a global module (persistence) where you store game data, which are persisted when you save the game. That should be enough to guarantee that variations and options will work correctly in all your dialogues regardless how you organise them.
For the flags and game data you described, I believe the best thing is to keep your game data as the source of truth, storing it outside clyde. You can keep that data in sync by using the external_variable_changed
signal from clyde in case you change them inside the dialogue.
The things you know before starting the dialogue can be loaded like I showed you before:
for v in game_data.skills:
dialogue.set_external_variable(v, game_data[v])
If something changes externally during the dialogue you can also use dialogue.set_external_variable
to update the information.
That's how you would let the dialogue know that trash_can_kicked
happened or that the character has a given skill.
Hopefully this doesn't sound too vague. I need to play the game to understand this mechanics as this is not the first time people ask me something about it :D
If I understand correctly now, there is no way to query outside data directly. You need to load everything inside the dialogue before starting?
there is no way to query outside data directly
That is correct. Not as of now. I was playing with the idea of providing a data fetching callback for external variables, but I didn't go ahead with it. I'll revisit the idea
You need to load everything inside the dialogue before starting?
No necessarily before starting, but before you reach the content that uses it.
Your question made me see that this can be improved indeed. When I implemented external variables it was mostly to solve the issue of duplicated data being stored, but the data fetching aspect is lacking. I'll look into it next. But as of today things need to be loaded prior to the usage.
Can you point me into direction of what would need to be changed in order to access data without preloading first. Maybe can modify Clyde for my personal use.
I've already started working on it. This branch has an working implementation: https://github.com/viniciusgerevini/godot-clyde-dialogue/tree/external_variable_fetching
You can use it or fork from it.
I removed the methods to deal with external variables and now they are just a proxy for external data.
You can find usage examples here: https://github.com/viniciusgerevini/godot-clyde-dialogue/blob/external_variable_fetching/addons/clyde/examples/simple_example/example.gd#L21-L22
Or in the usage docs: https://github.com/viniciusgerevini/godot-clyde-dialogue/blob/external_variable_fetching/USAGE.md#external-variables
The idea is that when setting up the dialogue you can set two methods to proxy external data requests. Like this:
_dialogue.on_external_variable_fetch(func (variable_name: String):
return persistence.get(variable_name)
)
_dialogue.on_external_variable_update(func (variable_name: String, value):
persistence.set(variable_name, value)
)
With this, if you do something like:
Kim Kitsuragi: Please don't kick this trash can again. { @trash_can_kicked }
The value from persistence["trash_can_kicked"]
will be used.
My examples always use dictionaries returning the actual value, but it doesn't have to be like this. Using a "skills" scenario for instance, instead of exposing skills as a string it's possible to implement checks like this:
_dialogue.on_external_variable_fetch(func (variable_name: String):
if variable_name.begins_with("has_skill_"):
return _check_if_player_has_skill(variable_name)
return persistence.get(variable_name)
)
And in your dialogue you do something like:
Can you fix this for me? { @has_skill_hacker }
I think this provides the foundation blocks for runtime data retrieval. I still need to make it work in the editor player, but this implementation should work already in-game.
This looks like a great addition! It is a really easy and customizable solution! I like it!
What is the best way to handle a big amount of flags in a game that has a similar structure as DISCO ELYSIUM? Is there a way to set variables without loading them into the Resource first?