chore: initial commit
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>Interface for components that manage an <see cref="AnimancerGraph"/>.</summary>
|
||||
/// <remarks>
|
||||
/// Despite the name, this interface is not necessarily limited to only <see cref="Component"/>s.
|
||||
/// <para></para>
|
||||
/// This interface allows Animancer Lite to reference an <see cref="AnimancerComponent"/> inside the pre-compiled
|
||||
/// DLL while allowing that component to remain outside as a regular script. Otherwise everything would need to be
|
||||
/// in the DLL which would cause Unity to lose all the script references when upgrading from Animancer Lite to Pro.
|
||||
/// <para></para>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/playing/component-types">
|
||||
/// Component Types</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/IAnimancerComponent
|
||||
///
|
||||
public interface IAnimancerComponent
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#pragma warning disable IDE1006 // Naming Styles.
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Will this component be updated?</summary>
|
||||
bool enabled { get; }
|
||||
|
||||
/// <summary>The <see cref="GameObject"/> this component is attached to.</summary>
|
||||
GameObject gameObject { get; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#pragma warning restore IDE1006 // Naming Styles.
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The <see cref="UnityEngine.Animator"/> component which this script controls.</summary>
|
||||
Animator Animator { get; set; }
|
||||
|
||||
/// <summary>The internal system which manages the playing animations.</summary>
|
||||
AnimancerGraph Graph { get; }
|
||||
|
||||
/// <summary>Has the <see cref="Graph"/> been initialized?</summary>
|
||||
bool IsGraphInitialized { get; }
|
||||
|
||||
/// <summary>Will the object be reset to its original values when disabled?</summary>
|
||||
bool ResetOnDisable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines when animations are updated and which time source is used. This property is mainly a wrapper
|
||||
/// around the <see cref="Animator.updateMode"/>.
|
||||
/// </summary>
|
||||
AnimatorUpdateMode UpdateMode { get; set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns the dictionary key to use for the `clip`.</summary>
|
||||
object GetKey(AnimationClip clip);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#if UNITY_EDITOR
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Only] The name of the serialized backing field for the <see cref="Animator"/> property.</summary>
|
||||
string AnimatorFieldName { get; }
|
||||
|
||||
/// <summary>[Editor-Only]
|
||||
/// The name of the serialized backing field for the <see cref="AnimancerComponent.ActionOnDisable"/> property.
|
||||
/// </summary>
|
||||
string ActionOnDisableFieldName { get; }
|
||||
|
||||
/// <summary>[Editor-Only] The <see cref="UpdateMode"/> that was first used when this script initialized.</summary>
|
||||
/// <remarks>
|
||||
/// This is used to give a warning when changing to or from <see cref="AnimatorUpdateMode.AnimatePhysics"/> at
|
||||
/// runtime since it won't work correctly.
|
||||
/// </remarks>
|
||||
AnimatorUpdateMode? InitialUpdateMode { get; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endif
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8a65e009476d8f845836b9b0439dc8f3
|
||||
labels:
|
||||
- Interface
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,265 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>
|
||||
/// A variant of <see cref="IAnimationClipSource"/> which uses a <see cref="ICollection{T}"/>
|
||||
/// instead of a <see cref="List{T}"/> so that it can take a <see cref="HashSet{T}"/>
|
||||
/// to efficiently avoid adding duplicates. <see cref="AnimancerUtilities"/> contains
|
||||
/// various extension methods for this purpose.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="IAnimationClipSource"/> still needs to be the main point of entry
|
||||
/// for the Animation Window, so this interface is only used internally.
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/IAnimationClipCollection
|
||||
///
|
||||
public interface IAnimationClipCollection
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Adds all the animations associated with this object to the `clips`.</summary>
|
||||
void GatherAnimationClips(ICollection<AnimationClip> clips);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/AnimancerUtilities
|
||||
public static partial class AnimancerUtilities
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Animancer Extension]
|
||||
/// Adds the `clip` to the `clips` if it wasn't there already.
|
||||
/// </summary>
|
||||
public static void Gather(this ICollection<AnimationClip> clips, AnimationClip clip)
|
||||
{
|
||||
if (clip != null && !clips.Contains(clip))
|
||||
clips.Add(clip);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Animancer Extension]
|
||||
/// Calls <see cref="Gather(ICollection{AnimationClip}, AnimationClip)"/> for each of the `newClips`.
|
||||
/// </summary>
|
||||
public static void Gather(this ICollection<AnimationClip> clips, IList<AnimationClip> gatherFrom)
|
||||
{
|
||||
if (gatherFrom == null)
|
||||
return;
|
||||
|
||||
for (int i = gatherFrom.Count - 1; i >= 0; i--)
|
||||
clips.Gather(gatherFrom[i]);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Animancer Extension]
|
||||
/// Calls <see cref="Gather(ICollection{AnimationClip}, AnimationClip)"/> for each of the `newClips`.
|
||||
/// </summary>
|
||||
public static void Gather(this ICollection<AnimationClip> clips, IEnumerable<AnimationClip> gatherFrom)
|
||||
{
|
||||
if (gatherFrom == null)
|
||||
return;
|
||||
|
||||
foreach (var clip in gatherFrom)
|
||||
clips.Gather(clip);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Animancer Extension]
|
||||
/// Calls <see cref="Gather(ICollection{AnimationClip}, AnimationClip)"/> for each clip in the `asset`.
|
||||
/// </summary>
|
||||
public static void GatherFromAsset(this ICollection<AnimationClip> clips, PlayableAsset asset)
|
||||
{
|
||||
if (asset == null)
|
||||
return;
|
||||
|
||||
// We want to get the tracks out of a TimelineAsset without actually referencing that class directly
|
||||
// because it comes from an optional package and Animancer does not need to depend on that package.
|
||||
|
||||
var method = asset.GetType().GetMethod("GetRootTracks");
|
||||
if (method != null &&
|
||||
typeof(IEnumerable).IsAssignableFrom(method.ReturnType) &&
|
||||
method.GetParameters().Length == 0)
|
||||
{
|
||||
var rootTracks = method.Invoke(asset, null);
|
||||
GatherFromTracks(clips, rootTracks as IEnumerable);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Gathers all the animations in the `tracks`.</summary>
|
||||
private static void GatherFromTracks(ICollection<AnimationClip> clips, IEnumerable tracks)
|
||||
{
|
||||
if (tracks.IsNullOrDestroyed())
|
||||
return;
|
||||
|
||||
foreach (var track in tracks)
|
||||
{
|
||||
if (track.IsNullOrDestroyed())
|
||||
continue;
|
||||
|
||||
var trackType = track.GetType();
|
||||
|
||||
var getClips = trackType.GetMethod("GetClips");
|
||||
if (getClips != null &&
|
||||
typeof(IEnumerable).IsAssignableFrom(getClips.ReturnType) &&
|
||||
getClips.GetParameters().Length == 0)
|
||||
{
|
||||
if (getClips.Invoke(track, null) is IEnumerable trackClips)
|
||||
{
|
||||
foreach (var clip in trackClips)
|
||||
{
|
||||
var animationClip = clip.GetType().GetProperty("animationClip");
|
||||
if (animationClip != null &&
|
||||
animationClip.PropertyType == typeof(AnimationClip))
|
||||
{
|
||||
var getClip = animationClip.GetGetMethod();
|
||||
clips.Gather(getClip.Invoke(clip, null) as AnimationClip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var getChildTracks = trackType.GetMethod("GetChildTracks");
|
||||
if (getChildTracks != null &&
|
||||
typeof(IEnumerable).IsAssignableFrom(getChildTracks.ReturnType) &&
|
||||
getChildTracks.GetParameters().Length == 0)
|
||||
{
|
||||
var childTracks = getChildTracks.Invoke(track, null);
|
||||
GatherFromTracks(clips, childTracks as IEnumerable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Animancer Extension]
|
||||
/// Calls <see cref="Gather(ICollection{AnimationClip}, AnimationClip)"/>
|
||||
/// for each clip gathered by <see cref="IAnimationClipSource.GetAnimationClips"/>.
|
||||
/// </summary>
|
||||
public static void GatherFromSource(this ICollection<AnimationClip> clips, IAnimationClipSource source)
|
||||
{
|
||||
if (source.IsNullOrDestroyed())
|
||||
return;
|
||||
|
||||
var list = ListPool.Acquire<AnimationClip>();
|
||||
source.GetAnimationClips(list);
|
||||
clips.Gather(list);
|
||||
ListPool.Release(list);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Animancer Extension]
|
||||
/// Calls <see cref="GatherFromSource(ICollection{AnimationClip}, object)"/> for each item in the `source`.
|
||||
/// </summary>
|
||||
public static void GatherFromSource(this ICollection<AnimationClip> clips, IEnumerable source)
|
||||
{
|
||||
if (source.IsNullOrDestroyed() ||
|
||||
AnimationGathererRecursionGuard.DontGatherFrom.Contains(source.GetType()))
|
||||
return;
|
||||
|
||||
using var _ = AnimationGathererRecursionGuard.Begin();
|
||||
if (AnimationGathererRecursionGuard.HasCheckedObject(source))
|
||||
return;
|
||||
|
||||
foreach (var item in source)
|
||||
clips.GatherFromSource(item);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Animancer Extension]
|
||||
/// Calls <see cref="Gather(ICollection{AnimationClip}, AnimationClip)"/>
|
||||
/// for each clip in the `source`, supporting both
|
||||
/// <see cref="IAnimationClipSource"/> and <see cref="IAnimationClipCollection"/>.
|
||||
/// </summary>
|
||||
public static bool GatherFromSource(this ICollection<AnimationClip> clips, object source)
|
||||
{
|
||||
if (source.IsNullOrDestroyed() ||
|
||||
AnimationGathererRecursionGuard.DontGatherFrom.Contains(source.GetType()))
|
||||
return false;
|
||||
|
||||
using var _ = AnimationGathererRecursionGuard.Begin();
|
||||
if (AnimationGathererRecursionGuard.HasCheckedObject(source))
|
||||
return false;
|
||||
|
||||
if (TryGetWrappedObject(source, out AnimationClip clip))
|
||||
{
|
||||
clips.Gather(clip);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TryGetWrappedObject(source, out IAnimationClipCollection collectionSource))
|
||||
{
|
||||
collectionSource.GatherAnimationClips(clips);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TryGetWrappedObject(source, out IAnimationClipSource listSource))
|
||||
{
|
||||
clips.GatherFromSource(listSource);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TryGetWrappedObject(source, out IEnumerable enumerable))
|
||||
{
|
||||
clips.GatherFromSource(enumerable);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the <see cref="AnimationClip.frameRate"/> from the `clipSource`
|
||||
/// and returns true if successful. If it has multiple animations with different rates,
|
||||
/// this method returns false.
|
||||
/// </summary>
|
||||
public static bool TryGetFrameRate(object clipSource, out float frameRate)
|
||||
{
|
||||
using (SetPool<AnimationClip>.Instance.Acquire(out var clips))
|
||||
{
|
||||
clips.GatherFromSource(clipSource);
|
||||
if (clips.Count == 0)
|
||||
{
|
||||
frameRate = float.NaN;
|
||||
return false;
|
||||
}
|
||||
|
||||
frameRate = float.NaN;
|
||||
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
if (float.IsNaN(frameRate))
|
||||
{
|
||||
frameRate = clip.frameRate;
|
||||
}
|
||||
else if (frameRate != clip.frameRate)
|
||||
{
|
||||
frameRate = float.NaN;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return frameRate > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 425f4aed596b8a04b9c0f4ec49d37789
|
||||
labels:
|
||||
- Interface
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,203 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for components to indicate which <see cref="GameObject"/> is the root of a character when
|
||||
/// <see cref="AnimancerUtilities.FindRoot(GameObject)"/> is called.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/ICharacterRoot
|
||||
///
|
||||
public interface ICharacterRoot
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#pragma warning disable IDE1006 // Naming Styles.
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Transform"/> to search for <see cref="AnimationClip"/>s beneath.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <strong>Example:</strong>
|
||||
/// Implementing this interface in a <see cref="MonoBehaviour"/>
|
||||
/// will automatically inherit this property so you don't need to do anything else:
|
||||
/// <para></para><code>
|
||||
/// public class MyComponent : MonoBehaviour, ICharacterRoot
|
||||
/// {
|
||||
/// }
|
||||
/// </code>
|
||||
/// But if you want to have your script point to a different object as the root,
|
||||
/// you can explicitly implement this property:
|
||||
/// <para></para><code>
|
||||
/// public class MyComponent : MonoBehaviour, ICharacterRoot
|
||||
/// {
|
||||
/// Transform ICharacterRoot.transform => ???;
|
||||
/// }
|
||||
/// </code></remarks>
|
||||
Transform transform { get; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#pragma warning restore IDE1006 // Naming Styles.
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#if UNITY_EDITOR
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/AnimancerUtilities
|
||||
public static partial class AnimancerUtilities
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Only]
|
||||
/// Takes a `gameObject` and returns the root <see cref="Transform"/> of the character it is part of.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// This method first searches all parents for a component implementing <see cref="ICharacterRoot"/>.
|
||||
/// If it finds one, it returns the <see cref="ICharacterRoot.transform"/>.
|
||||
/// <para></para>
|
||||
/// Otherwise, if the object is part of a prefab then it returns the root of that prefab instance.
|
||||
/// <para></para>
|
||||
/// Otherwise, it counts the number of Animators in the children of the `gameObject` then does
|
||||
/// the same for each parent. If it finds a parent with a different number of child Animators, it
|
||||
/// assumes that object is the parent of multiple characters and returns the previous parent as the root.
|
||||
/// </remarks>
|
||||
///
|
||||
/// <example>
|
||||
/// <h2>Simple Hierarchy</h2>
|
||||
/// <code>
|
||||
/// - Character - Rigidbody, etc.
|
||||
/// - Model - Animator, AnimancerComponent
|
||||
/// - States - Various components which reference the AnimationClips they will play
|
||||
/// </code>
|
||||
/// Passing the <c>Model</c> into this method will return the <c>Character</c> because it has the same
|
||||
/// number of Animator components in its children.
|
||||
///
|
||||
/// <h2>Shared Hierarchy</h2>
|
||||
/// <code>
|
||||
/// - Characters - Empty object used to group all characters
|
||||
/// - Character - Rigidbody, etc.
|
||||
/// - Model - Animator, AnimancerComponent
|
||||
/// - States - Various components which reference the AnimationClips they will play
|
||||
/// - Another Character
|
||||
/// - Model
|
||||
/// - States
|
||||
/// </code>
|
||||
/// <list type="bullet">
|
||||
/// <item><c>Model</c> has one Animator and no more in its children.</item>
|
||||
/// <item>And <c>Character</c> has one Animator in its children (the same one).</item>
|
||||
/// <item>But <c>Characters</c> has two Animators in its children (one on each character).</item>
|
||||
/// </list>
|
||||
/// So it picks the <c>Character</c> as the root.
|
||||
///
|
||||
/// <h2>Complex Hierarchy</h2>
|
||||
/// <code>
|
||||
/// - Character - Rigidbody, etc.
|
||||
/// - Model - Animator, AnimancerComponent
|
||||
/// - States - Various components which reference the AnimationClips they will play
|
||||
/// - Another Model - Animator (maybe the character is holding a gun which has a reload animation)
|
||||
/// </code>
|
||||
/// In this case, the automatic system would see that the <c>Character</c> already has more child
|
||||
/// <see cref="Animator"/>s than the selected <c>Model</c> so it would only return the <c>Model</c> itself.
|
||||
/// This can be fixed by making any of the scripts on the <c>Character</c> implement <see cref="ICharacterRoot"/>
|
||||
/// to tell the system which object you want it to use as the root.
|
||||
/// </example>
|
||||
public static Transform FindRoot(GameObject gameObject)
|
||||
{
|
||||
var root = gameObject.GetComponentInParent<ICharacterRoot>();
|
||||
if (root != null)
|
||||
return root.transform;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
var path = UnityEditor.AssetDatabase.GetAssetPath(gameObject);
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
return gameObject.transform.root;
|
||||
|
||||
var status = UnityEditor.PrefabUtility.GetPrefabInstanceStatus(gameObject);
|
||||
if (status != UnityEditor.PrefabInstanceStatus.NotAPrefab)
|
||||
{
|
||||
gameObject = UnityEditor.PrefabUtility.GetOutermostPrefabInstanceRoot(gameObject);
|
||||
return gameObject.transform;
|
||||
}
|
||||
#endif
|
||||
|
||||
var animators = ListPool.Acquire<Animator>();
|
||||
gameObject.GetComponentsInChildren(true, animators);
|
||||
var animatorCount = animators.Count;
|
||||
|
||||
var parent = gameObject.transform;
|
||||
while (parent.parent != null)
|
||||
{
|
||||
animators.Clear();
|
||||
parent.parent.GetComponentsInChildren(true, animators);
|
||||
|
||||
if (animatorCount == 0)
|
||||
animatorCount = animators.Count;
|
||||
else if (animatorCount != animators.Count)
|
||||
break;
|
||||
|
||||
parent = parent.parent;
|
||||
}
|
||||
ListPool.Release(animators);
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Only]
|
||||
/// Calls <see cref="FindRoot(GameObject)"/> if the specified `obj` is a
|
||||
/// <see cref="GameObject"/> or <see cref="Component"/>.
|
||||
/// </summary>
|
||||
public static Transform FindRoot(Object obj)
|
||||
{
|
||||
if (obj is ICharacterRoot iRoot)
|
||||
return iRoot.transform;
|
||||
|
||||
return TryGetGameObject(obj, out var gameObject)
|
||||
? FindRoot(gameObject)
|
||||
: null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Only]
|
||||
/// Outputs the <see cref="GameObject"/> assignated with the `obj` and returns true if it exists.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the `obj` is a <see cref="GameObject"/> it is used as the result.
|
||||
/// <para></para>
|
||||
/// Or if the `obj` is a <see cref="Component"/> then its <see cref="Component.gameObject"/>
|
||||
/// is used as the result.
|
||||
/// </remarks>
|
||||
public static bool TryGetGameObject(Object obj, out GameObject gameObject)
|
||||
{
|
||||
if (obj is GameObject go)
|
||||
{
|
||||
gameObject = go;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj is Component component)
|
||||
{
|
||||
gameObject = component.gameObject;
|
||||
return true;
|
||||
}
|
||||
|
||||
gameObject = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endif
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0ce56961b7e72345913686a9ffed4be
|
||||
labels:
|
||||
- Interface
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,315 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
//#define ASSERT_CLONE_TYPES
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>An object that can be cloned.</summary>
|
||||
/// <remarks>See <see cref="Clone(CloneContext)"/> for example usage.</remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/ICloneable_1
|
||||
public interface ICloneable<out T>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new object with the same type and values this.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// Calling this method directly is not generally recommended.
|
||||
/// Use <see cref="CloneableExtensions.Clone{T}(ICloneable{T})"/> if you don't have a `context`
|
||||
/// or <see cref="CloneContext.GetOrCreateClone{T}(ICloneable{T})"/> if you do have one.
|
||||
/// <para></para>
|
||||
/// <strong>Example:</strong><code>
|
||||
/// class BaseClass : ICloneable
|
||||
/// {
|
||||
/// // Explicit implementation so that the recommended methods will be used instead.
|
||||
/// object ICloneable.Clone(CloneContext context)
|
||||
/// {
|
||||
/// var clone = (BaseClass)MemberwiseClone();
|
||||
/// clone.CopyFrom(this, context);
|
||||
/// return clone;
|
||||
/// }
|
||||
///
|
||||
/// // Protected method which should only be called by Clone.
|
||||
/// protected virtual void InitializeClone(CloneContext context)
|
||||
/// {
|
||||
/// // Alter any necessary BaseClass fields according to the context.
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// class DerivedClass : BaseClass
|
||||
/// {
|
||||
/// protected override void InitializeClone(CloneContext context)
|
||||
/// {
|
||||
/// base.CopyFrom(copyFrom, context);
|
||||
///
|
||||
/// var derived = (DerivedClass)copyFrom;
|
||||
/// // Alter any necessary DerivedClass fields according to the context.
|
||||
/// }
|
||||
/// }
|
||||
/// </code></remarks>
|
||||
T Clone(CloneContext context);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/// <summary>Extension methods for <see cref="ICloneable{T}"/>.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/CloneableExtensions
|
||||
public static partial class CloneableExtensions
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Calls <see cref="ICloneable{T}.Clone"/> using a <see cref="CloneContext"/> from the
|
||||
/// <see cref="CloneContext.Pool"/> and casts the result.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns <c>null</c> if the `original` is <c>null</c>.
|
||||
/// <para></para>
|
||||
/// Use <see cref="CloneContext.GetOrCreateClone{T}(ICloneable{T})"/>
|
||||
/// if you already have a <see cref="CloneContext"/>.
|
||||
/// </remarks>
|
||||
public static T Clone<T>(this ICloneable<T> original)
|
||||
{
|
||||
if (original == null)
|
||||
return default;
|
||||
|
||||
var context = CloneContext.Pool.Instance.Acquire();
|
||||
var clone = original.Clone(context);
|
||||
CloneContext.Pool.Instance.Release(context);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Assert-Conditional] Asserts that the `clone` has the same type as the `original`.</summary>
|
||||
[System.Diagnostics.Conditional(Strings.Assertions)]
|
||||
internal static void AssertCloneType<T>(this ICloneable<T> original, object clone)
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
var cloneType = clone.GetType();
|
||||
var originalType = original.GetType();
|
||||
if (cloneType != originalType)
|
||||
Debug.LogError($"Cloned object type ({cloneType.FullName}" +
|
||||
$" doesn't match original {originalType.FullName}." +
|
||||
$"\n• Original: {original}" +
|
||||
$"\n• Clone: {clone}");
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/// <summary>A dictionary which maps objects to their copies.</summary>
|
||||
/// <remarks>
|
||||
/// This class is used to clone complex object trees with potentially interconnected references so that
|
||||
/// references to original objects can be replaced with references to their equivalent clones.
|
||||
/// </remarks>
|
||||
public class CloneContext : Dictionary<object, object>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Will the <see cref="IUpdatable"/>s be cloned as part of the current operation?</summary>
|
||||
/// <remarks>
|
||||
/// This is used to prevent <see cref="AnimancerNode"/>s from cloning their <see cref="FadeGroup"/>s
|
||||
/// individually while cloning a whole <see cref="AnimancerGraph"/> because it will clone the whole groups
|
||||
/// after cloning all the nodes.
|
||||
/// </remarks>
|
||||
public bool WillCloneUpdatables { get; set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#region Pooling
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>An <see cref="ObjectPool{T}"/> for <see cref="CloneContext"/>.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/Pool
|
||||
public class Pool : ObjectPool<CloneContext>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Singleton.</summary>
|
||||
public static Pool Instance = new();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override CloneContext New()
|
||||
=> new();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override CloneContext Acquire()
|
||||
{
|
||||
var context = base.Acquire();
|
||||
CollectionPool<KeyValuePair<object, object>, CloneContext>.AssertEmpty(context);
|
||||
return context;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Release(CloneContext context)
|
||||
{
|
||||
context.Clear();
|
||||
base.Release(context);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value registered using `original` as its key if there is one.
|
||||
/// Otherwise, calls <see cref="CloneableExtensions.Clone"/>, adds the clone to this dictionary, and returns it.
|
||||
/// </summary>
|
||||
public T GetOrCreateClone<T>(ICloneable<T> original)
|
||||
{
|
||||
if (original == null)
|
||||
return default;
|
||||
|
||||
if (TryGetValue(original, out var value))
|
||||
return (T)value;
|
||||
|
||||
return Clone(original);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value registered using `original` as its key if there is one.
|
||||
/// Otherwise, if the `original` is <see cref="ICloneable{T}"/> it calls <see cref="CloneableExtensions.Clone"/>,
|
||||
/// adds the clone to this dictionary, and returns it.
|
||||
/// Otherwise, just returns the `original`.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If <typeparamref name="T"/> is <see cref="ICloneable{T}"/>,
|
||||
/// use <see cref="GetOrCreateClone{T}(ICloneable{T})"/> instead.
|
||||
/// </remarks>
|
||||
public T GetOrCreateCloneOrOriginal<T>(T original)
|
||||
{
|
||||
TryGetOrCreateCloneOrOriginal(original, out original);
|
||||
return original;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns <c>true</c> if there is a `clone` registered for the `original`.
|
||||
/// Otherwise, if the `original` is <see cref="ICloneable{T}"/> it calls <see cref="CloneableExtensions.Clone"/>,
|
||||
/// adds the `clone` to this dictionary, and returns <c>true</c>.
|
||||
/// Otherwise, outputs the `original` as the `clone` and returns <c>false</c>.
|
||||
/// </summary>
|
||||
/// <remarks>Outputs <c>null</c> and returns <c>true</c> if the `original` is <c>null</c>.</remarks>
|
||||
public bool TryGetOrCreateCloneOrOriginal<T>(T original, out T clone)
|
||||
{
|
||||
if (original == null)
|
||||
{
|
||||
clone = default;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TryGetValue(original, out var value))
|
||||
{
|
||||
clone = (T)value;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (original is ICloneable<T> cloneable)
|
||||
{
|
||||
clone = Clone(cloneable);
|
||||
return true;
|
||||
}
|
||||
|
||||
clone = original;
|
||||
return false;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Calls <see cref="ICloneable{T}.Clone"/> and registers the clone.</summary>
|
||||
/// <exception cref="System.ArgumentException">A clone is already registered for the `original`.</exception>
|
||||
public T Clone<T>(ICloneable<T> original)
|
||||
{
|
||||
var clone = original.Clone(this);
|
||||
if (clone != null)
|
||||
{
|
||||
original.AssertCloneType(clone);
|
||||
Add(original, clone);
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns the clone of the `original` if one was registered. Otherwise, throws.</summary>
|
||||
/// <exception cref="KeyNotFoundException">No clone of the `original` is registered.</exception>
|
||||
public T GetClone<T>(T original)
|
||||
=> (T)this[original];
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns the clone of the `original` if one is registered. Otherwise, returns the `original`.</summary>
|
||||
public T GetCloneOrOriginal<T>(T original)
|
||||
=> original != null && TryGetValue(original, out var value)
|
||||
? (T)value
|
||||
: original;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Replaces the `item` with its clone and returns true if one is registered.</summary>
|
||||
public bool TryGetClone<T>(ref T item)
|
||||
{
|
||||
if (item == null)
|
||||
return false;
|
||||
|
||||
if (!TryGetValue(item, out var value) ||
|
||||
value is not T valueT)
|
||||
{
|
||||
item = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
item = valueT;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Calls <see cref="Dictionary{TKey, TValue}.TryGetValue(TKey, out TValue)"/> and casts the result.</summary>
|
||||
public bool TryGetClone<T>(T original, out T clone)
|
||||
{
|
||||
clone = original;
|
||||
return TryGetClone(ref clone);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the `destination` has the same size as `source`
|
||||
/// then clones all its items to the `destination`.
|
||||
/// </summary>
|
||||
public void CloneArray<T>(T[] source, ref T[] destination)
|
||||
where T : class
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
destination = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (destination == null || destination.Length != source.Length)
|
||||
destination = new T[source.Length];
|
||||
|
||||
for (int i = 0; i < source.Length; i++)
|
||||
{
|
||||
var item = source[i];
|
||||
destination[i] = item != null ? GetCloneOrOriginal(item) : null;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 522bd2533176e0a4981c7545b7066f05
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,133 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>An object which can be converted to another type.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/IConvertable_1
|
||||
public interface IConvertable<out T>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns the equivalent of this object as <typeparamref name="T"/>.</summary>
|
||||
T Convert();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/// <summary>Utility methods for <see cref="IConvertable{T}"/>.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/ConvertableUtilities
|
||||
public static partial class ConvertableUtilities
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Custom conversion functions used as a fallback
|
||||
/// for types that can't implement <see cref="IConvertable{T}"/>.
|
||||
/// </summary>
|
||||
public static readonly Dictionary<Type, Func<object, Type, object>>
|
||||
CustomConverters = new();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Tries to convert the `original` to <typeparamref name="T"/>.</summary>
|
||||
/// <remarks>
|
||||
/// <list type="bullet">
|
||||
/// <item>If the `original` is already a <typeparamref name="T"/> then it's returned directly.</item>
|
||||
/// <item>Or if it's an <see cref="IConvertable{T}"/> then <see cref="IConvertable{T}.Convert"/> is used.</item>
|
||||
/// <item>Otherwise, this method throws an <see cref="ArgumentException"/>.</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public static T ConvertOrThrow<T>(object original)
|
||||
{
|
||||
if (TryConvert<T>(original, out var converted))
|
||||
return converted;
|
||||
|
||||
throw new ArgumentException(
|
||||
$"Unable to convert '{AnimancerUtilities.ToStringOrNull(original)}'" +
|
||||
$" to '{typeof(T).GetNameCS()}'.");
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Tries to convert the `original` to <typeparamref name="T"/>.</summary>
|
||||
/// <remarks>
|
||||
/// <list type="bullet">
|
||||
/// <item>If the `original` is already a <typeparamref name="T"/> then it's returned directly.</item>
|
||||
/// <item>Or if it's an <see cref="IConvertable{T}"/> then <see cref="IConvertable{T}.Convert"/> is used.</item>
|
||||
/// <item>Otherwise, this method returns the <c>default(T)</c>.</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public static T ConvertOrDefault<T>(object original)
|
||||
{
|
||||
TryConvert<T>(original, out var converted);
|
||||
return converted;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Tries to convert the `original` to <typeparamref name="T"/>.</summary>
|
||||
/// <remarks>
|
||||
/// <list type="bullet">
|
||||
/// <item>If the `original` is already a <typeparamref name="T"/> then it's returned directly.</item>
|
||||
/// <item>Or if it's an <see cref="IConvertable{T}"/> then <see cref="IConvertable{T}.Convert"/> is used.</item>
|
||||
/// <item>Otherwise, this method returns <c>false</c>.</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public static bool TryConvert<T>(object original, out T converted)
|
||||
{
|
||||
if (original is null)
|
||||
{
|
||||
converted = default;
|
||||
return converted == null;// True for value type, false for reference type.
|
||||
}
|
||||
|
||||
if (original is T t)
|
||||
{
|
||||
converted = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (original is IConvertable<T> convertable)
|
||||
{
|
||||
converted = convertable.Convert();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (CustomConverters.TryGetValue(original.GetType(), out var converter))
|
||||
{
|
||||
converted = (T)converter(original, typeof(T));
|
||||
return converted != null;
|
||||
}
|
||||
|
||||
converted = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Initializes the inbuilt custom converters.</summary>
|
||||
static ConvertableUtilities()
|
||||
{
|
||||
CustomConverters.Add(typeof(GameObject), TryGetComponent);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Tries to get a component if the `original` is a <see cref="GameObject"/>.</summary>
|
||||
private static object TryGetComponent(object original, Type type)
|
||||
{
|
||||
if (original is GameObject gameObject &&
|
||||
gameObject.TryGetComponent(type, out var component))
|
||||
return component;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 494e170250d1e9b43933f8cd65181646
|
||||
labels:
|
||||
- Interface
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,82 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>An object that can be copied.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/ICopyable_1
|
||||
public interface ICopyable<in T>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Copies the contents of `copyFrom` into this object, replacing its previous contents.</summary>
|
||||
void CopyFrom(T copyFrom, CloneContext context);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/// <summary>Extension methods for <see cref="ICopyable{T}"/>.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/CopyableExtensions
|
||||
public static partial class CopyableExtensions
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Calls <see cref="ICopyable{T}.CopyFrom"/>
|
||||
/// using a <see cref="CloneContext"/> from the <see cref="CloneContext.Pool"/>.
|
||||
/// </summary>
|
||||
public static void CopyFrom<T>(this T copyTo, T copyFrom)
|
||||
where T : ICopyable<T>
|
||||
{
|
||||
var context = CloneContext.Pool.Instance.Acquire();
|
||||
copyTo.CopyFrom(copyFrom, context);
|
||||
CloneContext.Pool.Instance.Release(context);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <typeparamref name="T"/> and calls <see cref="ICopyable{T}.CopyFrom"/>.
|
||||
/// </summary>
|
||||
public static T CopyableClone<T>(this T original, CloneContext context)
|
||||
where T : ICopyable<T>
|
||||
{
|
||||
if (original == null)
|
||||
return default;
|
||||
|
||||
var clone = (T)Activator.CreateInstance(original.GetType());
|
||||
clone.CopyFrom(original, context);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <typeparamref name="T"/> and calls <see cref="ICopyable{T}.CopyFrom"/>
|
||||
/// using a <see cref="CloneContext"/> from the <see cref="CloneContext.Pool"/>.
|
||||
/// </summary>
|
||||
public static T CopyableClone<T>(this T original)
|
||||
where T : ICopyable<T>
|
||||
{
|
||||
var context = CloneContext.Pool.Instance.Acquire();
|
||||
var clone = original.CopyableClone(context);
|
||||
CloneContext.Pool.Instance.Release(context);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Calls <see cref="ICopyable{T}.CopyFrom"/> using the appropriate type.</summary>
|
||||
public static void CopyFromBase<TChild, TBase>(this TChild copyTo, TBase copyFrom, CloneContext context)
|
||||
where TChild : ICopyable<TChild>, ICopyable<TBase>
|
||||
where TBase : ICopyable<TBase>
|
||||
{
|
||||
if (copyFrom is TChild copyFromChild)
|
||||
copyTo.CopyFrom(copyFromChild, context);
|
||||
else
|
||||
copyTo.CopyFrom(copyFrom, context);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 79cb441c59981cb4d834d3c7c331c6c9
|
||||
labels:
|
||||
- Interface
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,111 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>An object which can give a detailed description of itself.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/IHasDescription
|
||||
///
|
||||
public interface IHasDescription
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Appends a detailed descrption of the current details of this object.</summary>
|
||||
/// <remarks>
|
||||
/// <see cref="AnimancerUtilities.GetDescription"/> calls this method with a pooled
|
||||
/// <see cref="StringBuilder"/>.
|
||||
/// </remarks>
|
||||
void AppendDescription(StringBuilder text, string separator = "\n");
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/AnimancerUtilities
|
||||
///
|
||||
public static partial class AnimancerUtilities
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Calls <see cref="IHasDescription.AppendDescription"/> with a pooled <see cref="StringBuilder"/>.
|
||||
/// </summary>
|
||||
public static string GetDescription(
|
||||
this IHasDescription hasDescription,
|
||||
string separator = "\n")
|
||||
{
|
||||
if (hasDescription == null)
|
||||
return "Null";
|
||||
|
||||
var text = StringBuilderPool.Instance.Acquire();
|
||||
hasDescription.AppendDescription(text, separator);
|
||||
return text.ReleaseToString();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Appends "Null" if `maybeHasDescription` is null. Otherwise calls
|
||||
/// <see cref="IHasDescription.AppendDescription"/>.
|
||||
/// </summary>
|
||||
public static StringBuilder AppendDescription<T>(
|
||||
this StringBuilder text,
|
||||
T maybeHasDescription,
|
||||
string separator = "\n",
|
||||
bool fullNodeDescription = false)
|
||||
=> maybeHasDescription is IHasDescription hasDescription
|
||||
? text.AppendDescription(hasDescription, separator, fullNodeDescription)
|
||||
: text.Append(ToStringOrNull(maybeHasDescription));
|
||||
|
||||
/// <summary>
|
||||
/// Appends "Null" if `hasDescription` is null. Otherwise calls
|
||||
/// <see cref="IHasDescription.AppendDescription"/>.
|
||||
/// </summary>
|
||||
public static StringBuilder AppendDescription(
|
||||
this StringBuilder text,
|
||||
IHasDescription hasDescription,
|
||||
string separator = "\n",
|
||||
bool fullNodeDescription = false)
|
||||
{
|
||||
if (hasDescription == null)
|
||||
return text.Append("Null");
|
||||
|
||||
if (!fullNodeDescription && hasDescription is AnimancerNode node)
|
||||
return text.Append(node.GetPath());
|
||||
|
||||
hasDescription.AppendDescription(text, separator);
|
||||
return text;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Appends <c>{prefix}{name}: {value}</c>.</summary>
|
||||
public static StringBuilder AppendField<T>(
|
||||
this StringBuilder text,
|
||||
string prefix,
|
||||
string name,
|
||||
T value,
|
||||
string separator = "\n",
|
||||
bool fullNodeDescription = false)
|
||||
=> text
|
||||
.Append(prefix)
|
||||
.Append(name)
|
||||
.Append(": ")
|
||||
.AppendDescription(value, separator, fullNodeDescription);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Does the `text` start with a new line character?</summary>
|
||||
public static bool StartsWithNewLine(this string text)
|
||||
{
|
||||
if (text == null || text.Length == 0)
|
||||
return false;
|
||||
|
||||
var start = text[0];
|
||||
return start == '\n' || start == '\r';
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e1432005918ff746bfad8bf98a74a0f
|
||||
labels:
|
||||
- Interface
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>An object which has an <see cref="AnimancerEvent.Sequence.Serializable"/>.</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer">
|
||||
/// Animancer Events</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/IHasEvents
|
||||
public interface IHasEvents
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Events which will be triggered as the animation plays.</summary>
|
||||
AnimancerEvent.Sequence Events { get; }
|
||||
|
||||
/// <summary>Events which will be triggered as the animation plays.</summary>
|
||||
AnimancerEvent.Sequence.Serializable SerializedEvents { get; set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 80d48ef5d29e8294a8cd83260dfcdb8b
|
||||
labels:
|
||||
- Interface
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>
|
||||
/// An object with a <see cref="Key"/> which can be used in dictionaries and hash sets.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/playing/keys">
|
||||
/// Keys</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/IHasKey
|
||||
public interface IHasKey
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>A key which can be used in dictionaries and hash sets.</summary>
|
||||
object Key { get; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1b8aa66f0b587ce42bfad1625ac75b74
|
||||
labels:
|
||||
- Interface
|
||||
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 //
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>An object with an <see cref="Initialize(T)"/> method.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/IInitializable_1
|
||||
public interface IInitializable<T>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Initializes this object.</summary>
|
||||
void Initialize(T details);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e8eb7147c2f996f42a93d5a8f27fb4d5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>An object with an <see cref="Invoke"/> method.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/IInvokable
|
||||
#if UNITY_ASSERTIONS
|
||||
[AnimancerHelpUrl(Strings.DocsURLs.AnimancerEventParameters)]
|
||||
#endif
|
||||
public interface IInvokable : IPolymorphic
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Executes the main function of this object.</summary>
|
||||
void Invoke();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6bd555edaea668e40b666b8bf0a326eb
|
||||
labels:
|
||||
- Interface
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,77 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>An object with an <see cref="AverageAngularSpeed"/> and <see cref="AverageVelocity"/>.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/IMotion
|
||||
///
|
||||
public interface IMotion
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The initial <see cref="Motion.averageAngularSpeed"/> that the created state will have.</summary>
|
||||
/// <remarks>The actual average can vary in states like <see cref="ManualMixerState"/>.</remarks>
|
||||
float AverageAngularSpeed { get; }
|
||||
|
||||
/// <summary>The initial <see cref="Motion.averageSpeed"/> that the created state will have.</summary>
|
||||
/// <remarks>The actual average can vary in states like <see cref="ManualMixerState"/>.</remarks>
|
||||
Vector3 AverageVelocity { get; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/AnimancerUtilities
|
||||
public static partial class AnimancerUtilities
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Outputs the <see cref="Motion.averageAngularSpeed"/> or <see cref="IMotion.AverageAngularSpeed"/>.</summary>
|
||||
/// <remarks>Returns false if the `motion` is null or an unsupported type.</remarks>
|
||||
public static bool TryGetAverageAngularSpeed(object motion, out float averageAngularSpeed)
|
||||
{
|
||||
if (motion is Motion unityMotion)
|
||||
{
|
||||
averageAngularSpeed = unityMotion.averageAngularSpeed;
|
||||
return true;
|
||||
}
|
||||
else if (AnimancerUtilities.TryGetWrappedObject(motion, out IMotion iMotion))
|
||||
{
|
||||
averageAngularSpeed = iMotion.AverageAngularSpeed;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
averageAngularSpeed = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Outputs the <see cref="Motion.averageSpeed"/> or <see cref="IMotion.AverageVelocity"/>.</summary>
|
||||
/// <remarks>Returns false if the `motion` is null or an unsupported type.</remarks>
|
||||
public static bool TryGetAverageVelocity(object motion, out Vector3 averageVelocity)
|
||||
{
|
||||
if (motion is Motion unityMotion)
|
||||
{
|
||||
averageVelocity = unityMotion.averageSpeed;
|
||||
return true;
|
||||
}
|
||||
else if (AnimancerUtilities.TryGetWrappedObject(motion, out IMotion iMotion))
|
||||
{
|
||||
averageVelocity = iMotion.AverageVelocity;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
averageVelocity = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 931e39069bbaaf94db1aaa3ae8822bb9
|
||||
labels:
|
||||
- Interface
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,41 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// An object that will be drawn by a <see cref="Editor.PolymorphicDrawer"/>
|
||||
/// which allows the user to select its type in the Inspector.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Implement this interface in a <see cref="UnityEditor.PropertyDrawer"/> to indicate that it
|
||||
/// should entirely replace the <see cref="Editor.PolymorphicDrawer"/>.
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/IPolymorphic
|
||||
public interface IPolymorphic { }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>An <see cref="IPolymorphic"/> with a <see cref="Reset"/> method.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/IPolymorphicReset
|
||||
public interface IPolymorphicReset : IPolymorphic
|
||||
{
|
||||
/// <summary>Called when an instance of this type is created in a [<see cref="SerializeReference"/>] field.</summary>
|
||||
void Reset(object oldValue = null);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// The attributed field will be drawn by a <see cref="Editor.PolymorphicDrawer"/>
|
||||
/// which allows the user to select its type in the Inspector.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/PolymorphicAttribute
|
||||
public sealed class PolymorphicAttribute : PropertyAttribute { }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c561d589eabe454fa62852dd84cbb14
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,110 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>An object which can create an <see cref="AnimancerState"/> and set its details.</summary>
|
||||
/// <remarks>
|
||||
/// Transitions are generally used as arguments for <see cref="AnimancerLayer.Play(ITransition)"/>.
|
||||
/// <para></para>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions">
|
||||
/// Transitions</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/ITransition
|
||||
public interface ITransition :
|
||||
IHasEvents,
|
||||
IHasKey,
|
||||
IPolymorphic
|
||||
// Most transition types return themselves as the IHasKey.Key, but some wrapper types like
|
||||
// TransitionAsset and TransitionAssetReference contain another transition which they want to
|
||||
// use as the key instead so all transitions must implement it so systems can support any type.
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
// Core Members - The main features required for a transition to be usable.
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The amount of time this transition should take (in seconds).</summary>
|
||||
float FadeDuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Animancer.FadeMode"/> which should be used when this transition is passed into
|
||||
/// <see cref="AnimancerLayer.Play(ITransition)"/>.
|
||||
/// </summary>
|
||||
FadeMode FadeMode { get; }
|
||||
|
||||
/// <summary>Creates and returns a new <see cref="AnimancerState"/> defuned by this transition.</summary>
|
||||
/// <remarks>
|
||||
/// The first time a transition is used on an object, this method creates a state
|
||||
/// which is registered in the internal dictionary using the <see cref="IHasKey.Key"/>
|
||||
/// so that it can be reused later on.
|
||||
/// <para></para>
|
||||
/// Methods like <see cref="AnimancerLayer.Play(ITransition)"/> will also call <see cref="Apply"/>,
|
||||
/// so if you call this method manually you may want to call that method as well.
|
||||
/// Or you can just use <see cref="AnimancerUtilities.CreateStateAndApply"/>.
|
||||
/// </remarks>
|
||||
AnimancerState CreateState();
|
||||
|
||||
/// <summary>Applies the details of this transition to the `state`.</summary>
|
||||
/// <remarks>This method is called by every <see cref="AnimancerLayer.Play(ITransition)"/>.</remarks>
|
||||
void Apply(AnimancerState state);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
// Extra Members - Additional features for convenience.
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Can this transition create a valid <see cref="AnimancerState"/>?</summary>
|
||||
/// <remarks>
|
||||
/// Use <see cref="AnimancerUtilities.IsValid(ITransition)"/> to check for <c>null</c> as well.
|
||||
/// </remarks>
|
||||
bool IsValid { get; }
|
||||
|
||||
/// <summary>What will the value of <see cref="AnimancerState.IsLooping"/> be for the created state?</summary>
|
||||
bool IsLooping { get; }
|
||||
|
||||
/// <summary>The <see cref="AnimancerState.NormalizedTime"/> to start the animation at.</summary>
|
||||
/// <remarks><see cref="float.NaN"/> allows the animation to continue from its current time.</remarks>
|
||||
float NormalizedStartTime { get; set; }
|
||||
|
||||
/// <summary>The maximum expected value of the <see cref="AnimancerState.Length"/>.</summary>
|
||||
/// <remarks>
|
||||
/// In a <see cref="ClipState"/> this is equal to the <see cref="AnimationClip.length"/>
|
||||
/// but the actual length can vary depending on the current parameters in states like
|
||||
/// <see cref="LinearMixerState"/> and <see cref="ControllerState"/>.
|
||||
/// </remarks>
|
||||
float MaximumLength { get; }
|
||||
|
||||
/// <summary>The <see cref="AnimancerNodeBase.Speed"/> to play the animation at.</summary>
|
||||
float Speed { get; set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/// <summary>An <see cref="ITransition"/> which creates a specific type of <see cref="AnimancerState"/>.</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions">
|
||||
/// Transitions</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/ITransition_1
|
||||
public interface ITransition<out TState> : ITransition
|
||||
where TState : AnimancerState
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// The state that was created by this object. Specifically, this is the state that was most recently
|
||||
/// passed into <see cref="ITransition.Apply"/> (usually by <see cref="AnimancerLayer.Play(ITransition)"/>).
|
||||
/// </summary>
|
||||
TState State { get; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates and returns a new <typeparamref name="TState"/>.</summary>
|
||||
new TState CreateState();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38dc5de456105324097093ebe2ee0bfd
|
||||
labels:
|
||||
- Interface
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,141 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.Editor
|
||||
{
|
||||
/// <summary>[Editor-Only] An object that can draw custom GUI elements relating to transitions.</summary>
|
||||
/// <remarks>
|
||||
/// Implement this in a custom transition type to give it custom GUI elements.
|
||||
/// <para></para>
|
||||
/// <strong>Example:</strong><code>
|
||||
/// using Animancer;
|
||||
/// using UnityEngine;
|
||||
///
|
||||
/// // AttackTransition.cs contains your custom transition type.
|
||||
/// public partial class AttackTransition : ClipTransition
|
||||
/// {
|
||||
/// [SerializeField] private Bounds _HitBox;
|
||||
/// [SerializeField] private float _HitStartTime;
|
||||
/// [SerializeField] private float _HitEndTime;
|
||||
///
|
||||
/// // Damage, Knockback, etc.
|
||||
/// }
|
||||
///
|
||||
/// // AttackTransition.Drawer.cs contains the custom GUI for it.
|
||||
/// #if UNITY_EDITOR
|
||||
///
|
||||
/// using Animancer.Editor;
|
||||
/// using UnityEditor;
|
||||
/// using UnityEngine;
|
||||
///
|
||||
/// public partial class AttackTransition : ITransitionGUI
|
||||
/// {
|
||||
/// // See each method for an example.
|
||||
/// public void OnPreviewSceneGUI(TransitionPreviewDetails details) { }
|
||||
/// public void OnTimelineBackgroundGUI() { }
|
||||
/// public void OnTimelineForegroundGUI() { }
|
||||
/// }
|
||||
///
|
||||
/// #endif
|
||||
/// </code></remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/ITransitionGUI
|
||||
public interface ITransitionGUI
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Called while drawing the GUI for the <see cref="Previews.TransitionPreviewWindow"/> scene.</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Example:</strong><code>
|
||||
/// // For the AttackTransition example from ITransitionGUI.
|
||||
/// // Draw the hit box as a wireframe cube.
|
||||
/// public void OnPreviewSceneGUI(TransitionPreviewDetails details)
|
||||
/// {
|
||||
/// Color color = Handles.color;
|
||||
/// Handles.color = new(0.5f, 1, 0.5f);
|
||||
///
|
||||
/// Transform transform = details.Transform;
|
||||
///
|
||||
/// Handles.DrawWireCube(
|
||||
/// transform.TransformPoint(_HitBox.center),
|
||||
/// _HitBox.size);
|
||||
///
|
||||
/// Handles.color = color;
|
||||
/// }
|
||||
/// </code></remarks>
|
||||
void OnPreviewSceneGUI(TransitionPreviewDetails details);
|
||||
|
||||
/// <summary>
|
||||
/// Called while drawing the background GUI for the <see cref="TimelineGUI"/> for the
|
||||
/// <see cref="IHasEvents.Events"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <strong>Example:</strong><code>
|
||||
/// // For the AttackTransition example from ITransitionGUI.
|
||||
/// // Draw the hit time as a highlighted area.
|
||||
/// public void OnTimelineBackgroundGUI()
|
||||
/// {
|
||||
/// if (Event.current.type != EventType.Repaint)
|
||||
/// return;
|
||||
///
|
||||
/// Color previousColor = GUI.color;
|
||||
/// TimelineGUI timelineGUI = TimelineGUI.Current;
|
||||
///
|
||||
/// float start = timelineGUI.SecondsToPixels(_HitStartTime);
|
||||
/// float end = timelineGUI.SecondsToPixels(_HitEndTime);
|
||||
/// Rect area = new Rect(
|
||||
/// start,
|
||||
/// 0,
|
||||
/// end - start,
|
||||
/// timelineGUI.Area.height - timelineGUI.TickHeight);
|
||||
///
|
||||
/// Color color = new Color(0.9f, 0.4f, 0.25f, 0.5f);
|
||||
///
|
||||
/// EditorGUI.DrawRect(area, color);
|
||||
///
|
||||
/// GUI.color = previousColor;
|
||||
/// }
|
||||
/// </code></remarks>
|
||||
void OnTimelineBackgroundGUI();
|
||||
|
||||
/// <summary>
|
||||
/// Called while drawing the foreground GUI for the <see cref="TimelineGUI"/> for the
|
||||
/// <see cref="IHasEvents.Events"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method can be used similarly to the <see cref="OnTimelineBackgroundGUI"/> example,
|
||||
/// except that it draws in front of everything else.
|
||||
/// </remarks>
|
||||
void OnTimelineForegroundGUI();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/// <summary>[Editor-Only] Details about the current preview used by <see cref="ITransitionGUI.OnPreviewSceneGUI"/>.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/TransitionPreviewDetails
|
||||
public readonly struct TransitionPreviewDetails
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The <see cref="AnimancerGraph"/> used to play the preview.</summary>
|
||||
public readonly AnimancerGraph Animancer;
|
||||
|
||||
/// <summary>The <see cref="UnityEngine.Transform"/> of the <see cref="Animator"/> used to play the preview.</summary>
|
||||
public Transform Transform => Animancer.Component.Animator.transform;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="TransitionPreviewDetails"/>.</summary>
|
||||
public TransitionPreviewDetails(AnimancerGraph animancer)
|
||||
{
|
||||
Animancer = animancer;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 50ea015fe10ac1a4293e0a83f536d959
|
||||
labels:
|
||||
- Interface
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,169 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>[Pro-Only] An object that can be updated during Animancer's animation updates.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <strong>Example:</strong>
|
||||
/// Register to receive updates using <see cref="AnimancerGraph.RequirePreUpdate"/> or
|
||||
/// <see cref="AnimancerGraph.RequirePostUpdate"/> and stop
|
||||
/// receiving updates using <see cref="AnimancerGraph.CancelPreUpdate"/> or
|
||||
/// <see cref="AnimancerGraph.CancelPostUpdate"/>.
|
||||
/// <para></para><code>
|
||||
/// public sealed class MyUpdatable : IUpdatable
|
||||
/// {
|
||||
/// // Implement IUpdatable.
|
||||
/// // You can avoid this by inheriting from Updatable instead.
|
||||
/// int IUpdatable.UpdatableIndex { get; set; } = IUpdatable.List.NotInList;
|
||||
///
|
||||
/// private AnimancerComponent _Animancer;
|
||||
///
|
||||
/// public void StartUpdating(AnimancerComponent animancer)
|
||||
/// {
|
||||
/// _Animancer = animancer;
|
||||
///
|
||||
/// // If you want Update to be called before the playables get updated.
|
||||
/// _Animancer.Graph.RequirePreUpdate(this);
|
||||
///
|
||||
/// // If you want Update to be called after the playables get updated.
|
||||
/// _Animancer.Graph.RequirePostUpdate(this);
|
||||
/// }
|
||||
///
|
||||
/// public void StopUpdating()
|
||||
/// {
|
||||
/// // If you used RequirePreUpdate.
|
||||
/// _Animancer.Graph.CancelPreUpdate(this);
|
||||
///
|
||||
/// // If you used RequirePostUpdate.
|
||||
/// _Animancer.Graph.CancelPostUpdate(this);
|
||||
/// }
|
||||
///
|
||||
/// void IUpdatable.Update()
|
||||
/// {
|
||||
/// // Called during every animation update.
|
||||
///
|
||||
/// // AnimancerGraph.Current can be used to access the system it is being updated by.
|
||||
/// }
|
||||
/// }
|
||||
/// </code></remarks>
|
||||
///
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/IUpdatable
|
||||
///
|
||||
public interface IUpdatable
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The index of this object in its <see cref="IndexedList{TItem, TIndexer}"/>.</summary>
|
||||
/// <remarks>Must be initialized to -1 to indicate that this object is not yet in a list.</remarks>
|
||||
int UpdatableIndex { get; set; }
|
||||
|
||||
/// <summary>Updates this object.</summary>
|
||||
void Update();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>An <see cref="IIndexer{T}"/> for <see cref="IUpdatable"/>.</summary>
|
||||
public readonly struct Indexer : IIndexer<IUpdatable>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly int GetIndex(IUpdatable item)
|
||||
=> item.UpdatableIndex;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void SetIndex(IUpdatable item, int index)
|
||||
=> item.UpdatableIndex = index;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void ClearIndex(IUpdatable item)
|
||||
=> item.UpdatableIndex = -1;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>An <see cref="IndexedList{TItem, TAccessor}"/> of <see cref="IUpdatable"/>.</summary>
|
||||
public class List : IndexedList<IUpdatable, Indexer>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The default <see cref="IndexedList{TItem, TIndexer}.Capacity"/> for newly created lists.</summary>
|
||||
/// <remarks>Default value is 4.</remarks>
|
||||
public static new int DefaultCapacity { get; set; } = 4;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="List"/> with the <see cref="DefaultCapacity"/>.</summary>
|
||||
public List()
|
||||
: base(DefaultCapacity, new())
|
||||
{ }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Calls <see cref="Update"/> on all items in this list.</summary>
|
||||
/// <remarks>
|
||||
/// Uses <see cref="Debug.LogException(Exception, Object)"/> to handle exceptions and continues executing
|
||||
/// the remaining items if any occur.
|
||||
/// </remarks>
|
||||
public void UpdateAll()
|
||||
{
|
||||
BeginEnumeraton();
|
||||
ContinueEnumeration:
|
||||
try
|
||||
{
|
||||
while (TryEnumerateNext())
|
||||
{
|
||||
Current.Update();
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogException(exception, AnimancerGraph.Current?.Component as Object);
|
||||
goto ContinueEnumeration;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Clones any <see cref="ICloneable{T}"/> items.</summary>
|
||||
public void CloneFrom(
|
||||
List copyFrom,
|
||||
CloneContext context)
|
||||
{
|
||||
var count = copyFrom.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
if (copyFrom[i] is ICloneable<object> cloneable &&
|
||||
context.GetOrCreateClone(cloneable) is IUpdatable clone)
|
||||
Add(clone);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/AnimancerUtilities
|
||||
public static partial class AnimancerUtilities
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Is the `updatable` currently in a list?</summary>
|
||||
public static bool IsInList(this IUpdatable updatable)
|
||||
=> updatable.UpdatableIndex >= 0;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2972496b355d15d429f491b78d4cbecf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,72 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>An object which wraps a <see cref="WrappedObject"/> object.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/IWrapper
|
||||
public interface IWrapper
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The wrapped object.</summary>
|
||||
/// <remarks>
|
||||
/// Use <see cref="AnimancerUtilities.TryGetWrappedObject"/>
|
||||
/// in case the <see cref="WrappedObject"/> is also an <see cref="IWrapper"/>.
|
||||
/// </remarks>
|
||||
object WrappedObject { get; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/AnimancerUtilities
|
||||
public partial class AnimancerUtilities
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Returns the last <see cref="IWrapper.WrappedObject"/>
|
||||
/// which is a <typeparamref name="T"/>, including the `wrapper` itself.
|
||||
/// </summary>
|
||||
public static bool TryGetWrappedObject<T>(
|
||||
object wrapper,
|
||||
out T wrapped,
|
||||
bool logException = false)
|
||||
where T : class
|
||||
{
|
||||
wrapped = default;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (wrapper is T t)
|
||||
wrapped = t;
|
||||
|
||||
if (wrapper is IWrapper targetWrapper)
|
||||
{
|
||||
try
|
||||
{
|
||||
wrapper = targetWrapper.WrappedObject;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (logException)
|
||||
Debug.LogException(exception);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return wrapped != null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e936a32bb9d86144fad64356f6dd8258
|
||||
labels:
|
||||
- Interface
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user