Advanced Dialogue System + Node Editor

@Sol_ttu

I’ve found a critical flaw in the way you’ve implemented conditionals in this dialog system that makes it impossible to have conditions that are not pure functions (such as a condition that randomly selects the next prompt). It’s been causing a strange softlock in our game, Crown Academy, that I’ve been unable to pinpoint until I saw @XYZLambda’s reply.

The issue stems from the fact that conditions are treated as inputs to a node indicating whether it should run or not, and not as part of the main pathway that actually chooses the next node in the sequence.

Say you have a setup like @XYZLambda has:

image

If the condition’s Run function looks like this:

function Condition.Run()
	return math.random(1, 2)
end

Then you are opening yourself up to a potential softlock. Let’s run through the tree and see why:

  1. We find the DialogueRoot of this tree and see it has 2 outputs: Greetings and Prompt
  2. We traverse to Greetings, see it has a condition, and run the condition logic. Let’s say it returns 2. That doesn’t match the priority of Greetings, so we don’t run node Greetings
  3. We traverse backwards and try node Prompt, see it has a condition, and run the condition logic. Let’s say this time it returns 1. This again doesn’t match the priority of our node, 2, so we don’t run node Prompt either.
  4. We now softlock because we have no node to run.

This is also incredibly inefficient because you may end up running your condition logic n times, where n is the number of nodes you have, instead of just once. Not to mention it also clutters the node editor and makes the tree more difficult to follow.

So let’s run through the tree with alternative condition behavior, where nodes can output to a condition, the condition will run some logic, then select the next node in the pathway based on the result.

  1. We find the DialogueRoot of this tree and see it has 1 output: Condition
  2. We run the condition logic and it returns 1. We select the first output of Condition that matches this priority (Greetings).
  3. We run node Greetings.

Much better!

I was originally wary of the way you implemented conditionals with this system but overlooked it because I couldn’t figure out why. Now I realize why, and would recommend updating your system.

3 Likes

That is most likely a bug. I’ll try to get it fixed whenever I have time. In the meantime, I recommend not relying on that node setup unless you want to edit the code by hand or find some “hacky” solution. I aim do some big patch sometime in the future to make the entire system more robust. Thanks for bringing this to my attention!

1 Like

This is indeed a major flaw and something I definitely overlooked when I was developing the system. Looks like I gotta squeeze the development of the patch to this week’s schedule. I will be updating this post and leaving a patch note hopefully sometime this week.

Thank you for bringing this to my attention and for providing me a detailed explanation.

1 Like

you’re welcome, and take your time. I can’t do anything at the moment anyways, I broke my arm lol.

Actually, it turns out the fix for this is really simple. All it requires is removal of the CheckForCondition function and the addition of this code into the LoadNode function’s type checking logic:

elseif Type == "Condition" then
	local Module = require(Node:FindFirstChildWhichIsA("ModuleScript"))
	local Result: number = Module.Run()
	
	for _, OutputNode in pairs(GetOutputNodes(Node)) do
		local Priority = OutputNode:GetAttribute("Priority")
		if Priority == Result then
			self:LoadNode(OutputNode)
			break
		end
	end

The only other things it’ll require is changing the default condition type to accept inputs and adjusting existing dialog trees to work with the new condition structure.

1 Like

Glad you found a fix for it already, I figured it would be pretty straight forward.

Now that I’ve slowly picked up my development pace again, I think it’s time I give this entire system another look. That would mean some sort of rewrite of the system without changing the general behavior of the nodes to allow for more flexibility, clarity, and to just make the entire thing more robust. Also doing some quality of life changes like adding the input path for the condition node (Which btw was originally planned but scrapped for reasons I don’t remember)

There’s just one problem: I assume you guys have edited the code quite a bit so changing most of- if not all of it would be quite a hassle for you guys. So before I start working on any rewrites, I would like to hear what kind of changes you guys have made and if you’d like me to make them native for the system.

1 Like

We actually didn’t change the core of it all that much since we found it to be mostly complete for what we wanted. Among the things we added were the ability to skip the typewriter animation with arbitrary input and having text prompts wait for user interaction before moving onto the next one (meaning we can have two back-to-back text prompts that aren’t separated by responses, something the base system does not support very well).

If you are considering rewriting the whole system again, I would suggest a couple things:

  • Try to make the system as UI-agnostic as possible. This system should ideally only handle the internal logic of traversing through the dialog tree. Users of the system should be able to put whatever UI they want on top of it without having to go through and edit the internals. This could be done most simply with events.
  • Move the node editor into a dock widget instead of it being rendered on top of the main viewport.
  • Polish up the node editor a bit more. Allow selecting and dragging multiple nodes at once (not having this makes reorganizing trees a pain), align nodes to a grid so they aren’t just everywhere, improve zoom and pan behavior, and improve the inconsistent node connecting behavior.
  • Integrate the node presets directly into the editor and add a widget to customize them instead of littering every single one of my places with an empty “NodePresets” folder.
3 Likes

Lookin spicy, I’d love to try this in my free time.

Noted.

Also you should be able to drag multiple nodes in the editor. If I remember correctly, you need to hold either CTRL or Shift to do that.

How do I change the Speaker from within a Function node?

This is great!

However, I would recommend you make an uncopylocked place demonstrating how the system works, that way people can easily pick up on how it works.

is there a way to control how long a prompt would stay on the screen before vanishing, if there is a long prompt at the end, or a string of prompts, they transition too fast, forcing you to read fast.

There is no native feature to change it per prompt but that shouldn’t be hard to add. There should be a wait function somewhere in the script after all of the text has appeared. If you find that you can easily change it to whatever you want. I can try and make a quick edit of the script and send it your way sometime tomorrow.

1 Like

ill add it myself, i can send an edited version of it back if you want to look at it and fully add it, then again not too hard, ill probably make it read from the prompt itself via attribute, as it is already
Edit: was really easy lol, i move the wait time intot he if prompt, and it reads from a value inside the folder, it even shows up in your little plugin ui, so, nice and easy to change.
SmolChange.rbxm (16.0 KB)

I updated the test one to include it, nothing else was changed
Check line 257 to see what I did

.1 is instant, but I set it to 1 in there, feel free to mess with it.
If you do use it, don’t care if you add a thanks or not, was a simple mod lol.

hey man, does this still work? I’ve been trying to do this, but I can’t seem to get it to work.
Don’t get any errors.

But when I do “The time is {dayTime}”. It shows it as “The time is {dayTime}”.
Same with {playerName}

Any idea why?

I haven’t used this plugin in a long time since I switched to the other dialogue editor, but I’m fairly certain my addition works I can’t be too certain since its been updated since I posted.

Ah, I see. I’m trying to find the problem with me and it fails here:

if s and e then
 --Doesn't reach this
	local before = string.sub(textValue,1,s-1)
	local middle
	if typeof(val) == 'function' then
		local ret = val()
		middle = before..""..ret
	else
		middle = before..""..val
	end
	local after = middle..string.sub(textValue,e+1,string.len(textValue))
	textValue = after
end

maybe I can figure it out.

edit: Figured it out.

add

v.Value = textValue

after

local after = middle..string.sub(textValue,e+1,string.len(textValue))
textValue = after

so

local after = middle..string.sub(textValue,e+1,string.len(textValue))
textValue = after
v.Value = textValue
1 Like

Hi, is there a way to implement colored text? (Like a custom rich text system where you can do “{Color: Red} text {Color=/}” or smth like that)

This has really aided in my game’s workflow. I planned to make a basic dialogue system but with this I can really do a lot more in terms of secret dialogue, quest systems, cutscenes, etc etc. Thank you, friend.
Here is what managed to create with this system you made.

3 Likes

I’m trying to capture the player’s response when a button is activated, but when i loop through the responses it captures the first response twice, and the second response once. It never captures the third response text.

This code is inside a conditional linked to the three responses… it always works with the first two just not the third. and ideas on how to fix this? I also tried it in a command prompt but .Activated didnt work at all there. plz help thank youu

local MainFrame = Player.PlayerGui.DialogueUI.MainFrame
local ResponseFrame = MainFrame.ResponseFrame
local responses = ResponseFrame:GetChildren()

for index, item in pairs(responses) do
		if item:IsA("TextButton") and item.Name ~= "SampleResponse" then
			print(item.Text)
			item.Activated:Connect(function()
			       -- code stuff here 
                  -- (last response always never captured/activated)
			end)
		end
	end

Output should be: Bread Stew Cheese