I look forward to version 2.0. Finally I won't have to assign actors and conversants all the time, right?
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:
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).
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.
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).
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:
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?
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 ) converts exported Articy:draft markup inside dialogue fragments into Unity's rich text – which should be enabled in the settings.
Accessible via right-click on an exported XML (use before attempting to convert it to a database).
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.)
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(@"'", "'"); // 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.)
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(@"'", "'"); // 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.
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.)
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.
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.