chore: initial commit
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Animancer.Editor
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// Keeps track of <see cref="AnimancerGraph"/> instances
|
||||
/// to ensure that they're properly cleaned up.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/AnimancerGraphCleanup
|
||||
public static class AnimancerGraphCleanup
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static List<AnimancerGraph> _AllGraphs;
|
||||
|
||||
/// <summary>[Editor-Only] Registers a `graph` to make sure it gets cleaned up properly.</summary>
|
||||
public static void AddGraph(AnimancerGraph graph)
|
||||
{
|
||||
if (_AllGraphs == null)
|
||||
{
|
||||
_AllGraphs = new();
|
||||
|
||||
AssemblyReloadEvents.beforeAssemblyReload +=
|
||||
() => DestroyAll(EditorApplication.isPlaying);
|
||||
|
||||
EditorApplication.playModeStateChanged += change =>
|
||||
{
|
||||
switch (change)
|
||||
{
|
||||
case PlayModeStateChange.EnteredEditMode:
|
||||
DestroyAll(true);
|
||||
break;
|
||||
|
||||
case PlayModeStateChange.ExitingEditMode:
|
||||
DestroyAll(false);
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
EditModeDestroyOldInstances();
|
||||
}
|
||||
|
||||
_AllGraphs.Add(graph);
|
||||
}
|
||||
|
||||
/// <summary>[Editor-Only] Removes the `graph` from the list of instances.</summary>
|
||||
public static void RemoveGraph(AnimancerGraph graph)
|
||||
{
|
||||
_AllGraphs?.Remove(graph);
|
||||
AnimancerGraph.ClearInactiveInitializationStackTrace(graph);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void DestroyAll(bool isPlaying)
|
||||
{
|
||||
for (int i = _AllGraphs.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var graph = _AllGraphs[i];
|
||||
if (graph.IsValidOrDispose())
|
||||
{
|
||||
if (isPlaying && graph.InactiveInitializationStackTrace != null)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"{graph} was not properly destroyed." +
|
||||
$" Its {nameof(GameObject)} was inactive and never activated," +
|
||||
$" meaning that Unity didn't call its AnimancerComponent.OnDestroy." +
|
||||
$"\n\nIf you need to use Animancer on an object that never gets activated," +
|
||||
$" you must call animancerComponent.Graph.Destroy() on it manually." +
|
||||
$"\n\nThis graph was created:\n{graph.InactiveInitializationStackTrace}\n",
|
||||
graph.Component as Object);
|
||||
|
||||
AnimancerGraph.ClearInactiveInitializationStackTrace(graph);
|
||||
}
|
||||
|
||||
graph.Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
_AllGraphs.Clear();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void EditModeDestroyOldInstances()
|
||||
{
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
return;
|
||||
|
||||
for (int i = _AllGraphs.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var graph = _AllGraphs[i];
|
||||
if (!ShouldStayAlive(graph))
|
||||
{
|
||||
if (graph.IsValidOrDispose())
|
||||
graph.Destroy();// This will remove it.
|
||||
else
|
||||
_AllGraphs.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Should this graph should stay alive instead of being destroyed?</summary>
|
||||
private static bool ShouldStayAlive(AnimancerGraph graph)
|
||||
{
|
||||
if (!graph.IsValidOrDispose())
|
||||
return false;
|
||||
|
||||
if (graph.Component == null)
|
||||
return true;
|
||||
|
||||
if (graph.Component is Object obj && obj == null)
|
||||
return false;
|
||||
|
||||
if (graph.Component.Animator == null)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Only]
|
||||
/// Returns true if the `initial` mode was <see cref="AnimatorUpdateMode.AnimatePhysics"/>
|
||||
/// and the `current` has changed to another mode or if the `initial` mode was something else
|
||||
/// and the `current` has changed to <see cref="AnimatorUpdateMode.AnimatePhysics"/>.
|
||||
/// </summary>
|
||||
public static bool HasChangedToOrFromAnimatePhysics(AnimatorUpdateMode? initial, AnimatorUpdateMode current)
|
||||
{
|
||||
if (initial == null)
|
||||
return false;
|
||||
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
var wasAnimatePhysics = initial.Value == AnimatorUpdateMode.Fixed;
|
||||
var isAnimatePhysics = current == AnimatorUpdateMode.Fixed;
|
||||
#else
|
||||
var wasAnimatePhysics = initial.Value == AnimatorUpdateMode.AnimatePhysics;
|
||||
var isAnimatePhysics = current == AnimatorUpdateMode.AnimatePhysics;
|
||||
#endif
|
||||
|
||||
return wasAnimatePhysics != isAnimatePhysics;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/AnimancerGraph
|
||||
public partial class AnimancerGraph
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Only] [Internal]
|
||||
/// A stack trace captured in <see cref="Initialize(IAnimancerComponent, bool)"/>
|
||||
/// if the <see cref="GameObject.activeInHierarchy"/> is false.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is used to warn if the graph isn't destroyed
|
||||
/// because Unity won't call <c>OnDestroy</c> if the object is never activated.
|
||||
/// </remarks>
|
||||
internal System.Diagnostics.StackTrace InactiveInitializationStackTrace { get; private set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Only] Captures the <see cref="InactiveInitializationStackTrace"/>.</summary>
|
||||
private void CaptureInactiveInitializationStackTrace(IAnimancerComponent animancer)
|
||||
{
|
||||
if (!animancer.gameObject.activeInHierarchy &&
|
||||
EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
InactiveInitializationStackTrace = new(1, true);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Only] [Internal] Discards the <see cref="InactiveInitializationStackTrace"/>.</summary>
|
||||
public static void ClearInactiveInitializationStackTrace(AnimancerGraph graph)
|
||||
{
|
||||
graph.InactiveInitializationStackTrace = null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c9c39323eed67d42bface66bcf9aabc
|
||||
timeCreated: 1515048758
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,410 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>Reflection utilities used throughout Animancer.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/AnimancerReflection
|
||||
public static class AnimancerReflection
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Commonly used <see cref="BindingFlags"/> combinations.</summary>
|
||||
public const BindingFlags
|
||||
AnyAccessBindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static,
|
||||
InstanceBindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
|
||||
StaticBindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the `type` using its parameterless constructor if it has one or a fully
|
||||
/// uninitialized object if it doesn't. Or returns <c>null</c> if the <see cref="Type.IsAbstract"/>.
|
||||
/// </summary>
|
||||
public static object CreateDefaultInstance(Type type)
|
||||
{
|
||||
if (type == null ||
|
||||
type.IsAbstract)
|
||||
return default;
|
||||
|
||||
var constructor = type.GetConstructor(InstanceBindings, null, Type.EmptyTypes, null);
|
||||
if (constructor != null)
|
||||
return constructor.Invoke(null);
|
||||
|
||||
return FormatterServices.GetUninitializedObject(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <typeparamref name="T"/> using its parameterless constructor if it has one or a fully
|
||||
/// uninitialized object if it doesn't. Or returns <c>null</c> if the <see cref="Type.IsAbstract"/>.
|
||||
/// </summary>
|
||||
public static T CreateDefaultInstance<T>()
|
||||
=> (T)CreateDefaultInstance(typeof(T));
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Animancer Extension]
|
||||
/// Returns the first <typeparamref name="TAttribute"/> attribute on the `member`
|
||||
/// or <c>null</c> if there is none.
|
||||
/// </summary>
|
||||
public static TAttribute GetAttribute<TAttribute>(
|
||||
this ICustomAttributeProvider member,
|
||||
bool inherit = false)
|
||||
where TAttribute : class
|
||||
{
|
||||
var type = typeof(TAttribute);
|
||||
return member.IsDefined(type, inherit)
|
||||
? (TAttribute)member.GetCustomAttributes(type, inherit)[0]
|
||||
: null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Invokes a method with the specified `methodName` if it exists on the `obj`.</summary>
|
||||
[Obfuscation(Exclude = true)]// Obfuscation seems to break IL2CPP Android builds here.
|
||||
public static object TryInvoke(
|
||||
object obj,
|
||||
string methodName,
|
||||
BindingFlags bindings = InstanceBindings | BindingFlags.FlattenHierarchy,
|
||||
Type[] parameterTypes = null,
|
||||
object[] parameters = null)
|
||||
{
|
||||
if (obj == null)
|
||||
return null;
|
||||
|
||||
parameterTypes ??= Type.EmptyTypes;
|
||||
|
||||
var method = obj.GetType().GetMethod(methodName, bindings, null, parameterTypes, null);
|
||||
return method?.Invoke(obj, parameters);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#region Delegates
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns a string describing the details of the `method`.</summary>
|
||||
public static string ToStringDetailed<T>(
|
||||
this T method,
|
||||
bool includeType = false)
|
||||
where T : Delegate
|
||||
{
|
||||
var text = StringBuilderPool.Instance.Acquire();
|
||||
text.AppendDelegate(method, includeType);
|
||||
return text.ReleaseToString();
|
||||
}
|
||||
|
||||
/// <summary>Appends the details of the `method` to the `text`.</summary>
|
||||
public static StringBuilder AppendDelegate<T>(
|
||||
this StringBuilder text,
|
||||
T method,
|
||||
bool includeType = false)
|
||||
where T : Delegate
|
||||
{
|
||||
var type = method != null
|
||||
? method.GetType()
|
||||
: typeof(T);
|
||||
|
||||
if (method == null)
|
||||
{
|
||||
return includeType
|
||||
? text.Append("Null(")
|
||||
.Append(type.GetNameCS())
|
||||
.Append(')')
|
||||
: text.Append("Null");
|
||||
}
|
||||
|
||||
if (includeType)
|
||||
text.Append(type.GetNameCS())
|
||||
.Append('(');
|
||||
|
||||
if (method.Target != null)
|
||||
text.Append("Method: ");
|
||||
|
||||
text.Append(method.Method.DeclaringType.GetNameCS())
|
||||
.Append('.')
|
||||
.Append(method.Method.Name);
|
||||
|
||||
if (method.Target != null)
|
||||
text.Append(", Target: '")
|
||||
.Append(method.Target)
|
||||
.Append("'");
|
||||
|
||||
if (includeType)
|
||||
text.Append(')');
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns the `method`'s <c>DeclaringType.Name</c>.</summary>
|
||||
public static string GetFullName(MethodInfo method)
|
||||
=> $"{method.DeclaringType.Name}.{method.Name}";
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static FieldInfo _DelegatesField;
|
||||
private static bool _GotDelegatesField;
|
||||
|
||||
/// <summary>
|
||||
/// Uses reflection to achieve the same as <see cref="Delegate.GetInvocationList"/> without allocating
|
||||
/// garbage every time.
|
||||
/// <list type="bullet">
|
||||
/// <item>If the delegate is <c>null</c> or , this method returns <c>false</c> and outputs <c>null</c>.</item>
|
||||
/// <item>If the underlying <c>delegate</c> field was not found, this method returns <c>false</c> and outputs <c>null</c>.</item>
|
||||
/// <item>If the delegate is not multicast, this method this method returns <c>true</c> and outputs <c>null</c>.</item>
|
||||
/// <item>If the delegate is multicast, this method this method returns <c>true</c> and outputs its invocation list.</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public static bool TryGetInvocationListNonAlloc(MulticastDelegate multicast, out Delegate[] delegates)
|
||||
{
|
||||
if (multicast == null)
|
||||
{
|
||||
delegates = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_GotDelegatesField)
|
||||
{
|
||||
const string FieldName = "delegates";
|
||||
|
||||
_GotDelegatesField = true;
|
||||
_DelegatesField = typeof(MulticastDelegate).GetField("delegates",
|
||||
BindingFlags.Public |
|
||||
BindingFlags.NonPublic |
|
||||
BindingFlags.Instance);
|
||||
|
||||
if (_DelegatesField != null && _DelegatesField.FieldType != typeof(Delegate[]))
|
||||
_DelegatesField = null;
|
||||
|
||||
if (_DelegatesField == null)
|
||||
Debug.LogError($"Unable to find {nameof(MulticastDelegate)}.{FieldName} field.");
|
||||
}
|
||||
|
||||
if (_DelegatesField == null)
|
||||
{
|
||||
delegates = null;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
delegates = (Delegate[])_DelegatesField.GetValue(multicast);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Tries to use <see cref="TryGetInvocationListNonAlloc"/>.
|
||||
/// Otherwise uses the regular <see cref="MulticastDelegate.GetInvocationList"/>.
|
||||
/// </summary>
|
||||
public static Delegate[] GetInvocationList(MulticastDelegate multicast)
|
||||
=> TryGetInvocationListNonAlloc(multicast, out var delegates) && delegates != null
|
||||
? delegates
|
||||
: multicast?.GetInvocationList();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Type Names
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly Dictionary<Type, string>
|
||||
TypeNames = new()
|
||||
{
|
||||
{ typeof(object), "object" },
|
||||
{ typeof(void), "void" },
|
||||
{ typeof(bool), "bool" },
|
||||
{ typeof(byte), "byte" },
|
||||
{ typeof(sbyte), "sbyte" },
|
||||
{ typeof(char), "char" },
|
||||
{ typeof(string), "string" },
|
||||
{ typeof(short), "short" },
|
||||
{ typeof(int), "int" },
|
||||
{ typeof(long), "long" },
|
||||
{ typeof(ushort), "ushort" },
|
||||
{ typeof(uint), "uint" },
|
||||
{ typeof(ulong), "ulong" },
|
||||
{ typeof(float), "float" },
|
||||
{ typeof(double), "double" },
|
||||
{ typeof(decimal), "decimal" },
|
||||
};
|
||||
|
||||
private static readonly Dictionary<Type, string>
|
||||
FullTypeNames = new(TypeNames);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns the name of the `type` as it would appear in C# code.</summary>
|
||||
/// <remarks>
|
||||
/// Returned values are stored in a dictionary to speed up repeated use.
|
||||
/// <para></para>
|
||||
/// <strong>Example:</strong>
|
||||
/// <para></para>
|
||||
/// <c>typeof(List<float>).FullName</c> would give you:
|
||||
/// <para></para>
|
||||
/// <c>System.Collections.Generic.List`1[[System.Single, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]</c>
|
||||
/// <para></para>
|
||||
/// This method would instead return <c>System.Collections.Generic.List<float></c> if `fullName` is <c>true</c>, or
|
||||
/// just <c>List<float></c> if it is <c>false</c>.
|
||||
/// </remarks>
|
||||
public static string GetNameCS(this Type type, bool fullName = true)
|
||||
{
|
||||
if (type == null)
|
||||
return "null";
|
||||
|
||||
// Check if we have already got the name for that type.
|
||||
var names = fullName
|
||||
? FullTypeNames
|
||||
: TypeNames;
|
||||
|
||||
if (names.TryGetValue(type, out var name))
|
||||
return name;
|
||||
|
||||
var text = StringBuilderPool.Instance.Acquire();
|
||||
|
||||
if (type.IsArray)// Array = TypeName[].
|
||||
{
|
||||
text.Append(type.GetElementType().GetNameCS(fullName));
|
||||
|
||||
text.Append('[');
|
||||
var dimensions = type.GetArrayRank();
|
||||
while (dimensions-- > 1)
|
||||
text.Append(',');
|
||||
text.Append(']');
|
||||
|
||||
goto Return;
|
||||
}
|
||||
|
||||
if (type.IsPointer)// Pointer = TypeName*.
|
||||
{
|
||||
text.Append(type.GetElementType().GetNameCS(fullName));
|
||||
text.Append('*');
|
||||
|
||||
goto Return;
|
||||
}
|
||||
|
||||
if (type.IsGenericParameter)// Generic Parameter = TypeName (for unspecified generic parameters).
|
||||
{
|
||||
text.Append(type.Name);
|
||||
goto Return;
|
||||
}
|
||||
|
||||
var underlyingType = Nullable.GetUnderlyingType(type);
|
||||
if (underlyingType != null)// Nullable = TypeName != null ?
|
||||
{
|
||||
text.Append(underlyingType.GetNameCS(fullName));
|
||||
text.Append('?');
|
||||
|
||||
goto Return;
|
||||
}
|
||||
|
||||
// Other Type = Namespace.NestedTypes.TypeName<GenericArguments>.
|
||||
|
||||
if (fullName && type.Namespace != null)// Namespace.
|
||||
{
|
||||
text.Append(type.Namespace);
|
||||
text.Append('.');
|
||||
}
|
||||
|
||||
var genericArguments = 0;
|
||||
|
||||
if (type.DeclaringType != null)// Account for Nested Types.
|
||||
{
|
||||
// Count the nesting level.
|
||||
var nesting = 1;
|
||||
var declaringType = type.DeclaringType;
|
||||
while (declaringType.DeclaringType != null)
|
||||
{
|
||||
declaringType = declaringType.DeclaringType;
|
||||
nesting++;
|
||||
}
|
||||
|
||||
// Append the name of each outer type, starting from the outside.
|
||||
while (nesting-- > 0)
|
||||
{
|
||||
// Walk out to the current nesting level.
|
||||
// This avoids the need to make a list of types in the nest or to insert type names instead of appending them.
|
||||
declaringType = type;
|
||||
for (int i = nesting; i >= 0; i--)
|
||||
declaringType = declaringType.DeclaringType;
|
||||
|
||||
// Nested Type Name.
|
||||
genericArguments = AppendNameAndGenericArguments(text, declaringType, fullName, genericArguments);
|
||||
text.Append('.');
|
||||
}
|
||||
}
|
||||
|
||||
// Type Name.
|
||||
AppendNameAndGenericArguments(text, type, fullName, genericArguments);
|
||||
|
||||
Return:// Remember and return the name.
|
||||
name = text.ReleaseToString();
|
||||
names.Add(type, name);
|
||||
return name;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Appends the generic arguments of `type` (after skipping the specified number).</summary>
|
||||
public static int AppendNameAndGenericArguments(StringBuilder text, Type type, bool fullName = true, int skipGenericArguments = 0)
|
||||
{
|
||||
var name = type.Name;
|
||||
text.Append(name);
|
||||
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
var backQuote = name.IndexOf('`');
|
||||
if (backQuote >= 0)
|
||||
{
|
||||
text.Length -= name.Length - backQuote;
|
||||
|
||||
var genericArguments = type.GetGenericArguments();
|
||||
if (skipGenericArguments < genericArguments.Length)
|
||||
{
|
||||
text.Append('<');
|
||||
|
||||
var firstArgument = genericArguments[skipGenericArguments];
|
||||
skipGenericArguments++;
|
||||
|
||||
if (firstArgument.IsGenericParameter)
|
||||
{
|
||||
while (skipGenericArguments < genericArguments.Length)
|
||||
{
|
||||
text.Append(',');
|
||||
skipGenericArguments++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
text.Append(firstArgument.GetNameCS(fullName));
|
||||
|
||||
while (skipGenericArguments < genericArguments.Length)
|
||||
{
|
||||
text.Append(", ");
|
||||
text.Append(genericArguments[skipGenericArguments].GetNameCS(fullName));
|
||||
skipGenericArguments++;
|
||||
}
|
||||
}
|
||||
|
||||
text.Append('>');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return skipGenericArguments;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 790e499f50e2ee740859b8d3855eeaa5
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,336 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.Editor
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// A system that procedurally gathers animations throughout the hierarchy without needing explicit references.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/AnimationGatherer
|
||||
///
|
||||
public class AnimationGatherer : IAnimationClipCollection
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#region Fields and Accessors
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>All the <see cref="AnimationClip"/>s that have been gathered.</summary>
|
||||
public readonly HashSet<AnimationClip> Clips = new();
|
||||
|
||||
/// <summary>All the <see cref="ITransition"/>s that have been gathered.</summary>
|
||||
public readonly HashSet<ITransition> Transitions = new();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void GatherAnimationClips(ICollection<AnimationClip> clips)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var clip in Clips)
|
||||
clips.Add(clip);
|
||||
|
||||
foreach (var transition in Transitions)
|
||||
clips.GatherFromSource(transition);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
HandleException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Cache
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly Dictionary<GameObject, AnimationGatherer>
|
||||
ObjectToGatherer = new();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
static AnimationGatherer()
|
||||
{
|
||||
UnityEditor.Selection.selectionChanged += ClearCache;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Clears all cached gatherers.</summary>
|
||||
public static void ClearCache()
|
||||
=> ObjectToGatherer.Clear();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Should exceptions thrown while gathering animations be logged?
|
||||
/// Default is false to ignore them.
|
||||
/// </summary>
|
||||
public static bool LogExceptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Logs the `exception` if <see cref="LogExceptions"/> is true.
|
||||
/// Otherwise does nothing.
|
||||
/// </summary>
|
||||
private static void HandleException(Exception exception)
|
||||
{
|
||||
if (LogExceptions)
|
||||
Debug.LogException(exception);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Returns a cached <see cref="AnimationGatherer"/> containing any <see cref="AnimationClip"/>s
|
||||
/// referenced by components in the same hierarchy as the `gameObject`.
|
||||
/// See <see cref="ICharacterRoot"/> for details.
|
||||
/// </summary>
|
||||
public static AnimationGatherer GatherFromGameObject(GameObject gameObject)
|
||||
{
|
||||
using var _ = AnimationGathererRecursionGuard.Begin();
|
||||
if (AnimationGathererRecursionGuard.HasCheckedObject(gameObject))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
if (!ObjectToGatherer.TryGetValue(gameObject, out var gatherer))
|
||||
{
|
||||
gatherer = new();
|
||||
ObjectToGatherer.Add(gameObject, gatherer);
|
||||
gatherer.GatherFromComponents(gameObject);
|
||||
}
|
||||
|
||||
return gatherer;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
HandleException(exception);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills the `clips` with any <see cref="AnimationClip"/>s
|
||||
/// referenced by components in the same hierarchy as the `gameObject`.
|
||||
/// See <see cref="ICharacterRoot"/> for details.
|
||||
/// </summary>
|
||||
public static void GatherFromGameObject(GameObject gameObject, ICollection<AnimationClip> clips)
|
||||
{
|
||||
var gatherer = GatherFromGameObject(gameObject);
|
||||
gatherer?.GatherAnimationClips(clips);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills the `clips` with any <see cref="AnimationClip"/>s
|
||||
/// referenced by components in the same hierarchy as the `gameObject`.
|
||||
/// See <see cref="ICharacterRoot"/> for details.
|
||||
/// </summary>
|
||||
public static void GatherFromGameObject(GameObject gameObject, ref AnimationClip[] clips, bool sort)
|
||||
{
|
||||
var gatherer = GatherFromGameObject(gameObject);
|
||||
if (gatherer == null)
|
||||
return;
|
||||
|
||||
using (SetPool<AnimationClip>.Instance.Acquire(out var clipSet))
|
||||
{
|
||||
gatherer.GatherAnimationClips(clipSet);
|
||||
AnimancerUtilities.SetLength(ref clips, clipSet.Count);
|
||||
clipSet.CopyTo(clips);
|
||||
}
|
||||
|
||||
if (sort)
|
||||
Array.Sort(clips, (a, b) => a.GetCachedName().CompareTo(b.GetCachedName()));
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void GatherFromComponents(GameObject gameObject)
|
||||
{
|
||||
var root = AnimancerUtilities.FindRoot(gameObject);
|
||||
|
||||
using (ListPool<MonoBehaviour>.Instance.Acquire(out var components))
|
||||
{
|
||||
root.GetComponentsInChildren(true, components);
|
||||
GatherFromComponents(components);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void GatherFromComponents(List<MonoBehaviour> components)
|
||||
{
|
||||
var i = components.Count;
|
||||
GatherClips:
|
||||
try
|
||||
{
|
||||
while (--i >= 0)
|
||||
{
|
||||
GatherFromObject(components[i], 0);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
HandleException(exception);
|
||||
goto GatherClips;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Gathers all animations from the `source`s fields.</summary>
|
||||
private void GatherFromObject(object source, int depth)
|
||||
{
|
||||
if (source.IsNullOrDestroyed())
|
||||
return;
|
||||
|
||||
if (AnimationGathererRecursionGuard.HasCheckedObject(source))
|
||||
return;
|
||||
|
||||
if (source is AnimationClip clip)
|
||||
{
|
||||
Clips.Add(clip);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MightContainAnimations(source.GetType()))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
if (Clips.GatherFromSource(source))
|
||||
return;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
HandleException(exception);
|
||||
}
|
||||
|
||||
GatherFromFields(source, depth);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Types mapped to a delegate that can quickly gather their clips.</summary>
|
||||
private static readonly Dictionary<Type, Action<object, AnimationGatherer>>
|
||||
TypeToGathererDelegate = new();
|
||||
|
||||
/// <summary>
|
||||
/// Uses reflection to gather <see cref="AnimationClip"/>s from fields on the `source` object.
|
||||
/// </summary>
|
||||
private void GatherFromFields(object source, int depth)
|
||||
{
|
||||
if (depth >= AnimationGathererRecursionGuard.MaxFieldDepth ||
|
||||
source.IsNullOrDestroyed())
|
||||
return;
|
||||
|
||||
var type = source.GetType();
|
||||
if (!TypeToGathererDelegate.TryGetValue(type, out var gatherClips))
|
||||
{
|
||||
gatherClips = BuildClipGathererDelegate(type, depth);
|
||||
TypeToGathererDelegate.Add(type, gatherClips);
|
||||
if (gatherClips == null)
|
||||
AnimationGathererRecursionGuard.DontGatherFrom.Add(type);
|
||||
}
|
||||
|
||||
gatherClips?.Invoke(source, this);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Creates a delegate to gather <see cref="AnimationClip"/>s
|
||||
/// from all relevant fields in a given `type`.
|
||||
/// </summary>
|
||||
private static Action<object, AnimationGatherer> BuildClipGathererDelegate(Type type, int depth)
|
||||
{
|
||||
if (!MightContainAnimations(type))
|
||||
return null;
|
||||
|
||||
Action<object, AnimationGatherer> gathererDelegate = null;
|
||||
|
||||
while (type != null)
|
||||
{
|
||||
var fields = type.GetFields(AnimancerReflection.InstanceBindings | BindingFlags.DeclaredOnly);
|
||||
for (int i = 0; i < fields.Length; i++)
|
||||
{
|
||||
var field = fields[i];
|
||||
var fieldType = field.FieldType;
|
||||
if (!MightContainAnimations(fieldType))
|
||||
continue;
|
||||
|
||||
if (fieldType == typeof(AnimationClip))
|
||||
{
|
||||
gathererDelegate += (obj, gatherer) =>
|
||||
{
|
||||
var clip = (AnimationClip)field.GetValue(obj);
|
||||
if (clip != null)
|
||||
gatherer.Clips.Add(clip);
|
||||
};
|
||||
}
|
||||
else if (typeof(IAnimationClipSource).IsAssignableFrom(fieldType) ||
|
||||
typeof(IAnimationClipCollection).IsAssignableFrom(fieldType))
|
||||
{
|
||||
gathererDelegate += (obj, gatherer) =>
|
||||
{
|
||||
var source = field.GetValue(obj);
|
||||
gatherer.Clips.GatherFromSource(source);
|
||||
};
|
||||
}
|
||||
else if (typeof(ICollection).IsAssignableFrom(fieldType))
|
||||
{
|
||||
gathererDelegate += (obj, gatherer) =>
|
||||
{
|
||||
var collection = (ICollection)field.GetValue(obj);
|
||||
if (collection != null)
|
||||
{
|
||||
foreach (var item in collection)
|
||||
{
|
||||
gatherer.GatherFromObject(item, depth + 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
gathererDelegate += (obj, gatherer) =>
|
||||
{
|
||||
var source = field.GetValue(obj);
|
||||
if (source.IsNullOrDestroyed())
|
||||
return;
|
||||
|
||||
gatherer.GatherFromObject(source, depth + 1);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
type = type.BaseType;
|
||||
}
|
||||
|
||||
return gathererDelegate;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static bool MightContainAnimations(Type type)
|
||||
=> !type.IsPrimitive
|
||||
&& !type.IsEnum
|
||||
&& !type.IsAutoClass
|
||||
&& !type.IsPointer
|
||||
&& !AnimationGathererRecursionGuard.DontGatherFrom.Contains(type);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f6a44097253ab046b078fea0cc30e0b
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 557caa37d59075043b6354f6b9e82e1c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,53 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>[Assert-Conditional]
|
||||
/// A <see cref="HelpURLAttribute"/> which points to Animancer's documentation.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
|
||||
[System.Diagnostics.Conditional(Strings.Assertions)]
|
||||
public class AnimancerHelpUrlAttribute : HelpURLAttribute
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="AnimancerHelpUrlAttribute"/>.</summary>
|
||||
public AnimancerHelpUrlAttribute(string url)
|
||||
: base(url)
|
||||
{ }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="AnimancerHelpUrlAttribute"/>.</summary>
|
||||
public AnimancerHelpUrlAttribute(Type type)
|
||||
: base(GetApiDocumentationUrl(type))
|
||||
{ }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns a URL for the given `type`'s API Documentation page.</summary>
|
||||
public static string GetApiDocumentationUrl(Type type)
|
||||
=> GetApiDocumentationUrl(Strings.DocsURLs.Documentation + "/api/", type);
|
||||
|
||||
/// <summary>Returns a URL for the given `type`'s API Documentation page.</summary>
|
||||
public static string GetApiDocumentationUrl(string prefix, Type type)
|
||||
{
|
||||
var url = StringBuilderPool.Instance.Acquire();
|
||||
|
||||
url.Append(prefix);
|
||||
|
||||
if (!string.IsNullOrEmpty(type.Namespace))
|
||||
url.Append(type.Namespace).Append('/');
|
||||
|
||||
url.Append(type.Name.Replace('`', '_'));
|
||||
|
||||
return url.ReleaseToString();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 378a8fd785e3db746ab5397f4d959e0d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>[Editor-Conditional]
|
||||
/// A <see cref="DefaultValueAttribute"/> which uses the <see cref="AnimancerGraph.DefaultFadeDuration"/> and 0.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/DefaultFadeValueAttribute
|
||||
///
|
||||
public class DefaultFadeValueAttribute : DefaultValueAttribute
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override object Primary => AnimancerGraph.DefaultFadeDuration;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="DefaultValueAttribute"/>.</summary>
|
||||
public DefaultFadeValueAttribute()
|
||||
{
|
||||
// This won't change so there's no need to box the value every time by overriding the property.
|
||||
Secondary = 0f;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8142045e327ce4340b15c6580c89b79f
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,40 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>[Editor-Conditional] Specifies the default value of a field and a secondary fallback.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/DefaultValueAttribute
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
[System.Diagnostics.Conditional(Strings.UnityEditor)]
|
||||
public class DefaultValueAttribute : Attribute
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The main default value.</summary>
|
||||
public virtual object Primary { get; protected set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The fallback value to use if the target value was already equal to the <see cref="Primary"/>.</summary>
|
||||
public virtual object Secondary { get; protected set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="DefaultValueAttribute"/>.</summary>
|
||||
public DefaultValueAttribute(object primary, object secondary = null)
|
||||
{
|
||||
Primary = primary;
|
||||
Secondary = secondary;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="DefaultValueAttribute"/>.</summary>
|
||||
protected DefaultValueAttribute() { }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 486843cd37aea9b4697ec896db0b0592
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>[Editor-Conditional]
|
||||
/// Causes an Inspector field in an <see cref="ITransition"/>
|
||||
/// to be drawn after its events where the events would normally be drawn last.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/DrawAfterEventsAttribute
|
||||
///
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
[System.Diagnostics.Conditional(Strings.UnityEditor)]
|
||||
public sealed class DrawAfterEventsAttribute : Attribute { }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e36db3eac3c4bb4c9c97a4833cb0cce
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,366 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using System.Collections;
|
||||
#endif
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>[Editor-Conditional]
|
||||
/// Specifies a set of acceptable names for <see cref="AnimancerEvent"/>s
|
||||
/// so they can display a warning in the Inspector if an unexpected name is used.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// Placing this attribute on a type applies it to all fields in that type.
|
||||
/// <para></para>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer/usage#event-names">
|
||||
/// Event Names</see>
|
||||
/// <para></para>
|
||||
/// <strong>Example:</strong><code>
|
||||
/// [EventNames(...)]// Apply to all fields in this class.
|
||||
/// public class AttackState
|
||||
/// {
|
||||
/// [SerializeField]
|
||||
/// [EventNames(...)]// Apply to only this field.
|
||||
/// private ClipTransition _Action;
|
||||
/// }
|
||||
/// </code>
|
||||
/// See the constructors for examples of their usage.
|
||||
/// </remarks>
|
||||
///
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/EventNamesAttribute
|
||||
///
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class | AttributeTargets.Struct, Inherited = true)]
|
||||
[System.Diagnostics.Conditional(Strings.UnityEditor)]
|
||||
public sealed class EventNamesAttribute : Attribute
|
||||
#if UNITY_EDITOR
|
||||
, IInitializable<MemberInfo>
|
||||
#endif
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>[Editor-Only] The names that can be used for events in the attributed field.</summary>
|
||||
public StringReference[] Names { get; private set; }
|
||||
|
||||
/// <summary>[Editor-Only] Has the <see cref="Names"/> array been initialized?</summary>
|
||||
public bool HasNames
|
||||
=> !Names.IsNullOrEmpty();
|
||||
#endif
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="EventNamesAttribute"/>
|
||||
/// with <see cref="Names"/> from the attributed type or declaring type of the attributed member.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <strong>Example:</strong><code>
|
||||
/// [EventNames]// Use all StringReference fields in this class for any transitions in this class.
|
||||
/// public class AttackState
|
||||
/// {
|
||||
/// public static readonly StringReference HitStart = "Hit Start";
|
||||
/// public static readonly StringReference HitEnd = "Hit End";
|
||||
///
|
||||
/// [SerializeField]
|
||||
/// [EventNames]// Use all StringReference fields in this class.
|
||||
/// private ClipTransition _Animation;
|
||||
///
|
||||
/// protected virtual void Awake()
|
||||
/// {
|
||||
/// _Animation.Events.SetCallback(HitStart, OnHitStart);
|
||||
/// _Animation.Events.SetCallback(HitEnd, OnHitEnd);
|
||||
/// }
|
||||
///
|
||||
/// private void OnHitStart() { }
|
||||
/// private void OnHitEnd() { }
|
||||
/// }
|
||||
/// </code></remarks>
|
||||
public EventNamesAttribute()
|
||||
{
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="EventNamesAttribute"/> containing the specified `names`.</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Example:</strong><code>
|
||||
/// public class AttackState
|
||||
/// {
|
||||
/// [SerializeField]
|
||||
/// [EventNames("Hit Start", "Hit End")]
|
||||
/// private ClipTransition _Animation;
|
||||
///
|
||||
/// protected virtual void Awake()
|
||||
/// {
|
||||
/// _Animation.Events.SetCallback("Hit Start", OnHitStart);
|
||||
/// _Animation.Events.SetCallback("Hit End", OnHitEnd);
|
||||
/// }
|
||||
///
|
||||
/// private void OnHitStart() { }
|
||||
/// private void OnHitEnd() { }
|
||||
/// }
|
||||
/// </code></remarks>
|
||||
public EventNamesAttribute(params string[] names)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
Names = StringReference.Get(names);
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="EventNamesAttribute"/> with <see cref="Names"/> from the `type`.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// If the `type` is an enum, all of its values will be used.
|
||||
/// <para></para>
|
||||
/// Otherwise the values of all static <see cref="string"/> and
|
||||
/// <see cref="StringReference"/> fields will be used.
|
||||
/// <para></para>
|
||||
/// <strong>Example:</strong><code>
|
||||
/// public class AttackState
|
||||
/// {
|
||||
/// public static readonly StringReference HitStart = "Hit Start";
|
||||
/// public static readonly StringReference HitEnd = "Hit End";
|
||||
///
|
||||
/// [SerializeField]
|
||||
/// [EventNames(typeof(AttackState))]// Use all StringReference fields in this class.
|
||||
/// private ClipTransition _Animation;
|
||||
///
|
||||
/// protected virtual void Awake()
|
||||
/// {
|
||||
/// _Animation.Events.SetCallback(HitStart, OnHitStart);
|
||||
/// _Animation.Events.SetCallback(HitEnd, OnHitEnd);
|
||||
/// }
|
||||
///
|
||||
/// private void OnHitStart() { }
|
||||
/// private void OnHitEnd() { }
|
||||
/// }
|
||||
/// </code></remarks>
|
||||
///
|
||||
/// <exception cref="ArgumentNullException"/>
|
||||
public EventNamesAttribute(Type type)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
Initialize(type);
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="EventNamesAttribute"/> with <see cref="Names"/> from a member in the `type`
|
||||
/// with the specified `name`.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// The specified member must be static and can be a Field, Property, or Method.
|
||||
/// <para></para>
|
||||
/// The member type can be anything implementing <see cref="IEnumerable"/> (including arrays, lists, and
|
||||
/// coroutines).
|
||||
/// <para></para>
|
||||
/// <strong>Example:</strong><code>
|
||||
/// public class AttackState
|
||||
/// {
|
||||
/// public static readonly StringReference[] Events = { "Hit Start", "Hit End" };
|
||||
///
|
||||
/// [SerializeField]
|
||||
/// [EventNames(typeof(AttackState), nameof(Events))]// Get the names from AttackState.Events.
|
||||
/// private ClipTransition _Animation;
|
||||
///
|
||||
/// protected virtual void Awake()
|
||||
/// {
|
||||
/// _Animation.Events.SetCallback(Events[0], OnHitStart);
|
||||
/// _Animation.Events.SetCallback(Events[1], OnHitEnd);
|
||||
/// }
|
||||
///
|
||||
/// private void OnHitStart() { }
|
||||
/// private void OnHitEnd() { }
|
||||
/// }
|
||||
/// </code></remarks>
|
||||
///
|
||||
/// <exception cref="ArgumentNullException"/>
|
||||
/// <exception cref="ArgumentException">No member with the specified `name` exists in the `type`.</exception>
|
||||
///
|
||||
public EventNamesAttribute(Type type, string name)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
var obj = GetValue(type, name)
|
||||
?? throw new ArgumentException(
|
||||
$"The collection retrieved from {type.GetNameCS()}.{name} is null");
|
||||
|
||||
if (obj is not IEnumerable collection)
|
||||
throw new ArgumentException(
|
||||
$"The object retrieved from {type.GetNameCS()}.{name} is not an {nameof(IEnumerable)}");
|
||||
|
||||
using (ListPool<StringReference>.Instance.Acquire(out var names))
|
||||
{
|
||||
foreach (var item in collection)
|
||||
{
|
||||
if (item == null)
|
||||
continue;
|
||||
|
||||
var itemName = item.ToString();
|
||||
if (string.IsNullOrEmpty(itemName))
|
||||
continue;
|
||||
|
||||
names.Add(itemName);
|
||||
}
|
||||
|
||||
if (names.Count == 0)
|
||||
throw new ArgumentException($"The collection retrieved from {type.GetNameCS()}.{name} is empty");
|
||||
|
||||
Names = names.ToArray();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#if UNITY_EDITOR
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Initializes the <see cref="Names"/> if they weren't already set in the constructor.</summary>
|
||||
public void Initialize(MemberInfo member)
|
||||
{
|
||||
if (HasNames)
|
||||
return;
|
||||
|
||||
if (member == null)
|
||||
throw new ArgumentNullException(nameof(member));
|
||||
|
||||
if (member is Type type)
|
||||
{
|
||||
Initialize(type);
|
||||
}
|
||||
else
|
||||
{
|
||||
Names = GatherNames(member.DeclaringType);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Initializes the <see cref="Names"/> if they weren't already set in the constructor.</summary>
|
||||
public void Initialize(Type type)
|
||||
{
|
||||
if (HasNames)
|
||||
return;
|
||||
|
||||
if (type == null)
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
|
||||
if (type.IsEnum)
|
||||
{
|
||||
Names = StringReference.Get(Enum.GetNames(type));
|
||||
}
|
||||
else
|
||||
{
|
||||
Names = GatherNames(type);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static object GetValue(Type type, string name)
|
||||
{
|
||||
if (type == null)
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
|
||||
if (name == null)
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
|
||||
var field = type.GetField(name, AnimancerReflection.StaticBindings);
|
||||
if (field != null)
|
||||
return field.GetValue(null);
|
||||
|
||||
var property = type.GetProperty(name, AnimancerReflection.StaticBindings);
|
||||
if (property != null)
|
||||
return property.GetValue(null, null);
|
||||
|
||||
var method = type.GetMethod(name, AnimancerReflection.StaticBindings, null, Type.EmptyTypes, null);
|
||||
if (method != null)
|
||||
return method.Invoke(null, null);
|
||||
|
||||
throw new ArgumentException($"{type.GetNameCS()} does not contain a member named '{name}'");
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static StringReference[] GatherNames(Type type)
|
||||
{
|
||||
using (ListPool<StringReference>.Instance.Acquire(out var names))
|
||||
{
|
||||
while (type != null)
|
||||
{
|
||||
var fields = type.GetFields(AnimancerReflection.StaticBindings | BindingFlags.DeclaredOnly);
|
||||
for (int i = 0; i < fields.Length; i++)
|
||||
{
|
||||
var field = fields[i];
|
||||
|
||||
if (field.DeclaringType.Assembly.FullName.StartsWith("Unity"))
|
||||
continue;
|
||||
|
||||
StringReference name;
|
||||
|
||||
if (field.FieldType == typeof(string))
|
||||
{
|
||||
name = (string)field.GetValue(null);
|
||||
}
|
||||
else if (field.FieldType == typeof(StringReference))
|
||||
{
|
||||
name = (StringReference)field.GetValue(null);
|
||||
}
|
||||
else continue;
|
||||
|
||||
if (!name.IsNullOrEmpty() && !names.Contains(name))
|
||||
names.Add(name);
|
||||
}
|
||||
|
||||
type = type.BaseType;
|
||||
}
|
||||
|
||||
if (names.Count == 0)
|
||||
return null;
|
||||
|
||||
names.Sort();
|
||||
return names.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private string _Prefix;
|
||||
private string _NamesToString;
|
||||
|
||||
/// <summary>Returns a string containing all the <see cref="Names"/>.</summary>
|
||||
public string NamesToString(string prefix, string delimiter = "\n• ")
|
||||
{
|
||||
if (!HasNames)
|
||||
return prefix;
|
||||
|
||||
if (_NamesToString != null && _Prefix == prefix)
|
||||
return _NamesToString;
|
||||
|
||||
var text = StringBuilderPool.Instance.Acquire();
|
||||
|
||||
_Prefix = prefix;
|
||||
text.Append(prefix);
|
||||
|
||||
for (int i = 0; i < Names.Length; i++)
|
||||
text.Append(delimiter).Append(Names[i]);
|
||||
|
||||
return _NamesToString = text.ReleaseToString();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endif
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6cecce105052724d99e5197ef26fdad
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,32 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
|
||||
namespace Animancer.Editor
|
||||
{
|
||||
/// <summary>[Editor-Conditional]
|
||||
/// A <see cref="MovedFromAttribute"/> which indicates that a type may have been previously
|
||||
/// defined in the pre-compiled Animancer Lite DLL in an earlier version of Animancer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This allows <see cref="UnityEngine.SerializeReference"/> fields of the attributed type
|
||||
/// to retain their values when upgrading from Animancer Lite to Animancer Pro.
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/InternalSerializableTypeAttribute
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = true)]
|
||||
[System.Diagnostics.Conditional(Strings.UnityEditor)]
|
||||
public sealed class InternalSerializableTypeAttribute : MovedFromAttribute
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="InternalSerializableTypeAttribute"/>.</summary>
|
||||
public InternalSerializableTypeAttribute()
|
||||
: base(true, sourceAssembly: Strings.LiteAssemblyName)
|
||||
{
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2582013e6d855fc4493ecf34833c8fa9
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,36 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>[Editor-Conditional]
|
||||
/// Specifies a custom display label for the <c>Thresholds</c> column of a mixer transition.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/ThresholdLabelAttribute
|
||||
///
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class | AttributeTargets.Struct, Inherited = true)]
|
||||
[System.Diagnostics.Conditional(Strings.UnityEditor)]
|
||||
public sealed class ThresholdLabelAttribute : Attribute
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>[Editor-Only] The label.</summary>
|
||||
public readonly string Label;
|
||||
#endif
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="ThresholdLabelAttribute"/>.</summary>
|
||||
public ThresholdLabelAttribute(string label)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
Label = label;
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2ca52e7a7aa74b8419c97f26eccc641b
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b78168dc02519bc418b0484be8f4a4ff
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
namespace Animancer.Units
|
||||
{
|
||||
/// <summary>[Editor-Conditional] Applies a different GUI for an animation speed field.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Units/AnimationSpeedAttribute
|
||||
[System.Diagnostics.Conditional(Strings.UnityEditor)]
|
||||
public sealed class AnimationSpeedAttribute : UnitsAttribute
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="AnimationTimeAttribute"/>.</summary>
|
||||
public AnimationSpeedAttribute()
|
||||
: base("x")
|
||||
{
|
||||
Rule = Validate.Value.IsFiniteOrNaN;
|
||||
IsOptional = true;
|
||||
DefaultValue = 1;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6cf93c876d129534c9262b20498de934
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
namespace Animancer.Units
|
||||
{
|
||||
/// <summary>[Editor-Conditional]
|
||||
/// Causes a float field to display using 3 fields: Normalized, Seconds, and Frames.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions#time-fields">
|
||||
/// Time Fields</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Units/AnimationTimeAttribute
|
||||
///
|
||||
[System.Diagnostics.Conditional(Strings.UnityEditor)]
|
||||
public sealed class AnimationTimeAttribute : UnitsAttribute
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>A unit of measurement used by the <see cref="AnimationTimeAttribute"/>.</summary>
|
||||
public enum Units
|
||||
{
|
||||
/// <summary>A value of 1 represents the end of the animation.</summary>
|
||||
Normalized = 0,
|
||||
|
||||
/// <summary>A value of 1 represents 1 second.</summary>
|
||||
Seconds = 1,
|
||||
|
||||
/// <summary>A value of 1 represents 1 frame.</summary>
|
||||
Frames = 2,
|
||||
}
|
||||
|
||||
/// <summary>An explanation of the suffixes used in fields drawn by this attribute.</summary>
|
||||
public const string Tooltip = "x = Normalized, s = Seconds, f = Frames";
|
||||
|
||||
/// <summary>The <see cref="UnitsAttribute.Multipliers"/> used by instances of this attribute.</summary>
|
||||
private static new readonly float[] Multipliers = new float[3];// Calculated immediately before each use.
|
||||
|
||||
/// <summary>The <see cref="UnitsAttribute.Suffixes"/> used by instances of this attribute.</summary>
|
||||
private static new readonly string[] Suffixes = new string[3] { "x", "s", "f" };
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Cretes a new <see cref="AnimationTimeAttribute"/>.</summary>
|
||||
public AnimationTimeAttribute(Units units)
|
||||
: base(Multipliers, Suffixes, (int)units)
|
||||
{ }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2bacb1073e1dc0241a57779a297b256d
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,143 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
namespace Animancer.Units
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Conditional] Angle measured in <c>degrees</c> (<c>º</c>).</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/other/units">
|
||||
/// Units Attribute</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Units/DegreesAttribute
|
||||
///
|
||||
[System.Diagnostics.Conditional(Strings.UnityEditor)]
|
||||
public sealed class DegreesAttribute : UnitsAttribute
|
||||
{
|
||||
/// <summary>Creates a new <see cref="DegreesAttribute"/>.</summary>
|
||||
public DegreesAttribute() : base(" º") { }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Conditional] Rotational speed measured in <c>degrees per second</c> (<c>º/s</c>).</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/other/units">
|
||||
/// Units Attribute</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Units/DegreesPerSecondAttribute
|
||||
///
|
||||
[System.Diagnostics.Conditional(Strings.UnityEditor)]
|
||||
public sealed class DegreesPerSecondAttribute : UnitsAttribute
|
||||
{
|
||||
/// <summary>Creates a new <see cref="DegreesPerSecondAttribute"/>.</summary>
|
||||
public DegreesPerSecondAttribute() : base(" º/s") { }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Conditional] Distance measured in <c>meters</c> (<c>m</c>).</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/other/units">
|
||||
/// Units Attribute</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Units/MetersAttribute
|
||||
///
|
||||
[System.Diagnostics.Conditional(Strings.UnityEditor)]
|
||||
public sealed class MetersAttribute : UnitsAttribute
|
||||
{
|
||||
/// <summary>Creates a new <see cref="MetersAttribute"/>.</summary>
|
||||
public MetersAttribute() : base(" m") { }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Conditional] Speed measured in <c>meters per second</c> (<c>m/s</c>).</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/other/units">
|
||||
/// Units Attribute</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Units/MetersPerSecondAttribute
|
||||
///
|
||||
[System.Diagnostics.Conditional(Strings.UnityEditor)]
|
||||
public sealed class MetersPerSecondAttribute : UnitsAttribute
|
||||
{
|
||||
/// <summary>Creates a new <see cref="MetersPerSecondAttribute"/>.</summary>
|
||||
public MetersPerSecondAttribute() : base(" m/s") { }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Conditional] Acceleration measured in <c>meters per second per second</c> (<c>m/s²</c>).</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/other/units">
|
||||
/// Units Attribute</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Units/MetersPerSecondPerSecondAttribute
|
||||
///
|
||||
[System.Diagnostics.Conditional(Strings.UnityEditor)]
|
||||
public sealed class MetersPerSecondPerSecondAttribute : UnitsAttribute
|
||||
{
|
||||
/// <summary>Creates a new <see cref="MetersPerSecondPerSecondAttribute"/>.</summary>
|
||||
public MetersPerSecondPerSecondAttribute() : base(" m/s\xB2") { }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Conditional] A multiplier displayed with an <c>x</c> suffix.</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/other/units">
|
||||
/// Units Attribute</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Units/MultiplierAttribute
|
||||
///
|
||||
[System.Diagnostics.Conditional(Strings.UnityEditor)]
|
||||
public sealed class MultiplierAttribute : UnitsAttribute
|
||||
{
|
||||
/// <summary>Creates a new <see cref="MultiplierAttribute"/>.</summary>
|
||||
public MultiplierAttribute() : base(" x") { }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Conditional] Time measured in <c>seconds</c> (<c>s</c>).</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/other/units">
|
||||
/// Units Attribute</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Units/SecondsAttribute
|
||||
///
|
||||
[System.Diagnostics.Conditional(Strings.UnityEditor)]
|
||||
public sealed class SecondsAttribute : UnitsAttribute
|
||||
{
|
||||
/// <summary>Creates a new <see cref="SecondsAttribute"/>.</summary>
|
||||
public SecondsAttribute() : base(" s") { }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Conditional] A value measured <c>per second</c> (<c>/s</c>).</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/other/units">
|
||||
/// Units Attribute</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Units/PerSecondAttribute
|
||||
///
|
||||
[System.Diagnostics.Conditional(Strings.UnityEditor)]
|
||||
public sealed class PerSecondAttribute : UnitsAttribute
|
||||
{
|
||||
/// <summary>Creates a new <see cref="PerSecondAttribute"/>.</summary>
|
||||
public PerSecondAttribute() : base(" /s") { }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd7f08fff8e76af4da23e74b033b2e60
|
||||
labels:
|
||||
- Example
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,76 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.Units
|
||||
{
|
||||
/// <summary>[Editor-Conditional]
|
||||
/// Causes a float field to display a suffix to indicate what kind of units the value represents as well as
|
||||
/// displaying it as several different fields which convert the value between different units.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/other/units">
|
||||
/// Units Attribute</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Units/UnitsAttribute
|
||||
///
|
||||
[System.Diagnostics.Conditional(Strings.UnityEditor)]
|
||||
public class UnitsAttribute : PropertyAttribute
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The multipliers to convert from the field's actual value to each unit type.</summary>
|
||||
/// <remarks><c>valueInUnitX = valueInBaseUnits * Multipliers[x];</c></remarks>
|
||||
public readonly float[] Multipliers;
|
||||
|
||||
/// <summary>The unit suffix to display at the end of the value in each field.</summary>
|
||||
public readonly string[] Suffixes;
|
||||
|
||||
/// <summary>The index of the multiplier where the field stores its actual value.</summary>
|
||||
/// <remarks>The multiplier at this index must always be 1.</remarks>
|
||||
public readonly int UnitIndex;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The validation rule applied to the value.</summary>
|
||||
public Validate.Value Rule { get; set; }
|
||||
|
||||
/// <summary>Should the field have a toggle to set its value to <see cref="float.NaN"/>?</summary>
|
||||
public bool IsOptional { get; set; }
|
||||
|
||||
/// <summary>The value to display if the actual value is <see cref="float.NaN"/>.</summary>
|
||||
public float DefaultValue { get; set; }
|
||||
|
||||
/// <summary>Optional text to display instead of the regular fields when the value is <see cref="float.NaN"/>.</summary>
|
||||
public string DisabledText { get; set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="UnitsAttribute"/>.</summary>
|
||||
protected UnitsAttribute() { }
|
||||
|
||||
/// <summary>Creates a new <see cref="UnitsAttribute"/>.</summary>
|
||||
public UnitsAttribute(string suffix)
|
||||
{
|
||||
Multipliers = new float[] { 1 };
|
||||
Suffixes = new string[] { suffix };
|
||||
}
|
||||
|
||||
/// <summary>Creates a new <see cref="UnitsAttribute"/>.</summary>
|
||||
public UnitsAttribute(float[] multipliers, string[] suffixes, int unitIndex = 0)
|
||||
{
|
||||
Multipliers = multipliers;
|
||||
Suffixes = suffixes;
|
||||
UnitIndex = unitIndex;
|
||||
|
||||
Debug.Assert(multipliers.Length == suffixes.Length,
|
||||
$"[Units] The {nameof(multipliers)} and {nameof(suffixes)} arrays have different lengths.");
|
||||
Debug.Assert((uint)UnitIndex < (uint)multipliers.Length,
|
||||
$"[Units] The {nameof(unitIndex)} is outside the {nameof(multipliers)} array.");
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f4ed3dc29f5f9a49b5c355028aec9db
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,122 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#if UNITY_ASSERTIONS
|
||||
|
||||
//#define ANIMANCER_DISABLE_NAME_CACHE
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>[Assert-Only]
|
||||
/// A simple system for caching <see cref="Object.name"/> since it allocates garbage every time it's accessed.
|
||||
/// </summary>
|
||||
public static class NameCache
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly ConditionalWeakTable<Object, string>
|
||||
ObjectToName = new();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Caches and returns the <see cref="Object.name"/>.</summary>
|
||||
public static string GetCachedName(this Object obj)
|
||||
{
|
||||
#if ANIMANCER_DISABLE_NAME_CACHE
|
||||
return obj.name;
|
||||
#else
|
||||
if (obj == null)
|
||||
{
|
||||
if (obj is not null)
|
||||
ObjectToName.Remove(obj);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!ObjectToName.TryGetValue(obj, out var name))
|
||||
{
|
||||
name = obj.name;
|
||||
ObjectToName.Add(obj, name);
|
||||
}
|
||||
|
||||
return name;
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Tries to get the <see cref="Object.name"/> or <see cref="object.ToString"/>.</summary>
|
||||
public static bool TryToString(object obj, out string name)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
name = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (obj is Object unityObject)
|
||||
{
|
||||
if (unityObject != null)
|
||||
{
|
||||
name = unityObject.GetCachedName();
|
||||
}
|
||||
else
|
||||
{
|
||||
name = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
name = obj.ToString();
|
||||
}
|
||||
|
||||
return !string.IsNullOrEmpty(name);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Clears all cached names so they will be re-gathered when next accessed.</summary>
|
||||
public static void Clear()
|
||||
=> ObjectToName.Clear();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Sets the <see cref="Object.name"/> and caches it.</summary>
|
||||
public static void SetName(this Object obj, string name)
|
||||
{
|
||||
obj.name = name;
|
||||
ObjectToName.AddOrUpdate(obj, name);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#if UNITY_EDITOR
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private class Cleaner : UnityEditor.AssetPostprocessor
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void OnPostprocessAllAssets(
|
||||
string[] importedAssets,
|
||||
string[] deletedAssets,
|
||||
string[] movedAssets,
|
||||
string[] movedFromAssetPaths,
|
||||
bool didDomainReload)
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endif
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf02c5d0ebf292a4e864137385e89e4c
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,487 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>
|
||||
/// Bitwise flags used to determine which warnings Animancer should give.
|
||||
/// <para></para>
|
||||
/// <strong>These warnings are all optional</strong>.
|
||||
/// Feel free to disable any of them if you understand the <em>potential</em> issues they're referring to.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// All warnings are enabled by default, but are compiled out of runtime builds (except development builds).
|
||||
/// <para></para>
|
||||
/// You can manually disable warnings using the <c>AnimancerSettings</c> asset
|
||||
/// or the Animancer Settings panel in the Animancer Tools Window (<c>Window/Animation/Animancer Tools</c>).
|
||||
/// <para></para>
|
||||
/// <strong>Example:</strong>
|
||||
/// You can put a method like this in any class to disable whatever warnings you don't want on startup:
|
||||
/// <para></para><code>
|
||||
/// #if UNITY_ASSERTIONS
|
||||
/// [UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
/// private static void DisableAnimancerWarnings()
|
||||
/// {
|
||||
/// Animancer.OptionalWarning.ProOnly.Disable();
|
||||
///
|
||||
/// // You could disable OptionalWarning.All, but that's not recommended for obvious reasons.
|
||||
/// }
|
||||
/// #endif
|
||||
/// </code></remarks>
|
||||
///
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/OptionalWarning
|
||||
///
|
||||
[Flags]
|
||||
public enum OptionalWarning
|
||||
{
|
||||
/// <summary><c>default</c></summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// A <see href="https://kybernetik.com.au/animancer/docs/introduction/features">Pro-Only Feature</see>
|
||||
/// has been used in <see href="https://kybernetik.com.au/animancer/redirect/lite">Animancer Lite</see>.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// Some <see href="https://kybernetik.com.au/animancer/docs/introduction/features">Features</see>
|
||||
/// are only available in <see href="https://kybernetik.com.au/animancer/redirect/pro">Animancer Pro</see>.
|
||||
/// <para></para>
|
||||
/// <see href="https://kybernetik.com.au/animancer/redirect/lite">Animancer Lite</see>
|
||||
/// allows you to try out those features in the Unity Editor and gives this warning the
|
||||
/// first time each one is used to inform you that they will not work in runtime builds.
|
||||
/// </remarks>
|
||||
ProOnly = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="AnimancerComponent.Graph"/> is being initialized
|
||||
/// during a type of GUI event that isn't supposed to cause side effects.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <see cref="EventType.Layout"/> and <see cref="EventType.Repaint"/>
|
||||
/// should display the current details of things, but they should not modify things.
|
||||
/// </remarks>
|
||||
CreateGraphDuringGuiEvent = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="AnimancerComponent.Animator"/> is disabled so Animancer won't be able to play animations.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// The <see cref="Animator"/> doesn't need an Animator Controller,
|
||||
/// it just needs to be enabled via the checkbox in the Inspector
|
||||
/// or by setting <c>animancerComponent.Animator.enabled = true;</c> in code.
|
||||
/// </remarks>
|
||||
AnimatorDisabled = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="Animator.runtimeAnimatorController"/> is assigned
|
||||
/// but the Rig is Humanoid so it can't be blended with Animancer.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/animator-controllers#native">Native</see>
|
||||
/// Animator Controllers can blend with Animancer on Generic Rigs, but not on Humanoid Rigs
|
||||
/// (you can swap back and forth between the Animator Controller and Animancer,
|
||||
/// but it won't smoothly blend between them).
|
||||
/// <para></para>
|
||||
/// If you don't intend to blend between them, you can just disable this warning.
|
||||
/// </remarks>
|
||||
NativeControllerHumanoid = 1 << 3,
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="Animator.runtimeAnimatorController"/> is being used at the same time as a
|
||||
/// <see cref="ControllerState"/> or <see cref="HybridAnimancerComponent"/>.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// It is possible to use both, but it usually only happens when misunderstanding how the system works.
|
||||
/// The differences are explained on the
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/animator-controllers">
|
||||
/// Animator Controllers</see> page.
|
||||
/// If you do want both systems, just disable this warning.
|
||||
/// </remarks>
|
||||
NativeControllerState = 1 << 4,
|
||||
|
||||
/// <summary>
|
||||
/// An <see href="https://kybernetik.com.au/animancer/docs/manual/events/end">End Event</see>
|
||||
/// didn't actually end the animation.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// Animancer doesn't automatically do anything during an End Event
|
||||
/// so it's up to you to end the animation (usually by playing something else).
|
||||
/// <para></para>
|
||||
/// This warning is given when the event isn't used to stop the animation that caused it
|
||||
/// (usually by playing something else). This often indicates that the event hasn't been
|
||||
/// configured correctly, however it is sometimes intentional such as if the event doesn't
|
||||
/// immediately stop the animation but sets a flag to indicate the animation has ended for
|
||||
/// another system to act on at a later time. In that case this warning should be disabled.
|
||||
/// <para></para>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/end">End Events</see>
|
||||
/// are triggered every frame after their time has passed, so in this case it might be
|
||||
/// necessary to clear the event or simply use a regular
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer">Animancer Event</see>.
|
||||
/// </remarks>
|
||||
EndEventInterrupt = 1 << 5,
|
||||
|
||||
/// <summary>
|
||||
/// An <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer">Animancer Event</see>
|
||||
/// triggered by one character was used to play an animation on a different character.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// This most commonly happens when a Transition is shared by multiple characters and they
|
||||
/// all register their own callbacks to its events which leads to those events controlling
|
||||
/// the wrong character.
|
||||
/// </remarks>
|
||||
EventPlayMismatch = 1 << 6,
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="AnimancerEvent"/> called <see cref="AnimancerEvent.InvokeBoundCallback"/>
|
||||
/// but there was no callback bound to its name. This may mean the event is useless and therefore
|
||||
/// should be removed to avoid wasting performance checking and invoking it.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// Events in Transitions automatically call <see cref="AnimancerEvent.InvokeBoundCallback"/>
|
||||
/// by default so this warning is triggered if you don't bind a callback to the event's name
|
||||
/// in the <see cref="AnimancerComponent.Events"/>
|
||||
/// or use <see cref="AnimancerEvent.Sequence.SetCallback(int, Action)"/> on the Transition's events.
|
||||
/// </remarks>
|
||||
UselessEvent = 1 << 7,
|
||||
|
||||
/// <summary>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer">Animancer Events</see>
|
||||
/// are being used on a state which does not properly support them so they might not work as intended.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer">Animancer Events</see> on a
|
||||
/// <see cref="ControllerState"/> will be triggered based on its <see cref="AnimancerState.NormalizedTime"/>,
|
||||
/// which comes from the current state of its Animator Controller regardless of which state that may be.
|
||||
/// <para></para>
|
||||
/// If you intend for the event to be associated with a specific state inside the Animator Controller,
|
||||
/// you need to use Unity's regular
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/animation">Animation Events</see>
|
||||
/// instead.
|
||||
/// <para></para>
|
||||
/// But if you intend the event to be triggered by any state inside the Animator Controller,
|
||||
/// then you can simply disable this warning.
|
||||
/// </remarks>
|
||||
UnsupportedEvents = 1 << 8,
|
||||
|
||||
/// <summary>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/ik">Inverse Kinematics</see>
|
||||
/// cannot be dynamically enabled on some
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/playing/states">State</see> types.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// To use IK on a <see cref="ControllerState"/>
|
||||
/// you must instead enable it on the desired layer inside the Animator Controller.
|
||||
/// <para></para>
|
||||
/// IK is not supported by <see cref="PlayableAssetState"/>.
|
||||
/// <para></para>
|
||||
/// Setting <see cref="AnimancerNode.ApplyAnimatorIK"/> on such a state will simply do nothing,
|
||||
/// so feel free to disable this warning if you are enabling IK on states without checking their type.
|
||||
/// </remarks>
|
||||
UnsupportedIK = 1 << 9,
|
||||
|
||||
/// <summary>
|
||||
/// A Mixer State is being initialized with its <see cref="AnimancerNode.ChildCount"/> <= 1.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// The purpose of a mixer is to mix multiple child states
|
||||
/// so you are probably initializing it with incorrect parameters.
|
||||
/// <para></para>
|
||||
/// A mixer with only one child will simply play that child,
|
||||
/// so feel free to disable this warning if that's what you intend to do.
|
||||
/// </remarks>
|
||||
MixerMinChildren = 1 << 10,
|
||||
|
||||
/// <summary>
|
||||
/// A Mixer State is synchronizing a child with <see cref="AnimancerState.Length"/> = 0.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// Synchronization is based on the <see cref="AnimancerState.NormalizedTime"/>
|
||||
/// which can't be calculated if the <see cref="AnimancerState.Length"/> is 0.
|
||||
/// <para></para>
|
||||
/// Some state types can change their <see cref="AnimancerState.Length"/>,
|
||||
/// in which case you can just disable this warning.
|
||||
/// But otherwise, the indicated state should not be added to the synchronization list.
|
||||
/// </remarks>
|
||||
MixerSynchronizeZeroLength = 1 << 11,
|
||||
|
||||
/// <summary>
|
||||
/// When a transition with a non-zero <see cref="ITransition.FadeDuration"/>
|
||||
/// creates a state, that state will log this warning if it's ever played
|
||||
/// without a fade duration.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This helps identify situations where a state is accidentally played directly
|
||||
/// where the transition should be played instead to allow it to apply its fade
|
||||
/// and any other details.
|
||||
/// </remarks>
|
||||
ExpectFade = 1 << 12,
|
||||
|
||||
/// <summary>
|
||||
/// A <see href="https://kybernetik.com.au/animancer/docs/manual/blending/fading/custom">Custom Easing</see>
|
||||
/// is being started but its weight calculation does not go from 0 to 1.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// The <see cref="FadeGroup.Easing"/> method is expected to return 0 when the parameter is 0 and
|
||||
/// 1 when the parameter is 1. It can do anything you want with other values,
|
||||
/// but starting or ending at different values will likely lead to undesirable results.
|
||||
/// <para></para>
|
||||
/// If your <see cref="FadeGroup.Easing"/> method is expensive you could disable this warning to save
|
||||
/// some performance, but violating the above guidelines is not recommended.
|
||||
/// </remarks>
|
||||
FadeEasingBounds = 1 << 13,
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Animator.speed"/> property does not affect Animancer.
|
||||
/// Use <see cref="AnimancerGraph.Speed"/> instead.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// The <see cref="Animator.speed"/> property only works with Animator Controllers but does not affect the
|
||||
/// Playables API so Animancer has its own <see cref="AnimancerGraph.Speed"/> property.
|
||||
/// </remarks>
|
||||
AnimatorSpeed = 1 << 14,
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="AnimancerNodeBase.Graph"/> is null during finalization (garbage collection).
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// This probably means that node was never used for anything and should not have been created.
|
||||
/// <para></para>
|
||||
/// This warning can be prevented for a specific node by calling <see cref="AnimancerNodeBase.MarkAsUsed"/>.
|
||||
/// <para></para>
|
||||
/// To minimise the performance cost of checking this warning, it does not capture the stack trace of the
|
||||
/// node's creation by default. However, you can enable <see cref="AnimancerNode.TraceConstructor"/> on startup
|
||||
/// so that it can include the stack trace in the warning message for any nodes that end up being unused.
|
||||
/// </remarks>
|
||||
UnusedNode = 1 << 15,
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="AnimancerState.MainObject"/> doesn't match the <see cref="Transition{TState}.MainObject"/>
|
||||
/// of the transition being applied to it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If you set the <see cref="Transition{TState}.MainObject"/> of a transition, you should also call
|
||||
/// <see cref="Transition{TState}.ReconcileMainObject"/> to create a new state for the new object.
|
||||
/// </remarks>
|
||||
MainObjectMismatch = 1 << 16,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="PlayableAssetState.InitializeBindings"/> is trying to bind to the same <see cref="Animator"/>
|
||||
/// that is being used by Animancer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Doing this will replace Animancer's output so its animations would not work anymore.
|
||||
/// </remarks>
|
||||
PlayableAssetAnimatorBinding = 1 << 17,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="AnimancerLayer.GetOrCreateWeightlessState"/> is cloning a complex state such as a
|
||||
/// <see cref="ManualMixerState"/> or <see cref="ControllerState"/>.
|
||||
/// This has a larger performance cost than cloning a <see cref="ClipState"/> and these states
|
||||
/// generally have parameters that need to be controlled which may result in undesired behaviour
|
||||
/// if your scripts are only expecting to have one state to control.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The <see href="https://kybernetik.com.au/animancer/docs/manual/blending/fading/modes">Fade Modes</see>
|
||||
/// page explains why clones are created.
|
||||
/// </remarks>
|
||||
CloneComplexState = 1 << 18,
|
||||
|
||||
/// <summary>
|
||||
/// Unity doesn't suppport dynamically creating animations for Animancer in runtime builds
|
||||
/// so this warning is given when attempting to use an animation which isn't saved as an
|
||||
/// asset to explain this limitation as early as possible.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// This warning should be disabled if you're loading animations from Asset Bundles or Addressables
|
||||
/// or if you only intend to use the animation in the Unity Editor.
|
||||
/// </remarks>
|
||||
DynamicAnimation = 1 << 19,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Animancer.StringReference"/>s are generally more efficient for comparisons
|
||||
/// than raw <see cref="string"/>s and are not interchangeable so references should be preferred.
|
||||
/// </summary>
|
||||
StringReference = 1 << 20,
|
||||
|
||||
/// <summary>All warning types.</summary>
|
||||
All = ~0,
|
||||
}
|
||||
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/Validate
|
||||
public static partial class Validate
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
#if UNITY_ASSERTIONS
|
||||
/// <summary>[Assert-Only]
|
||||
/// The <see cref="OptionalWarning"/> flags that are currently disabled (default none).
|
||||
/// </summary>
|
||||
private static OptionalWarning _DisabledWarnings;
|
||||
#endif
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Animancer Extension] [Assert-Conditional]
|
||||
/// Disables the specified warning type. Supports bitwise combinations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <strong>Example:</strong>
|
||||
/// You can put a method like this in any class to disable whatever warnings you don't want on startup:
|
||||
/// <para></para><code>
|
||||
/// #if UNITY_ASSERTIONS
|
||||
/// [UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
/// private static void DisableAnimancerWarnings()
|
||||
/// {
|
||||
/// Animancer.OptionalWarning.ProOnly.Disable();
|
||||
///
|
||||
/// // You could disable OptionalWarning.All, but that's not recommended for obvious reasons.
|
||||
/// }
|
||||
/// #endif
|
||||
/// </code></remarks>
|
||||
[System.Diagnostics.Conditional(Strings.Assertions)]
|
||||
public static void Disable(this OptionalWarning type)
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
_DisabledWarnings |= type;
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Animancer Extension] [Assert-Conditional]
|
||||
/// Enables the specified warning type. Supports bitwise combinations.
|
||||
/// </summary>
|
||||
[System.Diagnostics.Conditional(Strings.Assertions)]
|
||||
public static void Enable(this OptionalWarning type)
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
_DisabledWarnings &= ~type;
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Animancer Extension] [Assert-Conditional]
|
||||
/// Enables or disables the specified warning type. Supports bitwise combinations.
|
||||
/// </summary>
|
||||
[System.Diagnostics.Conditional(Strings.Assertions)]
|
||||
public static void SetEnabled(this OptionalWarning type, bool enable)
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
if (enable)
|
||||
type.Enable();
|
||||
else
|
||||
type.Disable();
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Animancer Extension] [Assert-Conditional]
|
||||
/// Logs the `message` as a warning if the `type` is enabled.
|
||||
/// </summary>
|
||||
/// <remarks>Does nothing if the `message` is <c>null</c>.</remarks>
|
||||
[System.Diagnostics.Conditional(Strings.Assertions)]
|
||||
[HideInCallstack]
|
||||
public static void Log(this OptionalWarning type, string message, object context = null)
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
if (message == null || type.IsDisabled())
|
||||
return;
|
||||
|
||||
Debug.LogWarning(
|
||||
$"{nameof(OptionalWarning)}.{type} - Possible Issue Detected: {message}" +
|
||||
$"\n\nThis warning can be disabled via '{Strings.AnimancerSettingsPath}'" +
|
||||
$" or by calling {nameof(Animancer)}.{nameof(OptionalWarning)}.{type}.{nameof(Disable)}()" +
|
||||
" and it will automatically be compiled out of Runtime Builds (except for Development Builds)." +
|
||||
$" More information can be found at {Strings.DocsURLs.OptionalWarning.AsHtmlLink()}\n",
|
||||
context as Object);
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#if UNITY_ASSERTIONS
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Animancer Extension] [Assert-Only] Are none of the specified warning types disabled?</summary>
|
||||
public static bool IsEnabled(this OptionalWarning type) => (_DisabledWarnings & type) == 0;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Animancer Extension] [Assert-Only] Are all of the specified warning types disabled?</summary>
|
||||
public static bool IsDisabled(this OptionalWarning type) => (_DisabledWarnings & type) == type;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Animancer Extension] [Assert-Only]
|
||||
/// Disables the specified warnings and returns those that were previously enabled.
|
||||
/// </summary>
|
||||
/// <remarks>Call <see cref="Enable"/> on the returned value to re-enable it.</remarks>
|
||||
public static OptionalWarning DisableTemporarily(this OptionalWarning type)
|
||||
{
|
||||
var previous = _DisabledWarnings;
|
||||
type.Disable();
|
||||
return ~previous & type;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private const string PermanentlyDisabledWarningsKey = nameof(Animancer) + "." + nameof(PermanentlyDisabledWarnings);
|
||||
|
||||
/// <summary>[Assert-Only] Warnings that are automatically disabled</summary>
|
||||
/// <remarks>
|
||||
/// This value is stored in <see cref="PlayerPrefs"/>
|
||||
/// and can be manually edited via <see cref="Strings.AnimancerSettingsPath"/>.
|
||||
/// </remarks>
|
||||
public static OptionalWarning PermanentlyDisabledWarnings
|
||||
{
|
||||
#if NO_RUNTIME_PLAYER_PREFS && ! UNITY_EDITOR
|
||||
get => default;
|
||||
set
|
||||
{
|
||||
_DisabledWarnings = value;
|
||||
}
|
||||
#else
|
||||
get => (OptionalWarning)PlayerPrefs.GetInt(PermanentlyDisabledWarningsKey);
|
||||
set
|
||||
{
|
||||
_DisabledWarnings = value;
|
||||
PlayerPrefs.SetInt(PermanentlyDisabledWarningsKey, (int)value);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[UnityEditor.InitializeOnLoadMethod]
|
||||
#endif
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void InitializePermanentlyDisabledWarnings()
|
||||
{
|
||||
_DisabledWarnings = PermanentlyDisabledWarnings;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endif
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: badbcd8cbea965f4c846ac3437a09399
|
||||
timeCreated: 1515060256
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
190
Packages/com.kybernetik.animancer/Runtime/Editor/SimpleTimer.cs
Normal file
190
Packages/com.kybernetik.animancer/Runtime/Editor/SimpleTimer.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>A very simple timer system based on a <see cref="System.Diagnostics.Stopwatch"/>.</summary>
|
||||
public struct SimpleTimer : IDisposable
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The default <see cref="format"/> contains 3 decimal places.</summary>
|
||||
const string Format3DP = "0.000";
|
||||
|
||||
/// <summary>A default timer that hasn't been started.</summary>
|
||||
public static SimpleTimer Default = new(null);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The system used to track time.</summary>
|
||||
public static readonly Stopwatch
|
||||
Stopwatch = Stopwatch.StartNew();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>An optional prefix for <see cref="ToString"/>.</summary>
|
||||
public string name;
|
||||
|
||||
/// <summary>The string format to use for <see cref="ToString"/>.</summary>
|
||||
/// <remarks>
|
||||
/// If <c>null</c>, ticks will be used directly.
|
||||
/// Otherwise, the ticks will be converted to seconds and this format will be used.
|
||||
/// </remarks>
|
||||
public string format;
|
||||
|
||||
/// <summary>The <see cref="Stopwatch.ElapsedTicks"/> from when this timer was started.</summary>
|
||||
/// <remarks>If not started, this value will be <c>-1</c>.</remarks>
|
||||
public long startTicks;
|
||||
|
||||
/// <summary>The total number of ticks that have elapsed since the <see cref="startTicks"/>.</summary>
|
||||
/// <remarks>This value is updated by <see cref="Count"/>.</remarks>
|
||||
public long totalTicks;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Converts the <see cref="startTicks"/> to seconds.</summary>
|
||||
public readonly double StartTimeSeconds
|
||||
=> startTicks / (double)Stopwatch.Frequency;
|
||||
|
||||
/// <summary>Converts the <see cref="totalTicks"/> to seconds.</summary>
|
||||
public readonly double TotalTimeSeconds
|
||||
=> totalTicks / (double)Stopwatch.Frequency;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Has <see cref="Start()"/> been called and <see cref="Count"/> not?</summary>
|
||||
public readonly bool IsStarted
|
||||
=> startTicks != -1;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="SimpleTimer"/> with the specified `name`.</summary>
|
||||
/// <remarks>
|
||||
/// You will need to call <see cref="Start()"/> to start the timer.
|
||||
/// Or use the static <see cref="Start(string, string)"/>.
|
||||
/// <para></para>
|
||||
/// Use <c>null</c> as the `format` to have <see cref="Format"/> return the ticks instead of seconds.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public SimpleTimer(string name, string format = Format3DP)
|
||||
{
|
||||
this.name = name;
|
||||
this.format = format;
|
||||
startTicks = -1;
|
||||
totalTicks = 0;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="SimpleTimer"/> with the specified `name` and starts it.</summary>
|
||||
/// <remarks>Use <c>null</c> as the `format` to have <see cref="Format"/> return the ticks instead of seconds.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static SimpleTimer Start(string name = null, string format = Format3DP)
|
||||
=> new()
|
||||
{
|
||||
name = name,
|
||||
format = format,
|
||||
startTicks = Stopwatch.ElapsedTicks,
|
||||
};
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Stores the <see cref="Stopwatch.ElapsedTicks"/> in <see cref="startTicks"/>
|
||||
/// so that <see cref="Count"/> will be able to calculate how much time has passed.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Start()
|
||||
=> startTicks = Stopwatch.ElapsedTicks;
|
||||
|
||||
/// <summary>Clears the <see cref="startTicks"/>.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Cancel()
|
||||
=> startTicks = -1;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the amount of time that has passed since the <see cref="startTicks"/>
|
||||
/// and returns it after adding it to the <see cref="totalTicks"/>.
|
||||
/// Also resumes this timer.
|
||||
/// </summary>
|
||||
/// <remarks>Returns -1 if this timer wasn't started.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public long Count()
|
||||
{
|
||||
var endTicks = Stopwatch.ElapsedTicks;
|
||||
|
||||
long count;
|
||||
if (startTicks >= 0)
|
||||
{
|
||||
count = endTicks - startTicks;
|
||||
totalTicks += count;
|
||||
}
|
||||
else
|
||||
{
|
||||
count = -1;
|
||||
}
|
||||
|
||||
startTicks = endTicks;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static StringBuilder _StringBuilder;
|
||||
|
||||
/// <summary>Calls <see cref="Count"/> and returns a string describing the current values of this timer.</summary>
|
||||
public override string ToString()
|
||||
{
|
||||
var count = Count();
|
||||
|
||||
if (_StringBuilder == null)
|
||||
_StringBuilder = new();
|
||||
else
|
||||
_StringBuilder.Length = 0;
|
||||
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
_StringBuilder.Append(name)
|
||||
.Append(": ");
|
||||
|
||||
if (count != totalTicks && count >= 0)
|
||||
{
|
||||
_StringBuilder
|
||||
.Append("Count ")
|
||||
.Append(Format(count))
|
||||
.Append(", Total ");
|
||||
}
|
||||
|
||||
_StringBuilder.Append(Format(totalTicks));
|
||||
|
||||
return _StringBuilder.ToString();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Converts the given `ticks` to a string using the <see cref="format"/>.</summary>
|
||||
public readonly string Format(long ticks)
|
||||
=> format is null
|
||||
? $"{ticks} Ticks"
|
||||
: $"{(ticks / (double)Stopwatch.Frequency).ToString(format)}s";
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Logs <see cref="ToString"/> and calls <see cref="Cancel"/>.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
UnityEngine.Debug.Log(ToString());
|
||||
Cancel();
|
||||
totalTicks = 0;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 162a437c31814b344832914ef6db9905
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
219
Packages/com.kybernetik.animancer/Runtime/Editor/Strings.cs
Normal file
219
Packages/com.kybernetik.animancer/Runtime/Editor/Strings.cs
Normal file
@@ -0,0 +1,219 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using Animancer.Units;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>Various string constants used throughout <see cref="Animancer"/>.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/Strings
|
||||
///
|
||||
public static class Strings
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The name of this product.</summary>
|
||||
public const string ProductName = nameof(Animancer);
|
||||
|
||||
/// <summary>The standard prefix for <see cref="AddComponentMenu"/>.</summary>
|
||||
public const string MenuPrefix = ProductName + "/";
|
||||
|
||||
/// <summary>The standard prefix for the asset creation menu (for <see cref="UnityEditor.MenuItem"/>).</summary>
|
||||
public const string CreateMenuPrefix = "Assets/Create/" + MenuPrefix;
|
||||
|
||||
/// <summary>The standard prefix for <see cref="AddComponentMenu"/> for the samples.</summary>
|
||||
public const string SamplesMenuPrefix = MenuPrefix + "Samples/";
|
||||
|
||||
/// <summary>The menu path of the <see cref="Editor.Tools.AnimancerToolsWindow"/>.</summary>
|
||||
public const string AnimancerToolsMenuPath = "Window/Animation/Animancer Tools";
|
||||
|
||||
/// <summary>The menu path of the Animancer settings.</summary>
|
||||
public const string AnimancerSettingsPath = "Edit/Project Settings/Animancer";
|
||||
|
||||
/// <summary>
|
||||
/// The base value for <see cref="CreateAssetMenuAttribute.order"/> to group
|
||||
/// "Assets/Create/Animancer/..." menu items just under "Avatar Mask".
|
||||
/// </summary>
|
||||
public const int AssetMenuOrder = 410;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The conditional compilation symbol for Editor-Only code.</summary>
|
||||
public const string UnityEditor = "UNITY_EDITOR";
|
||||
|
||||
/// <summary>The conditional compilation symbol for assertions exists in the Unity Editor and Development Builds.</summary>
|
||||
public const string Assertions = "UNITY_ASSERTIONS";
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>4 spaces for indentation.</summary>
|
||||
public const string Indent = " ";
|
||||
|
||||
/// <summary>A prefix for tooltips on Pro-Only features.</summary>
|
||||
/// <remarks><c>"[Pro-Only] "</c> in Animancer Lite or <c>""</c> in Animancer Pro.</remarks>
|
||||
public const string ProOnlyTag = "";
|
||||
|
||||
/// <summary>The Assembly name of the pre-compiled Animancer Lite DLL.</summary>
|
||||
public const string LiteAssemblyName = "Kybernetik.Animancer.Lite";
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>An error message for when <see cref="AnimancerUtilities.IsFinite(float)"/> fails.</summary>
|
||||
public const string MustBeFinite = "must not be NaN or Infinity";
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
#if UNITY_ASSERTIONS
|
||||
/// <summary>[Assert-Only] A message for <see cref="OptionalWarning.AnimatorDisabled"/>.</summary>
|
||||
public const string AnimatorDisabledMessage
|
||||
= "The " + nameof(Animator) + " is disabled so Animancer won't be able to play animations.";
|
||||
#endif
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>URLs of various documentation pages.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/DocsURLs
|
||||
///
|
||||
public static class DocsURLs
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The URL of the website where the Animancer documentation is hosted.</summary>
|
||||
public const string Documentation = "https://kybernetik.com.au/animancer";
|
||||
|
||||
/// <summary>The URL of the website where the Animancer API documentation is hosted.</summary>
|
||||
public const string APIDocumentation = Documentation + "/api/" + nameof(Animancer);
|
||||
|
||||
/// <summary>The email address which handles support for Animancer.</summary>
|
||||
public const string DeveloperEmail = "animancer@kybernetik.com.au";
|
||||
|
||||
/// <summary>The URL of the file which lists Animancer's latest version.</summary>
|
||||
public const string LatestVersion = Documentation + "/latest-version.txt";
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public const string OptionalWarning = APIDocumentation + "/" + nameof(Animancer.OptionalWarning);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#if UNITY_ASSERTIONS
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public const string Docs = Documentation + "/docs/";
|
||||
|
||||
public const string AnimancerEvents = Docs + "manual/events/animancer";
|
||||
public const string EndEvents = Docs + "manual/events/end";
|
||||
public const string AnimancerEventParameters = AnimancerEvents + "/parameters";
|
||||
public const string AnimatorControllers = Docs + "manual/animator-controllers";
|
||||
public const string AnimatorControllersNative = AnimatorControllers + "#native";
|
||||
public const string Fading = Docs + "manual/blending/fading";
|
||||
public const string FadeModes = Fading + "/modes";
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endif
|
||||
/************************************************************************************************************************/
|
||||
#if UNITY_EDITOR
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public const string Samples = Docs + "samples";
|
||||
public const string UnevenGround = Docs + "samples/ik/uneven-ground";
|
||||
public const string OdinInspector = Docs + "samples/integration/odin-inspector";
|
||||
|
||||
public const string AnimancerTools = Docs + "manual/tools";
|
||||
public const string PackTextures = AnimancerTools + "/pack-textures";
|
||||
public const string ModifySprites = AnimancerTools + "/modify-sprites";
|
||||
public const string RenameSprites = AnimancerTools + "/rename-sprites";
|
||||
public const string GenerateSpriteAnimations = AnimancerTools + "/generate-sprite-animations";
|
||||
public const string RemapSpriteAnimation = AnimancerTools + "/remap-sprite-animation";
|
||||
public const string RemapAnimationBindings = AnimancerTools + "/remap-animation-bindings";
|
||||
|
||||
public const string Inspector = Docs + "manual/playing/inspector";
|
||||
public const string States = Docs + "manual/playing/states";
|
||||
|
||||
public const string Layers = Docs + "manual/blending/layers";
|
||||
|
||||
public const string Parameters = Docs + "manual/parameters";
|
||||
|
||||
public const string TransitionPreviews = Docs + "manual/transitions/previews";
|
||||
public const string TransitionLibraries = Docs + "manual/transitions/libraries";
|
||||
|
||||
public const string UpdateModes = Docs + "bugs/update-modes";
|
||||
|
||||
public const string VersionName = "v8.3.0";
|
||||
|
||||
public const string ChangeLogURL = Docs + "changes/animancer-v8-3";
|
||||
|
||||
public const string UpgradeGuideURL = ChangeLogURL + "/upgrade-guide";
|
||||
|
||||
public const string Discussions = "https://discussions.unity.com/t/animancer-less-animator-controller-more-animator-control/717489/99999";
|
||||
|
||||
public const string Issues = "https://github.com/KybernetikGames/animancer/issues";
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endif
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Tooltips for various fields.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/Tooltips
|
||||
///
|
||||
public static class Tooltips
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public const string MiddleClickReset =
|
||||
"\n• Middle Click = reset to default value";
|
||||
|
||||
public const string FadeDuration = ProOnlyTag +
|
||||
"The amount of time the transition will take, e.g:" +
|
||||
"\n• 0s = instant" +
|
||||
"\n• 0.25s = quarter of a second (Default)" +
|
||||
"\n• 0.25x = quarter of the animation length" +
|
||||
"\n• " + AnimationTimeAttribute.Tooltip +
|
||||
MiddleClickReset;
|
||||
|
||||
public const string Speed = ProOnlyTag +
|
||||
"How fast the animation will play, e.g:" +
|
||||
"\n• 0x = paused" +
|
||||
"\n• 1x = normal speed" +
|
||||
"\n• -2x = double speed backwards";
|
||||
|
||||
public const string OptionalSpeed = Speed +
|
||||
"\n• Disabled = continue at current speed" +
|
||||
MiddleClickReset;
|
||||
|
||||
public const string SpeedDisabled =
|
||||
"Continue at current speed";
|
||||
|
||||
public const string NormalizedStartTime = ProOnlyTag +
|
||||
"• Enabled = use " + nameof(FadeMode) + "." + nameof(FadeMode.FromStart) +
|
||||
" and restart at this time." +
|
||||
"\n• Disabled = use " + nameof(FadeMode) + "." + nameof(FadeMode.FixedSpeed) +
|
||||
" and continue from the current time if already playing." +
|
||||
"\n• " + AnimationTimeAttribute.Tooltip;
|
||||
|
||||
public const string StartTimeDisabled =
|
||||
"Continue from current time";
|
||||
|
||||
public const string EndTime = ProOnlyTag +
|
||||
"The time when the End Callback will be triggered." +
|
||||
"\n• " + AnimationTimeAttribute.Tooltip +
|
||||
"\n\nDisabling the toggle automates the value:" +
|
||||
"\n• Speed >= 0 ends at 1x" +
|
||||
"\n• Speed < 0 ends at 0x";
|
||||
|
||||
public const string CallbackTime = ProOnlyTag +
|
||||
"The time when the Event Callback will be triggered." +
|
||||
"\n• " + AnimationTimeAttribute.Tooltip;
|
||||
|
||||
public const string MixerParameterBinding =
|
||||
"[Optional] The mixer's parameter will be controlled by the Animancer parameter with this name";
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 23a5456b72939fd40814cc364610e26b
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,74 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/Validate
|
||||
public static partial class Validate
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>A rule that defines which values are valid.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/Value
|
||||
public enum Value
|
||||
{
|
||||
/// <summary>Any value is allowed.</summary>
|
||||
Any,
|
||||
|
||||
/// <summary>Only values between 0 (inclusive) and 1 (inclusive) are allowed.</summary>
|
||||
ZeroToOne,
|
||||
|
||||
/// <summary>Only 0 or positive values are allowed.</summary>
|
||||
IsNotNegative,
|
||||
|
||||
/// <summary>Infinity and NaN are not allowed.</summary>
|
||||
IsFinite,
|
||||
|
||||
/// <summary>Infinity is not allowed.</summary>
|
||||
IsFiniteOrNaN,
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Enforces the `rule` on the `value`.</summary>
|
||||
public static void ValueRule(ref float value, Value rule)
|
||||
{
|
||||
switch (rule)
|
||||
{
|
||||
case Value.Any:
|
||||
default:
|
||||
return;
|
||||
|
||||
case Value.ZeroToOne:
|
||||
if (!(value >= 0))// Reversed comparison to include NaN.
|
||||
value = 0;
|
||||
else if (value > 1)
|
||||
value = 1;
|
||||
break;
|
||||
|
||||
case Value.IsNotNegative:
|
||||
if (!(value >= 0))// Reversed comparison to include NaN.
|
||||
value = 0;
|
||||
break;
|
||||
|
||||
case Value.IsFinite:
|
||||
if (float.IsNaN(value))
|
||||
value = 0;
|
||||
else if (float.IsPositiveInfinity(value))
|
||||
value = float.MaxValue;
|
||||
else if (float.IsNegativeInfinity(value))
|
||||
value = float.MinValue;
|
||||
break;
|
||||
|
||||
case Value.IsFiniteOrNaN:
|
||||
if (float.IsPositiveInfinity(value))
|
||||
value = float.MaxValue;
|
||||
else if (float.IsNegativeInfinity(value))
|
||||
value = float.MinValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f0bb4317f2e91b4c8bd3803ff978a3e
|
||||
timeCreated: 1515060256
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
188
Packages/com.kybernetik.animancer/Runtime/Editor/Validate.cs
Normal file
188
Packages/com.kybernetik.animancer/Runtime/Editor/Validate.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>
|
||||
/// Enforces various rules throughout the system, most of which are compiled out if UNITY_ASSERTIONS is not defined
|
||||
/// (by default, it is only defined in the Unity Editor and in Development Builds).
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/Validate
|
||||
///
|
||||
public static partial class Validate
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Assert-Conditional]
|
||||
/// Throws if the `clip` is <c>null</c>, not an asset, or marked as <see cref="AnimationClip.legacy"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="NullReferenceException"/>
|
||||
/// <exception cref="ArgumentException"/>
|
||||
[System.Diagnostics.Conditional(Strings.Assertions)]
|
||||
public static void AssertAnimationClip(AnimationClip clip, bool throwIfNull, string operation)
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
if (clip == null)
|
||||
{
|
||||
if (!throwIfNull)
|
||||
return;
|
||||
|
||||
#pragma warning disable IDE0041 // Use 'is null' check (that would suggest changing to == which is wrong).
|
||||
var error = ReferenceEquals(clip, null)
|
||||
? $"Unable to {operation} because the {nameof(AnimationClip)} is null."
|
||||
: $"Unable to {operation} because the {nameof(AnimationClip)} has been destroyed.";
|
||||
|
||||
throw new NullReferenceException(error);
|
||||
#pragma warning restore IDE0041 // Use 'is null' check.
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (OptionalWarning.DynamicAnimation.IsEnabled() &&
|
||||
!UnityEditor.EditorUtility.IsPersistent(clip))
|
||||
OptionalWarning.DynamicAnimation.Log(
|
||||
$"Attempted to {operation} using an {nameof(AnimationClip)} '{clip.name}' which is not an asset." +
|
||||
" Unity doesn't support dynamically creating animations for Animancer in runtime builds." +
|
||||
" This warning should be disabled if you're loading animations from Asset Bundles or Addressables" +
|
||||
" or if you only intend to use the animation in the Unity Editor.",
|
||||
clip);
|
||||
#endif
|
||||
|
||||
if (clip.legacy)
|
||||
throw new ArgumentException(
|
||||
$"Unable to {operation} because the {nameof(AnimationClip)} '{clip.name}' is a lagacy animation" +
|
||||
" and therefore cannot be used by Animancer" +
|
||||
" If it was imported as part of a model then the model's Rig type must be Humanoid or Generic." +
|
||||
" Otherwise you can use the 'Toggle Legacy' function in the clip's context menu" +
|
||||
" (via the cog icon in the top right of its Inspector).");
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Assert-Conditional] Throws if the <see cref="AnimancerNodeBase.Graph"/> is not the `graph`.</summary>
|
||||
/// <exception cref="ArgumentException"/>
|
||||
[System.Diagnostics.Conditional(Strings.Assertions)]
|
||||
public static void AssertGraph(AnimancerNode node, AnimancerGraph graph)
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
if (node.Graph != graph)
|
||||
{
|
||||
AnimancerNodeBase.MarkAsUsed(node);
|
||||
|
||||
throw new ArgumentException(
|
||||
$"{nameof(AnimancerNode)}.{nameof(AnimancerNode.Graph)} mismatch:" +
|
||||
$" cannot use a node in an {nameof(AnimancerGraph)} that is not its {nameof(AnimancerNode.Graph)}: " +
|
||||
node.GetDescription());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Assert-Conditional] Throws if the `node`'s <see cref="Playable"/> is invalid.</summary>
|
||||
/// <exception cref="InvalidOperationException"/>
|
||||
[System.Diagnostics.Conditional(Strings.Assertions)]
|
||||
public static void AssertPlayable(AnimancerNode node)
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
if (node._Playable.IsValid() &&
|
||||
node.Graph._PlayableGraph.IsValid())
|
||||
return;
|
||||
|
||||
var description = node.ToString();
|
||||
|
||||
var stackTrace = AnimancerNode.GetConstructorStackTrace(node);
|
||||
if (stackTrace != null)
|
||||
description += "\n\n" + stackTrace;
|
||||
|
||||
AnimancerNodeBase.MarkAsUsed(node);
|
||||
|
||||
if (node is AnimancerState state)
|
||||
state.Destroy();
|
||||
|
||||
if (node.Graph == null)
|
||||
throw new InvalidOperationException(
|
||||
$"{nameof(AnimancerNode)}.{nameof(AnimancerNode.Graph)} hasn't been set so its" +
|
||||
$" {nameof(Playable)} hasn't been created. It can be set by playing the state" +
|
||||
$" or calling {nameof(AnimancerState.SetGraph)} on it directly." +
|
||||
$" {nameof(AnimancerState.SetParent)} would also work if the parent has a" +
|
||||
$" {nameof(AnimancerNode.Graph)}." +
|
||||
$"\n• Node: {description}");
|
||||
else if (!node.Graph._PlayableGraph.IsValid())
|
||||
throw new InvalidOperationException(
|
||||
$"{nameof(AnimancerGraph)}.{nameof(AnimancerGraph.PlayableGraph)} has already been destroyed." +
|
||||
$" This is often caused by a character attempting to access a state on a different character," +
|
||||
$" such as if they share a Transition and are both accessing its State without realising it" +
|
||||
$" only holds the most recently played state." +
|
||||
$"\n• Graph: {node.Graph}" +
|
||||
$"\n• Node: {description}");
|
||||
else
|
||||
throw new InvalidOperationException(
|
||||
$"{nameof(AnimancerNode)}.{nameof(AnimancerNodeBase.Playable)}" +
|
||||
$" has either been destroyed or was never created." +
|
||||
$"\n• Graph: {node.Graph}" +
|
||||
$"\n• Node: {description}");
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Assert-Conditional]
|
||||
/// Throws if the `state` was not actually assigned to its specified <see cref="AnimancerNode.Index"/> in
|
||||
/// the `states`.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException"/>
|
||||
/// <exception cref="IndexOutOfRangeException">
|
||||
/// The <see cref="AnimancerNode.Index"/> is larger than the number of `states`.
|
||||
/// </exception>
|
||||
[System.Diagnostics.Conditional(Strings.Assertions)]
|
||||
public static void AssertCanRemoveChild(AnimancerState state, IList<AnimancerState> childStates, int childCount)
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
var index = state.Index;
|
||||
|
||||
if (index < 0)
|
||||
throw new InvalidOperationException(
|
||||
$"Cannot remove a child state that did not have an {nameof(state.Index)} assigned");
|
||||
|
||||
if ((uint)index >= (uint)childCount)
|
||||
throw new IndexOutOfRangeException(
|
||||
$"{nameof(AnimancerState)}.{nameof(state.Index)} ({index})" +
|
||||
$" is outside the collection of states (Count {childCount})");
|
||||
|
||||
if (childStates[index] != state)
|
||||
throw new InvalidOperationException(
|
||||
$"Cannot remove a child state that was not actually connected to its port on {state.Parent}:" +
|
||||
$"\n• Port: {index}" +
|
||||
$"\n• Connected Child: {AnimancerUtilities.ToStringOrNull(childStates[index])}" +
|
||||
$"\n• Disconnecting Child: {AnimancerUtilities.ToStringOrNull(state)}");
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Assert-Conditional] Throws if the `weight` is negative, infinity, or NaN.</summary>
|
||||
/// <exception cref="ArgumentOutOfRangeException"/>
|
||||
[System.Diagnostics.Conditional(Strings.Assertions)]
|
||||
public static void AssertSetWeight(AnimancerNode node, float weight)
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
if (!(weight >= 0) || weight == float.PositiveInfinity)// Reversed comparison includes NaN.
|
||||
{
|
||||
AnimancerNodeBase.MarkAsUsed(node);
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(weight),
|
||||
weight,
|
||||
$"{nameof(AnimancerNode.Weight)} must be a finite positive value");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 53ca7fd66d753e248a1e0faff8acfe17
|
||||
timeCreated: 1515060256
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user