Search Unity

Need to create conversation trees

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

  1. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,887
    Thanks a bundle, I'll give that a look over.

    EDIT: I'm looking through it- but I'm still in need of the branching of the yes/no options. If the conversations were linear with no choices in them, I'd be fine.

    EDIT 2: I think i've cracked how the code works and how to fill it dynamically. This could be the start of something!

    EDIT 2: now all that's left is creating the GUI per node- so if there are replies, create buttons for them, and if not, don't create any buttons. I'm using the ConvNode class mentioned earlier, so what do I fill the DisplayNode function with?

    EDIT 3: Here we go, getting somewhere. I'm currently just using the inspector to fill out each convnode before I add in XML or TXT reading (the former, most likely), and I'm getting an Array out of range error- on seemingly fine code.

    Here's the code :
    Code (csharp):
    1.  
    2. function DisplayNode(node : ConvNode) {
    3.  
    4.     GUI.Label(Rect(10,10,200,30), nodes[currentNode].text);
    5.     if (nodes[currentNode].replies.length > 0) {
    6.    
    7.         if (GUI.Button(Rect(140,40, 30, 30), nodes[currentNode].replies[0])) {
    8.             currentNode = nodes[currentNode].links[0];
    9.         }
    10.         if (GUI.Button(Rect(170,40, 30, 30), nodes[currentNode].replies[1])) {
    11.             currentNode = nodes[currentNode].links[1];
    12.         }
    13.        
    14.     }
    15.  
    16. }
    17.  
    Now, there are only ever two options presented, yes and no, which is why I've done it this way. Upon clicking the "No" button (which is replies[1]), I get taken to node 3, which is the reply's link[x] value for that option. However, when I click the "yes" button, (replies[0] to links[0]), It doesn't take me to node two, which is the value of links[0], I get an "IndexOutOfRangeException: Array index is out of range." error.

    Any idea why one of these works, yet the other doesn't?

    EDIT again! I tried to do a print(nodes[2].links[0]) and I get the error again. Is this perhaps a problem with filling out this in the inspector?

    Yet another edit : I tried assigning to nodes[2].links[0] directly, and the error is still thrown- yet on the line that I try to assign it on.This seems like the script doesn't even know there is a nodes[2].links[0], but knows there's a value for links[1]? I've tried building the node this :

    Code (csharp):
    1.  
    2. nodes[1] = new ConvNode();
    3. nodes[1].text = "Do you?";
    4. nodes[1].replies[0] = "yes";
    5. nodes[1].links[0] = 2;
    6. nodes[1].replies[1] = "no";
    7. nodes[1].links[1] = 3;
    8.  
    And now it throws a nullReference on nodes[1].replies[0] = "yes". Maybe it's about defining the number of replies manually?
     
    Last edited: Sep 4, 2013
  2. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,887
    An update, and I've gone nowhere.
    This code is confusing me for all the wrong reasons.
    Code (csharp):
    1.  
    2. function DisplayNode(node : ConvNode) {
    3.  
    4.     GUI.Label(Rect(10,10,200,30), nodes[currentNode].text);
    5.     if (nodes[currentNode].replies.length > 0) {
    6.    
    7.         if (GUI.Button(Rect(140,40, 30, 30), nodes[currentNode].replies[0])) {
    8.             currentNode = nodes[currentNode].links[0];
    9.         }
    10.         if (GUI.Button(Rect(170,40, 30, 30), nodes[currentNode].replies[1])) {
    11.             currentNode = nodes[currentNode].links[1];
    12.         }
    13.        
    14.     }
    15.    
    16. }
    17.  
    What I've found is no matter the value of the last button (in this case, links[1]) is always returned just fine, but whatever button is before that does not work. I've tried to set the length of the replies and links arrays manually, yet this doesn't work. If I put a currentNode++ in update, it cycles through all nodes just as you'd expect. But for some reason, these buttons won't do it. Anybody?

    EDIT 2- I've replaced the GUI.Buttons in DisplayNode() with Input.GetKeyDowns tied to the key 1 and 2 on the keyboard- and guess what? It works. So why do the GUI buttons not work?

    Code (csharp):
    1.  
    2. function DisplayNode(node : ConvNode) {
    3.  
    4.     GUI.Label(Rect(10,10,200,30), nodes[currentNode].text);
    5.     if (nodes[currentNode].replies.length > 0) {
    6.    
    7.         if (Input.GetKeyDown(KeyCode.Alpha1)) {
    8.     //  if (GUI.Button(Rect(140,40, 30, 30), nodes[currentNode].replies[0])) {
    9.             currentNode = nodes[currentNode].links[0];
    10.         }
    11.         //if (GUI.Button(Rect(170,40, 30, 30), nodes[currentNode].replies[1])) {
    12.         if (Input.GetKeyDown(KeyCode.Alpha2)) {
    13.             currentNode = nodes[currentNode].links[1];
    14.         }
    15.        
    16.     }
    17.    
    18. }
    19.  
     
    Last edited: Sep 5, 2013
  3. Hikiko66

    Hikiko66

    Joined:
    May 5, 2013
    Posts:
    1,304
    The button clicks won't work because they need to be in OnGUI()

    I wouldn't use arrays
    I'd use one dictionary to tie Reply ID's to Link ID's.

    Then I would use another dictionary to Hold All ID's and their associated text
     
    Last edited: Sep 5, 2013
  4. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,887


    I just don't understand why there are so many troubles with this.

    And the button clicks work fine- DisplayNode() is called within OnGUI, like the previous scripts in this topic. I guess I could move to dictionaries, I've never tried. I wouldn't know how to implement them here. All I want to know is why are the GUI Buttons giving me an index out of range when physical key presses are not.
     
    Last edited: Sep 5, 2013
  5. Hikiko66

    Hikiko66

    Joined:
    May 5, 2013
    Posts:
    1,304
    Your keypresses don't make reference to the nodes[currentNode].replies array

    nodes[currentNode].replies must not be holding the data you are expecting it to hold. What is in that array? Debug and take a look. I can see you testing the length before using it..

    I can only assume that nodes[currentNode].replies[1] is the line throwing the error.

    There is only one reply in that array. replies[0]
     
    Last edited: Sep 5, 2013
  6. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,887
    The .replies are just string values- and those print just fine as the labels on buttons. It's when the buttons are clicked on that the error appears. Fix that, and the script works without the need for key pressing.

    EDIT : well you're right, it is the nodes[currentNode].replies[0] that is throwing the error- swapping that out for a simple inline "Yes" string removes the error. Interesting that it prints the string, so it has to be there, yet clicking it throws the error itself.
     
    Last edited: Sep 5, 2013
  7. Hikiko66

    Hikiko66

    Joined:
    May 5, 2013
    Posts:
    1,304
    I think when you click the button, it changes the currentNode
    currentNode = nodes[currentNode].links[0];

    And it's that new node that doesn't have 2 replies which is when it throws the error
     
  8. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,887
    I guess it gets called more than once then? Because in theory, it should just move on- and the buttons only appear when the node has more than one reply.
     
    Last edited: Sep 6, 2013
  9. Hikiko66

    Hikiko66

    Joined:
    May 5, 2013
    Posts:
    1,304
    The buttons appear when there is more than zero replies.

    if (nodes[currentNode].replies.length > 0)

    So, if there is one reply, it will attempt to show two buttons and throw an error.

    If you want to make sure there are atleast two replies then use

    if (nodes[currentNode].replies.length > 1)
     
    Last edited: Sep 6, 2013
  10. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,887
    Well, that actually works. Thanks a bundle!

    Now, I'll go away, wire in some XML Reading, and then some nice boxes for everything and not bump this topic for a while.
     
  11. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,887
    Well, I hit another hitch! What's happening in here is I'm filling in the nodes[] array using XML. Now I can retrieve the XML values just fine- but here's my issue:

    When I call this -
    Code (csharp):
    1. nodeText = dialogueXML.GetValue("dialogue>0>cnode>" +i+ ">text>0>@prompt");
    , and then print it, I get all the values I need.
    When I call this -
    Code (csharp):
    1. nodeText = dialogueXML.GetValue("dialogue>0>cnode>" +i+ ">text>0>@prompt");
    2.         print (nodeText);
    3.  
    I get the same result. All the values in the XML, under the root, within a cnode, with a text tag, are returned fine as expected

    So I make all the values of nodes.text = nodeText, so each time it loops through, it fills it in.However- this is not what happens.
    When I call
    Code (csharp):
    1. print(nodes[i].text);
    after I've assigned it in the above way, this throws a nullReferenceError on the that line- like it's never been filled in.

    I don't quite understand it- the code is clear. I can retrieve the string and print it directly from XML. But when I assign that to a string value, I get nothing.
    Ideas?

    Code (csharp):
    1.  
    2. //the full Awake function.
    3. function Awake () {
    4.  
    5.     currentNode = startingNode;
    6.    
    7.     var numNodes : int;
    8.     var numReplies : int;
    9.     var nodeText : String;
    10.    
    11.     var DialogueXMLFile : TextAsset = Resources.Load("test");
    12.    
    13.     var parser : XMLParser;
    14.     parser = new XMLParser();
    15.    
    16.     var dialogueXML :  XMLNode = parser.Parse(DialogueXMLFile.text);   
    17.    
    18.     if(DialogueXMLFile!= null) {
    19.    
    20.     numNodes = dialogueXML.GetNodeList("dialogue>0>cnode").Count;
    21.         //numNodes = dialogueXML.GetNodeList("enemies>0>enemy").Count;
    22.     //  print (numNodes);
    23.         //print (dialogueXML.GetNodeList("dialogue>0>cnode>0>reply").Count);
    24.         //print (dialogueXML.GetValue("dialogue>0>cnode>0>text>0>@prompt"));
    25.        
    26.      for (var i : int = 0; i < numNodes; i++) {
    27.            
    28.             if (dialogueXML.GetNode("dialogue>0>cnode>"+i+">reply>"+ i) != null){
    29.            
    30.                 numReplies = dialogueXML.GetNodeList("dialogue>0>cnode>"+ i +">reply").Count;
    31.                
    32.                 } else {
    33.                
    34.                 numReplies = 0;
    35.                
    36.             }
    37.            
    38.             nodeText = dialogueXML.GetValue("dialogue>0>cnode>" +i+ ">text>0>@prompt");
    39.         //  print (nodeText);
    40.             nodes = new ConvNode[numNodes];
    41.             nodes[i] = new ConvNode();
    42.            
    43.             //print (dialogueXML.GetValue("dialogue>0>cnode>0>text>0>@prompt"));
    44.            
    45.             nodes[i].text = nodeText;
    46.            
    47.            
    48.            
    49.             if (numReplies > 1 ){
    50.        
    51.                 nodes[i].replies = new String[numReplies];
    52.                 nodes[i].links = new int[numReplies];
    53.                
    54.                 nodes[i].replies[0] = dialogueXML.GetValue("dialogue>0>cnode>"+ i +">reply>0>@text");
    55.                 nodes[i].replies[1] = dialogueXML.GetValue("dialogue>0>cnode>"+ i +">reply>1>@text");
    56.                 nodes[i].links[0] = int.Parse(dialogueXML.GetValue("dialogue>0>cnode>"+ i +">reply>0>@link"));
    57.                 nodes[i].links[1] = int.Parse(dialogueXML.GetValue("dialogue>0>cnode>"+ i +">reply>1>@link")); 
    58.             }
    59.         } print(nodes[1].text);
    60.     }
    61. }
    62.  
     
  12. Hikiko66

    Hikiko66

    Joined:
    May 5, 2013
    Posts:
    1,304
    You're re-initializing the array every time you loop

    Code (csharp):
    1. nodes = new ConvNode[numNodes];
    Should be outside of the loop, directly under
    Code (csharp):
    1. numNodes = dialogueXML.GetNodeList("dialogue>0>cnode").Count;
     
  13. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,887
    Can't believe I missed that. It's been a long day. Thanks!
     
  14. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,887
    Well, I'm having that OnGUI called-too-often problem.

    Code (csharp):
    1.  
    2. function DisplayNode(node : ConvNode) {
    3.  
    4.     GUI.Label(Rect(10,10,300,30), currentText);
    5.    
    6.     if (nodes[currentNode].hasReplies) {
    7.    
    8.         for (var i : int = 0; i < nodes[currentNode].replies.length; i++) {
    9.             if (GUI.Button(Rect(140,i * 40, 30, 30), nodes[currentNode].replies[i])) {
    10.                 currentNode = nodes[currentNode].links[i];
    11.             }
    12.            
    13.             /* if (GUI.Button(Rect(170,40, 30, 30), nodes[currentNode].replies[1])) {
    14.                 currentNode = nodes[currentNode].links[1];
    15.             } */
    16.         }
    17.     }
    18. }
    19.  
    The loop should only be being called if the node's hasReplies flag is checked- but when I switch nodes on a node that hasReplies, I'm getting an nullReferenceException- saying it can't find nodes[currentNode].replies.length- that's fine, because the node doesn't have any replies. However, it's like the if statement is even checking at all. Now if I advance the node by pressing a key again, it displays the node no problem. Just when clicking the buttons generated does it throw the error, on the for line.
     
  15. Hikiko66

    Hikiko66

    Joined:
    May 5, 2013
    Posts:
    1,304
    Your for loop is using the nodes[currentNode].replies.length to work out what its doing, and you're changing it mid loop by assigning a new currentNode. You could assign the currentNode outside of the loop (by using a temp variable inside the loop), or breaking from the loop immediately after the assignment might work.

    Code (csharp):
    1. currentNode = nodes[currentNode].links[i];
    2. break;
    or

    Code (csharp):
    1.  var NewNode = currentNode;
    2. for (var i : int = 0; i < nodes[currentNode].replies.length; i++) {
    3.  
    4.             if (GUI.Button(Rect(140,i * 40, 30, 30), nodes[currentNode].replies[i])) {
    5.  
    6.                 NewNode = nodes[currentNode].links[i];
    7.                 break;
    8.             }
    9. }
    10. currentNode = NewNode;
    11.  
     
    Last edited: Sep 8, 2013
  16. HolBol

    HolBol

    Joined:
    Feb 9, 2010
    Posts:
    2,887
    That's great, never thought about breaking the loop- I would have assumed it'd do it automatically, but I guess that's what break; is for! Thanks man, really appreciate it. In the near future, I'll gather up all the code, and put it up for free on the asset store.