Forum rules - please read before posting.

Dialog Import Branching Path[Repost]

Note: This is a repost from an older one (https://adventurecreator.org/forum/discussion/15644/dialog-import-branching-path#latest) that I accidentally violated in the forums. So, I've rectified it by posting a new discussion.

Is there a way to make the csv file support branching dialog paths like this diagram my teammate made as an example?
https://imgur.com/6dWOlg0

The player has the choice between two possible responses to Dalia's first question. I need to be able to tell the importer that these are two options within one conversation frame, rather than two consecutive player dialogue lines.

In addition, I need to be able to tell the importer that each of these player responses leads to a different line from Dalia, then after her line will converge back to linear dialogue. So, building off of what I have, would it be possible like this?
https://imgur.com/tYgPXFE

Comments

  • Technically it's possible, but the custom script would need extending a fair bit.

    Firstly, you'll want to grab this Action on the AC wiki, that lets you create Conversations in a single Action (no need for a separate Conversation object):

    https://adventure-creator.fandom.com/wiki/"One-off"_Conversations

    This Action can be generated through script, using:

    ActionConversationOneOff conversationAction = ActionConversationOneOff.CreateNew (labelsArray);
    

    Where labelsArray is a string array of the different options shown.

    Given your CSV format, what you'd need to do is modify the script to have it keep track of all lines that have a "GoToLine" entry in one block, and then - once the end of the block has been reached, create such an Action with those lines fed into the array.

    The next step would be a case of going through the Actions afterwards to connect them up, which needs to be done once the Actions have been generated. See the ScriptedActionListExample script file for an example of this. I'd say that simply bringing up the options as a Conversation would be the first step, however.

    It's worth mentioning, however, that there is a "Yarn Spinner" integration package over on the Downloads page that has similar functionality.

  • Do I put this line of code anywhere in the new ActionConversationOneOff script?

    ActionConversationOneOff conversationAction = ActionConversationOneOff.CreateNew (labelsArray);

    Question? Is Yarn Spinner package compatible with Dialogue Systems for Unity by Pixel Crushers?
    https://assetstore.unity.com/packages/tools/behavior-ai/dialogue-system-for-unity-11672

  • Do I put this line of code anywhere in the new ActionConversationOneOff script?

    No, it would go in the updated import script that is amended to process the additional column.

    Question? Is Yarn Spinner package compatible with Dialogue Systems for Unity by Pixel Crushers?

    That's a question for Pixel Crushers, but it's my understanding that the two assets both provide their own take on the same underlying functionality.

    AC does enjoy a robust integration with Dialogue System as well, provided by that asset's author.

  • To clarify, in one of your statements,

    "once the end of the block has been reached, create such an Action with those lines fed into the array."

    What do you mean by end of the block? and how do I create an Action? Are you referring to creating an Action Node in Unity with Adventure Creator installed? or is it this line of code (ActionConversationOneOff conversationAction = ActionConversationOneOff.CreateNew (labelsArray);) that is used to create such Actions and must be placed at the end of the block?

    "No, it would go in the updated import script that is amended to process the additional column."

    When you said "updated import script" are you referring to the CSV file like the one I posted earlier with an Imgur link? Or is it the ImportSpeechEditor.cs that you've made to allow Dialog Import via CSV file? And when you said, "amended to process the additional column"? What do you mean by this?

  • The ImportSpeechEditor script would need heavily modifying to allow for the changes you're looking to implement in your CSV file. Any code changes I'm referring to would go in there.

    With "end of the block", I'm referring to the block of rows in the CSV file that have an entry in the "GoToLine" column. Because the Conversation Action would need to have all possible outputs defined, before it can be generated, the script would need to record all subsequent rows - before GoToLine is found blank - before generating the Action.

    Creating a Conversation Action is indeed a case of calling ActionConversationOneOff.CreateNew to generate the Action. The original script was quite old - I've gone and updated it to use a similar CreateNew function for ActionSpeech as an example.

  • edited January 21

    Alright, I tried what you suggested, and this is what I got. I've made some minor edits with the code and am I doing it right? If it's possible, I'm interested on how you do it the same way as you made that ImportSpeechEditor.cs script in the other post?

    https://imgur.com/a/ENswxED

  • It's more involved than that.

    The GenerateNew function needs to pass in the labels that show - which means you need to hold off calling it until you've processed all rows in a block that have a "Go to line" column entry. You'll also need to store a reference to the variable it returns so that you can later link its outputs to the various Actions the "Go to line" columns reference.

  • Something more along these lines:

    using UnityEngine;
    using UnityEditor;
    using System.Collections;
    using System.Collections.Generic;
    using AC;
    
    public class ImportSpeechEditor : EditorWindow
    {
    
        private Cutscene cutsceneToOverwrite;
    
    
        [MenuItem ("Adventure Creator/Addons/Import speech lines", false, 10)]
        public static void Init ()
        {
            // Get existing open window or if none, make a new one:
            ImportSpeechEditor window = (ImportSpeechEditor) EditorWindow.GetWindow (typeof (ImportSpeechEditor));
            window.titleContent.text = "Speech importer";
        }
    
    
        private void OnGUI ()
        {
            cutsceneToOverwrite = (Cutscene) EditorGUILayout.ObjectField ("Cutscene to overwrite:", cutsceneToOverwrite, typeof (Cutscene), true);
    
            if (GUILayout.Button ("Import from file"))
            {
                ImportFromFile ();
            }
        }
    
    
        private void ImportFromFile ()
        {
            string fileName = EditorUtility.OpenFilePanel ("Import inventory item data", "Assets", "csv");
            if (fileName.Length == 0)
            {
                return;
            }
    
            if (System.IO.File.Exists (fileName))
            {
                string csvText = Serializer.LoadFile (fileName);
                string[,] csvOutput = CSVReader.SplitCsvGrid (csvText);
    
                GenerateLines (csvOutput);
            }
        }
    
    
        private void GenerateLines (string[,] csvData)
        {
            int numCols = csvData.GetLength (0)-1;
    
            if (numCols < 3) return;
    
            int numRows = csvData.GetLength (1);
    
            List<ImportedLine> importedLines = new List<ImportedLine>();
    
            List<string> blockOptionTexts = new List<string> ();
            List<int> blockGoToRows = new List<int> ();
            bool lastRowWasDialogueOption = false;
    
            for (int row = 1; row < numRows; row ++)
            {
                if (csvData [0, row] != null && csvData [0, row].Length > 0)
                {
                    bool isDialogueOption = numCols >= 4;
    
                    if (isDialogueOption)
                    {
                        string optionText = csvData [2, row];
                        int.TryParse (csvData [3, row], out int goToRowIndex);
    
                        blockOptionTexts.Add (optionText);
                        blockGoToRows.Add (goToRowIndex);
    
                        lastRowWasDialogueOption = true;
                    }
                    else
                    {
                        if (lastRowWasDialogueOption)
                        {
                            importedLines.Add (new ImportedConversationLine (row, blockOptionTexts.ToArray (), blockGoToRows.ToArray ()));
                            lastRowWasDialogueOption = false;
                            blockGoToRows.Clear ();
                            blockOptionTexts.Clear ();
                        }
    
                        int isPlayerInt = 0;
                        int.TryParse (csvData [0, row], out isPlayerInt);
    
                        bool isPlayer = (isPlayerInt == 1);
                        string characterName = csvData [1, row];
                        string lineText = csvData [2, row];
    
                        importedLines.Add (new ImportedSpeechLine (row, isPlayer, characterName, lineText));
                    }
                }
            }
    
            ImportLines (importedLines.ToArray ());
        }
    
    
        private void ImportLines (ImportedLine[] importedLines)
        {
            if (cutsceneToOverwrite != null)
            {
                cutsceneToOverwrite.actions.Clear ();
    
                foreach (ImportedLine importedLine in importedLines)
                {
                    var action = importedLine.GenerateAction ();
                    cutsceneToOverwrite.actions.Add (action);
                }
    
                foreach (ImportedLine importedLine in importedLines)
                {
                    importedLine.UpdateSockets (importedLines);
                }
    
                // Save
                UnityVersionHandler.CustomSetDirty (cutsceneToOverwrite, true);
            }
        }
    
    
        private class ImportedLine
        {
    
            public int RowIndex { get; protected set; }
            public virtual Action GenerateAction () => null;
            public virtual void UpdateSockets (ImportedLine[] importedLines) {}
            public virtual Action GeneratedAction => null;
    
        }
    
    
        private class ImportedSpeechLine : ImportedLine
        {
    
            public readonly bool IsPlayer;
            public readonly Char Speaker;
            public readonly string Text;
            private ActionSpeech generatedAction;
    
            private static AC.Char[] allCharacters;
    
    
            public ImportedSpeechLine (int rowIndex, bool isPlayer, string speakerName, string text)
            {
                RowIndex = rowIndex;
                IsPlayer = isPlayer;
                Speaker = null;
                Text = text;
    
                if (!string.IsNullOrEmpty (speakerName))
                {
                    if (allCharacters == null)
                        allCharacters = GameObject.FindObjectsOfType <AC.Char>();
    
                    foreach (AC.Char character in allCharacters)
                    {
                        if (character.GetName () == speakerName)
                        {
                            Speaker = character;
                            break;
                        }
                    }
                }
            }
    
    
            public override Action GenerateAction ()
            {
                generatedAction = IsPlayer
                                ? ActionSpeech.CreateNew_Player (Text)
                                : ActionSpeech.CreateNew (Speaker, Text);
    
                return generatedAction;
            }
    
    
            public override Action GeneratedAction => generatedAction;
    
        }
    
    
        private class ImportedConversationLine : ImportedLine
        {
    
            public readonly string[] Labels;
            public readonly int[] GoToRows;
            private ActionConversationOneOff generatedAction;
    
    
            public ImportedConversationLine (int rowIndex, string[] labels, int[] goToRows)
            {
                RowIndex = rowIndex;
                Labels = labels;
                GoToRows = goToRows;
            }
    
    
            public override Action GenerateAction ()
            {
                generatedAction = ActionConversationOneOff.CreateNew (Labels);
                return generatedAction;
            }
    
    
            public override void UpdateSockets (ImportedLine[] importedLines)
            {
                Action[] outputActions = new Action[GoToRows.Length];
                for (int i = 0; i < outputActions.Length; i++)
                {
                    int goToRowIndex = GoToRows[i];
                    foreach (var importedLine in importedLines)
                    {
                        if (goToRowIndex == importedLine.RowIndex)
                        {
                            outputActions[i] = importedLine.GeneratedAction;
                            break;
                        }
                    }
                }
    
                ActionEnd[] endings = new ActionEnd[outputActions.Length];
                for (int i = 0; i < endings.Length; i++)
                {
                    endings[i] = new ActionEnd (outputActions[i]);
                }
                generatedAction.SetOutputs (endings);
            }
    
        }
    
    }
    
  • edited January 28

    Oh wow! Thanks for the source code! I really appreciate for all the work you've done to help me. I've tested the code and works just fine with a table csv file with no branching path but the one with a branching path doesnt seem to work as intended. Is there something I'm missing in the script? I've been trying to understand it as much as I can.

    Here's the table image: https://imgur.com/a/HlaLLgq

    Here's the results after I import the table csv file with branching path: https://imgur.com/a/Tx7KR9k

  • using UnityEngine;
    using UnityEditor;
    using System.Collections;
    using System.Collections.Generic;
    using AC;
    
    public class ImportSpeechEditor : EditorWindow
    {
    
        private Cutscene cutsceneToOverwrite;
    
    
        [MenuItem ("Adventure Creator/Addons/Import speech lines", false, 10)]
        public static void Init ()
        {
            // Get existing open window or if none, make a new one:
            ImportSpeechEditor window = (ImportSpeechEditor) EditorWindow.GetWindow (typeof (ImportSpeechEditor));
            window.titleContent.text = "Speech importer";
        }
    
    
        private void OnGUI ()
        {
            cutsceneToOverwrite = (Cutscene) EditorGUILayout.ObjectField ("Cutscene to overwrite:", cutsceneToOverwrite, typeof (Cutscene), true);
    
            if (GUILayout.Button ("Import from file"))
            {
                ImportFromFile ();
            }
        }
    
    
        private void ImportFromFile ()
        {
            string fileName = EditorUtility.OpenFilePanel ("Import inventory item data", "Assets", "csv");
            if (fileName.Length == 0)
            {
                return;
            }
    
            if (System.IO.File.Exists (fileName))
            {
                string csvText = Serializer.LoadFile (fileName);
                string[,] csvOutput = CSVReader.SplitCsvGrid (csvText);
    
                GenerateLines (csvOutput);
            }
        }
    
    
        private void GenerateLines (string[,] csvData)
        {
            int numCols = csvData.GetLength (0)-1;
            if (numCols < 3) return;
    
            int numRows = csvData.GetLength (1);
            List<ImportedLine> importedLines = new List<ImportedLine>();
    
            List<string> blockOptionTexts = new List<string> ();
            List<int> blockGoToRows = new List<int> ();
            bool lastRowWasDialogueOption = false;
    
            for (int row = 1; row < numRows; row ++)
            {
                if (csvData [0, row] != null && csvData [0, row].Length > 0)
                {
                    int isPlayerInt = 0;
                    int.TryParse (csvData [1, row], out isPlayerInt);
                    bool isPlayer = (isPlayerInt == 1);
    
                    int goToRowIndex = -1;
                    if (!string.IsNullOrEmpty (csvData[4,row]))
                        int.TryParse (csvData [4, row], out goToRowIndex);
    
                    bool isDialogueOption = numCols >= 4 && !string.IsNullOrEmpty (csvData[4,row]) && isPlayer;
    
                    if (isDialogueOption)
                    {
                        string optionText = csvData [3, row];
    
                        blockOptionTexts.Add (optionText);
                        blockGoToRows.Add (goToRowIndex);
    
                        lastRowWasDialogueOption = true;
                    }
                    else
                    {
                        if (lastRowWasDialogueOption)
                        {
                            importedLines.Add (new ImportedConversationLine (100+row, blockOptionTexts.ToArray (), blockGoToRows.ToArray ()));
                            lastRowWasDialogueOption = false;
                            blockGoToRows.Clear ();
                            blockOptionTexts.Clear ();
                        }
    
                        string characterName = csvData [2, row];
                        string lineText = csvData [3, row];
                        importedLines.Add (new ImportedSpeechLine (row, isPlayer, characterName, lineText, goToRowIndex));
                    }
                }
            }
    
            ImportLines (importedLines.ToArray ());
        }
    
    
        private void ImportLines (ImportedLine[] importedLines)
        {
            if (cutsceneToOverwrite != null)
            {
                cutsceneToOverwrite.actions.Clear ();
    
                foreach (ImportedLine importedLine in importedLines)
                {
                    importedLine.GenerateActions ();
                    foreach (var action in importedLine.GeneratedActions)
                        cutsceneToOverwrite.actions.Add (action);
                }
    
                foreach (ImportedLine importedLine in importedLines)
                {
                    importedLine.UpdateSockets (importedLines);
                }
    
                // Save
                UnityVersionHandler.CustomSetDirty (cutsceneToOverwrite, true);
            }
        }
    
    
        private class ImportedLine
        {
    
            protected List<Action> generatedActions;
    
            public int RowIndex { get; protected set; }
            public virtual void GenerateActions () {}
            public virtual void UpdateSockets (ImportedLine[] importedLines) {}
            public  List<Action> GeneratedActions => generatedActions;
    
        }
    
    
        private class ImportedSpeechLine : ImportedLine
        {
    
            public readonly bool IsPlayer;
            public readonly Char Speaker;
            public readonly string Text;
            public readonly int GoToLine;
    
            private static AC.Char[] allCharacters;
    
    
            public ImportedSpeechLine (int rowIndex, bool isPlayer, string speakerName, string text, int goToLine)
            {
                RowIndex = rowIndex;
                IsPlayer = isPlayer;
                Speaker = null;
                Text = text;
                GoToLine = goToLine;
    
                if (!string.IsNullOrEmpty (speakerName))
                {
                    if (allCharacters == null)
                        allCharacters = GameObject.FindObjectsOfType <AC.Char>();
    
                    foreach (AC.Char character in allCharacters)
                    {
                        if (character.GetName () == speakerName)
                        {
                            Speaker = character;
                            break;
                        }
                    }
                }
            }
    
    
            public override void GenerateActions ()
            {
                generatedActions = new List<Action> ();
                generatedActions.Add (IsPlayer
                                     ? ActionSpeech.CreateNew_Player (Text)
                                     : ActionSpeech.CreateNew (Speaker, Text));
            }
    
    
            public override void UpdateSockets (ImportedLine[] importedLines)
            {
                if (GoToLine >= 0)
                {
                    foreach (var importedLine in importedLines)
                    {
                        if (GoToLine == importedLine.RowIndex)
                        {
                            GeneratedActions[0].SetOutput (new ActionEnd (importedLine.GeneratedActions[0]));
                            break;
                        }
                    }
                }
            }
    
        }
    
    
        private class ImportedConversationLine : ImportedLine
        {
    
            public readonly string[] Labels;
            public readonly int[] GoToRows;
    
    
            public ImportedConversationLine (int rowIndex, string[] labels, int[] goToRows)
            {
                RowIndex = rowIndex;
                Labels = labels;
                GoToRows = goToRows;
            }
    
    
            public override void GenerateActions ()
            {
                generatedActions = new List<Action> ();
                generatedActions.Add (ActionConversationOneOff.CreateNew (Labels));
    
                foreach (var label in Labels)
                {
                    generatedActions.Add (ActionSpeech.CreateNew_Player (label));
                }
            }
    
    
            public override void UpdateSockets (ImportedLine[] importedLines)
            {
                for (int i = 0; i < GoToRows.Length; i++)
                {
                    int goToRowIndex = GoToRows[i];
                    foreach (var importedLine in importedLines)
                    {
                        if (goToRowIndex == importedLine.RowIndex)
                        {
                            GeneratedActions[i+1].SetOutput (new ActionEnd (importedLine.GeneratedActions[0]));
                            break;
                        }
                    }
                }
    
                ActionEnd[] endings = new ActionEnd[GoToRows.Length];
                for (int i = 0; i < endings.Length; i++)
                {
                    endings[i] = new ActionEnd (GeneratedActions[i+1]);
                }
                GeneratedActions[0].SetOutputs (endings);
            }
    
        }
    
    }
    
  • edited February 4

    Hello again Chris, thanks for the updated source code. I tried using my multi dialog import.cs again(https://imgur.com/a/HlaLLgq) with the updated source code you are using and this is the result:

    https://imgur.com/a/yQTAd3w.
    https://imgur.com/a/RMfpj0D

    What am I missing here?

  • You're missing the "Line" column that was in the example sheet in your original post.

  • OH, I forgot! let me fix that. Thank you.

Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Welcome to the official forum for Adventure Creator.