Search Unity

  1. Unity 2019.1 is now released.
    Dismiss Notice

Need to create conversation trees

Discussion in 'Scripting' started by Katori, Feb 11, 2010.

  1. Katori

    Katori

    Joined:
    Nov 30, 2009
    Posts:
    55
    What's the best way to create conversation/dialogue trees? I have no problem getting my hands dirty, but I've looked on the Internet for a good solution for this, and haven't found one (except UDE, which costs $100 I don't have).

    I'd like to store it in a simple XML file or even better a text file. But whatever works, really.
     
  2. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    You could use a class that stores the prompt text (usually this represents what the NPC has just said) along with the reply texts and the conversation nodes they lead to:-
    Code (csharp):
    1. class ConvNode {
    2.     var prompt: String;
    3.     var replies: String[];
    4.     var nodeLinks: int[];
    5. }
    The idea is that each conversation node would have its own entry in an array of nodes. Each node link is simply an index into the array. Filling the array can be done quite easily by reading XML or some other text format, or it can be done in the source code. Progressing through the conversation is just a matter of having a variable that records which node is currently in use and then changing it when the user selects a reply.
     
  3. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    If you don't mind, I'll tag along with this conversation :). I, too, am trying to create a dialogue tree system (and I also don't want to buy UDE). I am not a programmer, and whle I'm very, very happily surprised by how easily I picked up to use the GUI, I'm looking for an effective way to make such a system.

    I've not used Arrays yet, but if I'm reading this right, andeeee, this array changes the text on, say, the buttons and the output windows? So a given button in the conversation should have a code that activates the next class? I'm really a beginner in both programming and Unity, so forgive me if this question seems simplistic :).
     
  4. Katori

    Katori

    Joined:
    Nov 30, 2009
    Posts:
    55
    I get what you're saying here and this is a great idea.

    For this I'm going to need to know two things, though: is it possible to select where the script is in an Array based on a variable? And also, I've been reading about using XML or text to populate the Array and I really have no idea where to even begin. Could you point me in the right direction?
     
  5. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    I'd like that as well, is there an Arrays tutorial to be found anywhere, maybe?
     
  6. Moredice_legacy

    Moredice_legacy

    Joined:
    Jan 31, 2010
    Posts:
    101
    A simple, non-scripted example of how an Array works:

    Array["hi there", "yo", "bye"]

    Because computers count from 0, and not 1, Array[0] therefore returns "hi there", while Array[1] will return "yo", and so on and so forth. The universal rule is, always keep in mind that when working with arrays, in this example, the first element in the array is always indexed as 0.


    Array documentation:
    http://unity3d.com/support/documentation/ScriptReference/Array.html

    Be sure to check the bottom of the page for every method of usage with an array.
     
  7. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    Okay, I think I understand how that works. Your example helped me understand it a little better than the reference did (which I had read before), so thanks :).

    Right, so how do we go about making what andeeee proposed? If I understand correct, you would fill up the array with loads and loads of text, possibly importing this from a text or XML file. Then you would do something like this?

    Code (csharp):
    1. if (nodeLinks = int[2]) {
    2.  
    3. }
    And then tell the script to call up a certain text from the array? I'm having a little trouble seeing it.
     
  8. lankoski

    lankoski

    Joined:
    Apr 20, 2008
    Posts:
    148
    We did work with dialog trees in Lies and Seductions. Source code is available (released with Apache 2 license)
    http://mlab.taik.fi/~plankosk/blog/?p=308

    We did also release our Java-based editor (http://mlab.taik.fi/~plankosk/blog/?p=314) that create XML definition that is used by the game.

    The assets for dialogue handling are mainly in Dialogue (and AI directory).
     
  9. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    Pff, can't make heads or tails of that, to be honest :p. And it's in C#. I don't know if Katori can use it, but all my scripts are Javascript.

    I really appreciate the help, though. I'm afraid it's a bit too much to wrap my head around, all at once...
     
  10. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    You would need an array of conversation nodes, and a integer to indicate which is the current node
    Code (csharp):
    1. var nodes: ConvNode[];
    2. var currentNode: int = 0;
    You could set up the nodes in the Start function:-
    Code (csharp):
    1. nodes = new ConvNode[numberOfNodes];
    2. nodes[0] = new ConvNode();
    3. nodes[0].replies = new String[numReplies];
    4. nodes[0].links = new int[numReplies];
    5. nodes[0].prompt = "Do you know where you are?";
    6. nodes[0].replies[0] = "Yes";
    7. // ...etc...
    8.  
    9. nodes[1] = new ConvNode();
    10. //...etc...
    11.  
    (Of course, you would really make a function to set up a node rather than set the fields directly like this.)

    Then, you need some code to display a particular node - this will be called within the OnGUI function:-
    Code (csharp):
    1. function DisplayNode(node: ConvNode) {
    2.     GUI.Label(promptRect, node.prompt);
    3.     GUI.Label(replyZeroRect, node.replies[0];
    4.     GUI.Label(replyOneRect, node.replies[1];
    5.     // ...or something along these lines...
    6. }
    In OnGUI, you need some code to display the current node:-
    Code (csharp):
    1. function OnGUI() {
    2.     DisplayNode(nodes[currentNode]);
    3.     ...
    4. }
    The input would then depend on whether you want to use keystrokes, button or whatever, but basically each option corresponds to one of the possible replies:-
    Code (csharp):
    1. if (GUI.Button(replyZeroButtonRect, buttonImage) {
    2.     currentNode = nodes[currentNode].links[0];
    3. } else if (GUI.Button(replyOneButtonRect, buttonImage) {
    4.     currentNode = nodes[currentNode].links[1];
    5. } else...
    6.  
    When a button is clicked, the current node is updated. Next time OnGUI is called, this new node is the one that will be displayed, ready for the next step of the conversation.
     
  11. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    Hey andeeee,

    Okay, so I copied your explanation:

    Code (csharp):
    1. var nodes : ConvNode[] ;
    2. var currentNode : int = 0 ;
    3.  
    4. function Start () {
    5.     //number of nodes
    6.     nodes = new ConvNode[3];
    7.     //node 0
    8.     nodes[0] = new ConvNode();
    9.     nodes[0].replies = new String[2];
    10.     nodes[0].links = new int[2];
    11.     nodes[0].prompt = "Testing, does it work?";
    12.     nodes[0].replies[0] = "Yes, it does.";
    13.     nodes[0].replies[1] = "No, it doesn't.";
    14.     // node 1
    15.     nodes[1] = new ConvNode();
    16.     nodes[1].replies = new String[1];
    17.     nodes[1].links = new int[1];
    18.     nodes[1].prompt = "This is node 2!";
    19.     nodes[1].replies[0] = "Awesome!";
    20.  
    21. }
    22.  
    23. function DisplayNode (nodes: ConvNode) {
    24.     GUI.Label (Rect (10, 10, 200, 200), nodes.prompt);
    25.     GUI.Label (Rect (10, 220, 200, 200), nodes.replies[0]);
    26.     GUI.Label (Rect (10, 430, 200, 200), nodes.replies[1]);
    27. }
    28.  
    29. function OnGUI () {
    30.     DisplayNode (nodes[0]);
    31.    
    32.     if (GUI.Button(Rect (15, 225, 190, 190), " ")) {
    33.         currentNode = nodes[currentNode].links[0];
    34.     }
    35.     else if (GUI.Button (Rect( 15, 435, 190, 190), " ")) {
    36.         currentNode = nodes[currentNode].links[1];
    37.     }
    38. }
    But I got this error:

    Code (csharp):
    1. Assets/Scripts/Intro1_Dialogue.js(1,13): BCE0018: The name 'ConvNode' does not denote a valid type. Did you mean 'System.Security.AccessControl.CompoundAceType' ?
    x3 at different places in the code.

    At first I thought I had made a mistake, so I changed the ":" to a "=", but then it said it was expecting a semicolon, even though I have one. So what am I doing wrong?
     
  12. lankoski

    lankoski

    Joined:
    Apr 20, 2008
    Posts:
    148
    You need to define class ConvNode before you can use it. See above messages how to do that.
     
  13. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    Ah, you're absolutely right, I forgot to define the Class from the initial topic replies.

    Okay, it seems to work :). But when I click the buttons, nothing happen. Am I correct in assuming that the button output must change the "currentNode" variable?
     
  14. Moredice_legacy

    Moredice_legacy

    Joined:
    Jan 31, 2010
    Posts:
    101
    Yes, that is correct. Change "currentNode" to the respective desired conversation tree branch that will display as the result of your choice.
     
  15. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    Cool, I think I get it now :). The last thing that's unclear to me is the function of the "nodes[].links". Right now, the buttons change the integer of the link, but what does that accomplish?
     
  16. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    The links field is an array of integers. Each one specifies the index of the next node if a particular link is chosen. So, if the replies are: "Yes", "No", "Maybe" and the links are 3, 7, 12, the next node will be 3 if the user says "Yes", 7 if "No" and 12 if "Maybe".
     
  17. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    Ah, and how do I specific which nodes.replies[] activates which nodes.links[]?
     
  18. Katori

    Katori

    Joined:
    Nov 30, 2009
    Posts:
    55
    I've got this working, now all I need is a way to easily populate the list. I could hard-code it, but that would be too finnicky for me.
     
  19. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    So can you maybe help me out with above question, Katori? Or andeeee?
     
  20. Katori

    Katori

    Joined:
    Nov 30, 2009
    Posts:
    55
    The number of the link is the number of the reply. For example, if you have replies[1] and you want it to link to node 2, you'd write:
    Code (csharp):
    1. nodes[1].replies[1] = "Yes, of course.";
    2. nodes[1].links[1] = 2;
    Similarly, if you had replies[5] and wanted to link it to node 4, you'd write:
    Code (csharp):
    1. nodes[1].replies[5] = "No, I hate the Loyalists";
    2. nodes[1].links[5] = 4;
    Hope that helps.

    Here's what I did for my implementation:
    I separated the code into one master file, ConvSystem, which declares the ConvNode class, keeps the currentNode variable, and declares DisplayNode. Then for each conversation I create another file, which creates the nodes array of ConvNode's, and fills the array up.

    It also contains all the OnGUI stuff, because I couldn't figure out how to separate it in a modular way.

    So each Converser in the game has the ConvSystem master script and a specific script to them.
     
  21. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    Thanks so much Katori, it totally works now :D.

    So how did you accomplish this implementation? Did you use GetComponent or something?

    Also, let's say the player can do stuff to add additional dialogue options, by picking up an item, for example. How would I do that? Something like this perhaps?:

    Code (csharp):
    1. nodes[0].replies[].Push (replies.[1]);
    That's probably wrong...

    And do I have to have all the buttons visible all the time, or can I hide the ones that aren't active (eg. if I have 2 replies and 3 buttons)?


    Really appreciate this guys! This is really helping me push my graduation project along :D. Unity rules.

    EDIT: Hm, weird...I get an "Array index out of range" when I want only one reply.
    Code (csharp):
    1. nodes[3].replies = new String[1];
    I get the same when I try to make a third label to display the third .replies. Maybe I should make a seperate topic for this. Don't want to hijack yours, Katori.
     
  22. Katori

    Katori

    Joined:
    Nov 30, 2009
    Posts:
    55
    No, it's fine, I'm encountering similar problems.

    I don't know how to fix Array Index Out of Range, I've just accepted it at this point.

    As far as the player adding additional dialogue options, I would store it in a global variable. When you're populating the nodes, look to see if that variable is at the state you want it at. If it is, put up that dialogue option. Else, do nothing.

    I also don't know how to hide the boxes, but I haven't worked on it in a few days, I could probably do it. I'll get back to you on that.
     
  23. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    Cool, I'm curious to what you might find.

    It's weird, that out of range thing... Especially since the rest does keep working.

    Also, would there be a way I can use this array to change a variable. Say that I want the player to lose health when a specific nodes.replies is chosen. Can I make something to do that? I've cracked my head on it, but my limited scripting knowledge really isn't sufficient.
     
  24. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    Maybe it has to do with the fact that not every node has the same amount of .replies and .links... Maybe if that button-hiding thing is accomplished it would simultaniously solve that problem...

    I've been thinking on how to make actions, but all I come up with is an if statement to check "currentNode". But that's not reply-specific. Man, this is so out of my barely existent skill level :p.
     
  25. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    Can you post the code that is causing the index-out-of-range errors?
     
  26. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    FYI, I took out a lot of code that isn't relevant to the issue. And I changed the output of the replies to directly be applied to the buttons, instead of a label.

    Code (csharp):
    1.  
    2. var message : TextAsset;
    3. var Soundscape : GameObject;
    4. var Music : GameObject;
    5. var guicharacters = false;
    6. var characterTalking : String;
    7. var characterAvatar : Texture2D;
    8. var characterSpeech : TextAsset;
    9. var characterSpeech2 : String;
    10. var message2 : String;
    11. //var screenWidth = Screen.width / Screen.width - 1;
    12. //var screenHeight = Screen.height / Screen.height - 1;
    13. var dialogueSwitch : int = 1;
    14. var textureBackground : Texture2D;
    15. var textureBackground2 : Texture2D;
    16. var textureBackground3 : Texture2D;
    17. var screenwidth = Screen.width;
    18. var screenheight = Screen.height;
    19. var nodes : ConvNode[] ;
    20. var currentNode : int = 0 ;
    21. var DialogueButtons = false;
    22.  
    23. class ConvNode {
    24.     var prompt : String;
    25.     var replies : String[];
    26.     var links : int[];
    27. }
    28.  
    29.  
    30. function Start () {
    31. //number of nodes
    32.     nodes = new ConvNode[5];
    33.     //node 0
    34.     nodes[0] = new ConvNode();
    35.     nodes[0].replies = new String[3];
    36.     nodes[0].links = new int[3];
    37.     nodes[0].prompt = "...";
    38.     nodes[0].replies[0] = "Hey buddy, what the hell are you doing?";
    39.     nodes[0].links[0] = 1;
    40.     nodes[0].replies[1] = "Let her go or I'll call the police!";
    41.     nodes[0].links[1] = 1;
    42.     nodes[0].replies[2] = "Sir, I suggest you let that woman go.";
    43.     nodes[0].links[2] = 1;
    44.     // node 1
    45.     nodes[1] = new ConvNode();
    46.     nodes[1].replies = new String[2];
    47.     nodes[1].links = new int[2];
    48.     nodes[1].prompt = "Get away from me! I don't want to hurt her but I will. And you, if you don't walk away!";
    49.     nodes[1].replies[0] = "Take it easy, we can just talk about it. What happened?";
    50.     nodes[1].links[0] = 2;
    51.     nodes[1].replies[1] = "Be smart, man. Give up, you're not going to get away.";
    52.     nodes[1].links[1] = 2;
    53.     //node 2
    54.     nodes[2] = new ConvNode();
    55.     nodes[2].replies = new String[2];
    56.     nodes[2].links = new int[2];
    57.     nodes[2].prompt = "No! I am done talking. Talking is what I've done for the past half year. Talking is what I've been doing in court. I won't let her take my Ben.";
    58.     nodes[2].replies[0] = "Your Ben? Is he your kid? Is this woman your wife?";
    59.     nodes[2].links[0] = 3;
    60.     nodes[2].replies[1] = "Look, buddy, relax. You're not going to accomplish anything like this. Put down the gun.";
    61.     nodes[2].links[1] = 3;
    62.     //node 3
    63.     nodes[3] = new ConvNode();
    64.     nodes[3].replies = new String[2];
    65.     nodes[3].links = new int[2];
    66.     nodes[3].prompt = "Ben is... He's my son. *to woman* Hear that, bitch? MY son! *to player* She's trying to take him from me. Josh, Ben needs a better father, she said. While he was in the damn living room with us!";
    67.     nodes[3].replies[0] = "<say nothing>";
    68.     nodes[3].links[0] = 4;
    69.     nodes[3].replies[1] = "<say nothing>";
    70.     nodes[3].links[1] = 4; 
    71.     //node 4
    72.     nodes[4] = new ConvNode();
    73.     nodes[4].replies = new String[2];
    74.     nodes[4].links = new int[2];   
    75.     nodes[4].prompt = "Woman: You know Ben deserves better than you! This proves it, doesn't it?";
    76.     nodes[4].replies[0] = "<say nothing>";
    77.     nodes[4].links[0] = 5;
    78.     nodes[4].replies[1] = "<say nothing>";
    79.     nodes[4].links[1] = 0;
    80.    
    81. }
    82.  
    83. function DisplayNode (nodes: ConvNode) {
    84.         //prompt locations
    85.     //GUI.Label (Rect (screenwidth * 0.18, screenheight * 0.05, screenwidth * 0.80, screenheight * 0.17), nodes.prompt);
    86.         //label locations
    87.     //GUI.Label (Rect (screenwidth * 0.06, screenheight * 0.83, screenwidth * 0.90, 110), nodes.replies[0]);
    88.     //GUI.Label (Rect (screenwidth * 0.06, screenheight * 0.87, screenwidth * 0.90, 110), nodes.replies[1]);
    89.     //GUI.Label (Rect (screenwidth * 0.06, screenheight * 0.91, screenwidth * 0.90, 110), nodes.replies[2]);
    90. }
    91.  
    92. function Update () {
    93.     print (currentNode);
    94. }
    95.  
    96.  
    97. function OnGUI () {
    98.    
    99.     switch(dialogueSwitch) {
    100.        
    101.         case 1:
    102.              //print ("one");
    103.        
    104.         break;
    105.        
    106.         case 2:
    107.             //print ("two");
    108.        
    109.         break;
    110.        
    111.         case 3:
    112.             //print ("three");
    113.        
    114.             GUI.DrawTexture (Rect (0, 0, screenwidth, screenheight), textureBackground2, ScaleMode.StretchToFill);
    115.         break;
    116.        
    117.         case 4:
    118.             //print ("four");
    119.            
    120.             GUI.DrawTexture (Rect (0, 0, screenwidth, screenheight), textureBackground3, ScaleMode.StretchToFill);
    121.         break;
    122.        
    123.         case 5:
    124.             //print ("five");
    125.             message2 = " ";
    126.             guicharacters = true;
    127.             GUI.DrawTexture (Rect (0, 0, screenwidth, screenheight), textureBackground3, ScaleMode.StretchToFill);
    128.             DialogueButtons = true;
    129.             //DisplayNode (nodes[currentNode]);
    130.         break;
    131.     }
    132.  
    133.     //Message box
    134.     GUI.Box (Rect (screenwidth * 0.02, screenheight * 0.8, screenwidth * 0.96, 120), "Messages");
    135.         //label that reads txt file
    136.     //GUI.Label (Rect (Screen.width * 0.05, Screen.height * 0.83, Screen.width * 0.90, 110), message.text);
    137.         //label that reads ugly hardcode
    138.     GUI.Label (Rect (screenwidth * 0.05, screenheight * 0.83, screenwidth * 0.90, 110), message2);
    139.  
    140.     //character box
    141.     if (guicharacters == true) {
    142.         //avatar box
    143.         GUI.Box (Rect (screenwidth * 0.02, screenheight * 0.02, screenwidth * 0.13, screenheight * 0.21), characterTalking);
    144.         GUI.Label (Rect (screenwidth * 0.03, screenheight * 0.04, screenwidth * 0.145, screenheight * 0.185), characterAvatar);
    145.         //speech box
    146.         GUI.Box (Rect (screenwidth * 0.16, screenheight * 0.02, screenwidth * 0.82, screenheight * 0.17), "Speech");
    147.             //label that reads txtfile
    148.         //GUI.Label (Rect (Screen.width * 0.17, Screen.height * 0.05, Screen.width * 0.80, Screen.height * 0.17), characterSpeech.text);
    149.             //label that reads nodesprompt
    150.         GUI.Label (Rect (screenwidth * 0.17, screenheight * 0.05, screenwidth * 0.80, screenheight * 0.17), nodes[currentNode].prompt);
    151.        
    152.     }
    153.  
    154.     //dialogue buttons location + toggle
    155.     if (DialogueButtons == true) {
    156.    
    157.         if (GUI.Button(Rect (screenwidth * 0.05, screenheight * 0.83, screenwidth * 0.90, 20), nodes[currentNode].replies[0])) {
    158.             currentNode = nodes[currentNode].links[0];
    159.         }
    160.         else if (GUI.Button (Rect(screenwidth * 0.05, screenheight * 0.87, screenwidth * 0.90, 20), nodes[currentNode].replies[1])) {
    161.             currentNode = nodes[currentNode].links[1];
    162.         }
    163.         else if (GUI.Button (Rect(screenwidth * 0.05, screenheight * 0.91, screenwidth * 0.90, 20), nodes[currentNode].replies[2])) {
    164.             currentNode = nodes[currentNode].links[2];
    165.         }      
    166.     }
    167.    
    168.  
    169.    
    170.    
    171.     if (GUI.Button (Rect (30, 150, 80, 20), "Next")) {
    172.         dialogueSwitch = dialogueSwitch + 1;
    173.        
    174.     }
    175.  
    176.    
    177.    
    178. }
    I took out a lot of text from the SWITCH, and don't mind the ugly codes in there. This file was meant to be a test.
     
  27. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    Bump :). Need anything else andeeee?
     
  28. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    The most obvious thing that seems to be a problem is this bit:-
    Code (csharp):
    1. if (GUI.Button(Rect (screenwidth * 0.05, screenheight * 0.83, screenwidth * 0.90, 20), nodes[currentNode].replies[0])) {
    2.          currentNode = nodes[currentNode].links[0];
    3.       }
    4.       else if (GUI.Button (Rect(screenwidth * 0.05, screenheight * 0.87, screenwidth * 0.90, 20), nodes[currentNode].replies[1])) {
    5.          currentNode = nodes[currentNode].links[1];
    6.       }
    7.       else if (GUI.Button (Rect(screenwidth * 0.05, screenheight * 0.91, screenwidth * 0.90, 20), nodes[currentNode].replies[2])) {
    8.          currentNode = nodes[currentNode].links[2];
    9.       }
    Here, you refer to replies[0], replies[1] and replies[2]. However, if you declare the replies array with only one element, then indices 1 and 2 will not exist and you will get the out-of-range error when you try to access them. You should either ensure all nodes have the same number of replies or else vary the number of buttons to match the number of available replies.
     
  29. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    Hey Katori and Andeee,

    Yesterday I went to a seminar at school and I casually asked the number one programmer in our class to take a look at my script. Eventually we (read: he) rewrote the entire thing to include a way to spawn buttons depending on how much you need. And a way to handle effects (eg. change a variable if you click a link). Here's the code, it work flawlessly, hope it helps you out :D. Still has some garbage in it, but I cleaned most of it out.

    Code (csharp):
    1.  
    2.  
    3. var nodes : Array = new Array();
    4. var currentNode : int = 0 ;
    5. var DialogueButtons = false;
    6.  
    7. class ConvNode {
    8.     var prompt : String;
    9.     var options: Array = new Array();
    10. }
    11.  
    12. class OptionButton {
    13.     var reply : String;
    14.     var effects : Array = new Array();
    15.     var link : int;
    16. }
    17.  
    18. function Start () {
    19.  
    20.     //node 0
    21.     nodes[0] = new ConvNode();
    22.     nodes[0].prompt = "...";
    23.     nodes[0].options[0] = new OptionButton();
    24.     nodes[0].options[1] = new OptionButton();
    25.     nodes[0].options[2] = new OptionButton();
    26.     nodes[0].options[0].reply = "Hey buddy, what the hell are you doing?";
    27.     nodes[0].options[0].link = 1;
    28.     nodes[0].options[0].effects = "effect1";
    29.    
    30.     nodes[0].options[1].reply = "Let her go or I'll call the police!";
    31.     nodes[0].options[1].link = 1;
    32.    
    33.     nodes[0].options[2].reply = "Sir, I suggest you let that woman go.";
    34.     nodes[0].options[2].link = 1;
    35.     nodes[0].options[2].effects[0] = "effect2";
    36.     nodes[0].options[2].effects[1] = "effect3";
    37.     // node 1
    38.     nodes[1] = new ConvNode();
    39.     nodes[1].prompt = "Get away from me! I don't want to hurt her but I will. And you, if you don't walk away!";
    40.     nodes[1].options[0] = new OptionButton();
    41.     nodes[1].options[1] = new OptionButton();
    42.     nodes[1].options[0].reply = "Take it easy, we can just talk about it. What happened?";
    43.     nodes[1].options[0].link = 2;
    44.     nodes[1].options[1].reply = "Be smart, man. Give up, you're not going to get away.";
    45.     nodes[1].options[1].link = 2;
    46.     //node 2
    47.     nodes[2] = new ConvNode();
    48.     nodes[2].prompt = "No! I am done talking. Talking is what I've done for the past half year. Talking is what I've been doing in court. I won't let her take my Ben.";
    49.     nodes[2].options[0] = new OptionButton();
    50.     nodes[2].options[1] = new OptionButton();
    51.     nodes[2].options[0].reply = "Your Ben? Is he your kid? Is this woman your wife?";
    52.     nodes[2].options[0].link = 3;
    53.     nodes[2].options[1].reply = "Look, buddy, relax. You're not going to accomplish anything like this. Put down the gun.";
    54.     nodes[2].options[1].link = 3;
    55.     //node 3
    56.     nodes[3] = new ConvNode();
    57.     nodes[3].prompt = "Ben is... He's my son. *to woman* Hear that, bitch? MY son! *to player* She's trying to take him from me. Josh, Ben needs a better father, she said. While he was in the damn living room with us!";
    58.     nodes[3].options[0] = new OptionButton();
    59.     nodes[3].options[1] = new OptionButton();
    60.     nodes[3].options[0].reply = "<say nothing>";
    61.     nodes[3].options[0].link = 4;
    62.     nodes[3].options[1].reply = "<say nothing>";
    63.     nodes[3].options[1].link = 4;  
    64.     //node 4
    65.     nodes[4] = new ConvNode();
    66.     nodes[4].prompt = "Woman: You know Ben deserves better than you! This proves it, doesn't it?";
    67.     nodes[4].options[0] = new OptionButton();
    68.     nodes[4].options[1] = new OptionButton();
    69.     nodes[4].options[0].reply = "<say nothing>";
    70.     nodes[4].options[0].link = 0;
    71.     nodes[4].options[1].reply = "<say nothing>";
    72.     nodes[4].options[1].link = 0;
    73.    
    74. }
    75.  
    76.  
    77. function OnGUI () {
    78.    
    79.     switch(dialogueSwitch) {
    80.        
    81.            
    82.         case 5:
    83.             //print ("five");
    84.             message2 = " ";
    85.             guicharacters = true;
    86.             GUI.DrawTexture (Rect (0, 0, screenwidth, screenheight), textureBackground3, ScaleMode.StretchToFill);
    87.             DialogueButtons = true;
    88.             //DisplayNode (nodes[currentNode]);
    89.         break;
    90.     }
    91.  
    92.     //Message box
    93.     GUI.Box (Rect (screenwidth * 0.02, screenheight * 0.8, screenwidth * 0.96, 120), "Messages");
    94.         //label that reads txt file
    95.     //GUI.Label (Rect (Screen.width * 0.05, Screen.height * 0.83, Screen.width * 0.90, 110), message.text);
    96.         //label that reads ugly hardcode
    97.     GUI.Label (Rect (screenwidth * 0.05, screenheight * 0.83, screenwidth * 0.90, 110), message2);
    98.  
    99.     //character box
    100.     if (guicharacters == true) {
    101.         //avatar box
    102.         GUI.Box (Rect (screenwidth * 0.02, screenheight * 0.02, screenwidth * 0.13, screenheight * 0.21), characterTalking);
    103.         GUI.Label (Rect (screenwidth * 0.03, screenheight * 0.04, screenwidth * 0.145, screenheight * 0.185), characterAvatar);
    104.         //speech box
    105.         GUI.Box (Rect (screenwidth * 0.16, screenheight * 0.02, screenwidth * 0.82, screenheight * 0.17), "Speech");
    106.             //label that reads txtfile
    107.         //GUI.Label (Rect (Screen.width * 0.17, Screen.height * 0.05, Screen.width * 0.80, Screen.height * 0.17), characterSpeech.text);
    108.             //label that reads ugly hardcode
    109.         GUI.Label (Rect (screenwidth * 0.17, screenheight * 0.05, screenwidth * 0.80, screenheight * 0.17), nodes[currentNode].prompt);
    110.        
    111.     }
    112.  
    113.     //dialogue buttons location + toggle
    114.     if (DialogueButtons == true) {
    115.         for (var i:int = 0; i< nodes[currentNode].options.length; i++) {   
    116.        
    117.             if (GUI.Button(Rect (screenwidth * 0.05, screenheight * (0.83 + 0.04 *i), screenwidth * 0.90, 20), nodes[currentNode].options[i].reply)) {
    118.                 for (var j:int = 0; j< nodes[currentNode].options[i].effects.length; j++) {
    119.                     handleeffects(nodes[currentNode].options[i].effects[j]);
    120.                 }
    121.                 currentNode = nodes[currentNode].options[i].link;
    122.             }
    123.         }
    124.     }
    125.    
    126.    
    127.     if (GUI.Button (Rect (30, 150, 80, 20), "Next")) {
    128.         dialogueSwitch = dialogueSwitch + 1;
    129.     }
    130.  
    131.    
    132. }
    133.  
    134.     function handleeffects(input:String):void {
    135.         print(input);
    136.         switch (input) {
    137.             case "effect1":
    138.             break;
    139.  
    140.  
    141.         }
    142.     }
     
  30. zigizal

    zigizal

    Joined:
    Jan 9, 2011
    Posts:
    4
    Is someone able to upload a file with this working and how the structure of the js file and the xml (or txt) file will look. Just a sample. I tried following along with this thread but don't fully grasp everything so a full working sample would be great if possible.
     
  31. xathos

    xathos

    Joined:
    Sep 24, 2009
    Posts:
    23
    This is completely separate code that I developed myself, but here's a Conversation Tree system that I built several several months ago. It's quite rough around the edges, and uses static arrays instead of XML, but it's functional; I was going to update it and release it a while ago, but the newer version (which includes an editor and a completely rebuilt codebase) is as of yet unfinished (original thread is here). I am still planning on releasing it when it's done, but that might be a little while off, unfortunately. If you're willing to wait, I'd advise that you hold off until the *official* version is released; if not, my original project can be downloaded here (link).
     
  32. jbufo

    jbufo

    Joined:
    Aug 16, 2010
    Posts:
    52
    Just wanted to hop on and say this thread helped me out a lot. I had been working on a pretty lame system. I knew I needed to do something with Arrays like what you guys used. For now I'm just reading text straight out of the script. The only change I made was that I used the effects system to call an EndDialogue function that records the link of the last node to a variable that is read in my BeginDialogue function. So basically conversation states so you don't have one big generic conversation. Can post if needed. Pretty easy stuff though. Thanks.
     
  33. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,886
    Sorry to bring this up from the grave, but could somebody help to re-go-over this for me? I'm having some difficulty in understanding how to make my own. I'd also like to get the text from external XML files, so I've got one XML file for each NPC. So yeah. A little help would be awesome!
     
  34. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,886
    Just a little bump here.
     
  35. Purpleshine84

    Purpleshine84

    Joined:
    Apr 8, 2013
    Posts:
    185
    Ok, here is a code, I am working to build a conversation system aswell. I am myself not very good in programming since its all very new for me, but my brother has all the basics for programming and he helped me. What the code does? Well, if you put this within a trigger, you will see text comming up after you pressed "e", when pressed again, the next line etc, its in an array, so all elements you can fill in yourself. The only problem is that when you use Mecanim (in my case) and run into the collider, you will get stuck, but by pressing x you come out of it. I know... its still buggy, but it does work, If you are interested (maybe not at all), I will let you know if we have created better code. And maybe this is not what you mean at all, but its at least a conversation tree..



    Code (csharp):
    1.  
    2. //var MyText     : String = String.Empty;
    3.  
    4.  
    5.  
    6. //var MyText_2   : String = String.Empty;
    7.  
    8.  
    9.  
    10. class MyText extends System.Object {
    11.  
    12.     var MyText : String = String.Empty;
    13.  
    14.     var font : Font;
    15.  
    16.     var fontSize  : int = 20;
    17.  
    18.     var color : Color = Color.white;
    19.  
    20.     var fontStyle : FontStyle;
    21.  
    22.     var AudioSound : AudioClip;
    23.  
    24. };
    25.  
    26.  
    27.  
    28. var MyTexts : MyText[] = new MyText[3];
    29.  
    30.  
    31.  
    32. var time : float;
    33.  
    34. var fading : boolean = false;
    35.  
    36. var hoppa : boolean = false;
    37.  
    38.  
    39.  
    40. var currentItem : int = 0;
    41.  
    42.  
    43.  
    44. var ScreenArea : Rect;
    45.  
    46.  
    47.  
    48. var ScreenArea_2 : Rect;
    49.  
    50.  
    51.  
    52. var TextArea : Rect;
    53.  
    54.  
    55.  
    56. var MyFont     : GUIStyle;
    57.  
    58.  
    59.  
    60. var DialogueBG : Texture2D;
    61.  
    62.  
    63.  
    64. var noob : Hashtable = new Hashtable();
    65.  
    66.  
    67.  
    68.  
    69.  
    70. private var DisplayThis : boolean = false;
    71.  
    72. private var textDisplay : boolean = false;
    73.  
    74.  
    75.  
    76. //function Start()
    77.  
    78. //{
    79.  
    80.    
    81.  
    82. //}
    83.  
    84.  
    85.  
    86. function Update()
    87.  
    88. {
    89.  
    90.  
    91.  
    92.   if (DisplayThis) {
    93.  
    94.       if (Input.GetKeyDown("e"))
    95.  
    96.         {       //Debug.Log("e");{}
    97.  
    98.             if (currentItem == MyTexts.Length-1) {
    99.  
    100.                 currentItem = 0;              
    101.  
    102.                 GetComponent("Animator").enabled = true;
    103.  
    104.             } else {
    105.  
    106.                 audio.Stop();
    107.  
    108.                 currentItem++;
    109.  
    110.                 if (MyTexts[currentItem].AudioSound != null) audio.PlayOneShot(MyTexts[currentItem].AudioSound);
    111.  
    112.                 GetComponent("Animator").enabled = false;
    113.  
    114.             }
    115.  
    116.        
    117.  
    118.             //DisplayThis = !DisplayThis;
    119.  
    120.             //textDisplay = false;
    121.  
    122.         }
    123.  
    124.     }
    125.  
    126.    
    127.  
    128.     //if (!MyTexts[currentItem].MyText.Equals("null")) {
    129.  
    130.     //  try { GetComponent("Animator").enabled = true; } catch(e) { /* dont care */ }
    131.  
    132.     //  currentItem = 0;
    133.  
    134.     //}
    135.  
    136.    
    137.  
    138.     if (Input.GetKeyDown("x")) {
    139.  
    140.         try { GetComponent("Animator").enabled = true; } catch(e) { /* dont care */ }
    141.  
    142.         for (var x : DictionaryEntry in noob) {
    143.  
    144.             Debug.Log("Trigger: " + x.Key + ":" + x.Value);
    145.  
    146.         }
    147.  
    148.     }
    149.  
    150.    
    151.  
    152. }
    153.  
    154.  
    155.  
    156.  
    157.  
    158.  
    159.  
    160. function OnGUI()
    161.  
    162. {
    163.  
    164.     if (DisplayThis || fading) {
    165.  
    166.         if (!MyTexts[currentItem].MyText.Equals("null")) {
    167.  
    168.             GUI.Box(ScreenArea, MyTexts[currentItem].MyText);
    169.  
    170.             GUI.skin.label.font = GUI.skin.button.font = GUI.skin.box.font = MyTexts[currentItem].font;
    171.  
    172.             GUI.skin.label.fontSize = GUI.skin.box.fontSize = GUI.skin.button.fontSize = MyTexts[currentItem].fontSize;
    173.  
    174.             GUI.skin.label.fontStyle = GUI.skin.box.fontStyle = GUI.skin.box.fontStyle = MyTexts[currentItem].fontStyle;
    175.  
    176.             if ((!hoppa)  (!fading)) GUI.skin.label.normal.textColor = GUI.skin.box.normal.textColor = GUI.skin.box.normal.textColor = MyTexts[currentItem].color;
    177.  
    178.             if ((!hoppa)  (!fading)) time = Time.time;
    179.  
    180.         }
    181.  
    182.     } else {
    183.  
    184.         currentItem = 0;
    185.  
    186.     }
    187.  
    188.    
    189.  
    190.     var start : float = time;
    191.  
    192.     var end : float = Time.time;
    193.  
    194.    
    195.  
    196.     if ((end - start) > 1f  (end - start) < 1.4f) {
    197.  
    198.         GUI.skin.label.normal.textColor.a = GUI.skin.box.normal.textColor.a = GUI.skin.box.normal.textColor.a = 90;
    199.  
    200.         hoppa = false;
    201.  
    202.     }
    203.  
    204.    
    205.  
    206.     if ((end - start) > 0.5f  (end - start) < 0.7f) {
    207.  
    208.         GUI.skin.label.normal.textColor.a = GUI.skin.box.normal.textColor.a = GUI.skin.box.normal.textColor.a = 30;
    209.  
    210.         fading = false;
    211.  
    212.     }
    213.  
    214.  
    215.  
    216.  
    217.  
    218.    
    219.  
    220. /*
    221.  
    222.   if (DisplayThis)
    223.  
    224.  
    225.  
    226.      GUI.Box(ScreenArea, MyText);
    227.  
    228.  
    229.  
    230.   if (textDisplay){
    231.  
    232.  
    233.  
    234.   GUI.Box(ScreenArea_2, MyText_2);
    235.  
    236.   }  
    237.  
    238. */
    239.  
    240.    
    241.  
    242.  
    243.  
    244. }
    245.  
    246.  
    247.  
    248.  
    249.  
    250. /*
    251.  
    252.     if (DisplayThis == false) GetComponent.CharacterController etc Characterontroller.enabled = false
    253.  
    254.         {
    255.  
    256.             textDisplay = true;
    257.  
    258.         }
    259.  
    260.         else if (textDisplay == true)
    261.  
    262.         {
    263.  
    264.             DisplayThis = false;
    265.  
    266.         }
    267.  
    268.  
    269.  
    270. */
    271.  
    272.  
    273.  
    274.  
    275.  
    276. function OnTriggerEnter (myTrigger : Collider){
    277.  
    278.     if (myTrigger.name == "Magicalcube") {
    279.  
    280.         DisplayThis = true;
    281.  
    282.     }
    283.  
    284.    
    285.  
    286.     if (!noob.ContainsKey(myTrigger.name)) {
    287.  
    288.         noob.Add(myTrigger.name,true);
    289.  
    290.     } else {
    291.  
    292.         noob[myTrigger.name] = true;
    293.  
    294.     }
    295.  
    296.          //Debug.Log("Trigger");
    297.  
    298.    
    299.  
    300.     //throw new System.Exception("HarryPootjesException");
    301.  
    302. }
    303.  
    304.  
    305.  
    306. function OnTriggerExit (myTrigger : Collider){
    307.  
    308.     if (myTrigger.name == "Magicalcube") {
    309.  
    310.         DisplayThis = false;
    311.  
    312.         fading = true;
    313.  
    314.         hoppa = true;
    315.  
    316.     }
    317.  
    318.    
    319.  
    320.     if (!noob.ContainsKey(myTrigger.name)) {
    321.  
    322.         noob.Add(myTrigger.name,false);
    323.  
    324.     } else {
    325.  
    326.         noob[myTrigger.name] = false;
    327.  
    328.     }
    329.  
    330. }
    331.  
    332.  
    333.  
    334.  
    335.  
    336. /*
    337.  
    338.  
    339.  
    340. //alternatively...
    341.  
    342.  
    343.  
    344.   if (displayThis)
    345.  
    346.  
    347.  
    348.   {
    349.  
    350.  
    351.  
    352.      GUI.Label(ScreenArea, DialogueBG);
    353.  
    354.  
    355.  
    356.      GUI.Label(TextArea, MyText);
    357.  
    358.  
    359.  
    360.   }
    361.  
    362.  
    363.  
    364. */
    365.  
    366.  
    367.  
    368.  
    369.  
     
  36. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,886
    Bringing this up again, thanks.
     
  37. ShadoX

    ShadoX

    Joined:
    Aug 25, 2010
    Posts:
    255
    Haven't tried to do this, but I'd probably would just write most of the dialog in a XML file (since you're talking about it) and just read it in when needed. A XML file could probably contains something like the following lines and contain all the possible dialog for 1 conversation.

    <conversation>
    <nr>1</>
    <text>Hey there</text>
    <answers>
    <a1>
    <text>Hi man!</text>
    <node>17</node>
    </a1>
    <a2>
    <text>...?</text>
    <node>5</node>
    </a2></answers></conversation>

    <conversation>
    <nr>17</nr>
    <text>How are ya?</text>
    <answers>
    <a1>
    <text>fine, you?</text>
    <node>..</node>
    </a1>
    <a2>
    <text>...</text>
    <node>..</node>
    </a2>
    </answrs></conversation>

    <conversation>
    <nr>5</nr>
    <text>Guess you don't wanna talk,fine.</text>
    <answers>
    <a1>
    <text>...</text>
    </a1>
    </answers>
    </conversation>

    The rest would be just a matter of reading in the content and handling it properly. If you chose a answer and it contians a node then you get thrown to that conversation, if it doesn't then conversation ends... and so on.

    If you'd like to use lists/arrays you could just read one conversation into a list and add that list to another list containing all the conversations from the file which would basically be a 2d array or 3d depending on how you feel like coding. Or you could just create objects for each conversation and add them to a list. Losts of options, you just have to pick one.

    I'd love to provide working code, but I'm not really in the mood for that, sorry. Hope this helps or at last gives you and idea or two that might be of some use. :)

    [edit]Or you could try using a DB if that's an option for you.
     
    Last edited: Aug 22, 2013
  38. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,886
    The problem is, I have no idea how to parse XML. I know how to write it ;). I'm just unsure on the way the dialogue system works, and so on, at a basic level.
     
    Last edited: Aug 22, 2013
  39. ShadoX

    ShadoX

    Joined:
    Aug 25, 2010
    Posts:
    255
  40. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,886
    Okay, I think I understand the XML better now.

    Now I tried to use the script on the previous page, compiling all the sections together from andee's posts. I've got this :

    Code (csharp):
    1.  
    2.  class ConvNode {
    3.         var prompt: String;
    4.         var replies: String[];
    5.         var links: int[];
    6.   }
    7.  
    8.   var nodes : ConvNode[] ;
    9.     var currentNode : int = 0 ;
    10.      
    11.    function Start () {
    12.         //number of nodes
    13.         nodes = new ConvNode[3];
    14.         //node 0
    15.         nodes[0] = new ConvNode();
    16.         nodes[0].replies = new String[2];
    17.         nodes[0].links = new int[2];
    18.         nodes[0].prompt = "Testing, does it work?";
    19.         nodes[0].replies[0] = "Yes, it does.";
    20.         nodes[0].replies[1] = "No, it doesn't.";
    21.         // node 1
    22.         nodes[1] = new ConvNode();
    23.         nodes[1].replies = new String[1];
    24.         nodes[1].links = new int[1];
    25.         nodes[1].prompt = "This is node 2!";
    26.         nodes[1].replies[0] = "Awesome!";
    27.      
    28.     }
    29.      
    30.     function DisplayNode (nodes: ConvNode) {
    31.         GUI.Label (Rect (10, 10, 200, 200), nodes.prompt);
    32.         GUI.Label (Rect (10, 220, 200, 200), nodes.replies[0]);
    33.         GUI.Label (Rect (10, 430, 200, 200), nodes.replies[1]);
    34.     }
    35.      
    36.     function OnGUI () {
    37.         DisplayNode (nodes[0]);
    38.        
    39.         if (GUI.Button(Rect (15, 225, 190, 190), " ")) {
    40.             currentNode = nodes[currentNode].links[0];
    41.         }
    42.         else if (GUI.Button (Rect( 15, 435, 190, 190), " ")) {
    43.             currentNode = nodes[currentNode].links[1];
    44.         }
    45.     }
    46.  
    But I get two buttons, and the labels, and when I click the buttons, currentNode isn't changing at all. It just sits there. I'm really kinda struggling on this point. Is it because of the array size being 3- and only having two sections?
     
  41. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,886
    Soz for the extra bump, just making sure this gets answered- it could prove even more valuable for the community if this is solved.
     
  42. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,886
    ANd again,
     
  43. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,886
    And also, the code here from page 2 doesn't work either.

    I have tried to understand it but it is throwing an unknown identifier of 'dialogueSwitch. This has obviously been left out in the code, and where it is supposed to go I don't know.

    All I want to know is how to be able to get the options from any XML file, and just have it displayed and working, for any case. This seems to be a trivial thing for so many people, yet no matter how much I search, I can find nothing on it. Somebody MUST know how this is done! Because right now, I feel like the only person here who doesn't know how the whole conversation tree and XML parsing stuff actually works.
     
    Last edited: Aug 25, 2013
  44. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,886
    Bump again.
     
    Last edited: Aug 29, 2013
  45. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,886
    Is there anyone here that can guide me through this? I've got my script to read from a text file with no issues- but I can't branch conversations this way or give yes and no options- and I know if I used XML, I can. I just have no idea how to structure it, import it, and get what I need out of it. Please?

    E: I don't even know how to structure it properly anymore. This is getting kind of ridiculous. so 6 bumps later, there must be someone who can help.
     
    Last edited: Aug 29, 2013
  46. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,886
    Bump again. I can import XML now. But how do I link it to the whole nodes[0].whatever stuff? So that it works in all cases?
     
  47. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,886
    Bump for the however many times it's been.
     
  48. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,886
    Bump again.
     
  49. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,886
    Bump....
     
  50. cjow

    cjow

    Joined:
    Feb 29, 2012
    Posts:
    132
    I'd just do it with a custom ScriptableObject asset instead. Has the benefit of being automatically serialized by Unity and incredibly easy to edit in-editor.

    Here is an example.