GameMaker Stuck on the logic of a branching dialogue system

Yizzard

Member
Hey, so I've been working on a branching dialogue system for a while now, and I just can't seem to figure some stuff out. It currently works basically by having an invisible object spawn in the room that in it's creation code sets all of the NPCs in the room's text, then the NPC creates a dialogue box when interacted with and the box itself prints out the text using custom fonts and whatnot until reaching the end of the message then let's you choose an option and responds. The problem I'm having is that I don't know how to standardize it to where no matter how many branches I have it still works, for example, this is the creation code for a few test NPCs:

Code:
obj_GenericNPC_John.myText[0] = "Hello! We're outside now! I'm not a vampire, so I can survive the sun! Btw, I hate your dog.";
obj_GenericNPC_John.optionTxt[0, 0] = "Well I hate your dog!";
obj_GenericNPC_John.optionTxt[0, 1] = "I'm sorry to hear that";
obj_GenericNPC_John.optionTxt[0, 2] = "Sadface";
obj_GenericNPC_John.response[0, 0] = "Oh yeah? Well... I hate your dog! Your dog sucks just as much, if not more than mine!";
obj_GenericNPC_John.response[0, 1] = "Well I truly am very sorry to hear that, sir.";
obj_GenericNPC_John.response[0, 2] = "Why would you say that to me? That makes me truly sad. :(";
obj_GenericNPC_John.canChoose = 1;

obj_Naldi.myText[0] = "Hello John! Your dog sucks. Please get rid of it. I do not enjoy its presence";
obj_Naldi.optionTxt[0, 0] = "I will";
obj_Naldi.optionTxt[0, 1] = "No";
obj_Naldi.response[0, 0] = "You're right... My dogo really does suck... I will get rid of it at once!";
obj_Naldi.response[0, 1] = "No, it's my dog, I can keep it if I want to!";
obj_Naldi.myText[1] = "Well whether or not you get rid of this dog doesn't really matter, all that matters is that you like my dog. Youd do like my dog, right?";
obj_Naldi.optionTxt[1, 0] = "Well...";
obj_Naldi.optionTxt[1, 1] = "I do";
obj_Naldi.response[1, 0] = "Well... I actually really hate your dog and think it is more of a monstrosity than my dog.";
obj_Naldi.response[1, 1] = "Don't worry, I love your dog.";
obj_Naldi.myText[2] = "Alright. I understand";
obj_Naldi.canChoose = 2;

obj_GenericNPC_John2.myText[0] = "John... Hey... John... Sabrina is secretly...";
obj_GenericNPC_John2.optionTxt[0, 0] = "Secretly...?";
obj_GenericNPC_John2.optionTxt[0, 1] = "Shut up, clone.";
obj_GenericNPC_John2.response[0, 0] = "Secretly... What exactly?";
obj_GenericNPC_John2.response[0, 1] = "I don't have time for your tricks, clone!";
obj_GenericNPC_John2.myText[1] = "She's an eveil Watch!?!?!!!";
obj_GenericNPC_John2.optionTxt[1, 0] = "Oh no!";
obj_GenericNPC_John2.optionTxt[1, 1] = "Why do I talk to you?";
obj_GenericNPC_John2.response[1, 0] = "This is horrible news! What should we do?!?";
obj_GenericNPC_John2.response[1, 1] = "Why do I even talk to you...";
obj_GenericNPC_John2.myText[2] = "WE MUST BURN HER!";
obj_GenericNPC_John2.optionTxt[2, 0] = "Yes! We definately must!";
obj_GenericNPC_John2.optionTxt[2, 1] = "We must burn you!";
obj_GenericNPC_John2.response[2, 0] = "Yes! We will set the burning for tomorrow evening at 5:30 P.M. Eastern Standard Time.";
obj_GenericNPC_John2.response[2, 1] = "No... We must burn you instead! Muhahahaha!";
obj_GenericNPC_John2.canChoose = 3;
myText shows what the NPC says, optionTxt is what pops up that you can click on (Is usually a shortened version of response) and response is what John actually says back to the NPC. The above code all works perfectly correctly as you would expect, choosing option 0 makes John say response 0 and then triggers the next myText. The problem is, I don't know how to make the myText change depending on what John said and change the options depending on that, in other words, I can't figure out how to make the previous choices affect the current choices. I would really like to make it work similarly to this:

Code:
test.myText = "First line";
test.optionTxt[0] = "Opt 0";
test.optionTxt[1] = "Opt 1";
test.response[0] = "Opt 0 Text";
test.response[1] = "Opt 1 Text";



test.myText[0] = "Opt 0 Line";
test.optionTxt[0, 0] = "Opt 0 0";
test.optionTxt[0, 1] = "Opt 0 1";
test.response[0, 0] = "Opt 0 0 Text";
test.response[0, 1] = "Opt 0 1 Text";

test.myText[1] = "Opt 1 Line";
test.optionTxt[1, 0] = "Opt 1 0";
test.optionTxt[1, 1] = "Opt 1 1";
test.response[1, 0] = "Opt 1 0 Text";
test.response[1, 1] = "Opt 1 1 Text";



text.myText[0, 0] = "Opt 0 0 Line";
test.optionTxt[0, 0, 0] = "Opt 0 0 0";
test.optionTxt[0, 0, 1] = "Opt 0 0 1";
test.response[0, 0, 0] = "Opt 0 0 0 Text";
test.response[0, 0, 1] = "Opt 0 0 1 Text";

test.myText[0, 1] = "Opt 0 1 Line";
test.optionTxt[0, 1, 0] = "Opt 0 1 0";
test.optionTxt[0, 1, 1] = "Opt 0 1 1";
test.response[0, 1, 0] = "Opt 0 1 0 Text";
test.response[0, 1, 1] = "Opt 0 1 1 Text";

text.myText[1, 0] = "Opt 1 0 Line";
test.optionTxt[1, 0, 0] = "Opt 0 0 0";
test.optionTxt[1, 0, 1] = "Opt 0 0 1";
test.response[1, 0, 0] = "Opt 0 0 0 Text";
test.response[1, 0, 1] = "Opt 0 0 1 Text";

test.myText[1, 1] = "Opt 1 1 Line";
test.optionTxt[1, 1, 0] = "Opt 0 1 0";
test.optionTxt[1, 1, 1] = "Opt 0 1 1";
test.response[1, 1, 0] = "Opt 0 1 0 Text";
test.response[1, 1, 1] = "Opt 0 1 1 Text";



text.myText[0, 0, 0] = "Opt 0 0 0 Line";
... (etc.)
Obviously that doesn't work because I'm making the same arrays be a string, an array, a 2D array, a 3D array, ...(etc.) all at the same time, which as far as I'm aware, isn't possible. Any info you could give me on how I could go about with the logic on this would be greatly appreciated. And if you have any further questions, please ask! (I could record a little video demonstrating how it works if that would help at all or show you some more of the code from the text box/NPC's if you need, etc.)

Thanks for the help!
 
T

Tom Jackson

Guest
Forgive me if I've misunderstood what exactly you're after with your system here but couldn't you just make the initial myText array a 3d one with [0, 0] then continue in a similar fashion from there?
So:
Code:
obj_Naldi.myText[0, 0] = "Hello John! Your dog sucks. Please get rid of it. I do not enjoy its presence";
obj_Naldi.optionTxt[0, 0] = "I will";
obj_Naldi.optionTxt[0, 1] = "No";
obj_Naldi.response[0, 0] = "You're right... My dogo really does suck... I will get rid of it at once!";
obj_Naldi.response[0, 1] = "No, it's my dog, I can keep it if I want to!";
obj_Naldi.myText[1, 0] = "Well whether or not you get rid of this dog doesn't really matter, all that matters is that you like my dog. Youd do like my dog, right?";
obj_Naldi.myText[1, 1] = "Put your alt dialogue here";
Then modify your dialogue executing script to alter the values after the first response instead of after myText?
 
Last edited by a moderator:
C

CedSharp

Guest
There are no 3D arrays in GameMaker. You'll have to use "Arrays of Arrays" instead.

You'll have to build yourself the equivalent of a timeline, but for text. so a "textline" :p

First, you might want to draft the dialog before you code it. every "text" and every "question" should have an id. So make a table similar to this on a paper or in a text file:
Code:
0  {
  Type: Branch
  Text: "Hello! We're outside now! I'm not a vampire, so I can survive the sun! Btw, I hate your dog."
  Choices: [
    "Well I hate your dog!"
        => "Oh yeah? Well... I hate your dog! Your dog sucks just as much, if not more than mine!"
        => Goto ID: 1
    "I'm sorry to hear that"
        => "Well I truly am very sorry to hear that, sir."
        => Goto ID: 2
    "sadface"
        => "Why would you say that to me? That makes me truly sad. :("
        => Goto ID: 3
  ]
}

1 {
    Type: Message
    Text: "You chose 'Well I hate your dog!'"
}

2 {
    Type: Message
    Text: "You chose 'I'm sorry to hear that'"
}

3 {
    Type: Message
    Text: "You chose 'sadface'"
}
Once you have your draft setup, you'll see a pattern. Something that repeats.
In this case, I have 2 "types" of messages: Normal Messages, and "Branch/Questions".
Note that for each options of a branch, I have a "target message", aka target ID.

Now, every indentation is a "new array". So the above would look like this:
Code:
// For a choice, First index is the choice number,
// Second index is: 0 = Button Text, 1 = Displayed Response, 2 = Target ID
var choices_dog_insult;
choice_hate_dog[0, 0] = "Well I hate your dog!";
choice_hate_dog[0, 1] = "Oh yeah? Well... I hate your dog! Your dog sucks just as much, if not more than mine!";
choice_hate_dog[0, 2] = 1;
choice_sorry[1, 0] = "I'm sorry to hear that";
choice_sorry[1, 1] = "Well I truly am very sorry to hear that, sir.";
choice_sorry[1, 2] = 2;
choice_sadface[2, 0] = "sadface";
choice_sadface[2, 1] = "Why would you say that to me? That makes me truly sad. :(";
choice_sadface[2, 2] = 3;

// For a branch, 0 = Type, 1 = Text, 2 = Choices
var branch_dog_insult;
branch_dog_insult[0] = "Question";
branch_dog_insult[1] = "Hello! We're outside now! I'm not a vampire, so I can survive the sun! Btw, I hate your dog.";
branch_dog_insult[2] = choices_dog_insult;

// For a message, 0 = Type, 1 = Text
var message_dog_insult_option_hate_dog;
message_dog_insult_option_hate_dog[0] = "Message";
message_dog_insult_option_hate_dog[1] = "You chose 'Well I hate your dog!'";

// etc for the rest of the messages/branch

// Finally, we assign each messages, in order, to the object
obj_GenericNPC_John.messages[0] = branch_dog_insult;
obj_GenericNPC_John.messages[1] = message_dog_insult_option_hate_dog;

// And you continue with the whole dialog
// obj_GenericNPC_John.messages[2] = message_dog_insult_option_sorry;
// obj_GenericNPC_John.messages[3] = message_dog_insult_option_sadface;
And then, I don't think I need to explain that you now have an "ID" that tells you which message to display next after a question ;)

Hope this helps you.
 

Yizzard

Member
Forgive me if I've misunderstood what exactly you're after with your system here but couldn't you just make the initial myText array a 3d one with [0, 0] then continue in a similar fashion from there?
So:
Code:
obj_Naldi.myText[0, 0] = "Hello John! Your dog sucks. Please get rid of it. I do not enjoy its presence";
obj_Naldi.optionTxt[0, 0] = "I will";
obj_Naldi.optionTxt[0, 1] = "No";
obj_Naldi.response[0, 0] = "You're right... My dogo really does suck... I will get rid of it at once!";
obj_Naldi.response[0, 1] = "No, it's my dog, I can keep it if I want to!";
obj_Naldi.myText[1, 0] = "Well whether or not you get rid of this dog doesn't really matter, all that matters is that you like my dog. Youd do like my dog, right?";
obj_Naldi.myText[1, 1] = "Put your alt dialogue here";
Then modify your dialogue executing script to alter the values after the first response instead of after myText?
The problem I'm seeing with this solution is that it still doesn't allow true branching, for example if we were to continue this formula we would have
Code:
obj_Naldi.myText[0, 0] = "Hello John! Your dog sucks. Please get rid of it. I do not enjoy its presence";
obj_Naldi.optionTxt[0, 0] = "I will";
obj_Naldi.optionTxt[0, 1] = "No";
obj_Naldi.response[0, 0] = "You're right... My dogo really does suck... I will get rid of it at once!";
obj_Naldi.response[0, 1] = "No, it's my dog, I can keep it if I want to!";
obj_Naldi.myText[1, 0] = "Aww! Thanks John!";
obj_Naldi.myText[1, 1] = "Well whether or not you get rid of this dog doesn't really matter, all that matters is that you like my dog. Youd do like my dog, right?";
obj_Naldi.optionTxt[1,0] = "Well...";
obj_Naldi.optionTxt[1, 1] = "I do";
obj_Naldi.response[1, 0] = "Well... I actually really hate your dog and think it is more of a monstrosity than my dog.";
obj_Naldi.response[1, 1] = "Don't worry, I love your dog.";
obj_Naldi.myText[2, 0] = "Good. I'm glad.";
obj_Naldi.myText[2, 1] = "Alright. I understand";
Which is a problem because if you choose "I will" then Naldi responds with "Aww! Thanks John!" And then you are prompted with the choices of "Well..." or "I do" which doesn't make sense, what I want is for there to be a way for John to have a completely separate set of options for each choice from the first question so that maybe if you choose "I will" Naldi will say "Thanks, that means a lot" and you can respond with "You're welcome" or "I didn't do it for you, you just made a good point" or something, whereas choosing "No" will give the the original options. What makes this even more difficult is I want the system to be able to handle any number of options from 0 (Meaning it's just an NPC who says something and you can't respond to it) to as many options as I want, each additional option will be exponentially more difficult as if each option happens to have 2 choices each and there are three choices, then the first choice will have two options then each of those two will have two options then each of those four will have two options so in total there would be 8 options (having two options per choice isn't necessarily true for each choice though.) Does that make more sense?
 

Yizzard

Member
There are no 3D arrays in GameMaker. You'll have to use "Arrays of Arrays" instead.

You'll have to build yourself the equivalent of a timeline, but for text. so a "textline" :p

First, you might want to draft the dialog before you code it. every "text" and every "question" should have an id. So make a table similar to this on a paper or in a text file:
Code:
0  {
  Type: Branch
  Text: "Hello! We're outside now! I'm not a vampire, so I can survive the sun! Btw, I hate your dog."
  Choices: [
    "Well I hate your dog!"
        => "Oh yeah? Well... I hate your dog! Your dog sucks just as much, if not more than mine!"
        => Goto ID: 1
    "I'm sorry to hear that"
        => "Well I truly am very sorry to hear that, sir."
        => Goto ID: 2
    "sadface"
        => "Why would you say that to me? That makes me truly sad. :("
        => Goto ID: 3
  ]
}

1 {
    Type: Message
    Text: "You chose 'Well I hate your dog!'"
}

2 {
    Type: Message
    Text: "You chose 'I'm sorry to hear that'"
}

3 {
    Type: Message
    Text: "You chose 'sadface'"
}
Once you have your draft setup, you'll see a pattern. Something that repeats.
In this case, I have 2 "types" of messages: Normal Messages, and "Branch/Questions".
Note that for each options of a branch, I have a "target message", aka target ID.

Now, every indentation is a "new array". So the above would look like this:
Code:
// For a choice, First index is the choice number,
// Second index is: 0 = Button Text, 1 = Displayed Response, 2 = Target ID
var choices_dog_insult;
choice_hate_dog[0, 0] = "Well I hate your dog!";
choice_hate_dog[0, 1] = "Oh yeah? Well... I hate your dog! Your dog sucks just as much, if not more than mine!";
choice_hate_dog[0, 2] = 1;
choice_sorry[1, 0] = "I'm sorry to hear that";
choice_sorry[1, 1] = "Well I truly am very sorry to hear that, sir.";
choice_sorry[1, 2] = 2;
choice_sadface[2, 0] = "sadface";
choice_sadface[2, 1] = "Why would you say that to me? That makes me truly sad. :(";
choice_sadface[2, 2] = 3;

// For a branch, 0 = Type, 1 = Text, 2 = Choices
var branch_dog_insult;
branch_dog_insult[0] = "Question";
branch_dog_insult[1] = "Hello! We're outside now! I'm not a vampire, so I can survive the sun! Btw, I hate your dog.";
branch_dog_insult[2] = choices_dog_insult;

// For a message, 0 = Type, 1 = Text
var message_dog_insult_option_hate_dog;
message_dog_insult_option_hate_dog[0] = "Message";
message_dog_insult_option_hate_dog[1] = "You chose 'Well I hate your dog!'";

// etc for the rest of the messages/branch

// Finally, we assign each messages, in order, to the object
obj_GenericNPC_John.messages[0] = branch_dog_insult;
obj_GenericNPC_John.messages[1] = message_dog_insult_option_hate_dog;

// And you continue with the whole dialog
// obj_GenericNPC_John.messages[2] = message_dog_insult_option_sorry;
// obj_GenericNPC_John.messages[3] = message_dog_insult_option_sadface;
And then, I don't think I need to explain that you now have an "ID" that tells you which message to display next after a question ;)

Hope this helps you.
And for this solution, It does seem like this could work, I would have to do some heavy remodeling on my dialogue system for this to work, but I mostly expected that. The problem I have with this though, is that it get's very complicated very quickly. My entire purpose for doing this was to try to make this as easy to program in the dialogue trees as possible. I went through and used your example for this to make Naldi's code and it got pretty difficult to keep track of and was very long, here's what I made it into:

Code:
// For a choice, First index is the choice number,
// Second index is: 0 = Button Text, 1 = Displayed Response, 2 = Target ID
var choices_dog_insult;
choices_dog_insult[0, 0] = "I will";
choices_dog_insult[0, 1] = "You're right... My dogo really does suck... I will get rid of it at once!";
choices_dog_insult[0, 2] = 1;
choices_dog_insult[1, 0] = "No";
choices_dog_insult[1, 1] = "No, it's my dog, I can keep it if I want to!";
choices_dog_insult[1, 2] = 2;

var choices_agree;
choices_agree[0, 0] = "You're welcome";
choices_agree[0, 1] = "You're very welcome Naldi.";
choices_agree[0, 2] = 3;
choices_agree[1, 0] = "I didn't do it for you";
choices_agree[1, 1] = "I didn't do it for you, you just made a good point";
choices_agree[1, 2] = 4;

var choices_decline;
choices_decline[0, 0] = "Well...";
choices_decline[0, 1] = "Well... I actually really hate your dog and think it is more of a monstrosity than my dog.";
choices_decline[0, 2] = 5;
choices_decline[1, 0] = "I do";
choices_decline[1, 1] = "Don't worry, I love your dog.";
choices_decline[1, 2] = 6;

// For a branch, 0 = Type, 1 = Text, 2 = Choices
var branch_dog_insult;
branch_dog_insult[0] = "Question";
branch_dog_insult[1] = "Hello John! Your dog sucks. Please get rid of it. I do not enjoy its presence";
branch_dog_insult[2] = choices_dog_insult;

var branch_agree;
branch_agree[0] = "Question";
branch_agree[1] = "Thanks, that means a lot";
branch_agree[2] = choices_agree;

var branch_decline;
branch_decline[0] = "Question";
branch_decline[1] = "I suppose it doesn't really matter if you get rid of it or not, but you do at least like my dog right?"
branch_decline[2] = choices_decline;

// For a message, 0 = Type, 1 = Text
var message_dog_insult_option_welcome;
message_dog_insult_option_welcome[0] = "Message";
message_dog_insult_option_welcome[1] = "You're the best John";

var message_dog_insult_option_for_you;
message_dog_insult_option_for_you[0] = "Message";
message_dog_insult_option_for_you[1] = "Huh! Well I never!";

var message_dog_insult_option_well;
message_dog_insult_option_well[0] = "Message";
message_dog_insult_option_well[1] = "I can't even believe it... How could you?!?";

var message_dog_insult_option_yes;
message_dog_insult_option_yes[0] = "Message";
message_dog_insult_option_yes[1] = "Well that's at least good to know.";

// Finally, we assign each messages, in order, to the object
obj_GenericNPC_John.messages[0] = branch_dog_insult;
obj_GenericNPC_John.messages[1] = branch_agree;
obj_GenericNPC_John.messages[2] = branch_decline;
obj_GenericNPC_John.messages[3] = message_dog_insult_option_welcome;
obj_GenericNPC_John.messages[4] = message_dog_insult_option_for_you;
obj_GenericNPC_John.messages[5] = message_dog_insult_option_well;
obj_GenericNPC_John.messages[6] = message_dog_insult_option_yes;
That was only for 2 choices each with two options. Throughout the game I may get to points where I have four or five choices with two to four options each. That would be a nightmare. I did have another idea though, but I'm not sure if it would be much better, what if I just made the array a "5D" array:
obj_GenericNPC_John.optionTxt[0, 0, 0, 0, 0] = "Well I hate your dog!";
and basically do the thing I did in the second coding box in my first post:

Code:
test.myText[0, 0, 0, 0, 0] = "First line";
test.optionTxt[0, 0, 0, 0, 1] = "Opt 1";
test.optionTxt[0, 0, 0, 0, 2] = "Opt 2";
test.response[0, 0, 0, 0, 1] = "Opt 1 Text";
test.response[0, 0, 0, 0, 2] = "Opt 2 Text";



test.myText[0, 0, 0, 0, 1] = "Opt 1 Line";
test.optionTxt[0, 0, 0, 1, 1] = "Opt 1 1";
test.optionTxt[0, 0, 0, 1, 2] = "Opt 1 2";
test.response[0, 0, 0, 1, 1] = "Opt 1 1 Text";
test.response[0, 0, 0, 1, 2] = "Opt 1 2 Text";

test.myText[0, 0, 0, 0, 2] = "Opt 2 Line";
test.optionTxt[0, 0, 0, 2, 1] = "Opt 2 1";
test.optionTxt[0, 0, 0, 2, 2] = "Opt 2 2";
test.response[0, 0, 0, 2, 1] = "Opt 2 1 Text";
test.response[0, 0, 0, 2, 2] = "Opt 2 2 Text";



text.myText[0, 0, 0, 1, 1] = "Opt 1 1 Line";
test.optionTxt[0, 0, 1, 1, 1] = "Opt 1 1 1";
test.optionTxt[0, 0, 1, 1, 2] = "Opt 1 1 2";
test.response[0, 0, 1, 1, 1] = "Opt 1 1 1 Text";
test.response[0, 0, 1, 1, 2] = "Opt 1 1 2 Text";

test.myText[0, 0, 0, 1, 2] = "Opt 1 2 Line";
test.optionTxt[0, 0, 1, 2, 1] = "Opt 1 2 1";
test.optionTxt[0, 0, 1, 2, 2] = "Opt 1 2 2";
test.response[0, 0, 1, 2, 1] = "Opt 1 2 1 Text";
test.response[0, 0, 1, 2, 2] = "Opt 1 2 2 Text";

text.myText[0, 0, 0, 2, 1] = "Opt 2 1 Line";
test.optionTxt[0, 0, 1, 1, 1] = "Opt 1 1 1";
test.optionTxt[0, 0, 1, 1, 2] = "Opt 1 1 2";
test.response[0, 0, 1, 1, 1] = "Opt 1 1 1 Text";
test.response[0, 0, 1, 1, 2] = "Opt 1 1 2 Text";

test.myText[0, 0, 0, 2, 2] = "Opt 2 2 Line";
test.optionTxt[0, 0, 1, 2, 1] = "Opt 1 2 1";
test.optionTxt[0, 0, 1, 2, 2] = "Opt 1 2 2";
test.response[0, 0, 1, 2, 1] = "Opt 1 2 1 Text";
test.response[0, 0, 1, 2, 2] = "Opt 1 2 2 Text";



text.myText[0, 0, 1, 1, 1] = "Opt 1 1 1 Line";
... (etc.)
Do you think that would work? That would be much easier to code and the only changes needed would be to set the program to work with "5D" arrays rather than "2D" arrays and I could just stop anywhere before I hit 5 options and just leave the rest of the array blank, for example if I were to stop the above example where I stopped it and just fill out the "myText" options for the rest of the options there would basically be "2 wasted dimensions" of the array, but it would probably still work, right? This would put a hard limit on me only being able to do 5 choices by one NPC but honestly I doubt there's any point where that would be necessary, and if it ever is, I suppose I could just hard code that one specific NPC. This seems like it wastes quite a bit of space and may be a terrible idea but it would be much easier to create for each dialogue, I could just copy and paste the above and change all the "Opt 1 2 Line" stuff to what it needs to be, and it reduces it to a minimum number of lines. Another option I thought of is somehow creating a new object for each choice, so like have the original NPC with it's one choice and each of it's options deletes that NPC and creates a new one with one choice, but changes depending on the option I chose. I'm not sure if this would help at all though, as I have no idea how I would set the variables all without the NPC text setter doing it all at the beginning which it clearly cannot do since the other NPC's wouldn't exist at that point. I don't know. If you have any thoughts that would be super helpful, and thanks for your suggestion!
 
Last edited:

Gamebot

Member
How about using strings in a script with a parser for those arrays? You don't have to worry about restructuring like arrays if something changes. Much easier to change, add or take away, and you can still keep everything separate. Less "Quotes " too! You will need a string parser which can be found many places, in this case with "|" as the parser something like:

John1 = "Hello were outside... | Well I hate your dog! | Im sorry to hear that...."
Naldi1 = "Hello John your dog sucks! | I will | No...."

yesno = "Yes|No|Maybe|We will see..."

This way your only dealing with 1d arrays. Which could be beneficial for the back and forth dialog by one simple counter...such as:

count = 0;

john1 [count] = "Hello were outside.."
then
naldi [count] = "Hello John your dog sucks.."

then count up with count++. Now count = 1;

john1 [count] = "Well I hate your dog.."
then
naldi [count] = "I will"

You could even array the array like nesting arrays so that when john[1] is done at the end of the array it automatically loads john[2]. Just a thought. You would have to come with the system itself but 1d arrays are much easier to work with. This is actually how I do all my 2d/3d arrays. Simply load what you need from a script and done. One last thing...you could use a temporary ds_list to shuffle or scramble all of those arrays as well to spice things up...if it would make sense to do so.
 

YanBG

Member
Example dialogue tree, notice how it's like a puzzle and a wrong choice(which we'll call answer) can get you to a previous line/question.
https://en.wikipedia.org/wiki/Dialogue_tree#/media/File:Dialog_tree_example.svg

If NPC line has no possible answers, then you can use a generic string as end of conversation. Answers can be more than 2-3, so you might want to add them to a scrolling list, when you draw the dialogue box.

To avoid your problem of too many lines of code, i'm using a csv(txt with ";") file and then load it as either 2d array or grid. You don't need anything bigger than a table, most important is the ID of each line(e.g. order you add them to the list). You use it to change the current dialogue NPC will show.

Depends on the game, but you might need to add a lot more columns with data to the list of dialogue lines. Right now i'm trying to figure an easy way to have requirements(stats, completed quests etc) for each answer to be available.

Here you can see my setup, only one file(i'm thinking of separating the answers to their own master file, because i might use some of them as possible choices to more than one question). It works that each time you click an answer it changes the NPC's line variable to the ID from the column after the answer. FIrst column is if that NPC line starts a quest and which one(number is quest ID).

 
C

CedSharp

Guest
And for this solution, It does seem like this could work, I would have to do some heavy remodeling on my dialogue system for this to work, but I mostly expected that. The problem I have with this though, is that it get's very complicated very quickly. My entire purpose for doing this was to try to make this as easy to program in the dialogue trees as possible. I went through and used your example for this to make Naldi's code and it got pretty difficult to keep track of and was very long, here's what I made it into:

Code:
// For a choice, First index is the choice number,
// Second index is: 0 = Button Text, 1 = Displayed Response, 2 = Target ID
var choices_dog_insult;
choices_dog_insult[0, 0] = "I will";
choices_dog_insult[0, 1] = "You're right... My dogo really does suck... I will get rid of it at once!";
choices_dog_insult[0, 2] = 1;
choices_dog_insult[1, 0] = "No";
choices_dog_insult[1, 1] = "No, it's my dog, I can keep it if I want to!";
choices_dog_insult[1, 2] = 2;

var choices_agree;
choices_agree[0, 0] = "You're welcome";
choices_agree[0, 1] = "You're very welcome Naldi.";
choices_agree[0, 2] = 3;
choices_agree[1, 0] = "I didn't do it for you";
choices_agree[1, 1] = "I didn't do it for you, you just made a good point";
choices_agree[1, 2] = 4;

var choices_decline;
choices_decline[0, 0] = "Well...";
choices_decline[0, 1] = "Well... I actually really hate your dog and think it is more of a monstrosity than my dog.";
choices_decline[0, 2] = 5;
choices_decline[1, 0] = "I do";
choices_decline[1, 1] = "Don't worry, I love your dog.";
choices_decline[1, 2] = 6;

// For a branch, 0 = Type, 1 = Text, 2 = Choices
var branch_dog_insult;
branch_dog_insult[0] = "Question";
branch_dog_insult[1] = "Hello John! Your dog sucks. Please get rid of it. I do not enjoy its presence";
branch_dog_insult[2] = choices_dog_insult;

var branch_agree;
branch_agree[0] = "Question";
branch_agree[1] = "Thanks, that means a lot";
branch_agree[2] = choices_agree;

var branch_decline;
branch_decline[0] = "Question";
branch_decline[1] = "I suppose it doesn't really matter if you get rid of it or not, but you do at least like my dog right?"
branch_decline[2] = choices_decline;

// For a message, 0 = Type, 1 = Text
var message_dog_insult_option_welcome;
message_dog_insult_option_welcome[0] = "Message";
message_dog_insult_option_welcome[1] = "You're the best John";

var message_dog_insult_option_for_you;
message_dog_insult_option_for_you[0] = "Message";
message_dog_insult_option_for_you[1] = "Huh! Well I never!";

var message_dog_insult_option_well;
message_dog_insult_option_well[0] = "Message";
message_dog_insult_option_well[1] = "I can't even believe it... How could you?!?";

var message_dog_insult_option_yes;
message_dog_insult_option_yes[0] = "Message";
message_dog_insult_option_yes[1] = "Well that's at least good to know.";

// Finally, we assign each messages, in order, to the object
obj_GenericNPC_John.messages[0] = branch_dog_insult;
obj_GenericNPC_John.messages[1] = branch_agree;
obj_GenericNPC_John.messages[2] = branch_decline;
obj_GenericNPC_John.messages[3] = message_dog_insult_option_welcome;
obj_GenericNPC_John.messages[4] = message_dog_insult_option_for_you;
obj_GenericNPC_John.messages[5] = message_dog_insult_option_well;
obj_GenericNPC_John.messages[6] = message_dog_insult_option_yes;
That was only for 2 choices each with two options. Throughout the game I may get to points where I have four or five choices with two to four options each. That would be a nightmare. I did have another idea though, but I'm not sure if it would be much better, what if I just made the array a "5D" array:
obj_GenericNPC_John.optionTxt[0, 0, 0, 0, 0] = "Well I hate your dog!";
and basically do the thing I did in the second coding box in my first post:

Code:
test.myText[0, 0, 0, 0, 0] = "First line";
test.optionTxt[0, 0, 0, 0, 1] = "Opt 1";
test.optionTxt[0, 0, 0, 0, 2] = "Opt 2";
test.response[0, 0, 0, 0, 1] = "Opt 1 Text";
test.response[0, 0, 0, 0, 2] = "Opt 2 Text";



test.myText[0, 0, 0, 0, 1] = "Opt 1 Line";
test.optionTxt[0, 0, 0, 1, 1] = "Opt 1 1";
test.optionTxt[0, 0, 0, 1, 2] = "Opt 1 2";
test.response[0, 0, 0, 1, 1] = "Opt 1 1 Text";
test.response[0, 0, 0, 1, 2] = "Opt 1 2 Text";

test.myText[0, 0, 0, 0, 2] = "Opt 2 Line";
test.optionTxt[0, 0, 0, 2, 1] = "Opt 2 1";
test.optionTxt[0, 0, 0, 2, 2] = "Opt 2 2";
test.response[0, 0, 0, 2, 1] = "Opt 2 1 Text";
test.response[0, 0, 0, 2, 2] = "Opt 2 2 Text";



text.myText[0, 0, 0, 1, 1] = "Opt 1 1 Line";
test.optionTxt[0, 0, 1, 1, 1] = "Opt 1 1 1";
test.optionTxt[0, 0, 1, 1, 2] = "Opt 1 1 2";
test.response[0, 0, 1, 1, 1] = "Opt 1 1 1 Text";
test.response[0, 0, 1, 1, 2] = "Opt 1 1 2 Text";

test.myText[0, 0, 0, 1, 2] = "Opt 1 2 Line";
test.optionTxt[0, 0, 1, 2, 1] = "Opt 1 2 1";
test.optionTxt[0, 0, 1, 2, 2] = "Opt 1 2 2";
test.response[0, 0, 1, 2, 1] = "Opt 1 2 1 Text";
test.response[0, 0, 1, 2, 2] = "Opt 1 2 2 Text";

text.myText[0, 0, 0, 2, 1] = "Opt 2 1 Line";
test.optionTxt[0, 0, 1, 1, 1] = "Opt 1 1 1";
test.optionTxt[0, 0, 1, 1, 2] = "Opt 1 1 2";
test.response[0, 0, 1, 1, 1] = "Opt 1 1 1 Text";
test.response[0, 0, 1, 1, 2] = "Opt 1 1 2 Text";

test.myText[0, 0, 0, 2, 2] = "Opt 2 2 Line";
test.optionTxt[0, 0, 1, 2, 1] = "Opt 1 2 1";
test.optionTxt[0, 0, 1, 2, 2] = "Opt 1 2 2";
test.response[0, 0, 1, 2, 1] = "Opt 1 2 1 Text";
test.response[0, 0, 1, 2, 2] = "Opt 1 2 2 Text";



text.myText[0, 0, 1, 1, 1] = "Opt 1 1 1 Line";
... (etc.)
Do you think that would work? That would be much easier to code and the only changes needed would be to set the program to work with "5D" arrays rather than "2D" arrays and I could just stop anywhere before I hit 5 options and just leave the rest of the array blank, for example if I were to stop the above example where I stopped it and just fill out the "myText" options for the rest of the options there would basically be "2 wasted dimensions" of the array, but it would probably still work, right? This would put a hard limit on me only being able to do 5 choices by one NPC but honestly I doubt there's any point where that would be necessary, and if it ever is, I suppose I could just hard code that one specific NPC. This seems like it wastes quite a bit of space and may be a terrible idea but it would be much easier to create for each dialogue, I could just copy and paste the above and change all the "Opt 1 2 Line" stuff to what it needs to be, and it reduces it to a minimum number of lines. Another option I thought of is somehow creating a new object for each choice, so like have the original NPC with it's one choice and each of it's options deletes that NPC and creates a new one with one choice, but changes depending on the option I chose. I'm not sure if this would help at all though, as I have no idea how I would set the variables all without the NPC text setter doing it all at the beginning which it clearly cannot do since the other NPC's wouldn't exist at that point. I don't know. If you have any thoughts that would be super helpful, and thanks for your suggestion!
Do you realize how you repeat the same thing over and over again?
That's when you create a script!

Here is a scriptified example version:
Code:
messages = ds_list_create();
ds_list_add(messages,
  /* 0 */ dialog_message("This is a message"),
  /* 1 */ dialog_branch("Question Yes-No",
            dialog_choice("Yes", "I will choose Yes", 2),
            dialog_choice("No", "I will choose No", 3)
          )
  /* 2 */ dialog_message("You chose Yes", 4),
  /* 3 */ dialog_message("You chose No"),
  /* 4 */ dialog_message("End of branch")
);
That's just an idea, I gave you how to make the "engine" but you can make it "easier to use" :p
That's programming for you xD
 

Yizzard

Member
Do you realize how you repeat the same thing over and over again?
That's when you create a script!

Here is a scriptified example version:
Code:
messages = ds_list_create();
ds_list_add(messages,
  /* 0 */ dialog_message("This is a message"),
  /* 1 */ dialog_branch("Question Yes-No",
            dialog_choice("Yes", "I will choose Yes", 2),
            dialog_choice("No", "I will choose No", 3)
          )
  /* 2 */ dialog_message("You chose Yes", 4),
  /* 3 */ dialog_message("You chose No"),
  /* 4 */ dialog_message("End of branch")
);
That's just an idea, I gave you how to make the "engine" but you can make it "easier to use" :p
That's programming for you xD

Ok, that makes sense. I took the idea and wrote a script. I'm honestly not super sure as to how writing functions works exactly in GMS2 as I have not yet done that, so this is the script I created called scr_Dialogue:
Code:
/// @function message(str, speaker)
/// @description Creates the message.
/// @param {string} str The message to be returned.
/// @param {real} speaker The object that will be the speaker of the message.

var mess;
mess[0] = argument1;
mess[1] = argument0;
return mess;

/// @function choice(answer, message, idNum)
/// @description Creates the choice with answer "answer" associated with "message" and it's corresponding id number.
/// @param {string} answer The answer you choose.
/// @param {string} message The message that is actually spoken.
/// @param {int} idNum The id number that the choice corresponds to.

var choices;
choices[0] = argument0;
choices[1] = argument1;
choices[2] = argument2;
return choices;

/// @function branch(str, choice1, choice2, choice3, choice4, choice5)
/// @description Creates a branch with multiple choices
/// @param {string} str The question prompting the chioce.
/// @param {choice()} choice1, The first choice.
/// @param {choice()} choice2, The second choice.
/// @param {choice()} choice3, The third choice.
/// @param {choice()} choice4, The fourth choice.
/// @param {choice()} choice5, The fifth choice.

var brnch;
brnch[0] = "Question";
brnch[1] = argument[0];
brnch[2] = argument[1];
if(array_length_1d(argument) > 2)
{
    brnch[3] = argument[2];
    if(array_length_1d(argument) > 3)
    {
        brnch[4] = argument[3];
        if(array_length_1d(argument) > 4)
        {
            brnch[5] = argument[4];
            if(array_length_1d(argument) > 5)
            {
                brnch[6] = argument[5];
            }
        }
    }
}
return brnch;
Then an example of the creation code I would have to type would be:

Code:
obj_GenericNPC_John.messages[0] = branch("Hi how are you today?", choice("Fantastic!", "I am doing fantastic today, thanks!", 1), choice("Bad...", "I'm not doing too well right now...", 2));
obj_GenericNPC_John.messages[1] = message("Hot diggity dog, that's amazing!", obj_GenericNPC_John);
obj_GenericNPC_John.messages[2] = branch("Well I'm sorry to hear that! Is there anything I can do?", choice("Give me ice cream", "If you gave me some ice cream, that may help...", 3), choice("No", "No... nothing that I can think of...", 4));
obj_GenericNPC_John.messages[3] = message("I'll see what I can do then!", obj_GenericNPC_John);
obj_GenericNPC_John.messages[4] = message("Well, if you think of anything, please let me know!", obj_GenericNPC_John);
Am I correct? The calls to the functions aren't showing up as recognized calls, as in, the function names are blue just like any other variable and it's not checking to make sure the parameters are correct or anything. Am I missing something? Do I need the function definitions each in their own script, or is having all three in the same script fine? If I do need them all in separate scripts, the the scripts have to be called the same as the function names? Sorry for all the questions but these function declarations are very different than what I am used to lol.
Also, I used arrays instead of lists for the creation code because I know how to use them better and don't really see a big advantage in using lists for this. If I should be using lists let me know, but I prefer arrays.
Again, thanks so much for the help!
 

vdweller

Member
Suppose you have an Npc named Peter.

Make a single gml script called interact_peter.

This script has only one argument: The interaction stage. By default it could be zero.

The last message box of a dialogue sequence can specify which interaction script to call and what stage to access in that script, ie the script index and its argument0. You must use script_execute(script_index,argument0,[argument1 if any etc]).

Essentially you'll be calling interact_peter() again and again...only you'll be providing a different "stage" argument each time.

Rough example (pseudocode)

interact_peter() :

switch argument0 {
case 0
message("Hi Mark! How are you?","I'm fine!","Buzz off!",1,2,interact_peter)

case 1
message("That's great!")

case 2
message("That was rude!")

}
================

Basically what happened above is that the message() script contains a variable amount of arguments each time. For example in

message("Hi Mark! How are you?","I'm fine!","Buzz off!",1,2,interact_peter)

Argument0 is the text to be displayed
Argument1 is your first choice
Argument2 is the second
Argument3 is the stage to be set when player selects first answer
Argument4 is stage number for the second answer
Argument5 is the script to be called where all this takes place. Essentially in this case the script calls itself again...but with a different stage argument each time.

You can use the number of arguments in message () to differentiate each case, eg
1 argument: Just text, nothing else happens.
3 arguments: Text, then an interaction script is called and a stage is set
6 arguments: Text and 2 dialogue choices, like the example above
8 arguments: Text and 3 choices
And so on. So the text box object is set up accordingly and when it ends (just a simple message, or the player selected an option), it calls the specified interaction script or does nothing.

Sorry for typos and not much elaboration, typing from a phone. I can give sample code if you're interested in this method. It doesn't beat general purpose scripting but you can definitely make it work for you.
 
C

CedSharp

Guest
Ok, that makes sense. I took the idea and wrote a script. I'm honestly not super sure as to how writing functions works exactly in GMS2 as I have not yet done that, so this is the script I created called scr_Dialogue:
Code:
/// @function message(str, speaker)
/// @description Creates the message.
/// @param {string} str The message to be returned.
/// @param {real} speaker The object that will be the speaker of the message.

var mess;
mess[0] = argument1;
mess[1] = argument0;
return mess;

/// @function choice(answer, message, idNum)
/// @description Creates the choice with answer "answer" associated with "message" and it's corresponding id number.
/// @param {string} answer The answer you choose.
/// @param {string} message The message that is actually spoken.
/// @param {int} idNum The id number that the choice corresponds to.

var choices;
choices[0] = argument0;
choices[1] = argument1;
choices[2] = argument2;
return choices;

/// @function branch(str, choice1, choice2, choice3, choice4, choice5)
/// @description Creates a branch with multiple choices
/// @param {string} str The question prompting the chioce.
/// @param {choice()} choice1, The first choice.
/// @param {choice()} choice2, The second choice.
/// @param {choice()} choice3, The third choice.
/// @param {choice()} choice4, The fourth choice.
/// @param {choice()} choice5, The fifth choice.

var brnch;
brnch[0] = "Question";
brnch[1] = argument[0];
brnch[2] = argument[1];
if(array_length_1d(argument) > 2)
{
    brnch[3] = argument[2];
    if(array_length_1d(argument) > 3)
    {
        brnch[4] = argument[3];
        if(array_length_1d(argument) > 4)
        {
            brnch[5] = argument[4];
            if(array_length_1d(argument) > 5)
            {
                brnch[6] = argument[5];
            }
        }
    }
}
return brnch;
Then an example of the creation code I would have to type would be:

Code:
obj_GenericNPC_John.messages[0] = branch("Hi how are you today?", choice("Fantastic!", "I am doing fantastic today, thanks!", 1), choice("Bad...", "I'm not doing too well right now...", 2));
obj_GenericNPC_John.messages[1] = message("Hot diggity dog, that's amazing!", obj_GenericNPC_John);
obj_GenericNPC_John.messages[2] = branch("Well I'm sorry to hear that! Is there anything I can do?", choice("Give me ice cream", "If you gave me some ice cream, that may help...", 3), choice("No", "No... nothing that I can think of...", 4));
obj_GenericNPC_John.messages[3] = message("I'll see what I can do then!", obj_GenericNPC_John);
obj_GenericNPC_John.messages[4] = message("Well, if you think of anything, please let me know!", obj_GenericNPC_John);
Am I correct? The calls to the functions aren't showing up as recognized calls, as in, the function names are blue just like any other variable and it's not checking to make sure the parameters are correct or anything. Am I missing something? Do I need the function definitions each in their own script, or is having all three in the same script fine? If I do need them all in separate scripts, the the scripts have to be called the same as the function names? Sorry for all the questions but these function declarations are very different than what I am used to lol.
Also, I used arrays instead of lists for the creation code because I know how to use them better and don't really see a big advantage in using lists for this. If I should be using lists let me know, but I prefer arrays.
Again, thanks so much for the help!
Hey there, sorry for late answer.
Yes that looks about right. There are only 2 points I would like to care:

1. In your code, you refer to "idNum" for choices. That is the ID of the message to jump to when that choice is selected right? That's what i meant for in my code at least :)

2. In your branching code, you can make use of "argument_count" and loop instead of creating this horrible if-tree:
Code:
/// @function branch(str, choice1, choice2, choice3, choice4, choice5)
/// @description Creates a branch with multiple choices
/// @param {string} str The question prompting the chioce.
/// @param {choice()} ..., One or more choices()

var i, brnch, choice_count;
brnch[0] = "Question";
brnch[1] = argument[0];
brnch[2] = argument[1];
for(i=2; i<argument_count; i++) {
  brnch[ i-1 ] = argument[ i ];
}
return brnch;
 

Rob

Member
Right now i'm trying to figure an easy way to have requirements(stats, completed quests etc) for each answer to be available.
One thing that springs to mind is the same trick for different coloured text. Using a different symbol to denote a different stat and then a number for the value to check?
 
C

CedSharp

Guest
Continuing on @Rob 's answer, you could either parse every message (most control but painful code to write) or just use a simple replace:
Code:
/// @function parse_message(str)
/// @description Parses message and replaces placeholders with their corresponding values
/// @param {string} str The message to parse
var mesg = string_replace(argument0, "[money]", string(objStats.money)+"G");
mesg = string_replace(argument0, "[name]", objStats.playerName);
//etc
return msg;
And then you could write something like "Hello [name]! How are you doing? I see you have [money]. Impressive!"
 

YanBG

Member
Great idea! For a long text file with many different variables to check in the various dialogues(i'm thinking of procedural conversations too), should i list them all with a switch? Or maybe loop from another text file?
 
Top