Dialogue System 1.8.2 Released!

Announcements, support questions, and discussion for the Dialogue System.
User avatar
Tony Li
Posts: 22059
Joined: Thu Jul 18, 2013 1:27 pm

Dialogue System 1.8.2 Released!

Post by Tony Li »

Version 1.8.2 is now available on the Pixel Crushers customer download site and should be on the Unity Asset Store in a few days.

Version 2.0 is on track for release next Monday.

This is intended to be the last release in the 1.x line. Highlights are:
  • Emerald AI support
  • More than 4 emphasis tags
  • Multiedit fields in dialogue nodes

Version 1.8.2
Core
  • Improved: Can now multiedit fields when selecting multiple dialogue entry nodes.
  • Improved: Can now define more than 4 emphasis tags.
  • Improved: Unity UI Dialogue UI animation handler improvements.
  • Improved: MoveTo() with no duration now moves rigidbody, not just transform.
  • Fixed: Bug with Continue() at time 0 not clearing previous subtitle.
  • Fixed: Build bug with Timeline() sequencer command.
Third Party Support
  • Adventure Creator: Start Conversation action can now specify an entry ID.
  • articy:draft: Added support for getProp() and getObj() functions.
  • Corgi Platformer / Inventory Engine: Inventory panel now refreshed when replacing items during conversations.
  • Emerald AI 2.0: Added support.
  • Inventory Third Person Controller: Updated for Invector 2.3.0.
  • PlayMaker: GetLocalizedText now falls back to Dialogue Manager's text table.
User avatar
Abelius
Posts: 318
Joined: Fri Jul 21, 2017 12:45 pm

Re: Dialogue System 1.8.2 Released!

Post by Abelius »

Great! I was looking forward to this one. :mrgreen:

Thank you for your work!
Unity 2019.4.9f1
Dialogue System 2.2.15
Racoon7
Posts: 7
Joined: Mon Oct 30, 2017 2:17 pm

Re: Dialogue System 1.8.2 Released!

Post by Racoon7 »

Thank you for the update! :)

I look forward to version 2.0. Finally I won't have to assign actors and conversants all the time, right? :D

And If you're still looking for feedback on the current version, I noticed a few things.
Feedback
Typewriter sound
Ever since I use Dialogue System, the typewriter sound effect always imports like this for me:

Image

Perhaps other people don't have this problem, but I generally have to delete the file to avoid errors.
Warnings
In scenes without a Dialogue Manager, these warnings pop up in playmode (entering and quitting).

Image

I might not always want to have a DM in a scene. ;)
One-time dialogue
I have a bunch of "opening" dialogues I want to trigger only the first time a location is visited.
In Articy, I check a condition in the input pin of the dialogue and mark it "finished" in the output pin. However, DS does this in reverse, first setting the variable to false, and then checking whether it's true – therefore no conversation is triggered.

Image
Connection colours and priority
DS allows you to set link priority by changing the colour of the connections.
Unfortunately, there was a bug in articy:draft which caused all colour values and even the colour table itself to change slightly every time you saved (rounding error).

Image

I reported it and it should be fixed in version 3.1.12, but it might still be an issue in earlier versions, and in current multi-user versions unless the new colour format is enforced, so I thought I'd let you know.
Nested groups list

First of all, Nested Conversation Groups setting is amazing! Thank you for listening to my input back then – it makes organizing my dialogue much easier. :)

Just a suggestion though. My conversations exported from articy:draft end up in this list:

Image

The actual conversations are listed at the top and the groups which are inside them are all listed below.
Would it be possible to put them in the submenus where they actually belong, i.e. like this or similarly?

Image
Articy markup
This is not a feedback per se, just a little script for anyone who might find it useful in any way.

I use italics quite a lot and didn't want to write <i>xxx</i> or emphasis tags all the time, so I wrote a script which (hopefully :D ) converts exported Articy:draft markup inside dialogue fragments into Unity's rich text – which should be enabled in the settings.

Image

Accessible via right-click on an exported XML (use before attempting to convert it to a database).

Image

It supports bold, italic and colourful text.

I wrote it in C#6 which can be turned on in Editor ► Project Settings ► Player / Scripting Runtime Version – but here's a C#4 version as well.
C#6

(Edit: Updated this to work with multiple paragraphs.)

Code: Select all

using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using UnityEditor;
using UnityEngine;
using static System.Environment;


/// <summary>Parses the Articy markup in dialogue fragments and prepares it for use in Dialogue System with rich text – which must be supported by your UI.</summary>
public static class ArticyMarkupConverter {

    const string XmlExtension = ".xml";

    const string DialogueFragmentName = "DialogueFragment";
    const string TextElementName = "Text";
    const string LocalizedStringName = "LocalizedString";

    static string defaultNamespace;

    const RegexOptions Options = RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase | RegexOptions.Singleline;
    static readonly Regex StylesRegex = new Regex(@"<style>(?<styles>.*?)</style>", Options); // Get the part of text dealing with styles
    static readonly Regex StyleRegex = new Regex(@"#(?<id>s[1-9]\d*) {(?<style>.*?)}", Options); // The first style "s0" is always a paragraph

    // Check a specific style for these
    static readonly Regex BoldRegex = new Regex(@"font-weight\s*?:\s*?bold", Options);
    static readonly Regex ItalicRegex = new Regex(@"font-style\s*?:\s*?italic", Options);
    static readonly Regex ColorRegex = new Regex(@"color\s*?:\s*?(?<color>#\w{6})", Options);

    // Apply the styles to the actual text. The style tags never overlap, so they can be processed in order.
    static readonly Regex TextRegex = new Regex(@"<p id=""s0"">(?<text>.*?)</p>", Options);
    static readonly Regex PartsRegex = new Regex(@"<span id=""(?<id>s[1-9]\d*)"">(?<text>.*?)</span>", Options); // Style id : Pure text  

    [MenuItem("Assets/Tools/Replace Articy markup with rich text", true)]
    public static bool IsXmlFile() {
        // Validation        
        string relativeFilePath = AssetDatabase.GetAssetPath(Selection.activeObject);
        return Path.GetExtension(relativeFilePath)?.ToLower() == XmlExtension;
    }

    [MenuItem("Assets/Tools/Replace Articy markup with rich text")]
    public static void ReplaceArticyMarkup() {
        string projectFolder = Application.dataPath.Remove(Application.dataPath.LastIndexOf("Assets", StringComparison.Ordinal));
        string filePath = projectFolder + AssetDatabase.GetAssetPath(Selection.activeObject);

        XDocument articyXml = XDocument.Load(filePath);
        defaultNamespace = articyXml.Root?.GetDefaultNamespace().NamespaceName ?? string.Empty;

        var dialogueFragments = articyXml.Descendants(XName.Get(DialogueFragmentName, defaultNamespace));

        // For all texts in dialogue fragments
        int total = 0;
        foreach (XElement fragment in dialogueFragments) {
            XElement localizedString = fragment.Element(XName.Get(TextElementName, defaultNamespace))
                                              ?.Element(XName.Get(LocalizedStringName, defaultNamespace));
            ReplaceMarkup(ref localizedString);
            total++;
        }
        Debug.Log($"Converted {total} dialogue fragments to rich text.");
        articyXml.Save(filePath);
    }

    static void ReplaceMarkup(ref XElement localizedString) {
        if (localizedString == null) return;
        string text = localizedString.Value;
        localizedString.Value = string.Empty; // Clear to avoid duplicating values

        string editedText = ConvertToRichText(text); // TODO: Check for ']]>' in the string and optionally split in in two CDATA sections ']]]]><![CDATA[>'
        localizedString.Add(new XCData(editedText)); // = Don't escape special characters
    }

    /// <summary>Parses given text and converts the Articy markup to rich text.</summary>
    static string ConvertToRichText(string dialogueLine) {
        dialogueLine = dialogueLine.Replace(@"&#39;", "'"); // Apostrophe

        // Get styles
        if (!StylesRegex.IsMatch(dialogueLine)) return dialogueLine; // No styles, pure text
        // TODO: Purge styles from Dialogues and Entities
        string stylesText = StylesRegex.Match(dialogueLine).Groups["styles"].Value;
        var numberedStyles = StyleRegex.Matches(stylesText)
                                       .Cast<Match>()
                                       .Select(match => new {
                                                Id = match.Groups["id"].Value,
                                                Style = match.Groups["style"].Value
                                        });
        var styles = numberedStyles.Select(style => new {
                style.Id,
                Bold = BoldRegex.IsMatch(style.Style),
                Italic = ItalicRegex.IsMatch(style.Style),
                Color = ColorRegex.Match(style.Style).Groups["color"].Value
        });

        // Get texts
        var paragraphs = TextRegex.Matches(dialogueLine)
                                  .Cast<Match>()
                                  .Select(match => match.Groups["text"].Value);
        string finalDialogueLine = string.Join($"{NewLine}{NewLine}",
                paragraphs.Select(paragraph => {
                            var innerTexts = PartsRegex.Matches(paragraph)
                                                       .Cast<Match>()
                                                       .Select(match => new {
                                                                StyleId = match.Groups["id"].Value,
                                                                Text = match.Groups["text"].Value
                                                        });

                            // Apply the styles to the texts
                            var editedParts = innerTexts.Select(text => {
                                var currentStyle = styles.First(style => style.Id == text.StyleId);
                                return ApplyStyle(
                                        innerText: text.Text,
                                        bold: currentStyle.Bold,
                                        italic: currentStyle.Italic,
                                        color: currentStyle.Color
                                );
                            });
                            string editedLine = string.Join(string.Empty, editedParts);
                            return editedLine;
                        }
                ));
        return finalDialogueLine;
    }

    /// <summary>Wraps a given text in rich text tags.</summary>
    static string ApplyStyle(string innerText, bool bold, bool italic, string color) {
        var builder = new StringBuilder(innerText);

        if (bold) WrapInTag(ref builder, "b");
        if (italic) WrapInTag(ref builder, "i");
        if (color != string.Empty) WrapInTag(ref builder, "color", color);

        return builder.ToString();
    }

    static void WrapInTag(ref StringBuilder builder, string tag, string value = "") {
        builder.Insert(0, (value != string.Empty) // opening tag
                ? $@"<{tag}={value}>" // the tag has a value
                : $@"<{tag}>"); // no value
        builder.Append($@"</{tag}>"); // closing tag
    }

}
C#4

(Note: Unlike the C#6 version, this doesn't work when the dialogue line consists of more than one paragraph.)

Code: Select all

using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using UnityEditor;
using UnityEngine;


/// <summary>Parses the Articy markup in dialogue fragments and prepares it for use in Dialogue System with rich text – which must be supported by your UI.</summary>
public static class ArticyMarkupConverterC4 {

    const string XmlExtension = ".xml";

    const string DialogueFragmentName = "DialogueFragment";
    const string TextElementName = "Text";
    const string LocalizedStringName = "LocalizedString";
    const string LangAttribute = "Lang";

    static string defaultNamespace;

    const RegexOptions Options = RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase;
    static readonly Regex StylesRegex = new Regex(@"<style>(?<styles>.*?)</style>", Options); // Get the part of text dealing with styles
    static readonly Regex StyleRegex = new Regex(@"#(?<id>s[1-9]\d*) {(?<style>.*?)}", Options); // The first style "s0" is always a paragraph

    // Check a specific style for these
    static readonly Regex BoldRegex = new Regex(@"font-weight\s*?:\s*?bold", Options);
    static readonly Regex ItalicRegex = new Regex(@"font-style\s*?:\s*?italic", Options);
    static readonly Regex ColorRegex = new Regex(@"color\s*?:\s*?(?<color>#\w{6})", Options);

    // Apply the styles to the actual text. The style tags never overlap, so they can be processed in order.
    static readonly Regex TextRegex = new Regex(@"<p id=""s0"">(?<text>.*?)</p>", Options);
    static readonly Regex PartsRegex = new Regex(@"<span id=""(?<id>s[1-9]\d*)"">(?<text>.*?)</span>", Options); // Style id : Pure text  

    [MenuItem("Assets/Tools/Replace Articy markup with rich text (C#4)", true)]
    public static bool IsXmlFile() {
        // Validation        
        string relativeFilePath = AssetDatabase.GetAssetPath(Selection.activeObject);
        return relativeFilePath != null && Path.GetExtension(relativeFilePath).ToLower() == XmlExtension;
    }

    [MenuItem("Assets/Tools/Replace Articy markup with rich text (C#4)")]
    public static void ReplaceArticyMarkup() {
        string projectFolder = Application.dataPath.Remove(Application.dataPath.LastIndexOf("Assets", StringComparison.Ordinal));
        string filePath = projectFolder + AssetDatabase.GetAssetPath(Selection.activeObject);

        XDocument articyXml = XDocument.Load(filePath);
        defaultNamespace = (articyXml.Root != null) ? articyXml.Root.GetDefaultNamespace().NamespaceName : string.Empty;

        var dialogueFragments = articyXml.Descendants(XName.Get(DialogueFragmentName, defaultNamespace));

        // For all texts in dialogue fragments
        int total = 0;
        foreach (XElement fragment in dialogueFragments) {
            var textElement = fragment.Element(XName.Get(TextElementName, defaultNamespace));
            XElement localizedString = (textElement != null)
                    ? textElement.Element(XName.Get(LocalizedStringName, defaultNamespace))
                    : null;
            ReplaceMarkup(ref localizedString);
            total++;
        }
        Debug.Log(string.Format("Converted {0} dialogue fragments to rich text.", total));
        articyXml.Save(filePath);
    }

    static void ReplaceMarkup(ref XElement localizedString) {
        if (localizedString == null) return;
        string text = localizedString.Value;
        localizedString.Value = string.Empty; // Clear to avoid duplicating values

        string editedText = ConvertToRichText(text);
        localizedString.Add(new XCData(editedText)); // = Don't escape special characters
    }

    /// <summary>Parses given text and converts the Articy markup to rich text.</summary>
    static string ConvertToRichText(string dialogueLine) {
        dialogueLine = dialogueLine.Replace(@"&#39;", "'"); // Apostrophe

        // Get styles
        if (!StylesRegex.IsMatch(dialogueLine)) return dialogueLine; // No styles, pure text
        string stylesText = StylesRegex.Match(dialogueLine).Value;
        var numberedStyles = StyleRegex.Matches(stylesText)
                                       .Cast<Match>()
                                       .Select(match => new {
                                                Id = match.Groups["id"].Value,
                                                Style = match.Groups["style"].Value
                                        });
        var styles = numberedStyles.Select(style => new {
                style.Id,
                Bold = BoldRegex.IsMatch(style.Style),
                Italic = ItalicRegex.IsMatch(style.Style),
                Color = ColorRegex.Match(style.Style).Groups["color"].Value
        });

        // Get texts
        var fullText = TextRegex.Match(dialogueLine).Value; // The dialogue text with <span> tags
        var innerTexts = PartsRegex.Matches(fullText)
                                   .Cast<Match>()
                                   .Select(match => new {
                                            StyleId = match.Groups["id"].Value,
                                            Text = match.Groups["text"].Value
                                    });

        // Apply the styles to the texts
        var editedParts = innerTexts.Select(text => {
            var currentStyle = styles.First(style => style.Id == text.StyleId);
            return ApplyStyle(
                    innerText: text.Text,
                    bold: currentStyle.Bold,
                    italic: currentStyle.Italic,
                    color: currentStyle.Color
            );
        }).ToArray();
        string editedLine = string.Join(string.Empty, editedParts);

        return editedLine;
    }

    /// <summary>Wraps a given text in rich text tags.</summary>
    static string ApplyStyle(string innerText, bool bold, bool italic, string color) {
        var builder = new StringBuilder(innerText);

        if (bold) WrapInTag(ref builder, "b");
        if (italic) WrapInTag(ref builder, "i");
        if (color != string.Empty) WrapInTag(ref builder, "color", color);

        return builder.ToString();
    }

    static void WrapInTag(ref StringBuilder builder, string tag, string value = "") {
        builder.Insert(0, (value != string.Empty) // opening tag
                ? string.Format(@"<{0}={1}>", tag, value) // the tag has a value
                : string.Format(@"<{0}>", tag)); // no value
        builder.Append(string.Format(@"</{0}>", tag)); // closing tag
    }

}
Last edited by Racoon7 on Sat Jun 02, 2018 8:03 pm, edited 1 time in total.
User avatar
Tony Li
Posts: 22059
Joined: Thu Jul 18, 2013 1:27 pm

Re: Dialogue System 1.8.2 Released!

Post by Tony Li »

Wow, thank you for all those details, @Racoon7! I'll try to make sure they're all fixed / included in 2.0.
User avatar
Tony Li
Posts: 22059
Joined: Thu Jul 18, 2013 1:27 pm

Re: Dialogue System 1.8.2 Released!

Post by Tony Li »

@Racoon7 - I have a question about this one:
In scenes without a Dialogue Manager, these warnings pop up in playmode (entering and quitting).
"Dialogue System: Quest Log couldn't access Lua Item[] table..."
What is trying to access the Quest Log? Would you please paste the full stack trace for that warning? (Click on the line in the Console window, press Ctrl+C, then paste into a reply post.)
User avatar
nathanj
Posts: 303
Joined: Sat May 28, 2016 12:30 am

Re: Dialogue System 1.8.2 Released!

Post by nathanj »

@Tony and @ Racoon7

Is that because there is a docked Dialogue Editor? I believe I’ve had this message before and that was the reason. Removing the editor removes the message, but a way to have the editor always present with or without the dialogue Manager in the active scene would be ideal.

Nathan
User avatar
Tony Li
Posts: 22059
Joined: Thu Jul 18, 2013 1:27 pm

Re: Dialogue System 1.8.2 Released!

Post by Tony Li »

nathanj wrote: Sat May 26, 2018 4:32 pmIs that because there is a docked Dialogue Editor? I believe I’ve had this message before and that was the reason. Removing the editor removes the message, but a way to have the editor always present with or without the dialogue Manager in the active scene would be ideal.

Nathan
Good point! The Watches tab can report this warning since it can't access the quests that it wants to watch. I'll make sure that's addressed.
User avatar
nathanj
Posts: 303
Joined: Sat May 28, 2016 12:30 am

Re: Dialogue System 1.8.2 Released!

Post by nathanj »

Ah, the Watches. Of course. That makes total sense.
Racoon7
Posts: 7
Joined: Mon Oct 30, 2017 2:17 pm

Re: Dialogue System 1.8.2 Released!

Post by Racoon7 »

Oh yeah, @nathanj is absolutely right, it only happens when there's a docked Dialogue Editor window.

Here's the stack trace if you still need it.
Stack trace

Short

Code: Select all

Dialogue System: Quest Log couldn't access Lua Item[] table. Has the Dialogue Manager loaded a database yet?
UnityEngine.Debug:LogWarning(Object)
PixelCrushers.DialogueSystem.QuestLog:GetAllQuests(QuestState, Boolean, String)
PixelCrushers.DialogueSystem.QuestLog:GetAllQuests(QuestState)
PixelCrushers.DialogueSystem.DialogueEditor.DialogueEditorWindow:CatalogueWatchableQuestNames()
PixelCrushers.DialogueSystem.DialogueEditor.DialogueEditorWindow:RefreshWatchableQuestList()
PixelCrushers.DialogueSystem.DialogueEditor.DialogueEditorWindow:ResetWatchSection()
PixelCrushers.DialogueSystem.DialogueEditor.DialogueEditorWindow:HandlePlayModeStateChanged()
PixelCrushers.DialogueSystem.DialogueEditor.DialogueEditorWindow:OnPlaymodeStateChanged()
UnityEditor.EditorApplication:Internal_PlayModeStateChanged(PlayModeStateChange)
Full

Code: Select all

Dialogue System: Quest Log couldn't access Lua Item[] table. Has the Dialogue Manager loaded a database yet?
 
0x00000001414F00A9 (Unity) StackWalker::GetCurrentCallstack
0x00000001414F66B6 (Unity) StackWalker::ShowCallstack
0x000000014138F54B (Unity) GetStacktrace
0x0000000140888ADC (Unity) DebugStringToFile
0x0000000141F886A8 (Unity) DebugLogHandler_CUSTOM_Internal_Log
0x000000003FA942A0 (Mono JIT Code) (wrapper managed-to-native) UnityEngine.DebugLogHandler:Internal_Log (UnityEngine.LogType,string,UnityEngine.Object)
0x000000003FA94173 (Mono JIT Code) [DebugLogHandler.cs:10] UnityEngine.DebugLogHandler:LogFormat (UnityEngine.LogType,UnityEngine.Object,string,object[]) 
0x000000003FA939F5 (Mono JIT Code) [Logger.cs:48] UnityEngine.Logger:Log (UnityEngine.LogType,object) 
0x000000003FA9368C (Mono JIT Code) [Debug.bindings.cs:149] UnityEngine.Debug:LogWarning (object) 
0x000000003FA9203B (Mono JIT Code) PixelCrushers.DialogueSystem.QuestLog:GetAllQuests (PixelCrushers.DialogueSystem.QuestState,bool,string)
0x000000003FA91A5B (Mono JIT Code) PixelCrushers.DialogueSystem.QuestLog:GetAllQuests (PixelCrushers.DialogueSystem.QuestState)
0x000000003FA91883 (Mono JIT Code) PixelCrushers.DialogueSystem.DialogueEditor.DialogueEditorWindow:CatalogueWatchableQuestNames ()
0x000000003FA916E3 (Mono JIT Code) PixelCrushers.DialogueSystem.DialogueEditor.DialogueEditorWindow:RefreshWatchableQuestList ()
0x000000003FA58833 (Mono JIT Code) PixelCrushers.DialogueSystem.DialogueEditor.DialogueEditorWindow:ResetWatchSection ()
0x000000003FA586C3 (Mono JIT Code) PixelCrushers.DialogueSystem.DialogueEditor.DialogueEditorWindow:HandlePlayModeStateChanged ()
0x000000003FA58323 (Mono JIT Code) PixelCrushers.DialogueSystem.DialogueEditor.DialogueEditorWindow:OnPlaymodeStateChanged ()
0x000000003FA581D1 (Mono JIT Code) [EditorApplication.cs:281] UnityEditor.EditorApplication:Internal_PlayModeStateChanged (UnityEditor.PlayModeStateChange) 
0x000000003EE28DDE (Mono JIT Code) (wrapper runtime-invoke) <Module>:runtime_invoke_void_int (object,intptr,intptr,intptr)
0x00007FFA1707A1AB (mono-2.0-bdwgc) [mini-runtime.c:2809] mono_jit_runtime_invoke 
0x00007FFA17001A42 (mono-2.0-bdwgc) [object.c:2915] do_runtime_invoke 
0x00007FFA1700AA2F (mono-2.0-bdwgc) [object.c:2962] mono_runtime_invoke 
0x0000000140BD4CF7 (Unity) CallStaticMonoMethod
0x0000000140BD4A7E (Unity) CallStaticMonoMethod
0x00000001413C6770 (Unity) PlayerLoopController::SetIsPlaying
0x00000001413CA05D (Unity) Application::TickTimer
0x00000001415C42ED (Unity) MainMessageLoop
0x00000001415C65DC (Unity) WinMain
0x000000014271FDA7 (Unity) __scrt_common_main_seh
0x00007FFA568B1FE4 (KERNEL32) BaseThreadInitThunk
0x00007FFA5940F061 (ntdll) RtlUserThreadStart
User avatar
Tony Li
Posts: 22059
Joined: Thu Jul 18, 2013 1:27 pm

Re: Dialogue System 1.8.2 Released!

Post by Tony Li »

Thanks! I'll fix this in the next release so the Watches tab doesn't try to catalogue the available quests if there's no Dialogue Manager.
Post Reply