added yarn spinner package

This commit is contained in:
2026-08-14 13:27:17 +02:00
parent c140236bf7
commit c483bda3ea
573 changed files with 81459 additions and 0 deletions
@@ -0,0 +1,47 @@
/*
Text Animator for Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
#nullable enable
namespace Yarn.Unity.Addons.TextAnimatorIntegration
{
using System.Collections;
using UnityEngine;
#if YS_USE_TEXT_ANIMATOR_2
using Febucci.UI.Core;
using Febucci.UI.Core.Parsing;
using Febucci.UI.Actions;
[System.Serializable]
public class ActionMarkupAction : ActionScriptableBase
{
private TextAnimatorYarnTypewriter? ytw;
public override IEnumerator DoAction(ActionMarker action, TypewriterCore typewriter, TypingInfo typingInfo)
{
if (ytw == null)
{
ytw = typewriter.GetComponentInParent<TextAnimatorYarnTypewriter>();
}
if (ytw != null)
{
ytw.StartMarkup(action.index);
while (!ytw.ActionMarkupFinishedProcessing)
{
yield return null;
}
}
}
}
#else
using ActionScriptableBase = UnityEngine.ScriptableObject;
public class ActionMarkupAction : ActionScriptableBase
{
#pragma warning disable CS8618
public string TagID => tagID;
public string tagID;
#pragma warning restore CS8618
}
#endif
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 92124a891cda04bf9a84ebd300c4ec16
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/ActionMarkupAction.cs
uploadId: 968678
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 35e4f4da531bc49359e2558b33e94a2f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,70 @@
/*
Text Animator for Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
#if YS_USE_TEXT_ANIMATOR_2 || YS_USE_TEXT_ANIMATOR_3
#define USE_TEXT_ANIMATOR
#endif
namespace Yarn.Unity.Addons.TextAnimatorIntegration.Editor
{
#nullable enable
#if USE_TEXT_ANIMATOR
using UnityEngine;
using UnityEditor;
using UnityEngine.EventSystems;
// Literally copy-pasted from YS, just with a different menuitem and guid
public static class CreateTextAnimatorDialogueRunnerMenuItem
{
// gonna have to change this so that it uses a different guid for each one
// which sucks but oh well
#if YS_USE_TEXT_ANIMATOR_3
const string DialogueRunnerPrefabGUID = "cd09801223b5f4504a921d33ef1ff98c";
#elif YS_USE_TEXT_ANIMATOR_2
const string DialogueRunnerPrefabGUID = "eeb327a65c4bc4376ae432ce641f04af";
#endif
/// <summary>
/// Instantiates the Dialogue System prefab in the currently active scene,
/// and returns the created <see cref="DialogueRunner"/>.
/// </summary>
/// <returns>A newly created <see cref="DialogueRunner"/>.</returns>
/// <exception cref="System.InvalidOperationException">Thrown when the
/// Dialogue System prefab cannot be found in the Yarn Spinner
/// package.</exception>
[MenuItem("GameObject/Yarn Spinner/Dialogue System with Text Animator")]
public static DialogueRunner CreateDialogueRunner()
{
string assetPath = AssetDatabase.GUIDToAssetPath(DialogueRunnerPrefabGUID);
var prefabAsset = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
if (prefabAsset == null)
{
throw new System.InvalidOperationException(
$"Can't create a new Dialogue System: Can't find the prefab to create a Dialogue System from."
);
}
var instantiatedPrefab = (GameObject)PrefabUtility.InstantiatePrefab(prefabAsset);
#if UNITY_2023_1_OR_NEWER
var eventSystems = Object.FindObjectsByType<EventSystem>(FindObjectsSortMode.None);
#else
var eventSystems = Object.FindObjectsOfType<EventSystem>();
#endif
if (eventSystems.Length > 1)
{
// At least one other event system is present in the scene. Turn off
// the one that came with the prefab - it's not needed.
var instantiatedEventSystem = instantiatedPrefab.GetComponentInChildren<EventSystem>();
instantiatedEventSystem.gameObject.SetActive(false);
}
return instantiatedPrefab.GetComponent<DialogueRunner>();
}
}
#endif
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: ff74191f08b6d4d5488de8f2e633dfd4
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/Editor/CreateTextAnimatorDialogueRunnerMenuItem.cs
uploadId: 968678
@@ -0,0 +1,6 @@
<engine:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:engine="UnityEngine.UIElements" xmlns:editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<Style src="project://database/Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/Editor/TextAnimatorConfigurationWindow.uss?fileID=7433441132597879392&amp;guid=e52662955dcee47bbbe24c3602e61bf7&amp;type=3#TextAnimatorConfigurationWindow" />
<engine:Label text="Text Animator for Unity is installed and Yarn Spinner is correctly configured to use it." style="white-space: normal; -unity-text-align: upper-left; margin-bottom: 4px; flex-wrap: wrap; margin-left: 4px;" />
<engine:Label text="The default Yarn Spinner action has not been registered with Text Animator yet.&#10;Depending on your setup this may not be an issue, if you want to register it you can here.&#10;Otherwise each typewriter will need to ensure their own action is registered." name="definedLabel" style="white-space: normal; -unity-text-align: upper-left; margin-left: 4px;" />
<engine:Button text="Register action" name="definedButton" style="margin-left: 8px; margin-right: 8px;" />
</engine:UXML>
@@ -0,0 +1,17 @@
fileFormatVersion: 2
guid: 02d739c3d6bb545fdb6d4bf14bf07822
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/Editor/InstalledAndDefinedElement.uxml
uploadId: 968678
@@ -0,0 +1,7 @@
<engine:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:engine="UnityEngine.UIElements" xmlns:editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<Style src="project://database/Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/Editor/TextAnimatorConfigurationWindow.uss?fileID=7433441132597879392&amp;guid=e52662955dcee47bbbe24c3602e61bf7&amp;type=3#TextAnimatorConfigurationWindow" />
<engine:Label text="Text Animator for Unity is installed but the scripting defines we use are not configured." style="overflow: visible; flex-wrap: nowrap; white-space: normal; padding-bottom: 8px; margin-bottom: 4px;" />
<engine:Label text="This will add the necessary scripting defines to the following build targets:" style="overflow: visible; flex-wrap: nowrap; white-space: normal;" />
<engine:VisualElement name="scriptingToggleContainer" style="flex-grow: 1;" />
<engine:Button text="Add Scripting Defines" name="scriptingButton" style="margin-left: 8px; margin-right: 8px;" />
</engine:UXML>
@@ -0,0 +1,17 @@
fileFormatVersion: 2
guid: b2fe556531a6644c0b405d6482d9de4f
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/Editor/InstalledAndNotDefinedElement.uxml
uploadId: 968678
@@ -0,0 +1,5 @@
<engine:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:engine="UnityEngine.UIElements" xmlns:editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<Style src="project://database/Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/Editor/TextAnimatorConfigurationWindow.uss?fileID=7433441132597879392&amp;guid=e52662955dcee47bbbe24c3602e61bf7&amp;type=3#TextAnimatorConfigurationWindow" />
<engine:Label text="Text Animator for Unity is not installed. You will need to install it before you can use it with Yarn Spinner." style="white-space: normal; margin-left: 4px; flex-wrap: wrap;" />
<engine:Button text="Get Text Animator for Unity" name="notInstalledButton" style="margin-left: 8px; margin-right: 8px; margin-bottom: 4px;" />
</engine:UXML>
@@ -0,0 +1,17 @@
fileFormatVersion: 2
guid: c39c92b7ce46b46329bda933bb30f5fc
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/Editor/NotInstalledElement.uxml
uploadId: 968678
@@ -0,0 +1,551 @@
/*
Text Animator for Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
#if YS_USE_TEXT_ANIMATOR_2 || YS_USE_TEXT_ANIMATOR_3
#define USE_TEXT_ANIMATOR
#endif
#nullable enable
namespace Yarn.Unity.Addons.TextAnimatorIntegration.Editor
{
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.Build;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
#if YS_USE_TEXT_ANIMATOR_2
using Febucci.UI;
using ActionScriptableBase = Febucci.UI.Actions.ActionScriptableBase;
#elif YS_USE_TEXT_ANIMATOR_3
using Febucci.TextAnimatorForUnity;
using ActionScriptableBase = Febucci.TextAnimatorForUnity.Actions.Core.ActionScriptableBase;
#else
using ActionScriptableBase = UnityEngine.ScriptableObject;
#endif
public class TextAnimatorConfigurationWindow : EditorWindow
{
private const string YSTA2Define = "YS_USE_TEXT_ANIMATOR_2";
private const string YSTA3Define = "YS_USE_TEXT_ANIMATOR_3";
private const string TextAnimatorV2SettingsType = "Febucci.UI.TextAnimatorSettings";
private const string TextAnimatorV3SettingsType = "Febucci.TextAnimatorForUnity.TextAnimatorSettings";
private const string TextAnimatorSettingsName = "TextAnimatorSettings";
private const string TextAnimatorPageURL = "https://www.febucci.com/tools/text-animator-unity";
private const string installedAndNotDefinedUXMLGuid = "b2fe556531a6644c0b405d6482d9de4f";
private const string notInstalledUXMLGuid = "c39c92b7ce46b46329bda933bb30f5fc";
private const string InstalledAndDefinedUXMLGuid = "02d739c3d6bb545fdb6d4bf14bf07822";
private const string TextAnimatorRunnerPrefabGuidV2 = "eeb327a65c4bc4376ae432ce641f04af";
private const string TextAnimatorRunnerPrefabGuidV3 = "cd09801223b5f4504a921d33ef1ff98c";
private const string TextAnimatorRunnerPackageGuidV2 = "5a3687d8c4744405692bd44867d6ac58";
private const string TextAnimatorRunnerPackageGuidV3 = "014e0ab0fc9ca4cd69ce3203bfe646f9";
// this is a terrible hack but seems to work
// it will get all the possible build targets for whatever version of unity the package is installed in
// this works because unity has kindly defined a whole bunch of public static build targets already
// would be nicer to be able to go like `NamedBuildTarget.All` and have it return a list of them, but oh well
private static List<NamedBuildTarget> AllBuildTargets
{
get
{
var targets = new List<NamedBuildTarget>();
// we have this because there are a few targets (like CloudRendering) that are redirected to a different target
// and these change on a unity version basis so we can't just manually ignore them like we do with Unknown
// so instead if we have run into a build target with a resolved name that we've already seen we ignore it
var names = new HashSet<string>();
var fields = typeof(NamedBuildTarget).GetFields(BindingFlags.Public | BindingFlags.Static);
foreach (var field in fields)
{
// so turns out weird things can happen when trying to set or get defines on unknown
// plus it doesn't really mean anything so why bother, let's just skip it
if (field.Name == "Unknown")
{
continue;
}
// if it isn't a NamedBuildTarget we ignore it
if (field.FieldType == typeof(NamedBuildTarget))
{
var t = (NamedBuildTarget)field.GetValue(null);
// Stadia is deprecated and will throw an error if we
// attempt to use it; ignore it
if (t.TargetName == "Stadia")
{
continue;
}
if (names.Add(t.TargetName))
{
targets.Add(t);
}
}
}
return targets;
}
}
// Because we don't know if TextAnimator is installed we can't really safely add the scripting define
// So this property does a check looking for the TextAnimator global settings in the resources folder
// if we find something with it's name we then check if the name of the type is what we'd expect
// this isn't perfect but seems to work pretty well and is mostly robust to change
private bool IsTextAnimatorInstalled
{
get
{
var resources = Resources.LoadAll(TextAnimatorSettingsName);
foreach (var resource in resources)
{
if (resource.GetType().ToString().EndsWith(TextAnimatorV2SettingsType))
{
return true;
}
else if (resource.GetType().ToString().EndsWith(TextAnimatorV3SettingsType))
{
return true;
}
}
return false;
}
}
private bool IsTextAnimatorV2Installed
{
get
{
var resources = Resources.LoadAll(TextAnimatorSettingsName);
foreach (var resource in resources)
{
if (resource.GetType().ToString().EndsWith(TextAnimatorV2SettingsType))
{
return true;
}
}
return false;
}
}
private bool IsTextAnimatorV3Installed
{
get
{
var resources = Resources.LoadAll(TextAnimatorSettingsName);
foreach (var resource in resources)
{
if (resource.GetType().ToString().EndsWith(TextAnimatorV3SettingsType))
{
return true;
}
}
return false;
}
}
private string? ScriptingDefine
{
get
{
if (IsTextAnimatorV2Installed)
{
return YSTA2Define;
}
else if (IsTextAnimatorV3Installed)
{
return YSTA3Define;
}
else
{
return null;
}
}
}
private void AddDefines(Dictionary<string, bool> filters)
{
var definitionSymbol = ScriptingDefine;
if (string.IsNullOrEmpty(definitionSymbol))
{
Debug.LogWarning("Was unable to determine the valid scripting define for this version of Text Animator. Cannot add the necessary defines.");
return;
}
foreach (var target in AllBuildTargets)
{
try
{
if (filters.TryGetValue(target.TargetName, out var value))
{
if (value == false)
{
Debug.Log($"skipping {target.TargetName}");
continue;
}
}
PlayerSettings.GetScriptingDefineSymbols(target, out var defines);
if (defines.Contains(definitionSymbol))
{
continue;
}
else
{
var allSymbols = defines.ToList();
allSymbols.Add(definitionSymbol);
PlayerSettings.SetScriptingDefineSymbols(target, allSymbols.ToArray());
}
}
catch (System.ArgumentException)
{
Debug.LogWarning($"{target.TargetName} is not supported");
continue;
}
}
this.Close();
}
private enum InstallationState
{
InstalledAndDefined, InstalledAndNotDefined, NotInstalledAndNotDefined, NotInstalledAndDefined,
}
private InstallationState installationState
{
get
{
#if USE_TEXT_ANIMATOR
if (IsTextAnimatorInstalled)
{
return InstallationState.InstalledAndDefined;
}
return InstallationState.NotInstalledAndDefined;
#else
if (IsTextAnimatorInstalled)
{
return InstallationState.InstalledAndNotDefined;
}
return InstallationState.NotInstalledAndNotDefined;
#endif
}
}
[SerializeField]
private VisualTreeAsset? m_VisualTreeAsset = default;
[MenuItem("Window/Yarn Spinner/Text Animator Integration Configuration")]
public static void OpenWindow()
{
TextAnimatorConfigurationWindow window = GetWindow<TextAnimatorConfigurationWindow>(true, "Yarn Spinner Text Animator Configuration", true);
var height = 438;
switch (window.installationState)
{
case InstallationState.NotInstalledAndDefined:
goto case InstallationState.NotInstalledAndNotDefined;
case InstallationState.NotInstalledAndNotDefined:
height += 54;
break;
case InstallationState.InstalledAndDefined:
if (TextAnimationActionState.DefaultActionIsRegistered() == TextAnimationActionState.RegistrationResult.NotRegistered)
{
height += 99;
}
else
{
height += 19;
}
break;
case InstallationState.InstalledAndNotDefined:
height += 63 + AllBuildTargets.Count * 19;
break;
}
window.minSize = new Vector2(512, height);
window.maxSize = window.minSize;
window.Show();
}
public void CreateGUI()
{
VisualElement root = rootVisualElement;
if (m_VisualTreeAsset == null)
{
return;
}
var installation = installationState;
string activeContainerGUID = installation switch
{
InstallationState.InstalledAndDefined => InstalledAndDefinedUXMLGuid,
InstallationState.InstalledAndNotDefined => installedAndNotDefinedUXMLGuid,
_ => notInstalledUXMLGuid,
};
// Instantiate UXML
VisualElement labelFromUXML = m_VisualTreeAsset.Instantiate();
root.Add(labelFromUXML);
root.Q<TemplateContainer>().style.flexGrow = 1;
var tree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(AssetDatabase.GUIDToAssetPath(activeContainerGUID));
if (tree == null)
{
return;
}
var container = tree.CloneTree();
root.Add(container);
switch (installation)
{
case InstallationState.InstalledAndDefined:
CreateInstalledAndDefinedGUI(container);
break;
case InstallationState.InstalledAndNotDefined:
CreateInstalledAndNotDefinedGUI(container);
break;
case InstallationState.NotInstalledAndDefined:
goto case InstallationState.NotInstalledAndNotDefined;
case InstallationState.NotInstalledAndNotDefined:
CreateNotInstalledGUI(container);
break;
}
}
public void CreateInstalledAndNotDefinedGUI(VisualElement root)
{
var container = root.Q<VisualElement>("scriptingToggleContainer");
if (container == null)
{
return;
}
var button = root.Q<Button>("scriptingButton");
if (button == null)
{
return;
}
Dictionary<string, bool> targetValues = new();
foreach (var target in AllBuildTargets)
{
targetValues.Add(target.TargetName, true);
var targetName = target.TargetName;
Toggle toogle = new Toggle(targetName);
toogle.name = targetName;
toogle.style.marginLeft = 20;
toogle.SetValueWithoutNotify(true);
container.Add(toogle);
toogle.RegisterValueChangedCallback((evt) =>
{
if (evt.currentTarget is not Toggle t)
{
return;
}
targetValues[t.name] = evt.newValue;
});
}
button.clicked += () =>
{
ExtractTextAnimatorPackage();
AddDefines(targetValues);
};
}
private bool GetAssetExists(string guid)
{
return string.IsNullOrEmpty(AssetDatabase.GUIDToAssetPath(guid)) == false;
}
#pragma warning disable IDE0051 // Private member is unused
private bool ExtractPackageIfAssetNotPresent(string assetGUID, string packageGUID)
{
AssetDatabase.Refresh();
if (GetAssetExists(assetGUID))
{
// The runner prefab already exists. Nothing left to do.
Debug.Log("Not unpacking Text Animator for Yarn Spinner assets, because it's already in the project.");
return false;
}
var pathToPackage = AssetDatabase.GUIDToAssetPath(packageGUID);
if (pathToPackage == string.Empty)
{
Debug.LogError($"Failed to find the Text Animator for Yarn Spinner package file to extract!");
return false;
}
Debug.Log("Importing package.");
AssetDatabase.ImportPackage(pathToPackage, false);
return true;
}
#pragma warning disable
private void ExtractTextAnimatorPackage()
{
if (IsTextAnimatorV2Installed)
{
ExtractPackageIfAssetNotPresent(TextAnimatorRunnerPrefabGuidV2, TextAnimatorRunnerPackageGuidV2);
}
else if (IsTextAnimatorV3Installed)
{
ExtractPackageIfAssetNotPresent(TextAnimatorRunnerPrefabGuidV3, TextAnimatorRunnerPackageGuidV3);
}
else
{
EditorUtility.DisplayDialog(
"Unknown Version of TextAnimator",
"We aren't able to detect which version of Text Animator you have installed. " +
"This means we can't complete the installation. Please contact Yarn Spinner support for help.",
"Close");
}
}
public void CreateInstalledAndDefinedGUI(VisualElement root)
{
// at this point we are fine to continue but might as well offer to see if the default action can be registered and do it
var message = root.Q<Label>("definedLabel");
var button = root.Q<Button>("definedButton");
if (message == null || button == null)
{
return;
}
if (TextAnimationActionState.DefaultActionIsRegistered() == TextAnimationActionState.RegistrationResult.NotRegistered)
{
message.style.display = DisplayStyle.Flex;
button.style.display = DisplayStyle.Flex;
button.clicked += () =>
{
this.Close();
TextAnimationActionState.RegisterDefaultAction();
};
}
else
{
message.style.display = DisplayStyle.None;
button.style.display = DisplayStyle.None;
}
}
public void CreateNotInstalledGUI(VisualElement root)
{
// basically get the button
// hook up the call
var button = root.Q<Button>("notInstalledButton");
if (button == null)
{
return;
}
button.clicked += GetTextAnimator;
}
private void GetTextAnimator()
{
this.Close();
Application.OpenURL(TextAnimatorConfigurationWindow.TextAnimatorPageURL);
}
}
class TextAnimationActionState
{
internal enum RegistrationResult
{
NotRegistered, Registered, ExistingRegistration, UnableToResolve,
}
#if YS_USE_TEXT_ANIMATOR_2
const string defaultActionGUID = "797c9181a760b4946ab39cc7d835909f";
internal static RegistrationResult IsActionRegistered(ActionScriptableBase action)
{
foreach (var registeredAction in TextAnimatorSettings.Instance.actions.defaultDatabase.Data)
{
if (registeredAction == null)
{
continue;
}
if (action.TagID == registeredAction.TagID)
{
if (action.GetType() == registeredAction.GetType())
{
return RegistrationResult.Registered;
}
return RegistrationResult.ExistingRegistration;
}
}
return RegistrationResult.NotRegistered;
}
internal static RegistrationResult DefaultActionIsRegistered()
{
ActionMarkupAction? value;
var path = AssetDatabase.GUIDToAssetPath(defaultActionGUID);
if (string.IsNullOrEmpty(path))
{
return RegistrationResult.UnableToResolve;
}
value = AssetDatabase.LoadAssetAtPath<ActionMarkupAction>(path);
if (value == null)
{
return RegistrationResult.UnableToResolve;
}
return IsActionRegistered(value);
}
internal static void RegisterDefaultAction()
{
ActionMarkupAction? value;
var path = AssetDatabase.GUIDToAssetPath(defaultActionGUID);
if (string.IsNullOrEmpty(path))
{
return;
}
value = AssetDatabase.LoadAssetAtPath<ActionMarkupAction>(path);
if (value == null)
{
return;
}
TextAnimatorSettings.Instance.actions.defaultDatabase.Add(value);
}
internal static void RegisterAction(ActionScriptableBase action)
{
TextAnimatorSettings.Instance.actions.defaultDatabase.Add(action);
}
#elif YS_USE_TEXT_ANIMATOR_3
// in TA3 we have a custom typewriter so don't need to worry about the registration
// so we can just say it's registered and otherwise move on with our life
internal static RegistrationResult DefaultActionIsRegistered()
{
return RegistrationResult.Registered;
}
internal static void RegisterDefaultAction()
{
return;
}
#else
internal static RegistrationResult DefaultActionIsRegistered() { return RegistrationResult.UnableToResolve; }
internal static void RegisterDefaultAction() { return; }
#endif
}
}
@@ -0,0 +1,20 @@
fileFormatVersion: 2
guid: 68ffb26081e84448faf47d2dec707c3b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- m_VisualTreeAsset: {fileID: 9197481963319205126, guid: b04973c1fd259487ebb3a4e5292b0c47,
type: 3}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/Editor/TextAnimatorConfigurationWindow.cs
uploadId: 968678
@@ -0,0 +1,5 @@
.custom-label {
font-size: 20px;
-unity-font-style: bold;
color: rgb(68, 138, 255);
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: e52662955dcee47bbbe24c3602e61bf7
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
disableValidation: 0
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/Editor/TextAnimatorConfigurationWindow.uss
uploadId: 968678
@@ -0,0 +1,5 @@
<engine:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:engine="UnityEngine.UIElements" xmlns:editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<Style src="project://database/Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/Editor/TextAnimatorConfigurationWindow.uss?fileID=7433441132597879392&amp;guid=e52662955dcee47bbbe24c3602e61bf7&amp;type=3#TextAnimatorConfigurationWindow" />
<engine:VisualElement style="flex-grow: 1; background-image: url(&quot;project://database/Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/Editor/banner.jpg?fileID=21300000&amp;guid=a4bc00c6001e94d6d85534ee24530d57&amp;type=3#banner&quot;); min-width: 315px; min-height: 250px; background-position-x: center 0; background-position-y: center 0; -unity-background-scale-mode: scale-to-fit; flex-direction: column;" />
<engine:Label text="Text Animator for Unity is a 3rd Party asset for creating gorgeous text effects." style="white-space: normal; margin-top: 2px; flex-wrap: wrap; -unity-text-align: upper-center; margin-bottom: 4px;" />
</engine:UXML>
@@ -0,0 +1,17 @@
fileFormatVersion: 2
guid: b04973c1fd259487ebb3a4e5292b0c47
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/Editor/TextAnimatorConfigurationWindow.uxml
uploadId: 968678
@@ -0,0 +1,80 @@
using UnityEngine;
using UnityEditor;
using Yarn.Unity.Addons.TextAnimatorIntegration;
using Yarn.Unity.Editor;
using Yarn.Unity.Addons.TextAnimatorIntegration.Editor;
namespace Yarn.Unity.Addons.TextAnimatorIntegration
{
#if YS_USE_TEXT_ANIMATOR_2
[CustomEditor(typeof(TextAnimatorYarnTypewriter))]
public class TextAnimatorTypewriterEditor : YarnEditor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
var value = target as TextAnimatorYarnTypewriter;
if (value == null)
{
return;
}
if (value.insertionAction == null)
{
if (value.tagMode != TagMode.TextAnimatorOnlyTags)
{
EditorGUILayout.HelpBox($"The tag mode is set to be {value.tagMode}, but {nameof(value.insertionAction)} is null. You must have an action configured to use this tag mode.", MessageType.Warning);
}
return;
}
var tagID = value.insertionAction.TagID ?? "NULL";
var registrationStatus = TextAnimationActionState.IsActionRegistered(value.insertionAction);
switch (registrationStatus)
{
case TextAnimationActionState.RegistrationResult.ExistingRegistration:
if (value.tagMode != TagMode.TextAnimatorOnlyTags)
{
EditorGUILayout.HelpBox($"There already exists a Text Animator action registered with tag id: \"{tagID}\". You must only have a single action registered for this tag.", MessageType.Warning);
}
break;
case TextAnimationActionState.RegistrationResult.NotRegistered:
if (value.tagMode == TagMode.TextAnimatorOnlyTags)
{
break;
}
EditorGUILayout.HelpBox($"The tag mode is set to be {value.tagMode}, but {nameof(value.insertionAction)} is not registered with Text Animator. You must register the action to use this mode.", MessageType.Info);
if (GUILayout.Button("Register action"))
{
TextAnimationActionState.RegisterAction(value.insertionAction);
}
break;
case TextAnimationActionState.RegistrationResult.Registered:
if (value.tagMode == TagMode.TextAnimatorOnlyTags)
{
EditorGUILayout.HelpBox($"You have registered a Yarn Spinner action tag with Text Animator. This won't cause issues but is indicative of a configuration issue.", MessageType.Info);
}
break;
}
}
}
#elif !YS_USE_TEXT_ANIMATOR_3
[CustomEditor(typeof(TextAnimatorYarnTypewriter))]
public class TextAnimatorTypewriterEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
EditorGUILayout.HelpBox($"This package requires configuration before it can be used.", MessageType.Error);
if (GUILayout.Button("Open Configuration Window"))
{
TextAnimatorConfigurationWindow.OpenWindow();
}
}
}
#endif
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 75189881bc69a4f5da9a141b5624bc34
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/Editor/TextAnimatorTypewriterEditor.cs
uploadId: 968678
@@ -0,0 +1,23 @@
{
"name": "YarnSpinner.Addons.TextAnimatorIntegration.Editor",
"rootNamespace": "",
"references": [
"GUID:1e113d3b5d77bc04eab508251483e8ff",
"GUID:e539678cf69674b418d82e89c16a9753",
"GUID:34aa492b82754644eac2f903cd496268",
"GUID:44199294ed9884553846d30d420ad69a",
"GUID:3a299c53e4c683b4eb4a04c9ad9e648f",
"UniTask"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 33fdf84cdc6e643f8867a62469ab5024
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/Editor/YarnSpinner.Addons.TextAnimatorIntegration.Editor.asmdef
uploadId: 968678
Binary file not shown.

After

Width:  |  Height:  |  Size: 576 KiB

@@ -0,0 +1,124 @@
fileFormatVersion: 2
guid: a4bc00c6001e94d6d85534ee24530d57
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 0
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 1
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/Editor/banner.jpg
uploadId: 968678
@@ -0,0 +1,93 @@
/*
Text Animator for Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
#if YS_USE_TEXT_ANIMATOR_2 || YS_USE_TEXT_ANIMATOR_3
#define USE_TEXT_ANIMATOR
#endif
namespace Yarn.Unity.Addons.TextAnimatorIntegration
{
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
public class ExampleVersionChecker : MonoBehaviour
{
[Range(2, 3)]
[SerializeField]
internal int version = 0;
void Start()
{
ValidateVersion(version);
}
public void ValidateVersion(int supportedVersion)
{
#if YS_USE_TEXT_ANIMATOR_2
var expectedVersion = 2;
#elif YS_USE_TEXT_ANIMATOR_3
var expectedVersion = 3;
#endif
#if USE_TEXT_ANIMATOR
if (supportedVersion != expectedVersion)
{
if (EditorUtility.DisplayDialog("Unable to continue", $"This example was created for Text Animator v{supportedVersion}, it will not work.", "Exit Play mode"))
{
EditorApplication.ExitPlaymode();
}
}
#else
if (EditorUtility.DisplayDialog("Unable to continue", "This example requires Text Animator installed and configured, it will not work.", "Exit Play mode"))
{
EditorApplication.ExitPlaymode();
}
#endif
}
void OnValidate()
{
// have this set like this so after the example scene has been made it is annoying to change.
// Is only designed to stop casual changes.
// to change this set the flags to be HideFlags.None
this.hideFlags = HideFlags.NotEditable;
}
}
[CustomEditor(typeof(ExampleVersionChecker))]
public class ExampleVersionCheckerEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
var value = target as ExampleVersionChecker;
if (value == null)
{
return;
}
#if YS_USE_TEXT_ANIMATOR_2
if (value.version != 2)
{
EditorGUILayout.HelpBox($"This example was created for Text Animator version 2. It WILL not work correctly.", MessageType.Error);
}
#elif YS_USE_TEXT_ANIMATOR_3
if (value.version != 3)
{
EditorGUILayout.HelpBox($"This example was created for Text Animator version 3. It WILL not work correctly.", MessageType.Error);
}
#else
EditorGUILayout.HelpBox($"This example requires Text Animator installed and configured. It WILL not work correctly.", MessageType.Error);
#endif
}
}
#else // UNITY_EDITOR
public class ExampleVersionChecker : MonoBehaviour
{
// This class is only used in the editor, so we'll replace it with an
// empty class (to prevent Unity going 'hey there's no class with this
// guid!')
}
#endif
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: e4bedaf9f38a8445aa8761ad8c7f25e4
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/ExampleVersionChecker.cs
uploadId: 968678
@@ -0,0 +1,60 @@
/*
Text Animator for Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
namespace Yarn.Unity.Addons.TextAnimatorIntegration
{
#nullable enable
using System.Text;
using System.Collections.Generic;
using UnityEngine;
using Yarn.Unity;
using Yarn.Markup;
/// <summary>
/// This object exists to allow multiple Text Animator contexts to exist simultaneously but still have Yarn markup be converted into Text Animator form.
/// Without this object because each Typewriter does it themselves you get a collision of multiple objects attempting to provide replacements for the same markup.
/// </summary>
public class SharedTextAnimatorMarkupReplacementProcessor : MonoBehaviour, IAttributeMarkerProcessor
{
#if YS_USE_TEXT_ANIMATOR_3
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
var runner = DialogueRunner.FindRunner(this);
if (runner == null)
{
Debug.LogWarning("Was unable to find a dialogue runner to register the Text Animator replacement markup upon.");
return;
}
foreach (var tag in TextAnimatorMarkupManager.AllTags())
{
runner.LineProvider.RegisterMarkerProcessor(tag, this);
}
}
public ReplacementMarkerResult ProcessReplacementMarker(MarkupAttribute marker, StringBuilder childBuilder, List<MarkupAttribute> childAttributes, string localeCode)
{
int invisibleCharacters = 0;
if (TextAnimatorMarkupManager.ConvertedForm(marker, out var front, out var back))
{
childBuilder.Insert(0, front);
invisibleCharacters = front.Length;
if (marker.Length > 0)
{
childBuilder.Append(back);
invisibleCharacters += back.Length;
}
}
return new ReplacementMarkerResult(invisibleCharacters);
}
#else
public ReplacementMarkerResult ProcessReplacementMarker(MarkupAttribute marker, StringBuilder childBuilder, List<MarkupAttribute> childAttributes, string localeCode)
{
throw new System.InvalidOperationException("This object cannot exist without Text Animator 3 being installed and correctly configured!");
}
#endif
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 022b92e95b0fe4992998988bad36690b
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/SharedTextAnimatorMarkupReplacementProcessor.cs
uploadId: 968678
@@ -0,0 +1,249 @@
/*
Text Animator for Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
#if YS_USE_TEXT_ANIMATOR_2 || YS_USE_TEXT_ANIMATOR_3
#define USE_TEXT_ANIMATOR
#endif
namespace Yarn.Unity.Addons.TextAnimatorIntegration
{
#nullable enable
using Yarn.Markup;
public partial class TextAnimatorMarkupManager
{
#if USE_TEXT_ANIMATOR
private enum MarkupType
{
Unknown, Behaviour, Action, Appearances,
}
private const string disappearanceAttributeKey = "disappearance";
private const string typeAttributeKey = "type";
private const string trimAttributeKey = "trimwhitespace";
private const string behaviourValue = "behaviour";
private const string actionValue = "action";
private const string appearanceValue = "appearance";
// how do I handle playback parameters (aka <wave once> sorta things?), I think we just ignore that and in the docs go something like:
// "if you have very complex ones we suggest creating them as a palette markup which already handles direct replacement"
// converts a Yarn Spinner marker into Text Animator form
public static bool ConvertedForm(MarkupAttribute attribute, out string frontReplacement, out string backReplacement)
{
MarkupType markupType = MarkupType.Unknown;
if (attribute.TryGetProperty(typeAttributeKey, out string? result))
{
switch (result.ToLower())
{
case behaviourValue:
markupType = MarkupType.Behaviour;
break;
case actionValue:
markupType = MarkupType.Action;
break;
case appearanceValue:
markupType = MarkupType.Appearances;
break;
}
}
char opening = default;
char closing = default;
switch (markupType)
{
case MarkupType.Behaviour:
{
if (IsBehaviourTag(attribute.Name))
{
opening = TextAnimatorMarkupManager.OpeningBehavioursBracket;
closing = TextAnimatorMarkupManager.ClosingBehavioursBracket;
}
break;
}
case MarkupType.Action:
{
if (IsActionTag(attribute.Name))
{
opening = TextAnimatorMarkupManager.OpeningActionsBracket;
closing = TextAnimatorMarkupManager.ClosingActionsBracket;
}
break;
}
case MarkupType.Appearances:
{
if (IsAppearanceTag(attribute.Name))
{
opening = TextAnimatorMarkupManager.OpeningAppearancesBracket;
closing = TextAnimatorMarkupManager.ClosingAppearancesBracket;
}
break;
}
case MarkupType.Unknown:
{
// we don't know what it is
// so we are gonna check them all, one by one
// but first we'll check to see if we have done this before for this tag
if (cachedAttributeTypes.TryGetValue(attribute.Name, out var tuple))
{
opening = tuple.start;
closing = tuple.end;
break;
}
else
{
if (IsBehaviourTag(attribute.Name))
{
opening = TextAnimatorMarkupManager.OpeningBehavioursBracket;
closing = TextAnimatorMarkupManager.ClosingBehavioursBracket;
cachedAttributeTypes.TryAdd(attribute.Name, (opening, closing));
break;
}
if (IsActionTag(attribute.Name))
{
opening = TextAnimatorMarkupManager.OpeningActionsBracket;
closing = TextAnimatorMarkupManager.ClosingActionsBracket;
cachedAttributeTypes.TryAdd(attribute.Name, (opening, closing));
break;
}
if (IsAppearanceTag(attribute.Name))
{
opening = TextAnimatorMarkupManager.OpeningAppearancesBracket;
closing = TextAnimatorMarkupManager.ClosingAppearancesBracket;
cachedAttributeTypes.TryAdd(attribute.Name, (opening, closing));
break;
}
}
// if we got here then we didn't find it
// give up here
frontReplacement = string.Empty;
backReplacement = string.Empty;
return false;
}
default:
frontReplacement = string.Empty;
backReplacement = string.Empty;
return false;
}
var conversion = ConvertMarkupIntoTextAnimatorTag(attribute, opening, closing);
if (!string.IsNullOrWhiteSpace(conversion.Item1) && !string.IsNullOrWhiteSpace(conversion.Item2))
{
frontReplacement = conversion.Item1;
backReplacement = conversion.Item2;
return true;
}
else
{
frontReplacement = string.Empty;
backReplacement = string.Empty;
return false;
}
}
private static (string?, string?) ConvertMarkupIntoTextAnimatorTag(MarkupAttribute attribute, char openingSymbol, char closingSymbol)
{
System.Text.StringBuilder stringBuilder = new();
stringBuilder.Append(openingSymbol);
string closing = $"{openingSymbol}/{attribute.Name}{closingSymbol}";
// ok so if it is a disappearance then we need to special case that right now
// because we want {#tag} and not {tag} in that case
if (attribute.TryGetProperty(disappearanceAttributeKey, out bool disappear))
{
if (disappear)
{
stringBuilder.Append(DisappearanceSymbol);
closing = $"{openingSymbol}/{DisappearanceSymbol}{attribute.Name}{closingSymbol}";
}
}
stringBuilder.Append(attribute.Name);
// ok so this is gonna be a bit jank
// because if we have a self-property we NEED to handle that first
// then otherwise we can do the rest
if (attribute.Properties.Count > 0)
{
if (attribute.Properties.TryGetValue(attribute.Name, out var value))
{
switch (value.Type)
{
case MarkupValueType.Integer:
stringBuilder.Append("=");
stringBuilder.Append(value.IntegerValue);
break;
case MarkupValueType.Float:
stringBuilder.Append("=");
stringBuilder.Append(value.FloatValue);
break;
case MarkupValueType.String:
stringBuilder.Append("=");
stringBuilder.Append(value.StringValue);
break;
case MarkupValueType.Bool:
stringBuilder.Append("=");
stringBuilder.Append(value.BoolValue ? "1" : "0");
break;
}
}
// ok now we need to do the next bit which is the properties
foreach (var property in attribute.Properties)
{
// there are four properties we ignore, self, type, whitespace, and disapperance
// all of these are already handled or not relevant to us
if (property.Key == attribute.Name)
{
continue;
}
if (property.Key == trimAttributeKey)
{
continue;
}
if (property.Key == disappearanceAttributeKey)
{
continue;
}
if (property.Key == typeAttributeKey)
{
continue;
}
switch (property.Value.Type)
{
case MarkupValueType.Integer:
stringBuilder.Append(" ");
stringBuilder.Append(property.Key);
stringBuilder.Append("=");
stringBuilder.Append(property.Value.IntegerValue);
break;
case MarkupValueType.Float:
stringBuilder.Append(" ");
stringBuilder.Append(property.Key);
stringBuilder.Append("=");
stringBuilder.Append(property.Value.FloatValue);
break;
case MarkupValueType.String:
stringBuilder.Append(" ");
stringBuilder.Append(property.Key);
stringBuilder.Append("=");
stringBuilder.Append(property.Value.StringValue);
break;
case MarkupValueType.Bool:
stringBuilder.Append(" ");
stringBuilder.Append(property.Key);
stringBuilder.Append("=");
stringBuilder.Append(property.Value.BoolValue ? "1" : "0");
break;
}
}
}
var front = stringBuilder.Append(closingSymbol).ToString();
return (front, closing);
}
#endif
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 0434e130b589842d488bd865f7435ae9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/TextAnimatorMarkupManager.cs
uploadId: 968678
@@ -0,0 +1,111 @@
/*
Text Animator for Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
namespace Yarn.Unity.Addons.TextAnimatorIntegration
{
#nullable enable
using Yarn.Markup;
using System.Collections.Generic;
#if YS_USE_TEXT_ANIMATOR_2
using Febucci.UI;
#endif
public partial class TextAnimatorMarkupManager
{
#if YS_USE_TEXT_ANIMATOR_2
public static char OpeningActionsBracket => TextAnimatorSettings.Instance.actions.openingSymbol;
public static char ClosingActionsBracket => TextAnimatorSettings.Instance.actions.closingSymbol;
public static char OpeningAppearancesBracket => TextAnimatorSettings.Instance.appearances.openingSymbol;
public static char ClosingAppearancesBracket => TextAnimatorSettings.Instance.appearances.closingSymbol;
public static char DisappearanceSymbol => TextAnimatorSettings.Instance.disappearancesMiddleSymbol;
public static char OpeningBehavioursBracket => TextAnimatorSettings.Instance.behaviors.openingSymbol;
public static char ClosingBehavioursBracket => TextAnimatorSettings.Instance.behaviors.closingSymbol;
public static bool IsTextAnimatorTag(string tagID)
{
if (tagID == "notype")
{
return true;
}
if (TextAnimatorSettings.Instance.actions.defaultDatabase.ContainsKey(tagID))
{
return true;
}
if (TextAnimatorSettings.Instance.behaviors.defaultDatabase.ContainsKey(tagID))
{
return true;
}
if (TextAnimatorSettings.Instance.appearances.defaultDatabase.ContainsKey(tagID))
{
return true;
}
return false;
}
public static HashSet<string> AllTags()
{
HashSet<string> tags = new();
foreach (var action in TextAnimatorSettings.Instance.actions.defaultDatabase.Data)
{
if (action == null)
{
continue;
}
if (string.IsNullOrEmpty(action.TagID))
{
continue;
}
tags.Add(action.TagID);
}
foreach (var behaviors in TextAnimatorSettings.Instance.behaviors.defaultDatabase.Data)
{
if (behaviors == null)
{
continue;
}
if (string.IsNullOrEmpty(behaviors.TagID))
{
continue;
}
tags.Add(behaviors.TagID);
}
foreach (var appearance in TextAnimatorSettings.Instance.appearances.defaultDatabase.Data)
{
if (appearance == null)
{
continue;
}
if (string.IsNullOrEmpty(appearance.TagID))
{
continue;
}
tags.Add(appearance.TagID);
}
tags.Add("notype");
return tags;
}
private static bool IsBehaviourTag(string tagID)
{
return TextAnimatorSettings.Instance.behaviors.defaultDatabase.ContainsKey(tagID);
}
private static bool IsActionTag(string tagID)
{
return TextAnimatorSettings.Instance.actions.defaultDatabase.ContainsKey(tagID);
}
private static bool IsAppearanceTag(string tagID)
{
return TextAnimatorSettings.Instance.appearances.defaultDatabase.ContainsKey(tagID);
}
private static Dictionary<string, (char start, char end)> cachedAttributeTypes = new()
{
{"notype", ('<', '>') },
};
#endif
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 28fe7fba654914103b39d5974bd21da4
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/TextAnimatorMarkupManager_v2.cs
uploadId: 968678
@@ -0,0 +1,73 @@
/*
Text Animator for Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
namespace Yarn.Unity.Addons.TextAnimatorIntegration
{
#nullable enable
using Yarn.Markup;
using System.Collections.Generic;
#if YS_USE_TEXT_ANIMATOR_3
using Febucci.TextAnimatorForUnity;
#endif
public partial class TextAnimatorMarkupManager
{
#if YS_USE_TEXT_ANIMATOR_3
public static char OpeningActionsBracket => '<';
public static char ClosingActionsBracket => '>';
public static char OpeningAppearancesBracket => TextAnimatorSettings.Instance.Settings.parsingAppearances.openingBracket;
public static char ClosingAppearancesBracket => TextAnimatorSettings.Instance.Settings.parsingAppearances.closingBracket;
public static char DisappearanceSymbol => TextAnimatorSettings.Instance.Settings.parsingDisappearances.middleSymbol;
public static char OpeningBehavioursBracket => TextAnimatorSettings.Instance.Settings.parsingBehaviors.openingBracket;
public static char ClosingBehavioursBracket => TextAnimatorSettings.Instance.Settings.parsingBehaviors.closingBracket;
public static bool IsTextAnimatorTag(string tagID)
{
if (tagID == "notype")
{
return true;
}
if (TextAnimatorSettings.Instance.Settings.GlobalActionsDatabase.Database.ContainsKey(tagID))
{
return true;
}
if (TextAnimatorSettings.Instance.Settings.GlobalEffectsDatabase.Database.ContainsKey(tagID))
{
return true;
}
return false;
}
public static HashSet<string> AllTags()
{
HashSet<string> tags = new();
tags.UnionWith(TextAnimatorSettings.Instance.Settings.GlobalActionsDatabase.Database.Keys);
tags.UnionWith(TextAnimatorSettings.Instance.Settings.GlobalEffectsDatabase.Database.Keys);
tags.Add("notype");
return tags;
}
private static bool IsBehaviourTag(string tagID)
{
return TextAnimatorSettings.Instance.Settings.GlobalEffectsDatabase.Database.ContainsKey(tagID);
}
private static bool IsActionTag(string tagID)
{
return TextAnimatorSettings.Instance.Settings.GlobalActionsDatabase.Database.ContainsKey(tagID);
}
private static bool IsAppearanceTag(string tagID)
{
return TextAnimatorSettings.Instance.Settings.GlobalEffectsDatabase.Database.ContainsKey(tagID);
}
private static Dictionary<string, (char start, char end)> cachedAttributeTypes = new()
{
{"notype", ('<', '>') },
};
#endif
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 6e44de3f910b7417a812372a78eae38e
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/TextAnimatorMarkupManager_v3.cs
uploadId: 968678
@@ -0,0 +1,94 @@
/*
Text Animator for Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
#if YS_USE_TEXT_ANIMATOR_2 || YS_USE_TEXT_ANIMATOR_3
#define USE_TEXT_ANIMATOR
#endif
#nullable enable
namespace Yarn.Unity.Addons.TextAnimatorIntegration
{
using System.Threading;
using System.Collections.Generic;
using UnityEngine;
using Yarn.Unity;
using Yarn.Markup;
#if UNITY_EDITOR
using UnityEditor;
using Yarn.Unity.Editor;
#endif
#if !USE_TEXT_ANIMATOR
using TextAnimator_TMP = UnityEngine.Object;
using TypewriterCore = UnityEngine.Object;
#endif
public enum TagMode
{
YarnOnlyTags, TextAnimatorOnlyTags, DefaultYarnMarkup,
}
#if !USE_TEXT_ANIMATOR
// This only exists if Text Animator isn't installed
// in which case it doesn't really do anything except make sure the serialised references aren't lost
// this means it also has the references for both versions of Text Animator
public class TextAnimatorYarnTypewriter : MonoBehaviour, IAsyncTypewriter, IAttributeMarkerProcessor
{
#pragma warning disable CS8618
[HideInInspector] public List<IActionMarkupHandler> ActionMarkupHandlers { get; set; }
#pragma warning restore CS8618
public TextAnimator_TMP? Animator;
public TypewriterCore? Typewriter;
[SerializeField] internal TagMode tagMode = TagMode.DefaultYarnMarkup;
public ActionMarkupAction? insertionAction;
#pragma warning disable CS8618
[SerializeField] private List<string> actionTags;
#pragma warning restore CS8618
public bool ActionMarkupFinishedProcessing => false;
public YarnTask RunTypewriter(MarkupParseResult line, CancellationToken cancellationToken)
{
throw new System.NotImplementedException();
}
public void PrepareForContent(MarkupParseResult line)
{
throw new System.NotImplementedException();
}
public void ContentWillDismiss()
{
throw new System.NotImplementedException();
}
public ReplacementMarkerResult ProcessReplacementMarker(MarkupAttribute marker, System.Text.StringBuilder childBuilder, List<MarkupAttribute> childAttributes, string localeCode)
{
throw new System.NotImplementedException();
}
public void CancelMarkupProcessing()
{
throw new System.NotImplementedException();
}
public void StartMarkup(int index)
{
throw new System.NotImplementedException();
}
public void ContentDidDismiss()
{
throw new System.NotImplementedException();
}
public TMPro.TMP_Text? TextElement { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
}
#endif
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 26ef910a7482f4ce9bbf4cd80ef5e3c5
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/TextAnimatorYarnTypewriter.cs
uploadId: 968678
@@ -0,0 +1,361 @@
/*
Text Animator for Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
#if YS_USE_TEXT_ANIMATOR_2 || YS_USE_TEXT_ANIMATOR_3
#define USE_TEXT_ANIMATOR
#endif
namespace Yarn.Unity.Addons.TextAnimatorIntegration
{
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
using Yarn.Unity;
using Yarn.Markup;
using Yarn.Unity.Attributes;
#if YS_USE_TEXT_ANIMATOR_2
using Febucci.UI;
using Febucci.UI.Core;
#else
using TextAnimator_TMP = UnityEngine.Object;
using TypewriterCore = UnityEngine.Object;
#endif
#nullable enable
#if YS_USE_TEXT_ANIMATOR_2
public class TextAnimatorYarnTypewriter : MonoBehaviour, IAsyncTypewriter, IAttributeMarkerProcessor
{
[HideInInspector] public List<IActionMarkupHandler> ActionMarkupHandlers { get; set; } = new();
[Group("Text Animator")]
public TextAnimator_TMP? Animator;
[Group("Text Animator")]
public TypewriterCore? Typewriter;
[Group("Tags")]
[SerializeField] public TagMode tagMode = TagMode.DefaultYarnMarkup;
[Group("Tags")]
[HideIf(nameof(tagMode), TagMode.TextAnimatorOnlyTags)]
public ActionMarkupAction? insertionAction;
[Group("Tags")]
[ShowIf(nameof(tagMode), TagMode.DefaultYarnMarkup)]
[SerializeField] private List<string> actionTags = new();
private YarnTaskCompletionSource? typewriterComplete;
private CancellationToken? currentContentCancellationToken;
private MarkupParseResult? currentContent;
private YarnTaskCompletionSource? markupHandlersProcessingCompletionSource;
/// <summary>
/// Indicates to the typewriter that it should prepare for displaying a new line.
/// </summary>
/// <param name="lineText">The line that will be shown.</param>
public void PrepareForContent(MarkupParseResult lineText)
{
if (ActionMarkupHandlers == null)
{
return;
}
if (Animator == null)
{
return;
}
foreach (var handler in ActionMarkupHandlers)
{
handler.OnPrepareForLine(lineText, Animator.TMProComponent);
}
switch (tagMode)
{
case TagMode.TextAnimatorOnlyTags:
this.Animator.SetText(lineText.Text, hideText: true);
break;
case TagMode.YarnOnlyTags:
{
var text = ProcessTagModeYarn(lineText);
if (text == null)
{
this.Animator.SetText(lineText.Text, hideText: true);
}
else
{
this.Animator.SetText(text, hideText: true);
}
break;
}
case TagMode.DefaultYarnMarkup:
{
this.Animator.SetText(lineText.Text, hideText: true);
break;
}
}
}
void Start()
{
var runner = DialogueRunner.FindRunner(this);
if (runner != null)
{
if (tagMode == TagMode.TextAnimatorOnlyTags)
{
return;
}
foreach (var tag in TextAnimatorMarkupManager.AllTags())
{
runner.LineProvider.RegisterMarkerProcessor(tag, this);
}
if (tagMode == TagMode.DefaultYarnMarkup)
{
HashSet<string> tags = new HashSet<string>(actionTags);
foreach (var tag in tags)
{
runner.LineProvider.RegisterMarkerProcessor(tag, this);
}
}
}
}
// This method can be thought of as a supersized replacement markup system
// it adds a Text Animator action (defaults to <y>) at every character in the line
// as it walks down the line inserting this potential pauses if it encounters markup that is a Text Animator tag it will instead convert this from Yarn Spinner markup to Text Animator tag form
// and then continue
private string? ProcessTagModeYarn(MarkupParseResult lineText)
{
if (insertionAction == null)
{
return null;
}
var insert = $"{TextAnimatorMarkupManager.OpeningActionsBracket}{insertionAction.TagID}{TextAnimatorMarkupManager.ClosingActionsBracket}";
System.Text.StringBuilder stringBuilder = new();
for (int i = 0; i < lineText.Text.Length; i++)
{
stringBuilder.Append(insert);
foreach (var attribute in lineText.Attributes)
{
if (TextAnimatorMarkupManager.IsTextAnimatorTag(attribute.Name))
{
if (attribute.Position == i || (attribute.Position + attribute.Length == i))
{
if (TextAnimatorMarkupManager.ConvertedForm(attribute, out var front, out var back))
{
// we are at the start
if (attribute.Position == i)
{
stringBuilder.Append(front);
}
else
{
stringBuilder.Append(back);
}
}
}
}
}
var character = lineText.Text[i];
stringBuilder.Append(character);
}
return stringBuilder.ToString();
}
/// <summary>
/// Indicates to the typewriter that the user interface elements necessary for
/// showing the the line screen have finished appearing.
/// </summary>
/// <param name="text"></param>
public void OnLineDisplayBegin(MarkupParseResult text)
{
if (ActionMarkupHandlers == null)
{
return;
}
if (Animator == null)
{
return;
}
foreach (var handler in ActionMarkupHandlers)
{
handler.OnLineDisplayBegin(text, Animator.TMProComponent);
}
}
/// <summary>
/// Indicates to the typewriter that the user interface elements
/// necessary for showing the line are about to disappear.
/// </summary>
public void ContentWillDismiss()
{
if (ActionMarkupHandlers == null)
{
return;
}
foreach (var handler in ActionMarkupHandlers)
{
handler.OnLineWillDismiss();
}
}
public void ContentDidDismiss()
{
Typewriter.SkipTypewriter();
}
public TMPro.TMP_Text? TextElement { get; set; }
public async YarnTask RunTypewriter(MarkupParseResult line, CancellationToken cancellationToken)
{
if (ActionMarkupHandlers == null)
{
return;
}
if (Animator == null)
{
return;
}
if (Typewriter == null)
{
return;
}
currentContent = line;
currentContentCancellationToken = cancellationToken;
// Let every markup handler know that display is about to begin
foreach (var markupHandler in ActionMarkupHandlers)
{
markupHandler.OnLineDisplayBegin(line, Animator.TMProComponent);
}
// Create a completion source that we'll complete when TextAnimator tells us
// that the typewriter has finished
typewriterComplete = new YarnTaskCompletionSource();
var registration = cancellationToken.Register(() =>
{
// If our cancellation token is tripped, the user wants us to hurry
// up our delivery. Tell TextAnimator to skip the rest of the
// presentation.
Typewriter.SkipTypewriter();
});
// We want to be notified when each character appears.
Typewriter.onTextShowed.AddListener(OnTypewriterComplete);
// Start the typewriter and wait for it to finish
Typewriter.StartShowingText();
await typewriterComplete.Task;
// Clean up
Typewriter.onTextShowed.RemoveListener(OnTypewriterComplete);
registration.Dispose();
typewriterComplete = null;
currentContent = null;
currentContentCancellationToken = null;
// Notify our action markup handlers that the line is done
foreach (var markupHandler in ActionMarkupHandlers)
{
markupHandler.OnLineDisplayComplete();
}
}
private async YarnTask ProcessActionMarkup(int index, MarkupParseResult content, IReadOnlyList<IActionMarkupHandler> actionMarkupHandlers, CancellationToken token)
{
// should also link the token to markupHandlersProcessingCompletionSource
foreach (var processor in actionMarkupHandlers)
{
await processor.OnCharacterWillAppear(index, content, token);
}
markupHandlersProcessingCompletionSource?.TrySetResult();
}
public void CancelMarkupProcessing()
{
markupHandlersProcessingCompletionSource?.TrySetCanceled();
}
private void OnTypewriterComplete()
{
// The TextAnimator typewriter has finished showing all of its text.
// Notify that we're done.
typewriterComplete?.TrySetResult();
}
public bool ActionMarkupFinishedProcessing
{
get
{
return markupHandlersProcessingCompletionSource == null || markupHandlersProcessingCompletionSource.Task.IsCompleted();
}
}
public void StartMarkup(int index)
{
if (ActionMarkupHandlers == null || ActionMarkupHandlers.Count == 0)
{
return;
}
if (!currentContent.HasValue)
{
return;
}
markupHandlersProcessingCompletionSource = new YarnTaskCompletionSource();
var token = this.currentContentCancellationToken ?? this.destroyCancellationToken;
ProcessActionMarkup(index, currentContent.Value, ActionMarkupHandlers, token).Forget();
}
public ReplacementMarkerResult ProcessReplacementMarker(MarkupAttribute marker, System.Text.StringBuilder childBuilder, List<MarkupAttribute> childAttributes, string localeCode)
{
int invisibleCharacters = 0;
if (tagMode == TagMode.DefaultYarnMarkup)
{
if (TextAnimatorMarkupManager.ConvertedForm(marker, out var front, out var back))
{
childBuilder.Insert(0, front);
invisibleCharacters = front.Length;
if (marker.Length > 0)
{
childBuilder.Append(back);
invisibleCharacters += back.Length;
}
}
else
{
// here we are doing a bit of a hack, we are adding an action tag into the string at the point of the markup (<y> by default)
// and then adding the attribute back in, that way it can still be processed just fine at runtime using the <y> marker when hit by Text Animator
// in combination with the above which converts Yarn Spinner markup into Text Animator tags a line such as
// Hello here is a line that [rainb]mixes and matches Yarn[pause=1000 /] and Text Animator[/rain] markup systems!
// would become:
// Hello here is a line that <rainb>mixes and matches Yarn<y> and Text Animator</rainb> markup systems!
// and when the <y> is hit by the Text Animator typewriter it will call out to our system which will forward onto pause
if (insertionAction != null)
{
childBuilder.Insert(0, $"{TextAnimatorMarkupManager.OpeningActionsBracket}{insertionAction.TagID}{TextAnimatorMarkupManager.ClosingActionsBracket}");
}
childAttributes.Add(marker);
}
}
else
{
// we aren't in the minimal mode
// so we don't want to swap out the content but have them just be normal markers
childAttributes.Add(marker);
}
return new ReplacementMarkerResult(invisibleCharacters);
}
}
#elif YS_USE_TEXT_ANIMATOR_3
public class NullTextAnimatorYarnTypewriter : MonoBehaviour { }
#endif
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: a5c5bc8be957c4b339e2814347bf7d0a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/TextAnimatorYarnTypewriter_v2.cs
uploadId: 968678
@@ -0,0 +1,288 @@
/*
Text Animator for Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
namespace Yarn.Unity.Addons.TextAnimatorIntegration
{
#nullable enable
#if YS_USE_TEXT_ANIMATOR_3
using UnityEngine;
using Yarn.Unity;
using Yarn.Markup;
using Febucci.TextAnimatorCore.Typing;
using Febucci.TextAnimatorCore.Text;
using Febucci.TextAnimatorForUnity;
using System.Threading;
using System.Collections.Generic;
using System.Text;
public class TextAnimatorYarnTypewriter : TypewriterComponent, IAsyncTypewriter, IAttributeMarkerProcessor
{
public Febucci.TextAnimatorForUnity.TextMeshPro.TextAnimator_TMP? Animator;
// IAttributeMarkerProcessor required elements
[HideInInspector] public List<IActionMarkupHandler> ActionMarkupHandlers { get; set; } = new();
// holds the current line being presented, or null otherwise
private MarkupParseResult? currentContent;
// the current cancellation token given to us by the dialogue presenter
// this will be cancelled if the line is declared as needing to hurry up or be skipped
private CancellationToken currentContentPresentationCancellationToken = CancellationToken.None;
// the completion source for the overall typewriting of the line
private YarnTaskCompletionSource? currentContentPresentation;
// the completion source for the overall typewriting of the current character being typewritten
private YarnTaskCompletionSource? currentCharacterProgressSource;
// the completion source for any markup processors (if necessary) for the current character
private YarnTaskCompletionSource? currentCharacterMarkupProgress;
// keeps track of the index of the last character we correctly finished processing
private int lastProcessedCharacterIndex = -1;
// this is a lot of different pieces the idea being that when a line comes in we will need to have a fresh currentContentPresentation created for that line
// then for each character as they typewrite along down the line we will create a new currentCharacterProgressSource for that specific character
// then for that character when they hit any markup we make a new currentCharacterMarkupProgress to hold that being done
// once markup finishes currentCharacterMarkupProgress and currentCharacterProgressSource will get completed
// which will make the typewriter advance to the next character
// eventually then currentContentPresentation finishes up and that frees up the await call in RunTypewriter
void Start()
{
// if we can find a SharedTextAnimatorMarkupReplacementProcessor we can safely assume that it is handling the processing of replacing the TA markup
// otherwise it's on us to do it
if (!GameObject.FindAnyObjectByType<SharedTextAnimatorMarkupReplacementProcessor>())
{
var runner = DialogueRunner.FindRunner(this);
if (runner != null)
{
foreach (var tag in TextAnimatorMarkupManager.AllTags())
{
runner.LineProvider.RegisterMarkerProcessor(tag, this);
}
}
else
{
Debug.LogWarning($"{name} is expecting to have to register itself as the object handling Text Animator markup but was unable to find a Dialogue Runner!");
}
}
}
public void PrepareForContent(MarkupParseResult line)
{
if (ActionMarkupHandlers == null)
{
return;
}
if (Animator == null)
{
return;
}
foreach (var handler in ActionMarkupHandlers)
{
handler.OnPrepareForLine(line, Animator.TMProComponent);
}
this.ShowText(line.Text);
}
public async YarnTask RunTypewriter(MarkupParseResult line, CancellationToken cancellationToken)
{
if (currentContentPresentation != null)
{
currentContentPresentation.TrySetCanceled();
}
if (Animator == null)
{
return;
}
// Let every markup handler know that display is about to begin
foreach (var markupHandler in ActionMarkupHandlers)
{
markupHandler.OnLineDisplayBegin(line, Animator.TMProComponent);
}
currentContentPresentation = new YarnTaskCompletionSource();
cancellationToken.Register(() =>
{
currentContentPresentation?.TrySetCanceled();
});
lastProcessedCharacterIndex = -1;
currentCharacterProgressSource?.TrySetResult();
currentCharacterProgressSource = null;
currentContent = line;
currentContentPresentationCancellationToken = cancellationToken;
currentCharacterProgressSource = new();
cancellationToken.Register(() =>
{
currentCharacterProgressSource?.TrySetCanceled();
});
this.StartShowingText();
try
{
await currentContentPresentation.Task;
}
catch (System.OperationCanceledException)
{
this.SkipTypewriter();
// run every markup from the last index we processed
// this ensure that if there is any markup that wasn't hit due to the hurrying up it still gets a chance to do anything it NEEDS to do
// because they are given a pre-cancelled token they will ideally finish instantly
// we do this character by character instead of all at once to avoid a situation where one piece of markup changes something that impacts a later event
// especially if it is changing poses or positions of game objects
if (lastProcessedCharacterIndex != -1)
{
for (int i = lastProcessedCharacterIndex; i < Animator.textWithoutAnyTag.Length; i++)
{
List<YarnTask> cancelledActions = new();
foreach (var processor in ActionMarkupHandlers)
{
cancelledActions.Add(processor.OnCharacterWillAppear(i, currentContent.Value, cancellationToken));
}
await YarnTask.WhenAll(cancelledActions);
}
}
}
// Notify our action markup handlers that the line is done
foreach (var markupHandler in ActionMarkupHandlers)
{
markupHandler.OnLineDisplayComplete();
}
// do final cleanup
currentCharacterProgressSource?.TrySetResult();
currentCharacterProgressSource = null;
currentContentPresentationCancellationToken = CancellationToken.None;
currentContentPresentation?.TrySetResult();
currentContentPresentation = null;
currentContent = null;
}
public void ContentWillDismiss()
{
if (ActionMarkupHandlers != null)
{
foreach (var handler in ActionMarkupHandlers)
{
handler.OnLineWillDismiss();
}
}
this.ShowText(string.Empty);
}
public ReplacementMarkerResult ProcessReplacementMarker(MarkupAttribute marker, StringBuilder childBuilder, List<MarkupAttribute> childAttributes, string localeCode)
{
int invisibleCharacters = 0;
if (TextAnimatorMarkupManager.ConvertedForm(marker, out var front, out var back))
{
childBuilder.Insert(0, front);
invisibleCharacters = front.Length;
if (marker.Length > 0)
{
childBuilder.Append(back);
invisibleCharacters += back.Length;
}
}
return new ReplacementMarkerResult(invisibleCharacters);
}
protected override ActionStatus OnBeforeShowingCharacter(CharacterData character)
{
// I am not sure if I am triggering this correctly, it seems to want to typewrite on first frame
// so for now I am just gonna tell it to chill out and wait until there is something to show
if (currentContentPresentation == null)
{
return ActionStatus.Running;
}
if (currentCharacterProgressSource == null)
{
Debug.LogWarning($"Attempting to calculate delay for character {character.index} but {nameof(currentCharacterProgressSource)} is null, we cannot determine if we have paused for the right duration.");
return base.OnBeforeShowingCharacter(character);
}
if (currentCharacterProgressSource.Task.IsCompleted())
{
return base.OnBeforeShowingCharacter(character);
}
// fire off the task
RunActionMarkup(character.index).Forget();
return ActionStatus.Running;
}
protected override ActionStatus OnAfterWaitingCharacter(CharacterData character)
{
// the character has been presented which means it is time to advance
// so we flag this as done (if not already done) and refresh it
currentCharacterProgressSource?.TrySetResult();
currentCharacterProgressSource = new();
currentContentPresentationCancellationToken.Register(() =>
{
currentCharacterProgressSource?.TrySetCanceled();
});
// need to check if this is the last character
// if it is we also want to finish off the entire line presentation
if (currentContentPresentation != null && Animator != null)
{
if (character.index == Animator.textWithoutAnyTag.Length -1)
{
currentContentPresentation?.TrySetResult();
}
}
lastProcessedCharacterIndex = character.index;
return ActionStatus.Finished;
}
private async YarnTask RunActionMarkup(int index)
{
if (!currentContent.HasValue)
{
return;
}
if (currentCharacterProgressSource == null)
{
return;
}
// if we aren't null that means we have a task already in progress for presenting the markup
// because this method might get called every frame we want to ensure that doesn't happen
// so we capture and move over it
if (currentCharacterMarkupProgress != null)
{
return;
}
currentCharacterMarkupProgress = new();
currentContentPresentationCancellationToken.Register(() =>
{
currentCharacterMarkupProgress?.TrySetCanceled();
});
foreach (var processor in ActionMarkupHandlers)
{
await processor.OnCharacterWillAppear(index, currentContent.Value, currentContentPresentationCancellationToken);
}
currentCharacterProgressSource.TrySetResult();
currentCharacterMarkupProgress.TrySetResult();
currentCharacterMarkupProgress = null;
}
public void ContentDidDismiss()
{
this.ShowText(string.Empty);
}
public TMPro.TMP_Text? TextElement { get; set; }
}
#elif YS_USE_TEXT_ANIMATOR_2
public class NullTextAnimatorYarnTypewriter : UnityEngine.MonoBehaviour {}
#endif
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 9c2c859ff89524cb4b0efeeb772dfd10
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity.addons.textanimator/Scripts/TextAnimatorYarnTypewriter_v3.cs
uploadId: 968678