Search Unity

[RELEASED] Dialogue System for Unity - easy conversations, quests, and more!

Discussion in 'Assets and Asset Store' started by TonyLi, Oct 12, 2013.

  1. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Got it, thanks. Appreciate the help.

    Edit: Alright, I just changed the if statement to this:

    Code (csharp):
    1. else if (subtitle.formattedText.text.Contains("[HideResponse "))
    2.         {
    3.             // If subtitle contains special tag [HideResponse #], stop showing a specific interjectable response:
    4.             var text = subtitle.formattedText.text;
    5.             var startIndex = text.IndexOf("[HideResponse ");
    6.             var length = text.Substring(startIndex).IndexOf("]") + 1;
    7.             var tagString = text.Substring(startIndex, length); // Entire [HideResponse #].
    8.             var idString = tagString.Remove(tagString.Length - 1).Remove(0, "[HideResponse ".Length); // Just #.
    9.             List<int> ids = new List<int>();
    10.             if (idString.Contains(","))
    11.             {
    12.                 string[] idsString = idString.Split(',');
    13.                 for(int i = 0; i < idsString.Length; i++)
    14.                 {
    15.                     ids.Add(Tools.StringToInt(idsString[i]));
    16.                 }
    17.             }
    18.             else
    19.             {
    20.                 ids.Add(Tools.StringToInt(idString));
    21.                 subtitle.formattedText.text = text.Remove(startIndex, length);
    22.                 HideResponseWithID(ids);
    23.             }
    24.            
    25.         }
    And a minor edit to the method:

    Code (csharp):
    1. private void HideResponseWithID(List<int> id)
    2.     {
    3.         // Find button that leads to the dialogue entry with the specified ID:
    4.         for(int i = 0; i < id.Count; i++)
    5.         {
    6.             var button = dialogue.responseMenu.instantiatedButtons.Find(x => x.GetComponent<UnityUIResponseButton>().response.destinationEntry.id == id[i]);
    7.             if (button != null)
    8.             {
    9.                 button.SetActive(false);
    10.                 OnHidResponse();
    11.             }
    12.         }
    13.     }
    14.  
     
    Last edited: Dec 21, 2017
  2. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    This is super cool. Tested it out with my own dialogue that I've shown before, and it's looking really nice.

    One question. How do I make the "Timeout" field mandatory for all response entries? If you were looking for a general solution it could be something like, if it stays 0 it doesn't get shown, while if it's something else it does get shown.
     
  3. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    Right now, the ShowResponses looks up each response's Timeout field. If the field doesn't exist, it defaults to 0:
    Code (csharp):
    1. var responseTimeout = Field.LookupFloat(response.destinationEntry.fields, "Timeout");
    You could change it to a different default value (e.g., 42) like this:
    Code (csharp):
    1. var responseTimeout = Field.FieldExists(response.destinationEntry.fields, "Timeout")
    2.     ? Field.LookupFloat(response.destinationEntry.fields, "Timeout")
    3.     : 42;
    Or you could add the Timeout field (with a default value) on the Templates tab, and select Menu > Apply Template To Assets.

    If you want to hide the button if Timeout is 0, you can add this line:
    Code (csharp):
    1.             if (Mathf.Approximately(0, responseTimeout))
    2.             {
    3.                 // If Timeout is zero or doesn't exist, hide the BUTTON //(was: slider if it exists):
    4.                 // var slider = button.GetComponentInChildren<UnityEngine.UI.Slider>();
    5.                 // if (slider != null) slider.gameObject.SetActive(false);
    6.                 button.gameObject.SetActive(false); //<-- ADD THIS LINE.
    7.             }
     
    EternalAmbiguity likes this.
  4. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Good to know. Another question. I was looking through the documentation on how to set up keyboard support. I found something on the "Legacy UI" subpage, but it doesn't seem to be applicable to the current setup.

    If I want to use a keyboard button, such as "Q," to cycle through choices, and use another to select them, how would I do that?
     
  5. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    It uses regular Unity UI navigation. You can change the button assignments on the scene's EventSystem. By default, you can use the Horizontal and Vertical input axes to navigate, and Space or Return to submit (click) a button. First inspect your dialogue UI and tick Auto Focus. This auto-highlights the first button so Unity UI has a starting point to navigate from.
     
    EternalAmbiguity likes this.
  6. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Alright, I did that and it's working. I had to enable "allow steal focus as well" because the ability to move up and down disappeared when moving beyond the first dialogue entry where the choices were introduced--but it's working now.

    One thing, not majorly important: each time a new dialogue entry (by the NPC) appears, the player's selected option returns to the top. Like, I press Q a couple of times to move down the list of responses, but when the NPC's line ends and the next one begins it jumps back to the top response. Is there any simple way to fix this?

    Edit: Another thing entirely. How do I loop a dialogue entry until a player selects an option? Not just stopping like the normal use case of this system--but looping it? I apologize if this is described somewhere obvious and I've overlooked it.
     
    Last edited: Dec 22, 2017
  7. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    Since we're manipulating the list of response buttons, I'm sure there we'll need to do a few things like this to get navigation set up perfectly. If the Unity UI Dialogue UI's Explicit Navigation checkbox is ticked (which is the default), then the basic ShowResponses method will set up explicit navigation between response buttons. This tells Unity UI which UI element to navigate to when the player navigates up, down, left, or right. It makes it so the player can't accidentally navigate away from the response buttons. But, in your case, we may then deactivate some response buttons when they time out. This leave gaps that allow the player to navigate to an inactive response button. The quick and dirty solution is to tick Allow Steal Focus, which you did. This forces the focus back onto an active response button. Another solution would be to set up explicit navigation again after adding or removing response buttons.

    In the part of ShowSubtitle where it calls ShowResponses, record EventSystem.currentSelectedGameObject before calling ShowResponses. After, set EventSystem.currentSelectedGameObject to the recorded value.

    Do you want to keep replaying the same dialogue entry, or just play it once and wait on it until the player selects an option? I'm going to guess the latter. To do this, set the Sequence to something that never ends, such as:
    Code (csharp):
    1. WaitForMessage(Forever)
    The message, "Forever", is an arbitrary string. As long as you don't have any code that calls Sequencer.Message("Forever"), the Sequence will never end, so the dialogue entry will stay onscreen until the player clicks an option.
     
  8. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    I'll take a look at the rest in a moment, but...

    The first, actually. This is a request based on a specific circumstance, not how I intend to regularly use the system. The scenario I'm thinking of begins with something like a voice asking the user for a response repeatedly until they pick a choice.

    (wasn't thinking of it this way, but actually this will allow the user to get familiar with the somewhat unique system of only navigating by "Q" and "E")
     
  9. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    Inspect the dialogue entry node. In the "Link To:" dropdown, I think you can select the same node, which should cause it to loop on itself.
     
  10. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Excellent, thank you.

    Hmm. Tried adding this

    Code (csharp):
    1. var currentSelection = UnityEngine.EventSystems.EventSystem.currentSelectedGameObject;
    right before "ShowResponses(subtitle, DialogueManager.CurrentConversationState.pcResponses, 0);", but I'm getting an "object reference required for non-static field" error. Any tips there?

    And for that matter--will this be an issue the first time the responses get shown? Any time after that it should default to the last selection, but the time before that it has nothing to select. And what about when all Dialogue options are removed, the conversation goes on, and then new ones appear? Will the old selection interfere there?
     
  11. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    @EternalAmbiguity - Try:
    Code (csharp):
    1. var currentSelection = UnityEngine.EventSystems.EventSystem.current.currentSelectedGameObject;
    After calling ShowResponses, you could look through dialogue.responseMenu.instantiatedButtons. If currentSelection is equal to one of the buttons, select it:
    Code (csharp):
    1. foreach (var button in dialogue.responseMenu.instantiatedButtons) {
    2.     if (currentSelection == button) UnityEngine.EventSystems.EventSystem.SetSelectedGameObject(button);
    3. }
     
  12. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Okay, I had to pull the "var" out and make it a GameObject instead so I could access it from within the ShowResponses method (while setting it in the ShowSubtitle method).

    To be clear, I start with this:

    Code (csharp):
    1. GameObject currentSelection;
    at the very top, right below "private bool showingInterjectableResponses = false;". I then move on to what I had before slightly modified, this--

    Code (csharp):
    1. currentSelection = UnityEngine.EventSystems.EventSystem.current.currentSelectedGameObject;
    right after entering the "if(hasInterjectableResponses)" statement and right before the "ShowResponses" method.

    I then have this:

    Code (csharp):
    1. UnityEngine.EventSystems.EventSystem.current.SetSelectedGameObject(currentSelection);
    right at the beginning of the ShowResponses method, right before "List<GameObject> existingButtons = null;".

    Unfortunately, not working.

    Haven't had a chance to check the foreach yet since the above isn't working.
     
  13. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    @EternalAmbiguity - All the code can go in ShowSubtitle. See the code below. It's near the bottom. I'm out of the office at the moment, so I'm afriad I can't test it.

    Code (csharp):
    1.     public override void ShowSubtitle(Subtitle subtitle)
    2.     {
    3.         if (subtitle.formattedText.text.Contains("[HideResponses]"))
    4.         {
    5.             // If subtitle contains special tag [HideResponses], stop showing all interjectable responses:
    6.             subtitle.formattedText.text = subtitle.formattedText.text.Replace("[HideResponses]", string.Empty);
    7.             HideAllResponses();
    8.         }
    9.         else if (subtitle.formattedText.text.Contains("[HideResponse "))
    10.         {
    11.             // If subtitle contains special tag [HideResponse #], stop showing a specific interjectable response:
    12.             var text = subtitle.formattedText.text;
    13.             var startIndex = text.IndexOf("[HideResponse ");
    14.             var length = text.Substring(startIndex).IndexOf("]") + 1;
    15.             var tagString = text.Substring(startIndex, length); // Entire [HideResponse #].
    16.             var idString = tagString.Remove(tagString.Length - 1).Remove(0, "[HideResponse ".Length); // Just #.
    17.             var id = Tools.StringToInt(idString);
    18.             subtitle.formattedText.text = text.Remove(startIndex, length);
    19.             HideResponseWithID(id);
    20.         }
    21.         // Show the subtitle:
    22.         base.ShowSubtitle(subtitle);
    23.         // If the NPC is speaking but the state also has valid PC responses, show them as interjectable responses:
    24.         if (subtitle.speakerInfo.IsNPC)
    25.         {
    26.             // Save current selection:
    27.             var currentSelection = UnityEngine.EventSystems.EventSystem.current.currentSelectedGameObject;
    28.             var hasInterjectableResponses = DialogueManager.CurrentConversationState.HasNPCResponse && DialogueManager.CurrentConversationState.HasPCResponses;
    29.             if (hasInterjectableResponses)
    30.             {
    31.                 ShowResponses(subtitle, DialogueManager.CurrentConversationState.pcResponses, 0);
    32.                 showingInterjectableResponses = true;
    33.             }
    34.             // Re-select the current selection:
    35.             foreach (var button in dialogue.responseMenu.instantiatedButtons)
    36.             {
    37.                 if (currentSelection == button) UnityEngine.EventSystems.EventSystem.current.SetSelectedGameObject(button);
    38.             }
    39.         }
    40.     }
     
  14. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Hmm. Tried that, it isn't working. Well, whenever you get the chance to check it I'd appreciate it.
     
  15. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    Okay, I steered you wrong. Here's a working example:

    InterjectableResponsesExample_2017-12-22.unitypackage

    The problem is that Auto Focus briefly selects the continue button, even if you're not using a continue button mode. So the solution I suggested wouldn't work. Instead, I added an Event Trigger component to the response button template and pointed its Select event to a new method InterjectableDialogueUI.OnSelectedButton:
    Code (csharp):
    1. public void OnSelectedButton(UnityEngine.EventSystems.BaseEventData eventData)
    2. {
    3.     currentButtonSelection = eventData.selectedObject;
    4. }
    where currentButtonSelection is a private GameObject variable I added to the class.

    The end of ShowSubtitle runs this line:
    Code (csharp):
    1. if (currentButtonSelection != null) UnityEngine.EventSystems.EventSystem.current.SetSelectedGameObject(currentButtonSelection);
    which re-selects the last-selected response button.
     
    EternalAmbiguity likes this.
  16. Funbakersstudio

    Funbakersstudio

    Joined:
    Oct 24, 2016
    Posts:
    1
    Hello! We are using Dialogue System with i2 localization in Unity. (http://www.inter-illusion.com/tools/i2-localization) i2 has parsed DS database, but Dialogue System cannot get i2 database. We have installed third party support package for i2. Need guidelines and help!
     
  17. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    Hi @Funbakersstudio - What versions of the Dialogue System and I2 Localization are you using? Are there any errors or warnings in the Unity editor's Console window?
     
  18. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Alright, I'm going to take a look at this in a little while. Thanks. Does it update when the player picks a choice and changes the "path" through the dialogue tree/grid/M.C.Escher walkway?

    One more question, hopefully the last for this project: is there a way to cut off the NPC's line when the player chooses a response? Currently it continues to play out the NPC line and merely places the player's below it, but I'd like it to stop the NPC's line (and audio as well) and just move forward with the player's response.
     
  19. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    Yes.

    Add this to the beginning of the InterjectableDialogueUI's OnClick(object data) method:
    Code (csharp):
    1. dialogue.npcSubtitle.Hide();
    2. DialogueManager.Instance.GetComponent<Sequencer>().Stop();

    I forgot to post back here that I worked with Funbakers over Skype, and we identified an issue with the length of i2 terms that the Dialogue System could generate. The updated i2 Localization support package on the Dialogue System Extras page (and in version 1.7.7.1) generates shorter terms that i2 is happier with.

    While you're reading this, check out Funbakers' Silent Streets, a really interesting mobile AR game that transports you to the mysterious streets of a foggy Victorian port town. The writing looks really good! (The game isn't available in all countries yet, though.)
     
    EternalAmbiguity likes this.
  20. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Okay, so I tried adding the latest version of the InterjectableResponses package to my project and then just tried running it (with a test dialogue of mine) to see if it maintained the player's "place" in the list of responses. Didn't work, however I just tried a new project with an import of the evaluation version and the latest of Interjectable Responses and it worked. So I'll see if I can figure out why it didn't work for my dialogue.

    One problem: when the option the player had selected was removed, the "selector" disappeared and I could no longer move up or down. You see this if you pick that option that first disappears in your test dialogue.

    In such a case I wouldn't mind if it jumped back to the beginning, or moving to one of the adjacent options would be okay. But either way, any idea how to fix that?
     
  21. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    Good catch. In the HideResponseWithID method, add this:
    Code (csharp):
    1. button.SetActive(false);
    2. OnHidResponse();
    3. // ADD THE LINES BELOW:
    4. if (dialogue.responseMenu.instantiatedButtons.Length > 0)
    5. {
    6.     UnityEngine.EventSystems.EventSystem.current.SetSelectedGameObject(dialogue.responseMenu.instantiatedButtons[0]);
    7. }
    This will jump back to the beginning.

    EDIT: Corrected code:
    Code (csharp):
    1.     private void HideResponseWithID(int id)
    2.     {
    3.         // Find button that leads to the dialogue entry with the specified ID:
    4.         var button = dialogue.responseMenu.instantiatedButtons.Find(x => x.GetComponent<UnityUIResponseButton>().response.destinationEntry.id == id);
    5.         if (button != null)
    6.         {
    7.             var wasSelected = UnityEngine.EventSystems.EventSystem.current.currentSelectedGameObject == button;
    8.  
    9.             button.SetActive(false);
    10.             OnHidResponse();
    11.  
    12.             // If this was the selected button, select another one:
    13.             if (wasSelected && dialogue.responseMenu.instantiatedButtons.Count > 0)
    14.             {
    15.                 UnityEngine.EventSystems.EventSystem.current.SetSelectedGameObject(dialogue.responseMenu.instantiatedButtons[0]);
    16.             }
    17.         }
    18.     }
     
    Last edited: Dec 31, 2017
    EternalAmbiguity likes this.
  22. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Hmm. This...doesn't really work.

    First thing is that apparently it's "Count" instead of "Length." But anyway, in the example scene, after adding this code, if I move to the middle option and wait for it to disappear, the "selection" disappears as well. The "selection" highlighting does not appear on the first option, nor can I move up and down to select. However, after the second option is removed and the final one remains, then the selection highlighting appears again. Strange.
     
  23. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    I corrected the code excerpt in my post above.

    Here's the updated example: InterjectableResponsesExample_2017-12-31.unitypackage

    And the full script for reference:

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections.Generic;
    3. using PixelCrushers.DialogueSystem;
    4.  
    5. /// <summary>
    6. /// Overrides UnityUIDialogueUI with handling for response menus that can occur while
    7. /// the NPC is talking (called interjectable responses in this example).
    8. /// </summary>
    9. public class InterjectableDialogueUI : UnityUIDialogueUI
    10. {
    11.  
    12.     // Are we showing interjectable responses?
    13.     private bool showingInterjectableResponses = false;
    14.  
    15.     // How many responses are currently visible?
    16.     private int numVisibleResponses;
    17.  
    18.     private GameObject currentButtonSelection = null;
    19.  
    20.     public override void Open()
    21.     {
    22.         base.Open();
    23.         showingInterjectableResponses = false; // When conversation starts, we're not showing any interjectable responses.
    24.         numVisibleResponses = 0;
    25.         currentButtonSelection = null;
    26.     }
    27.  
    28.     public override void ShowSubtitle(Subtitle subtitle)
    29.     {
    30.         if (subtitle.formattedText.text.Contains("[HideResponses]"))
    31.         {
    32.             // If subtitle contains special tag [HideResponses], stop showing all interjectable responses:
    33.             subtitle.formattedText.text = subtitle.formattedText.text.Replace("[HideResponses]", string.Empty);
    34.             HideAllResponses();
    35.         }
    36.         else if (subtitle.formattedText.text.Contains("[HideResponse "))
    37.         {
    38.             // If subtitle contains special tag [HideResponse #], stop showing a specific interjectable response:
    39.             var text = subtitle.formattedText.text;
    40.             var startIndex = text.IndexOf("[HideResponse ");
    41.             var length = text.Substring(startIndex).IndexOf("]") + 1;
    42.             var tagString = text.Substring(startIndex, length); // Entire [HideResponse #].
    43.             var idString = tagString.Remove(tagString.Length - 1).Remove(0, "[HideResponse ".Length); // Just #.
    44.             var id = Tools.StringToInt(idString);
    45.             subtitle.formattedText.text = text.Remove(startIndex, length);
    46.             HideResponseWithID(id);
    47.         }
    48.  
    49.         // Show the subtitle:
    50.         base.ShowSubtitle(subtitle);
    51.  
    52.         // If the NPC is speaking but the state also has valid PC responses, show them as interjectable responses:
    53.         if (subtitle.speakerInfo.IsNPC)
    54.         {
    55.             var hasInterjectableResponses = DialogueManager.CurrentConversationState.HasNPCResponse && DialogueManager.CurrentConversationState.HasPCResponses;
    56.             if (hasInterjectableResponses)
    57.             {
    58.                 ShowResponses(subtitle, DialogueManager.CurrentConversationState.pcResponses, 0);
    59.                 showingInterjectableResponses = true;
    60.             }
    61.             if (currentButtonSelection != null) UnityEngine.EventSystems.EventSystem.current.SetSelectedGameObject(currentButtonSelection);
    62.         }
    63.     }
    64.  
    65.     public void OnSelectedButton(UnityEngine.EventSystems.BaseEventData eventData)
    66.     {
    67.         currentButtonSelection = eventData.selectedObject;
    68.     }
    69.  
    70.     public override void ShowResponses(Subtitle subtitle, Response[] responses, float timeout)
    71.     {
    72.         // If we're already showing interjectable responses, save them in a temporary list:
    73.         List<GameObject> existingButtons = null;
    74.         if (showingInterjectableResponses)
    75.         {
    76.             existingButtons = dialogue.responseMenu.instantiatedButtons;
    77.             dialogue.responseMenu.instantiatedButtons.Clear();
    78.         }
    79.  
    80.         // Set up the new response buttons:
    81.         base.ShowResponses(subtitle, responses, timeout);
    82.         numVisibleResponses = responses.Length;
    83.  
    84.         // Set response-specific timeouts:
    85.         for (int i = 0; i < responses.Length; i++)
    86.         {
    87.             var response = responses[i];
    88.             var button = dialogue.responseMenu.instantiatedButtons[i];
    89.             var responseTimeout = Field.LookupFloat(response.destinationEntry.fields, "Timeout");
    90.             if (Mathf.Approximately(0, responseTimeout))
    91.             {
    92.                 // If Timeout is zero or doesn't exist, hide the slider if it exists:
    93.                 var slider = button.GetComponentInChildren<UnityEngine.UI.Slider>();
    94.                 if (slider != null) slider.gameObject.SetActive(false);
    95.             }
    96.             else if (responseTimeout > 0)
    97.             {
    98.                 // If response has a positive Timeout field, set up the timer with Timeout's value:
    99.                 StartResponseTimer(button, responseTimeout);
    100.             }
    101.             else if (responseTimeout < 0)
    102.             {
    103.                 // If response has a negative Timeout field, compute the timer value from the dialogue tree:
    104.                 StartResponseTimer(button, ComputeDurationUntilHideResponseWithID(response.destinationEntry.id));
    105.             }
    106.         }
    107.  
    108.         // If we were already showign interjectable responses, re-add them to the button list:
    109.         if (showingInterjectableResponses)
    110.         {
    111.             dialogue.responseMenu.instantiatedButtons.AddRange(existingButtons);
    112.         }
    113.     }
    114.  
    115.     private void StartResponseTimer(GameObject button, float timeout)
    116.     {
    117.         var timedResponseButton = button.GetComponent<TimedResponseButton>();
    118.         if (timedResponseButton == null) timedResponseButton = button.gameObject.AddComponent<TimedResponseButton>();
    119.         timedResponseButton.StartTimer(timeout, OnHidResponse);
    120.     }
    121.  
    122.     public void OnHidResponse()
    123.     {
    124.         // If a response button just disappeared, update the count.
    125.         // If none are left, hide the response menu.
    126.         numVisibleResponses--;
    127.         if (numVisibleResponses <= 0)
    128.         {
    129.             showingInterjectableResponses = false;
    130.             HideResponses();
    131.         }
    132.     }
    133.  
    134.     private void HideResponseWithID(int id)
    135.     {
    136.         // Find button that leads to the dialogue entry with the specified ID:
    137.         var button = dialogue.responseMenu.instantiatedButtons.Find(x => x.GetComponent<UnityUIResponseButton>().response.destinationEntry.id == id);
    138.         if (button != null)
    139.         {
    140.             var wasSelected = UnityEngine.EventSystems.EventSystem.current.currentSelectedGameObject == button;
    141.  
    142.             button.SetActive(false);
    143.             OnHidResponse();
    144.  
    145.             // If this was the selected button, select another one:
    146.             if (wasSelected && dialogue.responseMenu.instantiatedButtons.Count > 0)
    147.             {
    148.                 UnityEngine.EventSystems.EventSystem.current.SetSelectedGameObject(dialogue.responseMenu.instantiatedButtons[0]);
    149.             }
    150.         }
    151.     }
    152.  
    153.     private void HideAllResponses()
    154.     {
    155.         showingInterjectableResponses = false;
    156.         HideResponses();
    157.     }
    158.  
    159.     public override void HideResponses()
    160.     {
    161.         // Only hide if we're not showing interjectable responses:
    162.         if (!showingInterjectableResponses)
    163.         {
    164.             base.HideResponses();
    165.             numVisibleResponses = 0;
    166.         }
    167.     }
    168.  
    169.     public override void OnClick(object data)
    170.     {
    171.         // Hide NPC subtitle and stop its sequence:
    172.         dialogue.npcSubtitle.Hide();
    173.         DialogueManager.Instance.GetComponent<Sequencer>().Stop();
    174.  
    175.         // After clicking, we're no longer showing interjectable responses:
    176.         showingInterjectableResponses = false;
    177.  
    178.         base.OnClick(data);
    179.     }
    180.  
    181.     private float ComputeDurationUntilHideResponseWithID(int id)
    182.     {
    183.         // To compute the duration, we'll simulate a run through the conversation until
    184.         // we reach [HideResponses], [HideResponse id], or no NPC responses.
    185.         var hideResponseTag = "[HideResponse " + id + "]";
    186.  
    187.         // First, we save the actual state of the variables:
    188.         // var saved = PersistentDataManager.GetSaveData(); //<-- This line is overkill; it saves everything. We just save variables below:
    189.         var savedVariableTable = VariableTableUtility.SaveVariableTable();
    190.  
    191.         // Then we create a new conversation model. This one will run through the
    192.         // conversation's nodes without using a dialogue UI.
    193.         var model = new ConversationModel(DialogueManager.MasterDatabase, DialogueManager.LastConversationStarted,
    194.             DialogueManager.CurrentActor, DialogueManager.CurrentConversant,
    195.             false, DialogueManager.IsDialogueEntryValid, DialogueManager.CurrentConversationState.subtitle.dialogueEntry.id);
    196.         float duration = 0;
    197.         int safeguard = 0; // Follow a maximum of 999 nodes to prevent unexpected infinite loops.
    198.         var done = false;
    199.         var state = model.FirstState;
    200.         while (!done && safeguard < 999)
    201.         {
    202.             var text = state.subtitle.formattedText.text;
    203.             if (!state.HasNPCResponse || text.Contains("[HideResponses]") || text.Contains(hideResponseTag))
    204.             {
    205.                 // If there are no more NPC nodes or we need to hide our response, we're done:
    206.                 done = true;
    207.             }
    208.             else
    209.             {
    210.                 // Otherwise add the node's duration and progress to the next node:
    211.                 duration += GetDefaultSubtitleDuration(text);
    212.                 state = model.GetState(state.FirstNPCResponse.destinationEntry);
    213.             }
    214.         }
    215.  
    216.         // Finally, restore the saved state:
    217.         //PersistentDataManager.ApplySaveData(saved); //<-- Corresponding overkill line.
    218.         VariableTableUtility.RestoreVariableTable(savedVariableTable);
    219.  
    220.         return duration;
    221.     }
    222.  
    223.     private float GetDefaultSubtitleDuration(string text)
    224.     {
    225.         int numCharacters = string.IsNullOrEmpty(text) ? 0 : Tools.StripRichTextCodes(text).Length;
    226.         return Mathf.Max(DialogueManager.DisplaySettings.GetMinSubtitleSeconds(), numCharacters / Mathf.Max(1, DialogueManager.DisplaySettings.GetSubtitleCharsPerSecond()));
    227.     }
    228.  
    229. }
     
    EternalAmbiguity likes this.
  24. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Thanks. Tried this on my laptop...and it didn't work. Tried it on my desktop and it worked fine. Smh.
     
  25. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    Different version of Unity or the Dialogue System? I used vanilla Unity 5.6.0 and Dialogue System 1.7.6.
     
  26. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Both are using the eval version with the file name 1_7_7_1. Laptop is on Unity 2017.1.2f1. Desktop is on 2017.1.0f3. One more thing was different, this may have caused it: On my laptop, I imported the InterjectableResponses package first (causing numerous errors until the next step), then the eval version. On the desktop, I imported the eval version first.
     
  27. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    Happy New Year, everyone!

    I'm not sure why that would do it, but maybe there was some obscure compilation issue.
     
  28. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Hmm. Think I discovered it. It still doesn't work if you use the WASD keys, but it does work with the arrow keys. Very strange.

    This brings up another thing. As I've mentioned before, my goal is to use this while the player is moving and possibly interacting with things. As a result, I'd like to isolate the Dialogue System inputs to their own special buttons, rather than the universal "forward" or "backward." Is there somewhere where I can manually put in the input names it accepts?
     
  29. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    Technically, when it comes to the dialogue UI, you can do whatever you want by implementing the C# IDialogueUI interface.

    However, in the example we've been working on, we've been using the UnityUIDialogueUI implementation that ships with the Dialogue System. This implementation uses standard Unity UI navigation, which is driven by the scene's EventSystem. A typical EventSystem looks like this:



    The Event System script keeps track of what's selected, and routes events to selectable UI elements.

    The Standalone Input Module is the default input handler. When it detects input on the input axis assigned to Vertical Axis, it tells the Event System to navigate to the selectable that's above or below the current selection.

    You can define a new input axis in your project's Input Manager. In the example below, I defined an input axis named "UIVertical" bound to the keypad's + and - keys:



    And then you can assign that input axis to the Event System:



    Note that this will affect all Unity UI navigation, not just your dialogue UI.

    If you want it to only apply when a conversation is running, you could add this as a second Standalone Input Module on the EventSystem GameObject, and disable it. Then add a Dialogue System Events component to your Dialogue Manager. Configure the OnConversationStart event to disable the original Standalone Input Module and enable your custom one. Configure OnConversationStart to undo that.
     
    EternalAmbiguity likes this.
  30. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    Version 1.7.7.1 Available On Asset Store!

    This is a big update, with lots of improvements and some bug fixes.

    Release Notes:

    Core
    • Updated for compatibility with Unity 2017.3.
    • Editor:
      • Improved: Conversation node editor now highlights incoming link arrows in a different color.
      • Improved: Added option to show node ID on nodes.
      • Improved: Moved Link to: "Another conversation" & "New entry" to top of dropdown menu.
      • Improved: Auto-backups no longer inherit original asset's AssetBundle designation.
      • Improved: Removing field from template now gives option to remove field from all assets in database.
      • Changed: Inspector "-" button no longer also deletes child node, making it more consistent with node context menu.
      • Fixed: Releasing MMB no longer deselects node.
      • Fixed: Auto backup error message.
      • Fixed: When changing databases, actor dropdown didn't refresh to new database's actor list.
      • Improved: Added search bar to localized text table editor.
    • Unity UI:
      • Improved: Dialogue UI now allows overrides on extra actors, not just primary participants.
      • Improved: UnityUITypewriterEffect automatically adds audio source if necessary.
      • Improved: Dialogue UI uses UnityUITypewriterEffect more effectively by calling it directly instead of relying on OnEnable.
      • Fixed: QuestLogWindow auto-scrollbar didn't work when Canvas was set to Pixel Perfect mode.
      • Fixed: Clicking QuestLogWindow Track button on/off many times fast would cause increasingly slower updates.
      • Fixed: UnityUISelectorDisplay & OverrideUnityUIControls continue button weren’t syncing with Dialogue Manager after load game.
      • Changed: Unity UI JRPG prefab now shows player image and name with response menu.
    • Added: Ability to override QuestLog.Current/SetQuestState, Current/SetQuestEntryState, and StringToState implementations.
    • Improved: Conversations now set Variable["ActorIndex"] and Variable["ConversantIndex"] to participants' indices in Actor[] Lua table.
    • Improved: The {{end}} keyword is now available in bark sequences.
    • Improved: Selector / ProximitySelector now has UnityEvents.
    • Improved: Added option to save all conversation fields.
    • Improved: Persistent Active Data now has an option to check the condition on start, not just when applying persistent data.
    • Improved: Exposed utility methods in PersistentDataManager.
    • Fixed: DialogueSystemController acting as singleton now calls Destroy instead of DestroyImmediate.
    • Fixed: Timeline() sequencer command no longer destroys GameObject unless it was instantiated for the command.
    • Fixed: Calling PersistentDataManager.ApplyData() with an empty string and SimStatus enabled now keeps SimStatus intact.
    • Fixed: Persistent Data Manager SimX bug.
    Third Party Support
    • articy:draft:
      • Updated for articy:draft 3.1.8 compatibility.
      • CHANGED: Articy Converter window's "FlowFragments Are" dropdown has new option. Check your current value before converting.
      • Added: support for Documents.
      • Fixed: Conditions on input pins are now converted.
      • Fixed: When "FlowFragments Are" is set to Nested Conversation Groups, conversations follow FlowFragments' subconversations.
      • Fixed: Order of jumps and conditions (by vertical node position).
    • CSV: Added additional error reporting to CSV Converter.
    • Corgi Platformer Engine: Updated for Corgi 1.4 & Inventory Engine support.
    • Customizable SciFi Holo Interface: Added support.
    • NLua: Optimized Get/SetTableField, implemented compress SimStatus saving (smaller save files); handles blank table element names (e.g., blank actor names) more gracefully.
    • I2 Localization: Added support.
    • Ink: Updated for Unity 2017.2 compatibility.
    • Inventory Engine: Added support.
    • PlayMaker:
      • Dialogue System Events To PlayMaker component now doesn't send events to disabled FSMs.
      • Set/GetVariable actions now support Vector3. Added Sync<Type> actions.
    • Realistic FPS Prefab: Updated for RFPS 1.44.
    • Rog: Added ReplaceActorSprite() Lua function.
    • RPG Kit: Updated Dialogue Manager prefab & Loading scene for RPG Kit 3.1.8.
    • TextMesh Pro: Improved dialogue UI animation transitions. Added TextMeshProSelectorDisplay component.
     
  31. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    articy:draft 3.1.9 Available - Fixes XML Export Bug

    Nevigo just released articy:draft version 3.1.9, which is now the default download on their download page. This update fixes an XML export bug that caused articy to fail to export some content to the XML file. If you're using articy with the Dialogue System and you notice some content (usually Global Variables) is missing, consider updating to 3.1.9.
     
    BackwoodsGaming likes this.
  32. Eleid

    Eleid

    Joined:
    Dec 8, 2015
    Posts:
    7
    Hi!

    I would like to export conversations to translate them but I see one problem. When I use csv file which is the most comfortable to me, dialogue entries are sorted by conversation id then entry id. It is fine and expected. The problem is that I have mixed ids for entries. There is 1 then 4 then 2 then 28 then 13. A translator will be completely out of context what is unacceptable for us. I would like to change all indexes in all conversations to more linear way. It will allow us to send the csv file to the translator.

    I attached file with red wrong current values and green expected ones.

    I am a technical man so I can write a script for that but I don't know where I should start. Can you help me with this issue? I don't want to do it manually because I have a lot of entries and I will expect the same issue in the future.
     

    Attached Files:

  33. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    Hi @Eleid! The Dialogue Editor has two features that may be helpful. Version 1.7.7.1 has an option to show ID numbers. In the screenshot below, the conversation's ID numbers are in a mixed order:



    In version 1.7.4 and above, you can sort them using menu item Sort > Reorder IDs > This Conversation:



    Note that this sorts them down, not across. This helps to follow a single line of thought. For example, AAA-->DDD-->GGG might be one topic ("Tell me about the murder victim."), while BBB-->EEE-->HHH might be another topic ("What about the murder weapon?").

    If you don't want to use that order, you can write a script to reorder IDs. This Dialogue Editor code might be helpful:

    Code (csharp):
    1.         private void ChangeEntryIDEverywhere(int conversationID, int oldID, int newID)
    2.         {
    3.             for (int c = 0; c < database.conversations.Count; c++)
    4.             {
    5.                 var conversation = database.conversations[c];
    6.                 for (int e = 0; e < conversation.dialogueEntries.Count; e++)
    7.                 {
    8.                     var entry = conversation.dialogueEntries[e];
    9.                     if (conversation.id == conversationID && entry.id == oldID)
    10.                     {
    11.                         entry.id = newID;
    12.                     }
    13.                     for (int i = 0; i < entry.outgoingLinks.Count; i++)
    14.                     {
    15.                         var link = entry.outgoingLinks[i];
    16.                         if (link.originConversationID == conversationID && link.originDialogueID == oldID) link.originDialogueID = newID;
    17.                         if (link.destinationConversationID == conversationID && link.destinationDialogueID == oldID) link.destinationDialogueID = newID;
    18.                     }
    19.                 }
    20.             }
    21.         }
    Example, using a swap so you don't duplicate IDs:
    Code (csharp):
    1. // In conversation 42, swap IDs 3 and 7:
    2. ChangeEntryIDEverywhere(42, 3, 99999); // Change ID 3 to a temporary ID 99999.
    3. ChangeEntryIDEverywhere(42, 7, 3); // Change ID 7 to ID 3.
    4. ChangeEntryIDEverywhere(42, 99999, 7); // Change ID 99999 to ID 7.

    If you want to unpack the source code, you'll find the code at the bottom of Dialogue System / Scripts / Editor / Dialogue Editor / DialogueEditorWindowConversationSection.cs.
     
  34. louiselindvall

    louiselindvall

    Joined:
    Jun 26, 2017
    Posts:
    3
    Hi, I'm using Unity 2017.1 and trying out the evaluation version of this asset with ufps. Unfortunately it doesn't work, the conversations do not freeze the player or disable the hud and I cannot choose any of the dialogue options. Apart from this I really like the asset, but I need to know it will work with my setup and my game before I can pay for it. The ufps example scene does not work either. The main issue is freezing the player on dialogue, it seems that the script doesn't even run at all. Is there anything I could do?
     
  35. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    Hi @louiselindvall - Thanks for evaluating the Dialogue System! I just tested a new Unity 2017.1 project containing the Dialogue System 1.7.7.1 evaluation version and UFPS 1.7.1. I imported Dialogue System / Third Party Support / UFPS Support.unitypackage, and then I played the scene in Third Party Support / UFPS / Example. It appears to work, so let's see if we can quickly find out what the difference is in your project.

    Are there any warnings or errors in the Unity editor's Console window? These warnings are expected and harmless:

    OnLevelWasLoaded was found on FPSyncLuaPlayerOnLoadLevel
    Dialogue System: The scene is missing an EventSystem. Adding one.
    Warning (4Mace (vp_FPWeaponMeleeAttack)) This component requires...
    Warning (4Mace (vp_FPWeaponMeleeAttack)) WeaponShooter for this melee weapon...​

    But any other messages may indicate the problem.

    Have you tried UFPS and the Dialogue System in a new, empty project?

    Please feel free to send your project to tony (at) pixelcrushers.com. I'll be happy to take a look.
     
  36. louiselindvall

    louiselindvall

    Joined:
    Jun 26, 2017
    Posts:
    3
    Hi again,

    Just to make sure I ran a clean install of Unity 2017.1, imported ufps and the dialogue evaluation package into a new project, got the third party support and played the example scene in the ufps folder... and it still doesn't work. A speech bubble appears on private hart but I can't click on it, nor can I pick up the gun. I noticed though that the player does freeze when the menu pops up, and I am able to choose options from there. I have no idea what is going on, so weird since you got it to function properly. No errors are reported either. If I force the conversation on start, still the same result (player doesn't freeze, can't choose options). Any ideas at all on what to do about this would be greatly appreciated
     
  37. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    Hi! Is this the speech bubble that you see?



    If so, what happens if you press the 'F' key?

    The example conversation is set up using UFPS's standard interaction system. The speech bubble shown above tells you that interaction is possible with this GameObject (Private Hart), but you need to press the "interact" input to actually interact. By default, the interact input is mapped to the 'F' key and joystick button 2 (the X button).

    (If you're using a joystick, you'll need to enable joystick navigation of the conversation menu. To do this, in the Hierarchy inspect Dialogue Manager > Canvas > Generic Unity UI Dialogue UI, and on the Unity UI Dialogue UI component tick Auto Focus.)
     
  38. louiselindvall

    louiselindvall

    Joined:
    Jun 26, 2017
    Posts:
    3
    Wow, I am so stupid. It WORKS! Player freezes and everything! I got the trigger function to work as well. Can't figure out the oncollisiontrigger yet or why I can't pick up the gun, but I'm too happy to care. Rushing off to try it in my own project now. Thank you, you just made my evening :)
     
  39. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    Great!
    UFPS won't let you pick up the gun if you already have one. If you give your gun to Private Hart, you can pick up the one on the ground.

    For UFPS's Interact Type > Collision Trigger, the collider needs to have Is Trigger unticked (i.e., not a trigger collider), and it needs a Rigidbody with Is Kinematic unticked (i.e., not a kinematic rigidbody). To get Collision Trigger to work with Private Hart, inspect his AI child GameObject. On the Sphere Collider component, untick Is Trigger. Then add a Rigidbody component.

    Have fun! :)
     
  40. Khermit

    Khermit

    Joined:
    Jul 29, 2017
    Posts:
    6
    Hi Tony !

    My purpose is to create "simple" dialog like Baldur's gate, and use them for handcrafted quest. I would like to know if your QuestMachine has any added value compared to Dialog-system if I will not use procedural generated quest ?

    Have a nice day !
     
  41. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    Hi @Khermit! Apart from procedural generation, it's mostly a matter of preference.

    The main differences are in the editor and in quest progression.

    The Dialogue System's built-in quest editor looks like this:



    It's compact but not particularly visual. If you use third party editors such as articy:draft or Chat Mapper, you can edit quests there, too, but they're also heavily text-based.

    Quest Machine's editor, on the other hand, is node-based:



    And it has a live runtime view:




    In terms of quest progression, Quest Machine has more built-in features to control quests outside of conversations. It also provides a way to define your own custom condition types and action types. And of course procedural quest generation.

    Managing quests is a slightly more manual process in the Dialogue System. However, plenty of RPGs, big, small, even MMOs, use the Dialogue System's quest system.
     
    TeagansDad and AGregori like this.
  42. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    @TonyLi

    I'm sorry for the bother, but could you try making a new project, first importing the Eval version of the DS (I tried with both the 1.7.7.1 and 1.7.6 versions, though Unity did the script updating for 1.7.6) and then the 12-31 variant of the Interjectable Dialogue package, then run the "Example Scene with Timer" and try moving down to the middle option, then waiting until it disappears and seeing if the selection jumps to either still-remaining option?

    I may be taking crazy pills but now this isn't working for me for either of my computers. Tested most recently on my desktop (2017.1.f3).
     
  43. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    Hi @EternalAmbiguity - I'll try this in a few minutes and let you know how it goes.
     
    EternalAmbiguity likes this.
  44. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    No crazy pills. After deleting a button, it's not sufficient to just select another button. We also need to tell the dialogue UI to redo the UI navigation without that deleted button. This version should do the trick:

    InterjectableResponsesExample_2018-01-16.unitypackage
     
    EternalAmbiguity likes this.
  45. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    This is working.

    Thanks very much. I think I have everything I need to begin.
     
    TonyLi likes this.
  46. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    :)

    I'm sorry...I'm back.

    SO. I've mentioned adjusting the "[HideResponse X]" part of things to allowing removing multiple (but not all) responses at once. I did this. I've already shown this before, but anyway. Here's how it is in your script:

    Code (csharp):
    1. else if (subtitle.formattedText.text.Contains("[HideResponse "))
    2.         {
    3.             // If subtitle contains special tag [HideResponse #], stop showing a specific interjectable response:
    4.             var text = subtitle.formattedText.text;
    5.             var startIndex = text.IndexOf("[HideResponse ");
    6.             var length = text.Substring(startIndex).IndexOf("]") + 1;
    7.             var tagString = text.Substring(startIndex, length); // Entire [HideResponse #].
    8.             var idString = tagString.Remove(tagString.Length - 1).Remove(0, "[HideResponse ".Length); // Just #.
    9.             var id = Tools.StringToInt(idString);
    10.             subtitle.formattedText.text = text.Remove(startIndex, length);
    11.             HideResponseWithID(id);
    12.         }
    13.  

    And here's my change:
    Code (csharp):
    1. else if (subtitle.formattedText.text.Contains("[HideResponse "))[/COLOR][/FONT][/LEFT]
    2.         {
    3.             // If subtitle contains special tag [HideResponse #], stop showing a specific interjectable response:
    4.             var text = subtitle.formattedText.text;
    5.             var startIndex = text.IndexOf("[HideResponse ");
    6.             var length = text.Substring(startIndex).IndexOf("]") + 1;
    7.             var tagString = text.Substring(startIndex, length); // Entire [HideResponse #].
    8.             var idString = tagString.Remove(tagString.Length - 1).Remove(0, "[HideResponse ".Length); // Just #.
    9.             subtitle.formattedText.text = text.Remove(startIndex, length);
    10.             List<int> ids = new List<int>();
    11.             if (idString.Contains(","))
    12.             {
    13.                 string[] idsString = idString.Split(',');
    14.                 for (int i = 0; i < idsString.Length; i++)
    15.                 {
    16.                     ids.Add(Tools.StringToInt(idsString[i]));
    17.                 }
    18.             }
    19.             else
    20.             {
    21.                 ids.Add(Tools.StringToInt(idString));
    22.                 subtitle.formattedText.text = text.Remove(startIndex, length);
    23.             }
    24.             HideResponseWithID(ids);
    25.         }
    26.  

    As you may have noticed I also changed HideResponseWithID but that's not important right now (it works fine).

    Anyway, I noticed that the timer wasn't working for situations where I have the modified form of "[HideResponse X,Y]". I quickly realized that this was because when the script looks for where the response is removed, it doesn't checked for my modified situation. So I made a modification to that side of things as well. Here's your method:

    Code (csharp):
    1. private float ComputeDurationUntilHideResponseWithID(int id)[/COLOR][/FONT][/LEFT]
    2.     {
    3.         // To compute the duration, we'll simulate a run through the conversation until
    4.         // we reach [HideResponses], [HideResponse id], or no NPC responses.
    5.         var hideResponseTag = "[HideResponse " + id + "]";
    6.         // First, we save the actual state of the variables:
    7.         // var saved = PersistentDataManager.GetSaveData(); //<-- This line is overkill; it saves everything. We just save variables below:
    8.         var savedVariableTable = VariableTableUtility.SaveVariableTable();
    9.         // Then we create a new conversation model. This one will run through the
    10.         // conversation's nodes without using a dialogue UI.
    11.         var model = new ConversationModel(DialogueManager.MasterDatabase, DialogueManager.LastConversationStarted,
    12.             DialogueManager.CurrentActor, DialogueManager.CurrentConversant,
    13.             false, DialogueManager.IsDialogueEntryValid, DialogueManager.CurrentConversationState.subtitle.dialogueEntry.id);
    14.         float duration = 0;
    15.         int safeguard = 0; // Follow a maximum of 999 nodes to prevent unexpected infinite loops.
    16.         var done = false;
    17.         var state = model.FirstState;
    18.         while (!done && safeguard < 999)
    19.         {
    20.             var text = state.subtitle.formattedText.text;
    21.             if (!state.HasNPCResponse || text.Contains("[HideResponses]") || text.Contains(hideResponseTag))
    22.             {
    23.                 // If there are no more NPC nodes or we need to hide our response, we're done:
    24.                 done = true;
    25.             }
    26.             else
    27.             {
    28.                 // Otherwise add the node's duration and progress to the next node:
    29.                 duration += GetDefaultSubtitleDuration(text);
    30.                 state = model.GetState(state.FirstNPCResponse.destinationEntry);
    31.             }
    32.         }
    33.         // Finally, restore the saved state:
    34.         //PersistentDataManager.ApplySaveData(saved); //<-- Corresponding overkill line.
    35.         VariableTableUtility.RestoreVariableTable(savedVariableTable);
    36.         return duration;
    37.     }
    38.  

    And here's mine:

    Code (csharp):
    1.     private float ComputeDurationUntilHideResponseWithID(int id)[/COLOR][/FONT][/LEFT]
    2.     {
    3.         // To compute the duration, we'll simulate a run through the conversation until
    4.         // we reach [HideResponses], [HideResponse id], or no NPC responses.
    5.         var hideResponseTag = "[HideResponse " + id + "]";
    6.         var groupHideResponsesTag = "[HideResponse ";
    7.         Debug.Log("ID: " + id);
    8.         // First, we save the actual state of the variables:
    9.         // var saved = PersistentDataManager.GetSaveData(); //<-- This line is overkill; it saves everything. We just save variables below:
    10.         var savedVariableTable = VariableTableUtility.SaveVariableTable();
    11.         // Then we create a new conversation model. This one will run through the
    12.         // conversation's nodes without using a dialogue UI.
    13.         var model = new ConversationModel(DialogueManager.MasterDatabase, DialogueManager.LastConversationStarted,
    14.             DialogueManager.CurrentActor, DialogueManager.CurrentConversant,
    15.             false, DialogueManager.IsDialogueEntryValid, DialogueManager.CurrentConversationState.subtitle.dialogueEntry.id);
    16.         float duration = 0;
    17.         int safeguard = 0; // Follow a maximum of 999 nodes to prevent unexpected infinite loops.
    18.         var done = false;
    19.         var state = model.FirstState;
    20.         while (!done && safeguard < 999)
    21.         {
    22.             var text = state.subtitle.formattedText.text;
    23.             if (!state.HasNPCResponse || text.Contains("[HideResponses]") || text.Contains(hideResponseTag))
    24.             {
    25.                 // If there are no more NPC nodes or we need to hide our response, we're done:
    26.                 done = true;
    27.             }
    28.             // The following only fires AFTER the previous check, so it only fires in situations where we're working with multiple ids.
    29.             // We need to parse the string for each id, then check if it's the appropriate one.
    30.             else if (!state.HasNPCResponse || text.Contains(groupHideResponsesTag))
    31.             {
    32.                 Debug.Log("Checking hide reponses tag...");
    33.                 var newText = text;
    34.                 var startIndex = newText.IndexOf("[HideResponse ");
    35.                 var length = newText.Substring(startIndex).IndexOf("]") + 1;
    36.                 var tagString = newText.Substring(startIndex, length); // Entire [HideResponse #].
    37.                 var idString = tagString.Remove(tagString.Length - 1).Remove(0, "[HideResponse ".Length); // Just #.
    38.                 Debug.Log("idString: " + idString);
    39.                 if (idString.Contains(","))
    40.                 {
    41.                     Debug.Log("Found one with multiple ids");
    42.                     string[] idsString = idString.Split(',');
    43.                     for (int i = 0; i < idsString.Length; i++)
    44.                     {
    45.                         int tempInt = int.Parse(idsString[i]);
    46.                         Debug.Log("temp ID: " + tempInt);
    47.                         if (tempInt == id)
    48.                         {
    49.                             // We've found the id in this case
    50.                             done = true;
    51.                         }
    52.                     }
    53.                 }
    54.             }
    55.             else
    56.             {
    57.                 // Otherwise add the node's duration and progress to the next node:
    58.                 duration += GetDefaultSubtitleDuration(text);
    59.                 state = model.GetState(state.FirstNPCResponse.destinationEntry);
    60.                 Debug.Log("Moving down a node...");
    61.             }
    62.             safeguard++;
    63.         }
    64.         // Finally, restore the saved state:
    65.         //PersistentDataManager.ApplySaveData(saved); //<-- Corresponding overkill line.
    66.         VariableTableUtility.RestoreVariableTable(savedVariableTable);
    67.         return duration;
    68.     }
    69.  

    The important changes are at lines 6 and 30, where I declare a version of the "tag" that allows for the "multiple id hiding" and an additional else if statement. The debug lines may have clued you in, but it's not working.

    My dialogue looks like this:

    Unity_2018-01-18_05-36-08.png

    If you look at the console, you can see it moves down a node from where it is (ID 43, the response on the top right) five times before stopping.
    The "we're glad to have a place" line can go to all those different nodes, but based on that point in the conversation the first one, the "on weekends" line, is the only one it can reach. So from the initial response it's 1 "I like it," 2 "We're glad," 3 "On weekends," 4 "Please enjoy yourself play-" (this is on the far left, hidden), and 5 "Next we'll head this way."
    That's the place I highlighted, with the "[HideResponse 41]" string at the end.

    If you look back at the console you'll see that this seems to stop there and not move beyond it. It constantly repeats those two.

    The actual line it ends at is later on ( Unity_2018-01-18_05-42-13.png ). However, it's clearly not getting there. Any suggestions?

    P.S. I noticed your "safeguard" variable in the while loop. However, you never actually increment it, so the first time I tried adding my section Unity froze on me again :p
     
    Last edited: Jan 18, 2018
  47. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    Hi @EternalAmbiguity - Thanks for the details. I'll take a look today and let you know if I have any suggestions.(And, yikes, sorry about forgetting to increment the safeguard variable; how embarrassing!)
     
    EternalAmbiguity likes this.
  48. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    @EternalAmbiguity - Any chance you could send a copy of your script and dialogue database to tony (at) pixelcrushers.com? As I go through this, I find that I may not set up everything exactly the way you did. It would help if I could be working off the same code and database as you.
     
  49. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Just sent it.
     
  50. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    @EternalAmbiguity - Change the first line here:
    Code (csharp):
    1. else //<-- CHANGE THIS LINE
    2.             {
    3.                 // Otherwise add the node's duration and progress to the next node:
    4.                 duration += GetDefaultSubtitleDuration(text);
    5.                 state = model.GetState(state.FirstNPCResponse.destinationEntry);
    to this:
    Code (csharp):
    1. if (!done) //<-- THIS LINE
    2.             {
    3.                 // Otherwise add the node's duration and progress to the next node:
    4.                 duration += GetDefaultSubtitleDuration(text);
    5.                 state = model.GetState(state.FirstNPCResponse.destinationEntry);
    Each iteration of the while loop should move the conversation model forward one state if you're not done yet. If not, it will keep iterating on the same state.
     
    EternalAmbiguity likes this.