Insanely huge initial commit

This commit is contained in:
2026-02-21 17:04:05 -08:00
parent 9cdd36191a
commit 613d75914a
22525 changed files with 4035207 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 834807df07adbb843a9e2270df867e8f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,150 @@
using System;
using Febucci.UI.Core;
using UnityEditor;
using UnityEngine;
namespace Febucci.UI
{
public class AboutWindow : EditorWindow
{
const string currentVersion = "1.3.3";
#region Utilties
const string menuParent = "Tools/Febucci/TextAnimator/";
const string linksCategory = "Links/";
const string utilsCategory = "Utils/";
const string page_docs_name = "📄 Documentation";
const string page_docs_url = "https://www.febucci.com/text-animator-unity/docs/";
const string page_roadmap_name = "📅 Roadmap";
const string page_roadmap_url = "https://www.febucci.com/text-animator-unity/roadmap/";
const string page_changelog_name = "📝 Patch Notes";
const string page_changelog_url = "https://www.febucci.com/text-animator-unity/changelog/";
const string page_support_name = "🆘 Support";
const string page_support_url = "https://www.febucci.com/text-animator-unity/support/";
[MenuItem(menuParent + utilsCategory + "Locate Global Data", false, 0)]
static void LocateGlobalData()
{
var foundData = Resources.Load(TAnimGlobalDataScriptable.resourcesPath);
if (foundData != null)
{
Selection.activeObject = foundData;
}
else
{
Debug.LogWarning(
$"Text Animator: No Scriptable data found, please create one in path {TAnimGlobalDataScriptable.resourcesPath}");
}
}
#endregion
const int windowWidth = 350;
const int windowHeight = 485;
[InitializeOnLoadMethod]
private static void FirstSetup()
{
EditorApplication.delayCall += TryOpeningWindow;
}
private static void TryOpeningWindow()
{
const string key_installedVersion = "Febucci.UI.TextAnimator.Version";
string installedVersion = PlayerPrefs.GetString(key_installedVersion);
// Same version already exists
if (!string.IsNullOrWhiteSpace(installedVersion) && currentVersion == installedVersion)
return;
PlayerPrefs.SetString(key_installedVersion, currentVersion);
OpenWindow();
}
GUIContent logo;
void OnEnable()
{
var obj = EditorGUIUtility.Load(
"Assets/Plugins/Febucci/Text Animator/Scripts/Editor/febucci.tanimator.about_logo.png");
if (obj != null)
logo = new GUIContent(obj as Texture2D);
}
GUIStyle style_rightAligned;
public void OnGUI()
{
var rect = new Rect(5, 10, windowWidth - 10, windowHeight);
GUILayout.BeginArea(rect);
//Logo, if present
if (logo != null) GUILayout.Label(logo, EditorStyles.centeredGreyMiniLabel, GUILayout.MaxHeight(180));
GUILayout.Label("Welcome!", EditorStyles.boldLabel);
GUILayout.Label("Thank you for using Text Animator. Have fun bringing your project's texts to life!",
EditorStyles.wordWrappedLabel);
if (style_rightAligned == null)
{
style_rightAligned = new GUIStyle(EditorStyles.centeredGreyMiniLabel);
style_rightAligned.alignment = TextAnchor.MiddleRight;
}
GUILayout.Label($"Version: {currentVersion}", style_rightAligned);
GUILayout.Space(5);
//--Online Resources--
GUILayout.Label("Online Resources", EditorStyles.boldLabel);
GUILayout.Label("Here are some useful resources:",
EditorStyles.wordWrappedLabel);
GUILayout.BeginHorizontal();
//Docs
if (GUILayout.Button(page_docs_name)) Application.OpenURL(page_docs_url);
//Support
if (GUILayout.Button(page_support_name)) Application.OpenURL(page_support_url);
//Patch notes
if (GUILayout.Button(page_changelog_name)) Application.OpenURL(page_changelog_url);
//Roadmap
if (GUILayout.Button(page_roadmap_name)) Application.OpenURL(page_roadmap_url);
GUILayout.EndHorizontal();
GUILayout.Space(5);
//--Extras--
GUILayout.Label("Extras", EditorStyles.boldLabel);
GUILayout.Label("Would you like to be included in a future Text Animator showcase?",
EditorStyles.wordWrappedMiniLabel);
if (GUILayout.Button("-> Submit your game/project"))
Application.OpenURL("https://www.febucci.com/text-animator-unity/showcase/");
GUILayout.Space(1);
GUILayout.Label("Please consider writing a review for the asset. It takes one minute but it really helps. Thanks!",
EditorStyles.wordWrappedMiniLabel);
if (GUILayout.Button("♥ Review on the Asset Store"))
Application.OpenURL("https://assetstore.unity.com/packages/slug/158707");
GUILayout.Space(5);
GUILayout.Label("Cheers! @febucci", EditorStyles.centeredGreyMiniLabel);
GUILayout.EndArea();
}
[MenuItem("Tools/Febucci/TextAnimator/About", priority = 0)]
private static void OpenWindow()
{
var position = new Rect(100, 100, windowWidth, windowHeight);
GetWindowWithRect<AboutWindow>(position, true, "About Text Animator", true);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e1f30d900b20e8c40b52e4fb59726f6a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 001402f99874ca24780daf2947cf5e14
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,66 @@
using UnityEngine;
using UnityEditor;
namespace Febucci.Attributes
{
[CustomPropertyDrawer(typeof(CharsDisplayTimeAttribute))]
public class CharsDisplayTimeAttributeDrawer : PropertyDrawer
{
const float minWaitTime = 0.0001f;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
//delay in seconds
Rect delayValueRect = new Rect(position.x, position.y, 70 + 230 - position.x, position.height);
delayValueRect.width = Mathf.Clamp(position.width * 0.6f, 170, position.width);
Rect delayLabel = new Rect(delayValueRect);
delayLabel.x += delayLabel.width - 15;
delayLabel.width = 77;
Rect charPerSecValueRect = new Rect(delayLabel);
charPerSecValueRect.x += charPerSecValueRect.width - 15;
charPerSecValueRect.width = 65;
Rect charPerSecLabelRect = new Rect(charPerSecValueRect);
charPerSecLabelRect.x += charPerSecLabelRect.width - 15;
charPerSecLabelRect.width = 120;
switch (property.propertyType)
{
case SerializedPropertyType.Float:
property.floatValue = EditorGUI.FloatField(delayValueRect, label, property.floatValue);
EditorGUI.LabelField(delayLabel, $"s delay, ≈");
int charPerSecond = Mathf.RoundToInt(1 / property.floatValue);
EditorGUI.LabelField(charPerSecLabelRect, "chars per sec");
EditorGUI.BeginChangeCheck();
charPerSecond = EditorGUI.IntField(charPerSecValueRect, charPerSecond);
if (EditorGUI.EndChangeCheck())
{
property.floatValue = 1f/charPerSecond;
}
if (property.floatValue < minWaitTime)
property.floatValue = minWaitTime;
break;
default: //unsupported, fallback to the default OnGUI
EditorGUI.PropertyField(position, property, label);
return;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 06ce1d00801daba44918ac07070012a3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
using UnityEngine;
using UnityEditor;
namespace Febucci.Attributes
{
[CustomPropertyDrawer(typeof(MinValueAttribute))]
public class MinValueAttributeDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.PropertyField(position, property, label);
switch (property.propertyType)
{
case SerializedPropertyType.Integer:
property.intValue = Mathf.Clamp(property.intValue, (int)(attribute as MinValueAttribute).min, int.MaxValue);
break;
case SerializedPropertyType.Float:
property.floatValue = Mathf.Clamp(property.floatValue, (attribute as MinValueAttribute).min, float.MaxValue);
break;
default:
base.OnGUI(position, property, label);
break;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d6969374730fb584ea155e370491caf1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,49 @@
using UnityEngine;
using UnityEditor;
namespace Febucci.Attributes
{
[CustomPropertyDrawer(typeof(NotZeroAttribute))]
public class NotZeroAttributeDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
switch (property.propertyType)
{
case SerializedPropertyType.Integer:
int intValue = property.intValue;
intValue = EditorGUI.IntField(position, label, intValue);
if (intValue != 0)
property.intValue = intValue;
break;
case SerializedPropertyType.Float:
float floatValue = property.floatValue;
floatValue = EditorGUI.FloatField(position, label, floatValue);
if (floatValue != 0)
property.floatValue = floatValue;
break;
case SerializedPropertyType.Vector2:
Vector2 vecValue = property.vector2Value;
vecValue = EditorGUI.Vector2Field(position, label, vecValue);
property.vector2Value = new Vector2(
(vecValue.x != 0 || vecValue.y!=0) ? vecValue.x : property.vector2Value.x,
(vecValue.y != 0 || vecValue.x!=0) ? vecValue.y : property.vector2Value.y);
break;
default:
base.OnGUI(position, property, label);
break;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 319c5abbdd9c94e479d7c58013da0a7a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,47 @@
using UnityEngine;
using UnityEditor;
namespace Febucci.Attributes
{
[CustomPropertyDrawer(typeof(PositiveValueAttribute))]
public class PositiveValueAttributeDrawer : PropertyDrawer
{
const float minValue = .01f;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
switch (property.propertyType)
{
case SerializedPropertyType.Integer:
int intValue = property.intValue;
intValue = EditorGUI.IntField(position, label, intValue);
if (intValue >= minValue)
property.intValue = intValue;
break;
case SerializedPropertyType.Float:
float floatValue = property.floatValue;
floatValue = EditorGUI.FloatField(position, label, floatValue);
property.floatValue = Mathf.Clamp(floatValue, minValue, floatValue);
break;
case SerializedPropertyType.Vector2:
Vector2 vecValue = property.vector2Value;
vecValue = EditorGUI.Vector2Field(position, label, vecValue);
vecValue.x = Mathf.Clamp(vecValue.x, minValue, vecValue.x);
vecValue.y = Mathf.Clamp(vecValue.y, minValue, vecValue.y);
property.vector2Value = vecValue;
break;
default:
base.OnGUI(position, property, label);
break;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f9574f1311c20a8418059bbedf619737
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,59 @@
using UnityEditor;
using UnityEngine;
namespace Febucci.UI.Core.Editors
{
abstract class BuiltinDataScriptableDrawer : Editor
{
SerializedProperty scriptable;
TextAnimatorDrawer.BuiltinVariablesDrawer effectsDrawer;
protected virtual void OnEnable()
{
scriptable = serializedObject.FindProperty("effectValues");
effectsDrawer = InitializeDrawer(scriptable);
}
protected abstract TextAnimatorDrawer.BuiltinVariablesDrawer InitializeDrawer(SerializedProperty property);
public override void OnInspectorGUI()
{
EditorGUILayout.LabelField("Editing shared built-in values", EditorStyles.boldLabel);
GUI.enabled = false;
EditorGUILayout.LabelField("TextAnimators that reference this asset will use and share these built-in effect values.", EditorStyles.wordWrappedLabel);
GUI.enabled = true;
if (Application.isPlaying)
{
EditorGUILayout.LabelField("[!] Remember: Changes will be saved when you exit playmode (since you are editing a Scriptable Object).", EditorStyles.wordWrappedLabel);
}
EditorGUILayout.Space();
effectsDrawer.DrawBody();
if (serializedObject.hasModifiedProperties)
serializedObject.ApplyModifiedProperties();
}
}
[CustomEditor(typeof(BuiltinAppearancesDataScriptable))]
class BuiltinAppearancesDrawer : BuiltinDataScriptableDrawer
{
protected override TextAnimatorDrawer.BuiltinVariablesDrawer InitializeDrawer(SerializedProperty property)
{
return new TextAnimatorDrawer.AppearanceDefaultEffects(property);
}
}
[CustomEditor(typeof(BuiltinBehaviorsDataScriptable))]
class BuiltinBehaviorsDrawer : BuiltinDataScriptableDrawer
{
protected override TextAnimatorDrawer.BuiltinVariablesDrawer InitializeDrawer(SerializedProperty property)
{
return new TextAnimatorDrawer.BehaviorDefaultEffects(property);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4bc56c85b1a31164e882102be0e50509
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,17 @@
{
"name": "Febucci.TextAnimator.Editor",
"references": [
"Febucci.TextAnimator.Runtime",
"Unity.TextMeshPro"
],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 0306e69412d8fbf41a94465bbaf34341
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,129 @@
using UnityEditor;
using UnityEngine;
namespace Febucci.UI.Core.Editors
{
#if UNITY_EDITOR
[CustomEditor(typeof(TAnimGlobalDataScriptable))]
class TAnimGlobalDataScriptableDrawer : Editor
{
TAnimGlobalDataScriptable script;
SerializedProperty behaviorPresets;
SerializedProperty appearancesPresets;
SerializedProperty customActionsArray;
SerializedProperty customTagsFormatting;
SerializedProperty tagInfo_behaviors;
SerializedProperty tagInfo_appearances;
TextAnimatorDrawer.UserPresetDrawer[] behaviorDrawers = new TextAnimatorDrawer.UserPresetDrawer[0];
TextAnimatorDrawer.UserPresetDrawer[] appearancesDrawers = new TextAnimatorDrawer.UserPresetDrawer[0];
protected virtual void OnEnable()
{
behaviorPresets = serializedObject.FindProperty("globalBehaviorPresets");
appearancesPresets = serializedObject.FindProperty("globalAppearancePresets");
customActionsArray = serializedObject.FindProperty("customActions");
tagInfo_behaviors = serializedObject.FindProperty("tagInfo_behaviors");
tagInfo_appearances = serializedObject.FindProperty("tagInfo_appearances");
customTagsFormatting = serializedObject.FindProperty("customTagsFormatting");
script = (TAnimGlobalDataScriptable)target;
Undo.undoRedoPerformed += Redo;
}
private void OnDisable()
{
Undo.undoRedoPerformed -= Redo;
}
void Redo()
{
serializedObject.UpdateIfRequiredOrScript(); //I have spent too much searching this method... :(
Repaint();
TryResettingTextAnimators();
}
bool showBehaviors = false;
bool showAppearances = false;
public override void OnInspectorGUI()
{
if (Application.isPlaying)
EditorGUILayout.LabelField($"[!!] Remember: Saves are applied in play mode.");
{
EditorGUI.indentLevel++;
EditorGUILayout.LabelField("Effects", EditorStyles.boldLabel);
TextAnimatorDrawer.ShowPresets(ref behaviorDrawers, ref showBehaviors, ref behaviorPresets, false, true);
TextAnimatorDrawer.ShowPresets(ref appearancesDrawers, ref showAppearances, ref appearancesPresets, true, true);
EditorGUI.indentLevel--;
}
EditorGUILayout.Space();
{
EditorGUI.indentLevel++;
EditorGUILayout.LabelField("Actions", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(customActionsArray, true);
EditorGUI.indentLevel--;
}
EditorGUILayout.Space();
{
EditorGUI.indentLevel++;
EditorGUILayout.LabelField("Tags Info", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(customTagsFormatting, true);
if (customTagsFormatting.boolValue)
{
EditorGUILayout.PropertyField(tagInfo_behaviors, true);
EditorGUILayout.PropertyField(tagInfo_appearances, true);
}
EditorGUI.indentLevel--;
}
if (serializedObject.hasModifiedProperties)
{
//Repaint();
//Undo.RecordObject(serializedObject.targetObject, "Changed TextAnimator Global Data Scriptable");
Undo.RecordObject(script, "Changed TextAnimator Global Data Scriptable");
EditorUtility.SetDirty(script);
//Undo.RegisterCompleteObjectUndo(script, "Changed TextAnimator Global Data Scriptable");
serializedObject.ApplyModifiedProperties();
Repaint();
TryResettingTextAnimators();
}
}
void TryResettingTextAnimators()
{
if (EditorApplication.isPlaying)
{
TAnim_EditorHelper.TriggerEvent();
}
}
}
#endif
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 02a6e80de43c2b647ad09ff47f627646
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,338 @@
using UnityEditor;
using UnityEngine;
namespace Febucci.UI.Core.Editors
{
[CustomEditor(typeof(TAnimPlayerBase), true)]
class TAnimPlayerBaseDrawer : Editor
{
SerializedProperty showLettersDinamically;
SerializedProperty startTypewriterMode;
SerializedProperty canSkipTypewriter;
SerializedProperty hideAppearancesOnSkip;
SerializedProperty triggerEventsOnSkip;
SerializedProperty disappearanceOrientation;
SerializedProperty onTextShowed;
SerializedProperty onTypewriterStart;
SerializedProperty onCharacterVisible;
SerializedProperty onTextDisappeared;
SerializedProperty resetTypingSpeedAtStartup;
string[] propertiesToExclude = new string[0];
protected virtual string[] GetPropertiesToExclude()
{
return new string[] {
"m_Script",
"useTypeWriter",
"startTypewriterMode",
"canSkipTypewriter",
"hideAppearancesOnSkip",
"triggerEventsOnSkip",
"onTextShowed",
"onTypewriterStart",
"onCharacterVisible",
"resetTypingSpeedAtStartup",
"onTextDisappeared",
"disappearanceOrientation",
};
}
protected virtual void OnEnable()
{
showLettersDinamically = serializedObject.FindProperty("useTypeWriter");
startTypewriterMode = serializedObject.FindProperty("startTypewriterMode");
canSkipTypewriter = serializedObject.FindProperty("canSkipTypewriter");
hideAppearancesOnSkip = serializedObject.FindProperty("hideAppearancesOnSkip");
triggerEventsOnSkip = serializedObject.FindProperty("triggerEventsOnSkip");
disappearanceOrientation = serializedObject.FindProperty("disappearanceOrientation");
onTextShowed = serializedObject.FindProperty("onTextShowed");
onTypewriterStart = serializedObject.FindProperty("onTypewriterStart");
onCharacterVisible = serializedObject.FindProperty("onCharacterVisible");
onTextDisappeared = serializedObject.FindProperty("onTextDisappeared");
resetTypingSpeedAtStartup = serializedObject.FindProperty("resetTypingSpeedAtStartup");
propertiesToExclude = GetPropertiesToExclude();
}
bool ButtonPlaymode(string label)
{
bool prevGUI = GUI.enabled;
GUI.enabled = Application.isPlaying;
bool value = GUILayout.Button(label, EditorStyles.miniButton, GUILayout.MaxWidth(70));
GUI.enabled = prevGUI;
return value;
}
public override void OnInspectorGUI()
{
{
EditorGUILayout.LabelField("Main Settings", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(showLettersDinamically);
EditorGUI.indentLevel--;
}
EditorGUILayout.Space();
//Typewriter settings
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Typewriter", EditorStyles.boldLabel);
if (showLettersDinamically.boolValue)
{
if (ButtonPlaymode("Start"))
{
((TAnimPlayerBase)target).StartShowingText();
}
if (ButtonPlaymode("Stop"))
{
((TAnimPlayerBase)target).StopShowingText();
}
}
EditorGUILayout.EndHorizontal();
}
if (showLettersDinamically.boolValue)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(startTypewriterMode);
EditorGUILayout.PropertyField(resetTypingSpeedAtStartup);
EditorGUILayout.LabelField("Typewriter Skip", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField(canSkipTypewriter);
if (canSkipTypewriter.boolValue && ButtonPlaymode("Skip"))
{
((TAnimPlayerBase)target).SkipTypewriter();
}
EditorGUILayout.EndHorizontal();
GUI.enabled = canSkipTypewriter.boolValue;
EditorGUILayout.PropertyField(hideAppearancesOnSkip);
EditorGUILayout.PropertyField(triggerEventsOnSkip);
GUI.enabled = true;
EditorGUI.indentLevel--;
}
else
{
GUI.enabled = false;
EditorGUILayout.LabelField("The typewriter is disabled");
GUI.enabled = true;
}
EditorGUILayout.Space();
//Events
{
EditorGUILayout.LabelField("Events", EditorStyles.boldLabel);
// foldoutEvents = EditorGUILayout.Foldout(foldoutEvents, "Events");
//if (foldoutEvents)
{
EditorGUILayout.PropertyField(onTextShowed);
EditorGUILayout.PropertyField(onTextDisappeared);
//GUI.enabled = showLettersDinamically.boolValue;
if (showLettersDinamically.boolValue)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(onTypewriterStart);
EditorGUILayout.PropertyField(onCharacterVisible);
EditorGUI.indentLevel--;
}
//GUI.enabled = true;
}
}
EditorGUILayout.Space();
//Typewriter
{
EditorGUILayout.LabelField("Typewriter Wait", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
OnTypewriterSectionGUI();
EditorGUI.indentLevel--;
}
EditorGUILayout.Space();
//Disappearance
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Disappearances", EditorStyles.boldLabel);
if (ButtonPlaymode("Start"))
{
((TAnimPlayerBase)target).StartDisappearingText();
}
if (ButtonPlaymode("Stop"))
{
((TAnimPlayerBase)target).StopDisappearingText();
}
EditorGUILayout.EndHorizontal();
EditorGUI.indentLevel++;
GUI.enabled = false;
EditorGUILayout.LabelField("To start disappearances, please call the 'StartDisappearingText()' method. See the docs for more.", EditorStyles.wordWrappedMiniLabel);
GUI.enabled = true;
EditorGUILayout.PropertyField(disappearanceOrientation);
OnDisappearanceSectionGUI();
EditorGUI.indentLevel--;
}
//Draws parent without the children (so, TanimPlayerBase can have a custom inspector)
DrawPropertiesExcluding(serializedObject, propertiesToExclude);
if (serializedObject.hasModifiedProperties)
{
serializedObject.ApplyModifiedProperties();
}
}
protected virtual void OnTypewriterSectionGUI()
{
}
protected virtual void OnDisappearanceSectionGUI()
{
}
}
[CustomEditor(typeof(TextAnimatorPlayer), true)]
class TAnimPlayerDrawer : TAnimPlayerBaseDrawer
{
SerializedProperty waitForNormalChars;
SerializedProperty waitLong;
SerializedProperty waitMiddle;
SerializedProperty avoidMultiplePunctuactionWait;
SerializedProperty waitForNewLines;
SerializedProperty waitForLastCharacter;
PropertyWithDifferentLabel useTypewriterWaitForDisappearances;
PropertyWithDifferentLabel disappearanceWaitTime;
PropertyWithDifferentLabel disappearanceSpeedMultiplier;
struct PropertyWithDifferentLabel
{
public SerializedProperty property;
public GUIContent label;
public PropertyWithDifferentLabel(SerializedObject obj, string property, string label)
{
this.property = obj.FindProperty(property);
this.label = new GUIContent(label);
}
public void PropertyField()
{
EditorGUILayout.PropertyField(property, label);
}
}
protected override void OnEnable()
{
base.OnEnable();
waitForNormalChars = serializedObject.FindProperty("waitForNormalChars");
waitLong = serializedObject.FindProperty("waitLong");
waitMiddle = serializedObject.FindProperty("waitMiddle");
avoidMultiplePunctuactionWait = serializedObject.FindProperty("avoidMultiplePunctuactionWait");
waitForNewLines = serializedObject.FindProperty("waitForNewLines");
waitForLastCharacter = serializedObject.FindProperty("waitForLastCharacter");
useTypewriterWaitForDisappearances = new PropertyWithDifferentLabel(serializedObject, "useTypewriterWaitForDisappearances", "Use Typewriter Wait Times");
disappearanceSpeedMultiplier = new PropertyWithDifferentLabel(serializedObject, "disappearanceSpeedMultiplier", "Typewriter Speed Multiplier");
disappearanceWaitTime = new PropertyWithDifferentLabel(serializedObject, "disappearanceWaitTime", "Disappearances Wait");
}
protected override string[] GetPropertiesToExclude()
{
string[] newProperties = new string[] {
"script",
"waitForNormalChars",
"waitLong",
"waitMiddle",
"avoidMultiplePunctuactionWait",
"waitForNewLines",
"waitForLastCharacter",
"useTypewriterWaitForDisappearances",
"disappearanceSpeedMultiplier",
"disappearanceWaitTime"
};
string[] baseProperties = base.GetPropertiesToExclude();
string[] mergedArray = new string[newProperties.Length + baseProperties.Length];
for (int i = 0; i < baseProperties.Length; i++)
{
mergedArray[i] = baseProperties[i];
}
for (int i = 0; i < newProperties.Length; i++)
{
mergedArray[i + baseProperties.Length] = newProperties[i];
}
return mergedArray;
}
protected override void OnTypewriterSectionGUI()
{
EditorGUILayout.PropertyField(waitForNormalChars);
EditorGUILayout.PropertyField(waitLong);
EditorGUILayout.PropertyField(waitMiddle);
EditorGUILayout.PropertyField(avoidMultiplePunctuactionWait);
EditorGUILayout.PropertyField(waitForNewLines);
EditorGUILayout.PropertyField(waitForLastCharacter);
}
protected override void OnDisappearanceSectionGUI()
{
useTypewriterWaitForDisappearances.PropertyField();
if (useTypewriterWaitForDisappearances.property.boolValue)
disappearanceSpeedMultiplier.PropertyField();
else
disappearanceWaitTime.PropertyField();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c505a7fbdbbf6604f9f76bf0afbec264
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e547d15ba02680d41823b4288063b1d4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -0,0 +1,99 @@
fileFormatVersion: 2
guid: 04f06a54beeb7574a8c2b2083e7be396
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 9
mipmaps:
mipMapMode: 0
enableMipMap: 1
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
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: -1
mipBias: -100
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 2
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
- serializedVersion: 2
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 4c61e790940ecb64e876d955039cd056
vertices: []
indices:
edges: []
weights: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: df7d5220397fe2547bef0d83a82cd45c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 00efa41e48d5e5e4a8761a47913301d7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 40ee8b5e74c70d84aa6c610f4ad0dc3e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,93 @@
using UnityEngine;
using System.Collections.Generic;
namespace Febucci.UI.Core
{
/// <summary>
/// Represents visible characters in the text (including sprites and excluding bars)
/// </summary>
struct Character
{
#pragma warning disable 0649
internal bool initialized;
#pragma warning restore 0649
public float disappearancesMaxDuration;
public bool isDisappearing;
public bool wantsToDisappear;
public float appearancesMaxDuration;
public int[] indexBehaviorEffects;
public int[] indexAppearanceEffects;
public int[] indexDisappearanceEffects;
public CharacterSourceData sources;
public CharacterData data;
public void ResetVertices()
{
for (byte i = 0; i < sources.vertices.Length; i++)
{
data.vertices[i] = sources.vertices[i];
}
}
public void ResetColors()
{
for (byte i = 0; i < sources.colors.Length; i++)
{
data.colors[i] = sources.colors[i];
}
}
public void Hide()
{
for (byte i = 0; i < sources.vertices.Length; i++)
{
data.vertices[i] = Vector3.zero;
}
}
}
//Original character data
struct CharacterSourceData
{
public Color32[] colors;
public Vector3[] vertices;
}
/// <summary>
/// Contains characters data that can be modified during TextAnimator effects.
/// </summary>
public struct CharacterData
{
/// <summary>
/// Time passed since the character is visible (or just started the appearance effect).
/// </summary>
public float passedTime;
/// <summary>
/// A character's vertices colors.
/// </summary>
/// <remarks>
/// The array size is usually <see cref="TextUtilities.verticesPerChar"/>
/// </remarks>
public Color32[] colors;
/// <summary>
/// A character's vertices positions.
/// </summary>
/// <remarks>
/// The array size is usually <see cref="TextUtilities.verticesPerChar"/>
/// </remarks>
public Vector3[] vertices;
/// <summary>
/// Related character information from TextMeshPro.<br/>
/// P.S. If you want to modify vertices/colors, please use the <see cref="vertices"/> and <see cref="colors"/> arrays variables instead.
/// </summary>
public TMPro.TMP_CharacterInfo tmp_CharInfo;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 941782baec821244b95c78c1e0958167
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
namespace Febucci.UI.Core
{
/// <summary>
/// Attribute used to set effect settings
/// </summary>
/// <example>
/// In order to set a "jump" tag to the below class:
/// <code>
/// [EffectInfo(tag: "jump")]
/// public class JumpEffect : BehaviorEffect
/// {
/// ///[...]
/// </code>
/// </example>
[System.AttributeUsage(System.AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class EffectInfoAttribute : System.Attribute
{
public readonly string tag;
public EffectInfoAttribute(string tag)
{
this.tag = tag;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f4cbdc4700ca58b43bb202055a604740
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,217 @@
namespace Febucci.UI.Core
{
/// <summary>
/// Base class for TextAnimator effects' categories<br/>
/// Please do not inherit from this class directly, but do from <see cref="AppearanceBase"/> or <see cref="BehaviorBase"/>
/// </summary>
public abstract class EffectsBase
{
/// <summary>
/// Effect's tag without symbols, eg. "shake"
/// </summary>
public string effectTag { get; private set; }
/// <summary>
/// Intensity used to uniform effects that behave differently based on screen or font sizes.
/// </summary>
/// <remarks>
/// Multiply this by your effect values only if they behave differently with different screen resolutions, font sizes or similar. (e.g. adding or subtracting vectors)
/// </remarks>
public float uniformIntensity = 1;
[System.Obsolete("This value will be removed from next versions. Please use 'uniformIntensity' instead")] public float effectIntensity => uniformIntensity;
#region Internal/Core
//---Methods used only by TextAnimator's internal workflow---//
internal class RegionManager
{
public string entireRichTextTag;
System.Collections.Generic.List<TextRegion> textRegions = new System.Collections.Generic.List<TextRegion>();
struct TextRegion
{
public int startIndex;
public int endIndex;
public TextRegion(int startIndex)
{
this.startIndex = startIndex;
this.endIndex = int.MaxValue;
}
}
internal bool IsLastRegionClosed()
{
return textRegions.Count > 0 && textRegions[textRegions.Count - 1].endIndex != int.MaxValue;
}
internal void AddRegion(int startIndex)
{
textRegions.Add(new TextRegion(startIndex));
}
internal bool TryReutilizingWithTag(string richTextTag, int indexNewRegionStart)
{
if (!entireRichTextTag.Equals(richTextTag))
return false;
if (!IsLastRegionClosed())
return true;
AddRegion(indexNewRegionStart);
return true;
}
internal void CloseEffect(int index)
{
var region = textRegions[textRegions.Count - 1];
region.endIndex = index;
textRegions[textRegions.Count - 1] = region;
}
internal bool IsCharInsideRegion(int charIndex)
{
foreach (var region in textRegions)
{
if (charIndex >= region.startIndex && charIndex < region.endIndex)
return true;
}
return false;
}
public override string ToString()
{
string text = $"{entireRichTextTag} - {textRegions.Count} region(s): ";
for (int i = 0; i < textRegions.Count; i++)
{
if(textRegions[i].endIndex == int.MaxValue)
{
text += $"[{textRegions[i].startIndex}; Infinity] ";
}
else
{
text += $"[{textRegions[i].startIndex}; {textRegions[i].endIndex}] ";
}
}
return text;
}
}
internal RegionManager regionManager;
/// <summary>
/// For internal use only. Sets the effect settings such as tags, instead of a constructor.
/// </summary>
/// <param name="effectTag"></param>
internal void _Initialize(string effectTag, string entireRichTextTag)
{
this.effectTag = effectTag;
this.regionManager = new RegionManager();
this.regionManager.entireRichTextTag = entireRichTextTag;
}
#endregion
#region Utilities
//---Methods you can use in your classes---//
/// <summary>
/// Applies the modifier by performing a multiplication to the given value.
/// </summary>
/// <param name="value">The effect's value you want to modify</param>
/// <param name="modifierValue">The modifier value. eg. "0.5"</param>
/// <example>
/// <code>
/// string modifier = "0.5";
/// float amplitude = 1;
/// ApplyModifierTo(ref amplitude, modifier);
/// //amplitude becomes 0.5
/// </code>
/// </example>
protected void ApplyModifierTo(ref float value, string modifierValue)
{
if (FormatUtils.ParseFloat(modifierValue, out float multiplier))
{
value *= multiplier;
}
}
#endregion
#region Effect Methods
//---Methods you can override in your classes---//
/// <summary>
/// Invoked upon effect creation
/// </summary>
/// <param name="charactersCount"></param>
public virtual void Initialize(int charactersCount) { }
/// <summary>
/// Called once per frame, before applying the effect to letters.
/// Example: You could use this to calculate the effect variables that are indiependant from specific letters
/// </summary>
public virtual void Calculate() { }
/// <summary>
/// Called once for each letter, per each frame.<br/>
/// Use this to apply the effect to a letter/character, by modifying its <see cref="CharacterData"/> values.
/// </summary>
/// <param name="data">Letters' values like position and colors. It might have been already modified by previous effects.</param>
/// <param name="charIndex">Letter index/position in the text.</param>
public abstract void ApplyEffect(ref CharacterData data, int charIndex);
/// <summary>
/// Invoked when there is a modifier in your rich text tag, eg. &#60;shake a=3&#62;
/// </summary>
/// <remarks>You can also use the following helper methods:
/// - <see cref="EffectsBase.ApplyModifierTo"/>
/// - <see cref="FormatUtils.ParseFloat"/>
/// </remarks>
/// <param name="modifierName">modifier name. eg. in &#60;shake a=3&#62; this string is "a"</param>
/// <param name="modifierValue">modifier value. eg. in &#60;shake a=3&#62; this string is "3"</param>
/// <example>
/// <code>
/// float amplitude = 2;
/// //[...]
/// public override void SetModifier(string modifierName, string modifierValue){
/// switch(modifierName){
/// //changes the 'amplitude' variable based on the modifier written in the tag
/// //eg. when you write a tag like &#60;shake a=3&#62;
/// case "a": ApplyModifierTo(ref amplitude, modifierValue); return;
/// }
/// }
/// </code>
/// </example>
public abstract void SetModifier(string modifierName, string modifierValue);
#if UNITY_EDITOR
//Used only in the editor to set again modifiers if we change values in the inspector
System.Collections.Generic.List<Modifier> modifiers { get; set; } = new System.Collections.Generic.List<Modifier>();
internal void EDITOR_RecordModifier(string name, string value)
{
modifiers.Add(new Modifier
{
name = name,
value = value,
});
}
internal void EDITOR_ApplyModifiers()
{
for (int i = 0; i < modifiers.Count; i++)
{
SetModifier(modifiers[i].name, modifiers[i].value);
}
}
#endif
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 26f45964032b7f64eb17e016c9b25e42
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
namespace Febucci.UI.Core
{
#if UNITY_EDITOR
struct Modifier
{
public string name;
public string value;
}
#endif
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2d03f0e128b12b3478b9445710bec25d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,128 @@
using UnityEngine;
using Febucci.Attributes;
namespace Febucci.UI.Core
{
[System.Serializable]
//Do not touch this script
public class AppearanceDefaultValues
{
#region Default Effects' values
private const float defDuration = .3f;
[System.Serializable]
public class Defaults
{
[PositiveValue] public float sizeDuration = defDuration;
[Attributes.MinValue(0)] public float sizeAmplitude = 2;
[PositiveValue] public float fadeDuration = defDuration;
[PositiveValue] public float verticalExpandDuration = defDuration;
public bool verticalFromBottom = false;
[PositiveValue] public float horizontalExpandDuration = defDuration;
[SerializeField] internal HorizontalExpandAppearance.ExpType horizontalExpandStart = HorizontalExpandAppearance.ExpType.Left;
[PositiveValue] public float diagonalExpandDuration = defDuration;
public bool diagonalFromBttmLeft = false;
[NotZero] public Vector2 offsetDir = Vector2.one;
[PositiveValue] public float offsetDuration = defDuration;
[NotZero] public float offsetAmplitude = 1f;
[PositiveValue] public float rotationDuration = defDuration;
public float rotationStartAngle = 180;
[PositiveValue] public float randomDirDuration = defDuration;
[NotZero] public float randomDirAmplitude = 1f;
}
[SerializeField, Header("Default Appearances")]
public Defaults defaults = new Defaults();
#endregion
[SerializeField, Header("Preset Effects")]
internal PresetAppearanceValues[] presets = new PresetAppearanceValues[0];
}
[System.Serializable]
//Do not touch this script
public class BehaviorDefaultValues
{
#region Default Effects' values
[System.Serializable]
public class Defaults
{
//wiggle
[NotZero] public float wiggleAmplitude = 0.15f;
[NotZero] public float wiggleFrequency = 7.67f;
//wave
[NotZero] public float waveFrequency = 4.78f;
[NotZero] public float waveAmplitude = .2f;
public float waveWaveSize = .18f;
//rot
[NotZero] public float angleSpeed = 180;
public float angleDiffBetweenChars = 10;
//swing
[NotZero] public float swingAmplitude = 27.5f;
[NotZero] public float swingFrequency = 5f;
public float swingWaveSize = 0;
//shake
[NotZero] public float shakeStrength = 0.085f;
[PositiveValue] public float shakeDelay = .04f;
//size
public float sizeAmplitude = 1.4f;
[NotZero] public float sizeFrequency = 4.84f;
public float sizeWaveSize = .18f;
//slide
[NotZero] public float slideAmplitude = 0.12f;
[NotZero] public float slideFrequency = 5;
public float slideWaveSize = 0;
//bounce
[NotZero] public float bounceAmplitude = .08f;
[NotZero] public float bounceFrequency = 1f;
public float bounceWaveSize = 0.08f;
//rainb
[NotZero] public float hueShiftSpeed = 0.8f;
public float hueShiftWaveSize = 0.08f;
//fade
[PositiveValue] public float fadeDelay = 1.2f;
//dangle
[NotZero] public float dangleAmplitude = .13f;
[NotZero] public float dangleFrequency = 2.41f;
public float dangleWaveSize = 0.18f;
public bool dangleAnchorBottom = false;
//pendulum
[NotZero] public float pendAmplitude = 25;
[NotZero] public float pendFrequency = 3;
public float pendWaveSize = .2f;
public bool pendInverted = false;
}
[SerializeField, Header("Default Behaviors")]
public Defaults defaults = new Defaults();
#endregion
[SerializeField, Header("Preset Effects")]
internal PresetBehaviorValues[] presets = new PresetBehaviorValues[0];
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d4926f3dbd96a4c4088cb85200cb67e9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 35e8b95654a50e84593a284625387a04
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
namespace Febucci.UI.Core
{
struct EventMarker
{
public int charIndex;
public string eventMessage;
public bool triggered;
public int internalOrder;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 438f97e47f711044d9cf62066e9fc812
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,339 @@
using UnityEngine;
using System.Collections.Generic;
using System;
using System.Linq;
using System.Reflection;
namespace Febucci.UI.Core
{
#if UNITY_EDITOR
public static class TAnim_EditorHelper
{
internal delegate void VoidCallback();
internal static event VoidCallback onChangesApplied;
public static void TriggerEvent()
{
if (Application.isPlaying)
{
onChangesApplied?.Invoke();
}
}
}
#endif
public static class TAnimBuilder
{
[System.Serializable]
internal struct TagFormatting
{
public TagFormatting(char openingChar, char closingChar)
{
this.charOpeningTag = openingChar;
this.charClosingTag = closingChar;
}
public char charOpeningTag;
public char charClosingTag;
}
internal static TagFormatting tag_behaviors = new TagFormatting('<', '>');
internal static TagFormatting tag_appearances = new TagFormatting('{', '}');
static TAnimGlobalDataScriptable _data;
static bool hasData;
internal static TAnimGlobalDataScriptable data
{
get => _data;
}
#region Static Controller
static Dictionary<string, Type> behaviorsData = new Dictionary<string, Type>();
static Dictionary<string, Type> appearancesData = new Dictionary<string, Type>();
static HashSet<string> globalDefaultActions = new HashSet<string>();
static HashSet<string> globalCustomActions = new HashSet<string>();
static bool globalDatabaseInitialized;
public static string[] GetAllBehaviorsTags()
{
List<string> tags = new List<string>();
for (int i = 0; i < behaviorsData.Count; i++)
{
tags.Add(behaviorsData.Keys.ElementAt(i));
}
return tags.ToArray();
}
public static string[] GetAllApppearancesTags()
{
List<string> tags = new List<string>();
for (int i = 0; i < appearancesData.Count; i++)
{
tags.Add(appearancesData.Keys.ElementAt(i));
}
return tags.ToArray();
}
/// <summary>
/// Initializes and Load TextAnimator's effects and global settings, in case it has not been loaded already.
/// </summary>
public static void InitializeGlobalDatabase()
{
if (globalDatabaseInitialized)
return;
globalDatabaseInitialized = true;
TextUtilities.Initialize();
#region Local Methods
void PopulateEffectsFromAssembly<T>(ref Dictionary<string, Type> effectsList) where T : EffectsBase
{
List<Type> GetAssemblyClasses()
{
return (from domainAssembly in AppDomain.CurrentDomain.GetAssemblies()
from assemblyType in domainAssembly.GetTypes()
where assemblyType.IsSubclassOf(typeof(T))
where !assemblyType.IsAbstract
select assemblyType).ToList();
}
var effectsInAssembly = GetAssemblyClasses();
EffectInfoAttribute attribute;
string effectTag;
for (int i = 0; i < effectsInAssembly.Count; i++)
{
effectTag = string.Empty;
attribute = effectsInAssembly[i].GetCustomAttribute<EffectInfoAttribute>();
if (attribute != null)
{
effectTag = attribute.tag;
}
else
{
Debug.LogError($"TextAnimator: skipping class {effectsInAssembly[i].Name}. Please add a 'EffectInfoAttribute' on top of it.");
continue;
}
if (string.IsNullOrEmpty(effectTag))
{
continue;
}
if (!effectsList.ContainsKey(effectTag))
{
effectsList.Add(effectTag, effectsInAssembly[i]);
}
else
{
Debug.LogError($"TextAnimator: not adding effect <{effectTag}> (from class '{effectsInAssembly[i].Name}') to the database because an effect with the same tag has already been added (by class '{effectsList[effectTag].Name}')");
}
}
}
#endregion
PopulateEffectsFromAssembly<BehaviorBase>(ref behaviorsData);
PopulateEffectsFromAssembly<AppearanceBase>(ref appearancesData);
#region Default Actions
globalDefaultActions.Add("waitfor");
globalDefaultActions.Add("waitinput");
globalDefaultActions.Add("speed");
#endregion
hasData = false;
_data = Resources.Load(TAnimGlobalDataScriptable.resourcesPath) as TAnimGlobalDataScriptable;
if (data != null)
{
hasData = true;
#region Settings
if (data.customTagsFormatting)
{
if (data.tagInfo_behaviors.charOpeningTag != data.tagInfo_appearances.charOpeningTag
&& data.tagInfo_behaviors.charClosingTag != data.tagInfo_appearances.charClosingTag)
{
tag_behaviors = data.tagInfo_behaviors;
tag_appearances = data.tagInfo_appearances;
}
else
{
Debug.LogError("Not valid"); //todo error
}
}
#endregion
#region Global Effects
//Adds global effects
for (int i = 0; i < data.globalBehaviorPresets.Length; i++)
{
TryAddingPresetToDictionary(ref behaviorsData, data.globalBehaviorPresets[i].effectTag, typeof(PresetBehavior));
}
//Adds global effects
for (int i = 0; i < data.globalAppearancePresets.Length; i++)
{
TryAddingPresetToDictionary(ref appearancesData, data.globalAppearancePresets[i].effectTag, typeof(PresetAppearance));
}
#endregion
#region Custom Actions
if (data.customActions != null && data.customActions.Length > 0)
{
for (int i = 0; i < data.customActions.Length; i++)
{
if (data.customActions[i].Length <= 0)
{
Debug.LogError($"TextAnimator: Custom action {i} has an empty tag!");
continue;
}
if (globalCustomActions.Contains(data.customActions[i]))
{
Debug.LogError($"TextAnimator: Custom feature with tag '{data.customActions[i]}' is already present, it won't be added to the database.");
continue;
}
globalCustomActions.Add(data.customActions[i]);
}
}
#endregion
}
}
internal static bool TryGetGlobalPresetBehavior(string tag, out PresetBehaviorValues result)
{
if (!hasData) //avoids searching if data is null
{
result = default;
return false;
}
return GetPresetFromArray(tag, data.globalBehaviorPresets, out result);
}
internal static bool TryGetGlobalPresetAppearance(string tag, out PresetAppearanceValues result)
{
if (!hasData) //avoids searching if data is null
{
result = default;
return false;
}
return GetPresetFromArray(tag, data.globalAppearancePresets, out result);
}
internal static bool GetPresetFromArray<T>(string tag, T[] presets, out T result) where T : PresetBaseValues
{
if (presets.Length > 0)
{
for (int i = 0; i < presets.Length; i++)
{
if (tag.Equals(presets[i].effectTag))
{
result = presets[i];
return true;
}
}
}
result = default;
return false;
}
internal static bool IsDefaultAction(string tag)
{
if (globalDefaultActions.Count > 0 && globalDefaultActions.Contains(tag))
{
return true;
}
return false;
}
internal static bool IsCustomAction(string tag)
{
if (globalCustomActions.Count > 0 && globalCustomActions.Contains(tag))
{
return true;
}
return false;
}
internal static bool TryGetGlobalBehaviorFromTag(string effectTag, string entireRichTextTag, out BehaviorBase effectClass)
{
return TryGetEffectClassFromTag<BehaviorBase>(behaviorsData, effectTag, entireRichTextTag, out effectClass);
}
internal static bool TryGetGlobalAppearanceFromTag(string effectTag, string entireRichTextTag, out AppearanceBase effectClass)
{
return TryGetEffectClassFromTag(appearancesData, effectTag, entireRichTextTag, out effectClass);
}
internal static bool TryGetEffectClassFromTag<T>(Dictionary<string, Type> dictionary, string effectTag, string entireRichTextTag, out T effectClass) where T : EffectsBase
{
if (dictionary.ContainsKey(effectTag))
{
effectClass = Activator.CreateInstance(dictionary[effectTag]) as T;
effectClass._Initialize(effectTag, entireRichTextTag);
return true;
}
effectClass = default;
return false;
}
internal static void TryAddingPresetToDictionary(ref Dictionary<string, Type> database, string tag, Type type)
{
if (string.IsNullOrEmpty(tag))
{
Debug.LogWarning($"TextAnimator: Preset has a null or empty tag '{tag}'");
return;
}
if (!TextUtilities.IsTagLongEnough(tag))
{
Debug.LogWarning($"TextAnimator: Preset has tag '{tag}' shorter than three characters.");
return;
}
if (database.ContainsKey(tag))
{
Debug.LogWarning($"TextAnimator: A Preset has tag '{tag}' that's already present, it won't be added to the database.");
return;
}
database.Add(tag, type);
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 636e2806e423df447a5247629811c6a0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,35 @@
using UnityEngine;
using TagFormatting = Febucci.UI.Core.TAnimBuilder.TagFormatting;
namespace Febucci.UI.Core
{
/// <summary>
/// Stores TextAnimator's global data, shared in all your project (eg. Global Behaviors and Appearances).<br/>
/// Must be placed inside the Resources Path <see cref="resourcesPath"/><br/>
/// - Manual: <see href="https://www.febucci.com/text-animator-unity/docs/creating-effects-in-the-inspector/#global-effects">Creating Global Effects</see>
/// </summary>
[System.Serializable]
[CreateAssetMenu(fileName = "TextAnimator GlobalData", menuName = "TextAnimator/Create Global Text Animator Data")]
public class TAnimGlobalDataScriptable : ScriptableObject
{
/// <summary>
/// Resources Path where the scriptable object must be stored
/// </summary>
public const string resourcesPath = "TextAnimator GlobalData";
[SerializeField]
internal PresetBehaviorValues[] globalBehaviorPresets = new PresetBehaviorValues[0];
[SerializeField]
internal PresetAppearanceValues[] globalAppearancePresets = new PresetAppearanceValues[0];
[SerializeField]
internal string[] customActions = new string[0];
[SerializeField] internal bool customTagsFormatting = false;
[SerializeField] internal TagFormatting tagInfo_behaviors = new TagFormatting('<', '>');
[SerializeField] internal TagFormatting tagInfo_appearances = new TagFormatting('{', '}');
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a7bb413915491aa4d87d5f50a9bd1aaf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,717 @@
using System.Collections;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Events;
namespace Febucci.UI.Core
{
[System.Serializable]
public class CharacterEvent : UnityEvent<char> { }
/// <summary>
/// Base class for all TextAnimatorPlayers (typewriters). <br/>
/// - Manual: <see href="https://www.febucci.com/text-animator-unity/docs/text-animator-players/">TextAnimatorPlayers</see>.<br/>
/// </summary>
/// <remarks>
/// If you want to use the default TextAnimatorPlayer, see: <see cref="TextAnimatorPlayer"/><br/>
/// <br/>
/// You can also create custom typewriters by inheriting from this class. <br/>
/// Manual: <see href="https://www.febucci.com/text-animator-unity/docs/writing-custom-tanimplayers-c-sharp/">Writing Custom TextAnimatorPlayers (C#)</see>
/// </remarks>
[DisallowMultipleComponent]
[RequireComponent(typeof(TextAnimator))]
public abstract class TAnimPlayerBase : MonoBehaviour
{
[System.Flags]
enum StartTypewriterMode
{
/// <summary>
/// Typewriter starts typing ONLY if you invoke "StartShowingText" from any of your script.
/// </summary>
FromScriptOnly = 0,
/// <summary>
/// Typewriter automatically starts/resumes from the "OnEnable" method
/// </summary>
OnEnable = 1,
/// <summary>
/// Typewriter automatically starts once you call "ShowText" method [includes Easy Integration]
/// </summary>
OnShowText = 2,
AutomaticallyFromAllEvents = OnEnable | OnShowText //legacy support for unity 2018.x [instead of automatic recognition in 2019+]
}
#region Variables
#region Management Variables
string textToShow = string.Empty;
TextAnimator _textAnimator;
/// <summary>
/// The TextAnimator Component linked to this typewriter
/// </summary>
public TextAnimator textAnimator
{
get
{
if (_textAnimator != null)
return _textAnimator;
#if UNITY_2019_2_OR_NEWER
if(!TryGetComponent(out _textAnimator))
{
Debug.LogError($"TextAnimator: Text Animator component is null on GameObject {gameObject.name}");
}
#else
_textAnimator = GetComponent<TextAnimator>();
Assert.IsNotNull(_textAnimator, $"Text Animator component is null on GameObject {gameObject.name}");
#endif
return _textAnimator;
}
}
/// <summary>
/// <c>true</c> if the typewriter is currently showing letters.
/// </summary>
protected bool isBaseInsideRoutine => isInsideRoutine;
/// <summary>
/// <c>true</c> if the typewriter is waiting for the player input in the 'waitinput' action tag
/// </summary>
[HideInInspector] public bool isWaitingForPlayerInput { get; private set; }
bool isInsideRoutine = false;
bool isDisappearing = false;
/// <summary>
/// <c>true</c> if the player wants to skip the typewriter.<br/>
/// You can check/modify its value and also call <see cref="SkipTypewriter"/> to set it to <c>true</c>.
/// </summary>
/// <remarks>
/// P.S. It is reset back to <c>false</c> every time you show a new text.
/// </remarks>
protected bool wantsToSkip = false;
#endregion
#region Typewriter settings
/// <summary>
/// <c>true</c> if the typewriter is enabled
/// </summary>
[Tooltip("True if you want to shows the text dynamically")]
[SerializeField] public bool useTypeWriter = true;
[SerializeField, Tooltip("Controls from which method(s) the typewriter will automatically start/resume. Default is 'Automatic'")]
StartTypewriterMode startTypewriterMode = StartTypewriterMode.AutomaticallyFromAllEvents;
#region Typewriter Skip
[SerializeField]
bool canSkipTypewriter = true;
[SerializeField]
bool hideAppearancesOnSkip = false;
[SerializeField, Tooltip("True = plays all remaining events once the typewriter has been skipped")]
bool triggerEventsOnSkip = false;
#endregion
[SerializeField, Tooltip("True = resets the typewriter speed every time a new text is set/shown")] bool resetTypingSpeedAtStartup = true;
/// <summary>
/// Typewriter's speed (acts like a multiplier)<br/>
/// You can change this value or invoke <see cref="SetTypewriterSpeed(float)"/>
/// </summary>
protected float typewriterPlayerSpeed = 1;
public enum DisappearanceOrientation
{
SameAsTypewriter,
Inverted
}
[SerializeField] public DisappearanceOrientation disappearanceOrientation;
#endregion
#endregion
#region Events
/// <summary>
/// Called once the text is completely shown. <br/>
/// If the typewriter is enabled, this event is called once it has ended showing all letters.
/// </summary>
public UnityEvent onTextShowed;
/// <summary>
/// Called once the typewriter starts showing text.<br/>
/// It is only invoked when the typewriter is enabled.
/// </summary>
public UnityEvent onTypewriterStart;
/// <summary>
/// Callend once the typewriter has completed hiding all the letters.
/// </summary>
public UnityEvent onTextDisappeared;
/// <summary>
/// Called once a character has been shown by the typewriter.<br/>
/// It is only invoked when the typewriter is enabled.
/// </summary>
public CharacterEvent onCharacterVisible;
#endregion
/// <summary>
/// Shows remains letters dynamically
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
private IEnumerator ShowRemainingCharacters()
{
if (!textAnimator.allLettersShown)
{
isInsideRoutine = true;
isWaitingForPlayerInput = false;
isDisappearing = false;
wantsToSkip = false;
onTypewriterStart?.Invoke();
IEnumerator WaitTime(float time)
{
if (time > 0)
{
float t = 0;
while (t <= time && !HasSkipped())
{
t += textAnimator.time.deltaTime;
yield return null;
}
}
}
float timeToWait;
char characterShown;
if (resetTypingSpeedAtStartup)
typewriterPlayerSpeed = 1;
float typewriterTagsSpeed = 1;
bool HasSkipped()
{
return canSkipTypewriter && wantsToSkip;
}
float timePassed = 0;
float deltaTime;
UpdateDeltaTime();
void UpdateDeltaTime()
{
deltaTime = textAnimator.time.deltaTime * typewriterPlayerSpeed * typewriterTagsSpeed;
}
//Shows character by character until all are shown
while (!textAnimator.allLettersShown)
{
//searches for actions [before the character, incl. at the very start of the text]
if (textAnimator.hasActions)
{
//loops until features ended (there could be multiple ones in the same text position, example: when two tags are next to eachother without spaces
while (textAnimator.TryGetAction(out TypewriterAction action))
{
//Default features
switch (action.actionID)
{
case "waitfor":
float waitTime;
FormatUtils.TryGetFloat(action.parameters, 0, 1f, out waitTime);
yield return WaitTime(waitTime);
break;
case "waitinput":
isWaitingForPlayerInput = true;
yield return WaitInput();
isWaitingForPlayerInput = false;
break;
case "speed":
FormatUtils.TryGetFloat(action.parameters, 0, 1, out typewriterTagsSpeed);
//clamps speed (time cannot go backwards!)
if (typewriterTagsSpeed <= 0)
{
typewriterTagsSpeed = 0.001f;
}
break;
//Action is custom
default:
yield return DoCustomAction(action);
break;
}
}
}
//increases the visible chars count
textAnimator.maxVisibleCharacters++;
textAnimator.TriggerVisibleEvents();
characterShown = textAnimator.latestCharacterShown.character;
UpdateDeltaTime();
//triggers event unless it's a space
if (characterShown != ' ')
{
onCharacterVisible?.Invoke(characterShown);
}
//gets the time to wait based on the newly character showed
timeToWait = GetWaitAppearanceTimeOf(characterShown);
//waiting less time than a frame, we don't wait yet
if (timeToWait < deltaTime)
{
timePassed += timeToWait;
if (timePassed >= deltaTime) //waits only if we "surpassed" a frame duration
{
yield return null;
timePassed %= deltaTime;
}
}
else
{
while (timePassed < timeToWait && !HasSkipped())
{
OnTypewriterCharDelay();
timePassed += deltaTime;
yield return null;
UpdateDeltaTime();
}
timePassed %= timeToWait;
}
//Skips typewriter
if (HasSkipped())
{
textAnimator.ShowAllCharacters(hideAppearancesOnSkip);
if (triggerEventsOnSkip)
{
textAnimator.TriggerRemainingEvents();
}
break;
}
}
// triggers the events at the end of the text
//
// the typewriter is arrived here without skipping
// meaning that all events were triggered and we only
// have to fire the ones at the very end
// (outside tmpro's characters count length)
if (!canSkipTypewriter || !wantsToSkip)
{
textAnimator.TriggerRemainingEvents();
}
isInsideRoutine = false;
isWaitingForPlayerInput = false;
textToShow = string.Empty; //text has been showed, no need to store it now
onTextShowed?.Invoke();
}
}
#region Public Methods
/// <summary>
/// Sets the TextAnimator text. If enabled, it also starts showing letters dynamically. <br/>
/// - Manual: <see href="https://www.febucci.com/text-animator-unity/docs/text-animator-players/">Text Animator Players</see>
/// </summary>
/// <param name="text"></param>
/// <remarks>
/// If the typewriter is enabled but its start mode (editable in the Inspector) doesn't include <see cref="StartTypewriterMode.OnShowText"/>, this method won't start showing letters. You'd have to manually call <see cref="StartShowingText"/> in order to start the typewriter, or include different "start modes" like <see cref="StartTypewriterMode.OnEnable"/> and let the script manage it automatically.
/// </remarks>
public void ShowText(string text)
{
StopShowingText();
if (string.IsNullOrEmpty(text))
{
textToShow = string.Empty;
textAnimator.SetText(string.Empty, true);
return;
}
textToShow = text;
isWaitingForPlayerInput = false;
wantsToSkip = false;
textAnimator.SetText(textToShow, useTypeWriter);
textAnimator.firstVisibleCharacter = 0;
isDisappearing = false;
if (!useTypeWriter)
{
onTextShowed?.Invoke();
}
else
{
if (startTypewriterMode.HasFlag(StartTypewriterMode.OnShowText))
{
StartShowingText();
}
}
}
#region Typewriter
bool CanStartAnyCoroutine()
{
#if UNITY_EDITOR
if (!Application.isPlaying) //prevents from firing in edit mode from the context menu
return false;
#endif
if (!gameObject.activeInHierarchy)
{
Debug.LogWarning("TextAnimator: couldn't start coroutine because the gameobject is not active");
return false;
}
return true;
}
#region Appearing
/// <summary>
/// Starts showing letters dynamically
/// </summary>
/// <param name="resetVisibleCharacters"><code>false</code> if you want the typewriter to resume where it was left. <code>true</code> if the typewriter should restart from character 0</param>
public void StartShowingText(bool resetVisibleCharacters = false)
{
if (!useTypeWriter)
{
Debug.LogWarning("TextAnimator: couldn't start coroutine because 'useTypewriter' is disabled");
return;
}
if (!CanStartAnyCoroutine()) return;
if (resetVisibleCharacters)
{
textAnimator.firstVisibleCharacter = 0;
textAnimator.maxVisibleCharacters = 0;
}
if (!isInsideRoutine) //starts only if it is not already typing
{
StartCoroutine(ShowRemainingCharacters());
}
}
[ContextMenu("Skip Typewriter")]
/// <summary>
/// Skips the typewriter animation (if it's currently showing)
/// </summary>
public void SkipTypewriter()
{
#if UNITY_EDITOR
if (!Application.isPlaying) //prevents from firing in edit mode from the context menu
return;
#endif
wantsToSkip = true;
}
/// <summary>
/// Stops showing letters dynamically, leaving the text as it is.
/// </summary>
[ContextMenu("Stop Showing Text")]
public void StopShowingText()
{
#if UNITY_EDITOR
if (!Application.isPlaying) //prevents from firing in edit mode from the context menu
return;
#endif
//Stops only if we're inside the routine
if (isInsideRoutine)
{
isInsideRoutine = false;
StopAllCoroutines();
}
textToShow = string.Empty;
}
#endregion
#region Disappearing
/// <summary>
/// Starts disappearing the text dynamically
/// </summary>
[ContextMenu("Start Disappearing Text")]
public void StartDisappearingText()
{
if (!CanStartAnyCoroutine()) return;
if (disappearanceOrientation == DisappearanceOrientation.Inverted && isInsideRoutine)
{
Debug.LogWarning("TextAnimatorPlayer: Can't start disappearance routine in the opposite direction of the typewriter, because you're still showing the text! (the typewriter might get stuck trying to show and override letters that keep disappearing)");
return;
}
StartCoroutine(DisappearRoutine());
}
IEnumerator DisappearRoutine()
{
isDisappearing = true;
float t = 0;
float deltaTime = 0;
void UpdateDeltaTime()
{
deltaTime = textAnimator.time.deltaTime * typewriterPlayerSpeed;
}
UpdateDeltaTime();
bool CanDisappear() => isDisappearing && textAnimator.firstVisibleCharacter <= textAnimator.maxVisibleCharacters && textAnimator.maxVisibleCharacters > 0;
IEnumerator WaitFor(float timeToWait)
{
if (timeToWait <= 0)
yield break;
while (t < timeToWait) //waits
{
t += deltaTime;
yield return null;
UpdateDeltaTime();
}
t %= timeToWait;
}
if (disappearanceOrientation == DisappearanceOrientation.SameAsTypewriter)
{
var textInfo = textAnimator.tmproText.textInfo;
int charCount = textInfo.characterCount;
var charInfo = textInfo.characterInfo;
while (CanDisappear() && textAnimator.firstVisibleCharacter<charCount)
{
textAnimator.firstVisibleCharacter++;
float timeToWait = GetWaitDisappearanceTimeOf(charInfo[textAnimator.firstVisibleCharacter - 1].character);
//waiting less time than a frame, we don't wait yet
if (timeToWait < deltaTime)
{
t += timeToWait;
if (t >= deltaTime) //waits only if we "surpassed" a frame duration
{
yield return null;
t %= deltaTime;
}
}
else
yield return WaitFor(timeToWait);
}
}
else
{
while (CanDisappear())
{
textAnimator.maxVisibleCharacters--;
float timeToWait = GetWaitDisappearanceTimeOf(textAnimator.latestCharacterShown.character);
//waiting less time than a frame, we don't wait yet
if (timeToWait < deltaTime)
{
t += timeToWait;
if (t >= deltaTime) //waits only if we "surpassed" a frame duration
{
yield return null;
t %= deltaTime;
}
}
else yield return WaitFor(timeToWait);
}
}
//Waits until all letters are completely hidden/disappeared
while (textAnimator.anyLetterVisible)
yield return null;
//Fires the event if the entire text has been hidden (so, this method has not been interrupted)
if ((textAnimator.firstVisibleCharacter >= textAnimator.maxVisibleCharacters && textAnimator.allLettersShown) || textAnimator.maxVisibleCharacters == 0)
{
onTextDisappeared.Invoke();
}
isDisappearing = false;
}
/// <summary>
/// Stops the typewriter's from disappearing the text dynamically, leaving the text at its current state
/// </summary>
[ContextMenu("Stop Disappearing Text")]
public void StopDisappearingText()
{
isDisappearing = false;
}
#endregion
/// <summary>
/// Makes the typewriter slower/faster, by setting its internal speed multiplier.
/// </summary>
/// <param name="value"></param>
/// <example>
/// If the typewriter has to wait <c>1</c> second to show the next letter but you set the typewriter speed to <c>2</c>, the typewriter will wait <c>0.5</c> seconds.
/// </example>
/// <remarks>
/// The minimum value is 0.001
/// </remarks>
public void SetTypewriterSpeed(float value)
{
typewriterPlayerSpeed = Mathf.Clamp(value, .001f, value);
}
#endregion
#endregion
#region Virtual/Abstract Methods
/// <summary>
/// Waits for user input in order to continue showing text. Invoked when there is a waitinput action tag (Manual: <see href="http://localhost:4000/docs/performing-actions-while-typing/">Performing Actions while Typing</see>)
/// </summary>
/// <returns></returns>
/// <remarks>
/// You can customize this based on your project inputs.
/// </remarks>
protected abstract IEnumerator WaitInput();
/// <summary>
/// Returns the typewriter's appearance waiting time based on a given character/letter.
/// </summary>
/// <param name="character"></param>
/// <returns></returns>
/// <remarks>
/// You can customize this in your custom typewriter for your game.<br/>
/// Some variables/methods that you could use here:<br/>
/// - <seealso cref="TextAnimator.latestCharacterShown"/><br/>
/// - <seealso cref="TextAnimator.TryGetNextCharacter(out TMPro.TMP_CharacterInfo)"/><br/>
/// </remarks>
/// <example>
/// Waiting more time if the character is puntuaction.
/// <code>
/// protected override float WaitTimeOf(char character)
/// {
/// if (char.IsPunctuation(character))
/// return .06f;
///
/// return .03f;
/// }
/// </code>
/// </example>
protected abstract float GetWaitAppearanceTimeOf(char character);
///from previous versions
[System.Obsolete("'WaitTimeOf' is obsolete and will be removed from the next versions. Pleaase use 'GetWaitAppearanceTimeOf' instead.")]
protected virtual void WaitTimeOf(char character) => GetWaitAppearanceTimeOf(character);
/// <summary>
/// Returns the typewriter's disappearance waiting time based on a given character/letter.
/// </summary>
/// <param name="character"></param>
/// <returns></returns>
/// <remarks>
/// You can customize this in your custom typewriter for your game.<br/>
/// </remarks>
protected virtual float GetWaitDisappearanceTimeOf(char character) => GetWaitAppearanceTimeOf(character);
/// <summary>
/// Override this method in order to implement custom actions in your typewriter.<br/>
/// - Manual: <see href="https://www.febucci.com/text-animator-unity/docs/writing-custom-actions-c-sharp/">Writing Custom Actions C#</see>
/// </summary>
/// <param name="action"></param>
/// <returns></returns>
protected virtual IEnumerator DoCustomAction(TypewriterAction action)
{
throw new System.NotImplementedException($"TextAnimator: Custom Action not implemented with type: {action.actionID}. If you did implement it, please do not call the base method from your overridden one.");
}
/// <summary>
/// Invoked for every frame the typewriter is waiting to show the next letter.<br/>
/// </summary>
/// <remarks>
/// You could use this in order to speed up the waiting time based on the player input. <br/>
/// - See: <see cref="SetTypewriterSpeed(float)"/>
/// </remarks>
protected virtual void OnTypewriterCharDelay()
{
}
#endregion
/// <summary>
/// Unity's default MonoBehavior 'OnDisable' callback.
/// </summary>
/// <remarks>
/// P.S. If you're overriding this method, don't forget to invoke the base one.
/// </remarks>
protected virtual void OnDisable()
{
isInsideRoutine = false;
}
/// <summary>
/// Unity's default MonoBehavior 'OnEnable' callback.
/// </summary>
/// <remarks>
/// P.S. If you're overriding this method, don't forget to invoke the base one.
/// </remarks>
protected virtual void OnEnable()
{
if (!useTypeWriter)
return;
if (!startTypewriterMode.HasFlag(StartTypewriterMode.OnEnable))
return;
StartShowingText();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ac6b0a2b8ddbab342ad54b5409ccffec
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,70 @@
namespace Febucci.UI
{
/// <summary>
/// Contains all the tags for built-in effects.<br/>
/// - Manual: <seealso href="https://www.febucci.com/text-animator-unity/docs/built-in-effects-list/">Built-in Effects</seealso>
/// </summary>
public static class TAnimTags
{
#region Tags
public const string bh_Shake = "shake";
public const string bh_Rot = "rot";
public const string bh_Wiggle = "wiggle";
public const string bh_Wave = "wave";
public const string bh_Swing = "swing";
public const string bh_Incr = "incr";
public const string bh_Slide = "slide";
public const string bh_Bounce = "bounce";
public const string bh_Fade = "fade";
public const string bh_Rainb = "rainb";
public const string bh_Dangle = "dangle";
public const string bh_Pendulum = "pend";
public const string ap_Size = "size";
public const string ap_Fade = "fade";
public const string ap_Offset = "offset";
public const string ap_RandomDir = "rdir";
public const string ap_VertExp = "vertexp";
public const string ap_HoriExp = "horiexp";
public const string ap_DiagExp = "diagexp";
public const string ap_Rot = "rot";
#endregion
/// <summary>
/// Contains all default behavior effects tags
/// </summary>
public static readonly string[] defaultBehaviors = new string[]
{
bh_Shake,
bh_Rot,
bh_Wiggle,
bh_Wave,
bh_Swing,
bh_Incr,
bh_Slide,
bh_Bounce,
bh_Fade,
bh_Rainb,
bh_Dangle,
bh_Pendulum
};
/// <summary>
/// Contains all default appearance effects tags
/// </summary>
public static readonly string[] defaultAppearances = new string[]{
ap_Size,
ap_Fade,
ap_Offset,
ap_VertExp,
ap_HoriExp,
ap_DiagExp,
ap_Rot,
ap_RandomDir
};
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0527782f4a0130c469e55649e2c419e1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,29 @@
using System.Collections.Generic;
namespace Febucci.UI
{
/// <summary>
/// TextAnimator's typewriter action
/// </summary>
public struct TypewriterAction
{
/// <summary>
/// ID of the action without the tag symbols, eg. 'waitfor'
/// </summary>
public string actionID;
/// <summary>
/// Contains all the parameters passed via the rich text tag
/// </summary>
/// <example>
/// If you write <waitfor=5> in the text, the following code will result in:
/// <code>
/// float waitTime;
/// Febucci.UI.Core.FormatUtils.TryGetFloat(action.parameters, 0, 1f, out waitTime);
/// //waitTime is now 5
/// </code>
/// </example>
/// <see cref="Core.FormatUtils.ParseFloat(string, out float)"/>
public List<string> parameters;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 85b02a7fdec0a8840b184b90beaed9c7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 68a955ba748c0894588a272f4b7b1aa7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9c46d0e3b4076cf4395612be87694635
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fc174d1751ee25242a118f987b8f9f66
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,43 @@
namespace Febucci.UI.Core
{
/// <summary>
/// Base class for all appearance effects. <br/>
/// Inherit from this class if you want to create your own Appearance Effect in C#.
/// </summary>
public abstract class AppearanceBase : EffectsBase
{
public float effectDuration = .3f;
[System.Obsolete("This variable will be removed from next versions. Please use 'effectDuration' instead")]
protected float showDuration => effectDuration;
/// <summary>
/// Initializes the effect's default values. It is called before the effect is applied to letters.
/// </summary>
/// <remarks>
/// Use this to assign values to your effect.
/// </remarks>
/// <example>
/// <code>
/// effectDuration = data.defaults.sizeDuration;
/// amplitude = data.defaults.sizeAmplitude;
/// </code>
/// </example>
/// <param name="data"></param>
public abstract void SetDefaultValues(AppearanceDefaultValues data);
public virtual bool CanShowAppearanceOn(float timePassed)
{
return timePassed <= effectDuration;
}
public override void SetModifier(string modifierName, string modifierValue)
{
switch (modifierName)
{
//duration
case "d": ApplyModifierTo(ref effectDuration, modifierValue); break;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5168d9d1fb0c81d45a9c771529d45e4f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 41f2670940e258c4ca5bcac3643b6179
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,57 @@
using UnityEngine;
namespace Febucci.UI.Core
{
[UnityEngine.Scripting.Preserve]
[EffectInfo(tag: TAnimTags.ap_DiagExp)]
class DiagonalExpandAppearance : AppearanceBase
{
int targetA;
int targetB;
//--Temp variables--
Vector3 middlePos;
float pct;
public override void SetDefaultValues(AppearanceDefaultValues data)
{
effectDuration = data.defaults.diagonalExpandDuration;
SetOrientation(data.defaults.diagonalFromBttmLeft);
}
void SetOrientation(bool btmLeft)
{
if (btmLeft) //expands bottom left and top right
{
targetA = 0;
targetB = 2;
}
else //expands bottom right and top left
{
targetA = 1;
targetB = 3;
}
}
public override void ApplyEffect(ref CharacterData data, int charIndex)
{
middlePos = data.vertices.GetMiddlePos();
pct = Tween.EaseInOut(data.passedTime / effectDuration);
data.vertices[targetA] = Vector3.LerpUnclamped(middlePos, data.vertices[targetA], pct);
//top right copies from bottom right
data.vertices[targetB] = Vector3.LerpUnclamped(middlePos, data.vertices[targetB], pct);
}
public override void SetModifier(string modifierName, string modifierValue)
{
base.SetModifier(modifierName, modifierValue);
switch (modifierName)
{
case "bot": SetOrientation(modifierValue == "1"); break;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7303246fad8e2a049aebd6c0c71d2985
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,31 @@
using UnityEngine;
namespace Febucci.UI.Core
{
[UnityEngine.Scripting.Preserve]
[EffectInfo(tag: TAnimTags.ap_Fade)]
class FadeAppearance : AppearanceBase
{
public override void SetDefaultValues(AppearanceDefaultValues data)
{
effectDuration = data.defaults.fadeDuration;
}
Color32 temp;
public override void ApplyEffect(ref CharacterData data, int charIndex)
{
//from transparent to real color
for (int i = 0; i < TextUtilities.verticesPerChar; i++)
{
temp = data.colors[i];
temp.a = 0;
data.colors[i] = Color32.LerpUnclamped(data.colors[i], temp,
Tween.EaseInOut(1 - (data.passedTime / effectDuration)));
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6d26528e73e36f64dab95b69625232a7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,94 @@
using UnityEngine;
namespace Febucci.UI.Core
{
[UnityEngine.Scripting.Preserve]
[EffectInfo(tag: TAnimTags.ap_HoriExp)]
class HorizontalExpandAppearance : AppearanceBase
{
//expand type
public enum ExpType
{
Left, //from left to right
Middle, //expands from the middle to te extents
Right //from right to left
}
ExpType type = ExpType.Left;
//--Temp variables--
Vector2 startTop;
Vector2 startBot;
float pct;
public override void SetDefaultValues(AppearanceDefaultValues data)
{
effectDuration = data.defaults.horizontalExpandDuration;
type = data.defaults.horizontalExpandStart;
}
public override void ApplyEffect(ref CharacterData data, int charIndex)
{
pct = Tween.EaseInOut(data.passedTime / effectDuration);
switch (type)
{
default:
case ExpType.Left:
//top left and bot left
startTop = data.vertices[1];
startBot = data.vertices[0];
data.vertices[2] = Vector3.LerpUnclamped(startTop, data.vertices[2], pct);
data.vertices[3] = Vector3.LerpUnclamped(startBot, data.vertices[3], pct);
break;
case ExpType.Right:
//top right and bot right
startTop = data.vertices[2];
startBot = data.vertices[3];
data.vertices[1] = Vector3.LerpUnclamped(startTop, data.vertices[1], pct);
data.vertices[0] = Vector3.LerpUnclamped(startBot, data.vertices[0], pct);
break;
case ExpType.Middle:
//Middle positions
startTop = (data.vertices[1] + data.vertices[2]) / 2;
startBot = (data.vertices[0] + data.vertices[3]) / 2;
//top vertices
data.vertices[1] = Vector3.LerpUnclamped(startTop, data.vertices[1], pct);
data.vertices[2] = Vector3.LerpUnclamped(startTop, data.vertices[2], pct);
//bottom vertices
data.vertices[0] = Vector3.LerpUnclamped(startBot, data.vertices[0], pct);
data.vertices[3] = Vector3.LerpUnclamped(startBot, data.vertices[3], pct);
break;
}
}
public override void SetModifier(string modifierName, string modifierValue)
{
base.SetModifier(modifierName, modifierValue);
switch (modifierName)
{
case "x":
switch (modifierValue)
{
case "-1": type = ExpType.Left; break;
case "0": type = ExpType.Middle; break;
case "1": type = ExpType.Right; break;
default: Debug.LogError($"Text Animator: you set an '{modifierName}' modifier with value '{modifierValue}' for the HorizontalExpandAppearance effect, but it can only be '-1', '0', or '1'"); break;
}
break;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3dbde6098d9bea441b3ac872e52073ba
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,36 @@
using UnityEngine;
namespace Febucci.UI.Core
{
[UnityEngine.Scripting.Preserve]
[EffectInfo(tag: TAnimTags.ap_Offset)]
class OffsetAppearance : AppearanceBase
{
float amount;
Vector2 direction;
public override void SetDefaultValues(AppearanceDefaultValues data)
{
direction = data.defaults.offsetDir;
amount = data.defaults.offsetAmplitude * uniformIntensity;
effectDuration = data.defaults.offsetDuration;
}
public override void ApplyEffect(ref CharacterData data, int charIndex)
{
//Moves all towards a direction
data.vertices.MoveChar(direction * amount * Tween.EaseIn(1 - data.passedTime / effectDuration));
}
public override void SetModifier(string modifierName, string modifierValue)
{
base.SetModifier(modifierName, modifierValue);
switch (modifierName)
{
case "a": ApplyModifierTo(ref amount, modifierValue); break;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 146552007a93ecc42b0d63f26788e6cb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,49 @@
using UnityEngine;
namespace Febucci.UI.Core
{
[UnityEngine.Scripting.Preserve]
[EffectInfo(tag: TAnimTags.ap_RandomDir)]
class RandomDirectionAppearance : AppearanceBase
{
float amount;
Vector3[] directions;
public override void Initialize(int charactersCount)
{
base.Initialize(charactersCount);
directions = new Vector3[charactersCount];
//Calculates a random direction for each character (which won't change)
for(int i = 0; i < charactersCount; i++)
{
directions[i] = TextUtilities.fakeRandoms[Random.Range(0, TextUtilities.fakeRandomsCount - 1)] * Mathf.Sign(Mathf.Sin(i));
}
}
public override void SetDefaultValues(AppearanceDefaultValues data)
{
amount = data.defaults.randomDirAmplitude;
effectDuration = data.defaults.randomDirDuration;
}
public override void ApplyEffect(ref CharacterData data, int charIndex)
{
//Moves all towards a direction
data.vertices.MoveChar(directions[charIndex] * amount * uniformIntensity * Tween.EaseIn(1 - data.passedTime / effectDuration));
}
public override void SetModifier(string modifierName, string modifierValue)
{
base.SetModifier(modifierName, modifierValue);
switch (modifierName)
{
case "a": ApplyModifierTo(ref amount, modifierValue); break;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6a65a9b7775dded41a0295e6f75be25d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,40 @@
using UnityEngine;
namespace Febucci.UI.Core
{
[UnityEngine.Scripting.Preserve]
[EffectInfo(tag: TAnimTags.ap_Rot)]
class RotatingAppearance : AppearanceBase
{
float targetAngle;
public override void SetDefaultValues(AppearanceDefaultValues data)
{
effectDuration = data.defaults.rotationDuration;
targetAngle = data.defaults.rotationStartAngle;
}
public override void ApplyEffect(ref CharacterData data, int charIndex)
{
data.vertices.RotateChar(
Mathf.Lerp(
targetAngle,
0,
Tween.EaseInOut(data.passedTime / effectDuration)
)
);
}
public override void SetModifier(string modifierName, string modifierValue)
{
base.SetModifier(modifierName, modifierValue);
switch (modifierName)
{
case "a": ApplyModifierTo(ref targetAngle, modifierValue); break;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 465a2951870a6954d8205f9c58a546de
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,31 @@
namespace Febucci.UI.Core
{
[UnityEngine.Scripting.Preserve]
[EffectInfo(tag: TAnimTags.ap_Size)]
class SizeAppearance : AppearanceBase
{
float amplitude;
public override void SetDefaultValues(AppearanceDefaultValues data)
{
effectDuration = data.defaults.sizeDuration;
amplitude = data.defaults.sizeAmplitude * -1 + 1;
}
public override void ApplyEffect(ref CharacterData data, int charIndex)
{
data.vertices.LerpUnclamped(
data.vertices.GetMiddlePos(),
Tween.EaseIn(1 - (data.passedTime / effectDuration)) * amplitude
);
}
public override void SetModifier(string modifierName, string modifierValue)
{
base.SetModifier(modifierName, modifierValue);
switch (modifierName)
{
case "a": ApplyModifierTo(ref amplitude, modifierValue); break;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5ff5882c38f1b6c4682c7f207f0c98db
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,66 @@
using UnityEngine;
namespace Febucci.UI.Core
{
[UnityEngine.Scripting.Preserve]
[EffectInfo(tag: TAnimTags.ap_VertExp)]
class VerticalExpandAppearance : AppearanceBase
{
int startA, targetA;
int startB, targetB;
//--Temp variables--
float pct;
public override void SetDefaultValues(AppearanceDefaultValues data)
{
effectDuration = data.defaults.verticalExpandDuration;
SetOrientation(data.defaults.verticalFromBottom);
}
void SetOrientation(bool fromBottom)
{
if (fromBottom) //From bottom to top
{
//top left copies bottom left
startA = 0;
targetA = 1;
//top right copies bottom right
startB = 3;
targetB = 2;
}
else //from top to bottom
{
//bottom left copies top left
startA = 1;
targetA = 0;
//bottom right copies top right
startB = 2;
targetB = 3;
}
}
public override void ApplyEffect(ref CharacterData data, int charIndex)
{
pct = Tween.EaseInOut(data.passedTime / effectDuration);
data.vertices[targetA] = Vector3.LerpUnclamped(data.vertices[startA], data.vertices[targetA], pct);
data.vertices[targetB] = Vector3.LerpUnclamped(data.vertices[startB], data.vertices[targetB], pct);
}
public override void SetModifier(string modifierName, string modifierValue)
{
base.SetModifier(modifierName, modifierValue);
switch (modifierName)
{
case "bot": SetOrientation(modifierValue == "1"); break;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 351d9d9e5c9ea7946842aee1160f7731
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: efe369a01f5ffe54583002264aa47ea7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,206 @@
using UnityEngine;
namespace Febucci.UI.Core
{
[UnityEngine.Scripting.Preserve]
[EffectInfo(tag: "")]
class PresetAppearance : AppearanceBase
{
bool enabled;
//management
Matrix4x4 matrix;
Vector3 offset;
Quaternion rotationQua;
bool hasTransformEffects;
ThreeAxisEffector movement;
ThreeAxisEffector rotation;
TwoAxisEffector scale;
bool setColor;
Color32 color;
ColorCurve colorCurve;
public override void SetDefaultValues(AppearanceDefaultValues data)
{
effectDuration = 0;
enabled = false;
void AssignValues(PresetAppearanceValues result)
{
enabled = SetPreset(
true,
result,
ref movement,
ref effectDuration,
ref rotation,
ref scale,
ref rotationQua,
ref hasTransformEffects,
ref setColor,
ref colorCurve);
}
PresetAppearanceValues values;
//searches for local presets first, which override global presets
if (TAnimBuilder.GetPresetFromArray(effectTag, data.presets, out values))
{
AssignValues(values);
return;
}
//global presets
if (TAnimBuilder.TryGetGlobalPresetAppearance(effectTag, out values))
{
AssignValues(values);
return;
}
}
#region Effector classes
internal abstract class Effector
{
protected abstract Vector3 _EvaluateEffect(float passedTime, int charInde);
public Vector3 EvaluateEffect(float passedTime, int charIndex)
{
return _EvaluateEffect(passedTime, charIndex);
}
}
internal sealed class ThreeAxisEffector : Effector
{
EffectEvaluator x;
EffectEvaluator y;
EffectEvaluator z;
public ThreeAxisEffector(EffectEvaluator x, EffectEvaluator y, EffectEvaluator z)
{
this.x = x;
this.y = y;
this.z = z;
}
protected override Vector3 _EvaluateEffect(float passedTime, int charIndex)
{
return new Vector3(
x.Evaluate(passedTime, charIndex),
y.Evaluate(passedTime, charIndex),
z.Evaluate(passedTime, charIndex)
);
}
}
internal sealed class TwoAxisEffector : Effector
{
EffectEvaluator x;
EffectEvaluator y;
public TwoAxisEffector(EffectEvaluator x, EffectEvaluator y)
{
this.x = x;
this.y = y;
}
protected override Vector3 _EvaluateEffect(float passedTime, int charIndex)
{
return new Vector3(
x.Evaluate(passedTime, charIndex),
y.Evaluate(passedTime, charIndex),
1
);
}
}
#endregion
public static bool SetPreset<T>(
bool isAppearance,
T values,
ref ThreeAxisEffector movement,
ref float showDuration,
ref ThreeAxisEffector rotation,
ref TwoAxisEffector scale,
ref Quaternion rotationQua,
ref bool hasTransformEffects,
ref bool setColor,
ref ColorCurve colorCurve
) where T : PresetBaseValues
{
values.Initialize(isAppearance);
showDuration = values.GetMaxDuration();
movement = new ThreeAxisEffector(
values.movementX,
values.movementY,
values.movementZ);
scale = new TwoAxisEffector(
values.scaleX,
values.scaleY
);
rotation = new ThreeAxisEffector(
values.rotX,
values.rotY,
values.rotZ
);
rotationQua = Quaternion.identity;
hasTransformEffects = values.movementX.enabled || values.movementY.enabled || values.movementZ.enabled
|| values.rotX.enabled || values.rotY.enabled || values.rotZ.enabled
|| values.scaleX.enabled || values.scaleY.enabled;
setColor = values.color.enabled;
if (setColor)
{
colorCurve = values.color;
colorCurve.Initialize(isAppearance);
}
return hasTransformEffects || setColor;
}
public override void ApplyEffect(ref CharacterData data, int charIndex)
{
if (!enabled)
return;
if (hasTransformEffects)
{
offset = (data.vertices[0] + data.vertices[2]) / 2f;
rotationQua.eulerAngles = rotation.EvaluateEffect(data.passedTime, charIndex);
matrix.SetTRS(
movement.EvaluateEffect(data.passedTime, charIndex) * uniformIntensity,
rotationQua,
scale.EvaluateEffect(data.passedTime, charIndex));
for (byte i = 0; i < data.vertices.Length; i++)
{
data.vertices[i] -= offset;
data.vertices[i] = matrix.MultiplyPoint3x4(data.vertices[i]);
data.vertices[i] += offset;
}
}
if (setColor)
{
color = colorCurve.GetColor(data.passedTime, charIndex);
data.colors.LerpUnclamped(color, 1 - data.passedTime / effectDuration);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 36ad6bdd3b4d27947bf1042b1aed96c3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,13 @@
using UnityEngine;
namespace Febucci.UI.Core
{
[System.Serializable]
internal class PresetAppearanceValues : PresetBaseValues
{
public PresetAppearanceValues() : base()
{
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fff0e4bcc82c4e44cbb984e75da9a96b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 33a9b3141fa81fc40a97f74e04a25ab5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4b35db61dfff3d843ac76bc4f9b2669b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,27 @@
namespace Febucci.UI.Core
{
/// <summary>
/// Base class for all behavior effects.<br/>
/// Inherit from this class if you want to create your own Behavior Effect in C#.
/// </summary>
public abstract class BehaviorBase : EffectsBase
{
public abstract void SetDefaultValues(BehaviorDefaultValues data);
[System.Obsolete("This variable will be removed from next versions. Please use 'time.timeSinceStart' instead")]
public float animatorTime => time.timeSinceStart;
[System.Obsolete("This variable will be removed from next versions. Please use 'time.deltaTime' instead")]
public float animatorDeltaTime => time.deltaTime;
/// <summary>
/// Contains data/settings from the TextAnimator component that is linked to (and managing) this effect.
/// </summary>
public TextAnimator.TimeData time { get; private set; }
internal void SetAnimatorData(in TextAnimator.TimeData time)
{
this.time = time;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9ded2259c692766478d078fed5b5da3a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,39 @@
using UnityEngine;
namespace Febucci.UI.Core
{
/// <summary>
/// Behavior helper class that automatically manages the following modifiers: (a = <see cref="amplitude"/>), (f = <see cref="frequency"/>) and (w = <see cref="waveSize"/>).<br/><br/>
/// You can inerith from this class and use the modifiers as you prefer in your effects, without having to set up them inside the <see cref="SetModifier(string, string)"/> method.
/// </summary>
/// <example>
/// All the TextAnimator effects that have 3 modifiers inerith from this class. You can check their source code to see how they are set up, example: <see cref="WiggleBehavior"/> or <see cref="WaveBehavior"/>
/// </example>
public abstract class BehaviorSine : BehaviorBase
{
protected float amplitude = 1;
protected float frequency = 1;
protected float waveSize = .08f;
public override void SetModifier(string modifierName, string modifierValue)
{
switch (modifierName)
{
//amplitude
case "a": ApplyModifierTo(ref amplitude, modifierValue); break;
//frequency
case "f": ApplyModifierTo(ref frequency, modifierValue); break;
//wave size
case "w": ApplyModifierTo(ref waveSize, modifierValue); break;
}
}
public override string ToString()
{
return $"freq: {frequency}\n" +
$"ampl: {amplitude}\n" +
$"waveSize: {waveSize}" +
$"\n{base.ToString()}";
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f73a8843f32a2af40bd50de80f97ebb9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dd7d9417222ab984ca9326e54c7869e5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,41 @@
using UnityEngine;
namespace Febucci.UI.Core
{
[UnityEngine.Scripting.Preserve]
[EffectInfo(tag: TAnimTags.bh_Bounce)]
class BounceBehavior : BehaviorSine
{
public override void SetDefaultValues(BehaviorDefaultValues data)
{
amplitude = data.defaults.bounceAmplitude;
frequency = data.defaults.bounceFrequency;
waveSize = data.defaults.bounceWaveSize;
}
public override void ApplyEffect(ref CharacterData data, int charIndex)
{
//Calculates the tween percentage
float BounceTween(float t)
{
const float stillTime = .2f;
const float easeIn = .2f;
const float bounce = 1 - stillTime - easeIn;
if (t <= easeIn)
return Tween.EaseInOut(t / easeIn);
t -= easeIn;
if (t <= bounce)
return 1 - Tween.BounceOut(t / bounce);
return 0;
}
data.vertices.MoveChar(Vector3.up * uniformIntensity * BounceTween((Mathf.Repeat(time.timeSinceStart * frequency - waveSize * charIndex, 1))) * amplitude);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a333962955c4a08498d132d86d9ce19e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,43 @@
using UnityEngine;
namespace Febucci.UI.Core
{
[UnityEngine.Scripting.Preserve]
[EffectInfo(tag: TAnimTags.bh_Dangle)]
class DangleBehavior : BehaviorSine
{
float sin;
int targetIndex1;
int targetIndex2;
public override void SetDefaultValues(BehaviorDefaultValues data)
{
amplitude = data.defaults.dangleAmplitude;
frequency = data.defaults.dangleFrequency;
waveSize = data.defaults.dangleWaveSize;
//bottom
if (data.defaults.dangleAnchorBottom)
{
targetIndex1 = 1;
targetIndex2 = 2;
}
else
{
targetIndex1 = 0;
targetIndex2 = 3;
}
}
public override void ApplyEffect(ref CharacterData data, int charIndex)
{
sin = Mathf.Sin(frequency * time.timeSinceStart + charIndex * waveSize) * amplitude * uniformIntensity;
//moves one side (top or bottom) torwards one direction
data.vertices[targetIndex1] += Vector3.right * sin;
data.vertices[targetIndex2] += Vector3.right * sin;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 01a91f0b9c670b242ae5c34286409cd9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,75 @@
using UnityEngine;
namespace Febucci.UI.Core
{
[UnityEngine.Scripting.Preserve]
[EffectInfo(tag: TAnimTags.bh_Fade)]
class FadeBehavior : BehaviorBase
{
float delay = .3f;
float[] charPCTs;
public override void SetDefaultValues(BehaviorDefaultValues data)
{
delay = data.defaults.fadeDelay;
}
public override void Initialize(int charactersCount)
{
base.Initialize(charactersCount);
charPCTs = new float[charactersCount];
}
public override void SetModifier(string modifierName, string modifierValue)
{
switch (modifierName)
{
//delay
case "d": ApplyModifierTo(ref delay, modifierValue); break;
}
}
Color32 temp;
public override void ApplyEffect(ref CharacterData data, int charIndex)
{
if (data.passedTime <= delay) //not passed enough time yet
return;
charPCTs[charIndex] += time.deltaTime;
if (charPCTs[charIndex] > 1) charPCTs [charIndex] = 1;
//Lerps
if (charPCTs[charIndex] < 1 && charPCTs[charIndex] >= 0)
{
for (var i = 0; i < TextUtilities.verticesPerChar; i++)
{
temp = data.colors[i];
temp.a = 0;
data.colors[i] = Color32.LerpUnclamped(data.colors[i], temp, Tween.EaseInOut(charPCTs[charIndex]));
}
}
else //Keeps them hidden
{
for (var i = 0; i < TextUtilities.verticesPerChar; i++)
{
temp = data.colors[i];
temp.a = 0;
data.colors[i] = temp;
}
}
}
public override string ToString()
{
return $"delay: {delay}\n" +
$"\n{ base.ToString()}";
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d1f1dde3a46b6e748885a8d202c32044
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,41 @@
using UnityEngine;
namespace Febucci.UI.Core
{
[UnityEngine.Scripting.Preserve]
[EffectInfo(tag: TAnimTags.bh_Pendulum)]
class PendulumBehavior : BehaviorSine
{
int targetVertex1;
int targetVertex2;
public override void SetDefaultValues(BehaviorDefaultValues data)
{
frequency = data.defaults.pendFrequency;
amplitude = data.defaults.pendAmplitude;
waveSize = data.defaults.pendWaveSize;
if (data.defaults.pendInverted)
{
//anchored at the bottom
targetVertex1 = 0;
targetVertex2 = 3;
}
else
{
//anchored at the top
targetVertex1 = 1;
targetVertex2 = 2;
}
}
public override void ApplyEffect(ref CharacterData data, int charIndex)
{
data.vertices.RotateChar(
Mathf.Sin(-time.timeSinceStart * frequency + waveSize * charIndex) * amplitude,
(data.vertices[targetVertex1] + data.vertices[targetVertex2]) / 2 //bottom center as their rotation pivot
);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5005fc4984831834d826c8a9e3d42443
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,50 @@
using UnityEngine;
namespace Febucci.UI.Core
{
[UnityEngine.Scripting.Preserve]
[EffectInfo(tag: TAnimTags.bh_Rainb)]
class RainbowBehavior : BehaviorBase
{
float hueShiftSpeed = 0.8f;
float hueShiftWaveSize = 0.08f;
public override void SetDefaultValues(BehaviorDefaultValues data)
{
hueShiftSpeed = data.defaults.hueShiftSpeed;
hueShiftWaveSize = data.defaults.hueShiftWaveSize;
}
public override void SetModifier(string modifierName, string modifierValue)
{
switch (modifierName)
{
//frequency
case "f": ApplyModifierTo(ref hueShiftSpeed, modifierValue); break;
//wave size
case "s": ApplyModifierTo(ref hueShiftWaveSize, modifierValue); break;
}
}
Color32 temp;
public override void ApplyEffect(ref CharacterData data, int charIndex)
{
for (byte i = 0; i < TextUtilities.verticesPerChar; i++)
{
//shifts hue
temp = Color.HSVToRGB(Mathf.PingPong(time.timeSinceStart * hueShiftSpeed + charIndex * hueShiftWaveSize, 1), 1, 1);
temp.a = data.colors[i].a; //preserves original alpha
data.colors[i] = temp;
}
}
public override string ToString()
{
return $"hueShiftSpeed: {hueShiftSpeed}\n" +
$"hueShiftWaveSize: {hueShiftWaveSize}" +
$"\n{base.ToString()}";
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5400420051c0a9341be4aece65080148
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,43 @@
namespace Febucci.UI.Core
{
[UnityEngine.Scripting.Preserve]
[EffectInfo(tag: TAnimTags.bh_Rot)]
class RotationBehavior : BehaviorBase
{
float angleSpeed = 180;
float angleDiffBetweenChars = 10;
public override void SetDefaultValues(BehaviorDefaultValues data)
{
angleSpeed = data.defaults.angleSpeed;
angleDiffBetweenChars = data.defaults.angleDiffBetweenChars;
}
public override void SetModifier(string modifierName, string modifierValue)
{
switch (modifierName)
{
//frequency
case "f": ApplyModifierTo(ref angleSpeed, modifierValue); break;
//angle diff
case "s": ApplyModifierTo(ref angleDiffBetweenChars, modifierValue); break;
}
}
public override void ApplyEffect(ref CharacterData data, int charIndex)
{
data.vertices.RotateChar(-time.timeSinceStart * angleSpeed + angleDiffBetweenChars * charIndex);
}
public override string ToString()
{
return $"angleSpeed: {angleSpeed}\n" +
$"angleDiffBetweenChars: {angleDiffBetweenChars}" +
$"\n{base.ToString()}";
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c179eec3e2795104eb216e340b8d8513
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,92 @@
using UnityEngine;
namespace Febucci.UI.Core
{
[UnityEngine.Scripting.Preserve]
[EffectInfo(tag: TAnimTags.bh_Shake)]
class ShakeBehavior : BehaviorBase
{
public float shakeStrength;
public float shakeDelay;
float timePassed;
int randIndex;
public override void SetDefaultValues(BehaviorDefaultValues data)
{
shakeDelay = data.defaults.shakeDelay;
shakeStrength = data.defaults.shakeStrength;
ClampValues();
}
void ClampValues()
{
shakeDelay = Mathf.Clamp(shakeDelay, 0.002f, 500);
}
public override void Initialize(int charactersCount)
{
base.Initialize(charactersCount);
randIndex = Random.Range(0, TextUtilities.fakeRandomsCount);
lastRandomIndex = randIndex;
}
public override void SetModifier(string modifierName, string modifierValue)
{
switch (modifierName)
{
//amplitude
case "a": ApplyModifierTo(ref shakeStrength, modifierValue); break;
//delay
case "d": ApplyModifierTo(ref shakeDelay, modifierValue); break;
}
ClampValues();
}
int lastRandomIndex;
public override void Calculate()
{
timePassed += time.deltaTime;
//Changes the shake direction if enough time passed
if (timePassed >= shakeDelay)
{
timePassed = 0;
randIndex = Random.Range(0, TextUtilities.fakeRandomsCount);
//Avoids repeating the same index twice
if (lastRandomIndex == randIndex)
{
randIndex++;
if (randIndex >= TextUtilities.fakeRandomsCount)
randIndex = 0;
}
lastRandomIndex = randIndex;
}
}
public override void ApplyEffect(ref CharacterData data, int charIndex)
{
data.vertices.MoveChar
(
TextUtilities.fakeRandoms[
Mathf.RoundToInt((charIndex + randIndex) % (TextUtilities.fakeRandomsCount - 1))
] * shakeStrength * uniformIntensity
);
}
public override string ToString()
{
return $"shake delay: {shakeDelay}\n" +
$"strength: {shakeStrength}" +
$"\n{ base.ToString()}";
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 144066a43449f48469b89b2e2c9ab37d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
using UnityEngine;
namespace Febucci.UI.Core
{
[UnityEngine.Scripting.Preserve]
[EffectInfo(tag: TAnimTags.bh_Incr)]
sealed class SizeBehavior : BehaviorSine
{
public override void SetDefaultValues(BehaviorDefaultValues data)
{
amplitude = data.defaults.sizeAmplitude * -1 + 1;
frequency = data.defaults.sizeFrequency;
waveSize = data.defaults.sizeWaveSize;
}
public override void ApplyEffect(ref CharacterData data, int charIndex)
{
data.vertices.LerpUnclamped(
data.vertices.GetMiddlePos(),
(Mathf.Cos(time.timeSinceStart* frequency + charIndex * waveSize) + 1) / 2f * amplitude);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6da0c21b80efda84188912ce510ee1ca
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More