You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
802 lines
36 KiB
C#
802 lines
36 KiB
C#
2 months ago
|
// Animancer // Copyright 2020 Kybernetik //
|
||
|
|
||
|
using System;
|
||
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Text;
|
||
|
using UnityEngine;
|
||
|
|
||
|
namespace Animancer
|
||
|
{
|
||
|
partial struct AnimancerEvent
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// A variable-size list of <see cref="AnimancerEvent"/>s which keeps itself sorted by
|
||
|
/// <see cref="normalizedTime"/>.
|
||
|
/// <para></para>
|
||
|
/// Animancer Lite does not allow the use of events in a runtime build, except for <see cref="OnEnd"/>.
|
||
|
/// </summary>
|
||
|
public sealed partial class Sequence : IEnumerable<AnimancerEvent>
|
||
|
{
|
||
|
/************************************************************************************************************************/
|
||
|
#region Fields and Properties
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
private const string
|
||
|
NoCallbackError = "Event has no callback",
|
||
|
IndexTooHighError = "index must be less than Count and not negative";
|
||
|
|
||
|
/// <summary>
|
||
|
/// A zero length array of <see cref="AnimancerEvent"/>s which is used by all lists before any elements are
|
||
|
/// added to them (unless their <see cref="Capacity"/> is set manually).
|
||
|
/// </summary>
|
||
|
public static readonly AnimancerEvent[] EmptyArray = new AnimancerEvent[0];
|
||
|
|
||
|
/// <summary>The initial <see cref="Capacity"/> that will be used if another value is not specified.</summary>
|
||
|
public const int DefaultCapacity = 8;
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// An <see cref="AnimancerEvent"/> which denotes the end of the animation. Its values can be accessed via
|
||
|
/// <see cref="OnEnd"/> and <see cref="NormalizedEndTime"/>.
|
||
|
/// <para></para>
|
||
|
/// By default, the <see cref="normalizedTime"/> will be <see cref="float.NaN"/> so that it can choose the
|
||
|
/// correct value based on the current play direction: forwards ends at 1 and backwards ends at 0.
|
||
|
/// <para></para>
|
||
|
/// Animancer Lite does not allow the <see cref="normalizedTime"/> to be changed in a runtime build.
|
||
|
/// </summary>
|
||
|
///
|
||
|
/// <example>
|
||
|
/// <code>
|
||
|
/// void PlayAnimation(AnimancerComponent animancer, AnimationClip clip)
|
||
|
/// {
|
||
|
/// var state = animancer.Play(clip);
|
||
|
/// state.Events.OnEnd = OnAnimationEnd;
|
||
|
/// state.Events.NormalizedEndTime = 0.75f;
|
||
|
///
|
||
|
/// // Or set the time and callback at the same time:
|
||
|
/// state.Events.endEvent = new AnimancerEvent(0.75f, OnAnimationEnd);
|
||
|
/// }
|
||
|
///
|
||
|
/// void OnAnimationEnd()
|
||
|
/// {
|
||
|
/// Debug.Log("Animation ended");
|
||
|
/// }
|
||
|
/// </code>
|
||
|
/// </example>
|
||
|
///
|
||
|
/// <remarks>
|
||
|
/// See the documentation for more information about
|
||
|
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/end">
|
||
|
/// End Events</see>.
|
||
|
/// </remarks>
|
||
|
public AnimancerEvent endEvent = new AnimancerEvent(float.NaN, null);
|
||
|
|
||
|
/// <summary>The internal array in which the events are stored (excluding the <see cref="endEvent"/>).</summary>
|
||
|
private AnimancerEvent[] _Events;
|
||
|
|
||
|
/// <summary>[Pro-Only] The number of events in this sequence (excluding the <see cref="endEvent"/>).</summary>
|
||
|
public int Count { get; private set; }
|
||
|
|
||
|
/// <summary>[Pro-Only]
|
||
|
/// The number of times the contents of this sequence have been modified. This applies to general events,
|
||
|
/// but not the <see cref="endEvent"/>.
|
||
|
/// </summary>
|
||
|
public int Version { get; private set; }
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endregion
|
||
|
/************************************************************************************************************************/
|
||
|
#region Constructors
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates a new <see cref="Sequence"/> which starts at 0 <see cref="Capacity"/>.
|
||
|
/// <para></para>
|
||
|
/// Adding anything to the list will set the <see cref="Capacity"/> = <see cref="DefaultCapacity"/>
|
||
|
/// and then double it whenever the <see cref="Count"/> would exceed the <see cref="Capacity"/>.
|
||
|
/// </summary>
|
||
|
public Sequence()
|
||
|
{
|
||
|
_Events = EmptyArray;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[Pro-Only]
|
||
|
/// Creates a new <see cref="Sequence"/> which starts with the specified <see cref="Capacity"/>. It will be
|
||
|
/// initially empty, but will have room for the given number of elements before any reallocations are
|
||
|
/// required.
|
||
|
/// </summary>
|
||
|
public Sequence(int capacity)
|
||
|
{
|
||
|
_Events = capacity > 0 ? new AnimancerEvent[capacity] : EmptyArray;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates a new <see cref="Sequence"/>, copying the contents of `copyFrom` into it.
|
||
|
/// </summary>
|
||
|
public Sequence(Sequence copyFrom)
|
||
|
{
|
||
|
CopyFrom(copyFrom);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[Pro-Only]
|
||
|
/// Creates a new <see cref="Sequence"/>, copying and sorting the contents of the `collection` into it.
|
||
|
/// The <see cref="Count"/> and <see cref="Capacity"/> will be equal to the
|
||
|
/// <see cref="ICollection{T}.Count"/>.
|
||
|
/// </summary>
|
||
|
public Sequence(ICollection<AnimancerEvent> collection)
|
||
|
{
|
||
|
if (collection == null)
|
||
|
throw new ArgumentNullException("collection");
|
||
|
|
||
|
var count = collection.Count;
|
||
|
if (count == 0)
|
||
|
{
|
||
|
_Events = EmptyArray;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_Events = new AnimancerEvent[count];
|
||
|
AddRange(collection);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[Pro-Only]
|
||
|
/// Creates a new <see cref="Sequence"/>, copying and sorting the contents of the `enumerable` into it.
|
||
|
/// </summary>
|
||
|
public Sequence(IEnumerable<AnimancerEvent> enumerable)
|
||
|
{
|
||
|
if (enumerable == null)
|
||
|
throw new ArgumentNullException("enumerable");
|
||
|
|
||
|
_Events = EmptyArray;
|
||
|
AddRange(enumerable);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endregion
|
||
|
/************************************************************************************************************************/
|
||
|
#region Iteration
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Indicates whether the list has any events in it or the <see cref="endEvent"/> event's
|
||
|
/// <see cref="normalizedTime"/> is not at the default value (1).
|
||
|
/// </summary>
|
||
|
public bool IsEmpty
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return
|
||
|
endEvent.callback == null &&
|
||
|
float.IsNaN(endEvent.normalizedTime) &&
|
||
|
Count == 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[Pro-Only]
|
||
|
/// The size of the internal array used to hold events.
|
||
|
/// <para></para>
|
||
|
/// When set, the array is reallocated to the given size.
|
||
|
/// <para></para>
|
||
|
/// By default, the <see cref="Capacity"/> starts at 0 and increases to the <see cref="DefaultCapacity"/>
|
||
|
/// when the first event is added.
|
||
|
/// </summary>
|
||
|
public int Capacity
|
||
|
{
|
||
|
get { return _Events.Length; }
|
||
|
set
|
||
|
{
|
||
|
if (value < Count)
|
||
|
throw new ArgumentOutOfRangeException("value", "Capacity cannot be set lower than Count");
|
||
|
|
||
|
if (value == _Events.Length)
|
||
|
return;
|
||
|
|
||
|
if (value > 0)
|
||
|
{
|
||
|
var newEvents = new AnimancerEvent[value];
|
||
|
if (Count > 0)
|
||
|
Array.Copy(_Events, 0, newEvents, 0, Count);
|
||
|
_Events = newEvents;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_Events = EmptyArray;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[Pro-Only] Gets the event at the specified `index`.</summary>
|
||
|
public AnimancerEvent this[int index]
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
Debug.Assert((uint)index < (uint)Count, IndexTooHighError);
|
||
|
return _Events[index];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[Assert]
|
||
|
/// Throws an <see cref="ArgumentOutOfRangeException"/> if the <see cref="normalizedTime"/> of any events
|
||
|
/// is less than 0 or greater than or equal to 1.
|
||
|
/// <para></para>
|
||
|
/// This does not include the <see cref="endEvent"/> since it works differently to other events.
|
||
|
/// </summary>
|
||
|
[System.Diagnostics.Conditional(Strings.Assert)]
|
||
|
public void AssertNormalizedTimes()
|
||
|
{
|
||
|
if (Count == 0 ||
|
||
|
(_Events[0].normalizedTime >= 0 && _Events[Count - 1].normalizedTime < 1))
|
||
|
return;
|
||
|
|
||
|
throw new ArgumentOutOfRangeException("The normalized time of an event in the Sequence is" +
|
||
|
" < 0 or >= 1, which is not allowed on looping animations. " + DeepToString());
|
||
|
}
|
||
|
|
||
|
/// <summary>[Assert]
|
||
|
/// Calls <see cref="AssertNormalizedTimes()"/> if `isLooping` is true.
|
||
|
/// </summary>
|
||
|
[System.Diagnostics.Conditional(Strings.Assert)]
|
||
|
public void AssertNormalizedTimes(bool isLooping)
|
||
|
{
|
||
|
if (isLooping)
|
||
|
AssertNormalizedTimes();
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Returns a string containing the details of all events in this sequence.</summary>
|
||
|
public string DeepToString(bool multiLine = true)
|
||
|
{
|
||
|
var text = new StringBuilder()
|
||
|
.Append(ToString())
|
||
|
.Append(" [")
|
||
|
.Append(Count)
|
||
|
.Append(multiLine ? "]\n{" : "] { ");
|
||
|
|
||
|
for (int i = 0; i < Count; i++)
|
||
|
{
|
||
|
if (multiLine)
|
||
|
text.Append("\n ");
|
||
|
else if (i > 0)
|
||
|
text.Append(", ");
|
||
|
|
||
|
text.Append(this[i]);
|
||
|
}
|
||
|
|
||
|
text.Append(multiLine ? "\n}\nendEvent=" : " } (endEvent=")
|
||
|
.Append(endEvent);
|
||
|
|
||
|
if (!multiLine)
|
||
|
text.Append(")");
|
||
|
|
||
|
return text.ToString();
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[Pro-Only] Returns an <see cref="Enumerator"/> for this sequence.</summary>
|
||
|
public Enumerator GetEnumerator()
|
||
|
{
|
||
|
return new Enumerator(this);
|
||
|
}
|
||
|
|
||
|
IEnumerator<AnimancerEvent> IEnumerable<AnimancerEvent>.GetEnumerator()
|
||
|
{
|
||
|
return new Enumerator(this);
|
||
|
}
|
||
|
|
||
|
IEnumerator IEnumerable.GetEnumerator()
|
||
|
{
|
||
|
return new Enumerator(this);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[Pro-Only]
|
||
|
/// An iterator that can cycle through every event in a <see cref="Sequence"/> except for the
|
||
|
/// <see cref="endEvent"/>.
|
||
|
/// </summary>
|
||
|
public struct Enumerator : IEnumerator<AnimancerEvent>
|
||
|
{
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>The target <see cref="AnimancerEvent.Sequence"/>.</summary>
|
||
|
public readonly Sequence Sequence;
|
||
|
|
||
|
private int _Index;
|
||
|
private int _Version;
|
||
|
private AnimancerEvent _Current;
|
||
|
|
||
|
private const string InvalidVersion =
|
||
|
"AnimancerEvent.Sequence was modified. Enumeration operation may not execute.";
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>The event this iterator is currently pointing to.</summary>
|
||
|
public AnimancerEvent Current { get { return _Current; } }
|
||
|
|
||
|
/// <summary>The event this iterator is currently pointing to.</summary>
|
||
|
object IEnumerator.Current
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (_Index == 0 || _Index == Sequence.Count + 1)
|
||
|
throw new InvalidOperationException(
|
||
|
"Operation is not valid due to the current state of the object.");
|
||
|
|
||
|
return Current;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Creates a new <see cref="Enumerator"/>.</summary>
|
||
|
public Enumerator(Sequence sequence)
|
||
|
{
|
||
|
Sequence = sequence;
|
||
|
_Index = 0;
|
||
|
_Version = sequence.Version;
|
||
|
_Current = default(AnimancerEvent);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
void IDisposable.Dispose() { }
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Moves to the next event in the <see cref="Sequence"/> and returns true if there is one.
|
||
|
/// </summary>
|
||
|
/// <exception cref="InvalidOperationException">
|
||
|
/// Thrown if the <see cref="Version"/> has changed since this iterator was created.
|
||
|
/// </exception>
|
||
|
public bool MoveNext()
|
||
|
{
|
||
|
if (_Version != Sequence.Version)
|
||
|
throw new InvalidOperationException(InvalidVersion);
|
||
|
|
||
|
if ((uint)_Index < (uint)Sequence.Count)
|
||
|
{
|
||
|
_Current = Sequence._Events[_Index];
|
||
|
_Index++;
|
||
|
return true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_Index = Sequence.Count + 1;
|
||
|
_Current = default(AnimancerEvent);
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns this iterator to the start of the <see cref="Sequence"/>.
|
||
|
/// </summary>
|
||
|
/// <exception cref="InvalidOperationException">
|
||
|
/// Thrown if the <see cref="Version"/> has changed since this iterator was created.
|
||
|
/// </exception>
|
||
|
void IEnumerator.Reset()
|
||
|
{
|
||
|
if (_Version != Sequence.Version)
|
||
|
throw new InvalidOperationException(InvalidVersion);
|
||
|
|
||
|
_Index = 0;
|
||
|
_Current = default(AnimancerEvent);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endregion
|
||
|
/************************************************************************************************************************/
|
||
|
#region Modification
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[Pro-Only]
|
||
|
/// Adds the given event to this list. The <see cref="Count"/> is increased by one and if required, the
|
||
|
/// <see cref="Capacity"/> is doubled to fit the new event.
|
||
|
/// <para></para>
|
||
|
/// This methods returns the index at which the event is added, which is determined by its
|
||
|
/// <see cref="normalizedTime"/> in order to keep the list sorted in ascending order. If there are already
|
||
|
/// any events with the same <see cref="normalizedTime"/>, the new event is added immediately after them.
|
||
|
/// </summary>
|
||
|
public int Add(AnimancerEvent animancerEvent)
|
||
|
{
|
||
|
Debug.Assert(animancerEvent.callback != null, NoCallbackError);
|
||
|
var index = Insert(animancerEvent.normalizedTime);
|
||
|
_Events[index] = animancerEvent;
|
||
|
return index;
|
||
|
}
|
||
|
|
||
|
/// <summary>[Pro-Only]
|
||
|
/// Adds the given event to this list. The <see cref="Count"/> is increased by one and if required, the
|
||
|
/// <see cref="Capacity"/> is doubled to fit the new event.
|
||
|
/// <para></para>
|
||
|
/// This methods returns the index at which the event is added, which is determined by its
|
||
|
/// <see cref="normalizedTime"/> in order to keep the list sorted in ascending order. If there are already
|
||
|
/// any events with the same <see cref="normalizedTime"/>, the new event is added immediately after them.
|
||
|
/// </summary>
|
||
|
public int Add(float normalizedTime, Action callback)
|
||
|
{
|
||
|
return Add(new AnimancerEvent(normalizedTime, callback));
|
||
|
}
|
||
|
|
||
|
/// <summary>[Pro-Only]
|
||
|
/// Adds the given event to this list. The <see cref="Count"/> is increased by one and if required, the
|
||
|
/// <see cref="Capacity"/> is doubled to fit the new event.
|
||
|
/// <para></para>
|
||
|
/// This methods returns the index at which the event is added, which is determined by its
|
||
|
/// <see cref="normalizedTime"/> in order to keep the list sorted in ascending order. If there are already
|
||
|
/// any events with the same <see cref="normalizedTime"/>, the new event is added immediately after them.
|
||
|
/// </summary>
|
||
|
public int Add(int indexHint, AnimancerEvent animancerEvent)
|
||
|
{
|
||
|
Debug.Assert(animancerEvent.callback != null, NoCallbackError);
|
||
|
indexHint = Insert(indexHint, animancerEvent.normalizedTime);
|
||
|
_Events[indexHint] = animancerEvent;
|
||
|
return indexHint;
|
||
|
}
|
||
|
|
||
|
/// <summary>[Pro-Only]
|
||
|
/// Adds the given event to this list. The <see cref="Count"/> is increased by one and if required, the
|
||
|
/// <see cref="Capacity"/> is doubled to fit the new event.
|
||
|
/// <para></para>
|
||
|
/// This methods returns the index at which the event is added, which is determined by its
|
||
|
/// <see cref="normalizedTime"/> in order to keep the list sorted in ascending order. If there are already
|
||
|
/// any events with the same <see cref="normalizedTime"/>, the new event is added immediately after them.
|
||
|
/// </summary>
|
||
|
public int Add(int indexHint, float normalizedTime, Action callback)
|
||
|
{
|
||
|
return Add(indexHint, new AnimancerEvent(normalizedTime, callback));
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[Pro-Only]
|
||
|
/// Adds every event in the `enumerable` to this list. The <see cref="Count"/> is increased by one and if
|
||
|
/// required, the <see cref="Capacity"/> is doubled to fit the new event.
|
||
|
/// <para></para>
|
||
|
/// This methods returns the index at which the event is added, which is determined by its
|
||
|
/// <see cref="normalizedTime"/> in order to keep the list sorted in ascending order. If there are already
|
||
|
/// any events with the same <see cref="normalizedTime"/>, the new event is added immediately after them.
|
||
|
/// </summary>
|
||
|
public void AddRange(IEnumerable<AnimancerEvent> enumerable)
|
||
|
{
|
||
|
foreach (var item in enumerable)
|
||
|
Add(item);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[Pro-Only]
|
||
|
/// Replaces the <see cref="callback"/> of the event at the specified `index`.
|
||
|
/// </summary>
|
||
|
public void Set(int index, Action callback)
|
||
|
{
|
||
|
var animancerEvent = _Events[index];
|
||
|
animancerEvent.callback = callback;
|
||
|
_Events[index] = animancerEvent;
|
||
|
Version++;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[Pro-Only]
|
||
|
/// Determines the index where a new event with the specified `normalizedTime` should be added in order to
|
||
|
/// keep this list sorted, increases the <see cref="Count"/> by one, doubles the <see cref="Capacity"/> if
|
||
|
/// required, moves any existing events to open up the chosen index, and returns that index.
|
||
|
/// <para></para>
|
||
|
/// This overload starts searching for the desired index from the end of the list, using the assumption
|
||
|
/// that elements will usually be added in order.
|
||
|
/// </summary>
|
||
|
private int Insert(float normalizedTime)
|
||
|
{
|
||
|
var index = Count;
|
||
|
while (index > 0 && _Events[index - 1].normalizedTime > normalizedTime)
|
||
|
index--;
|
||
|
Insert(index);
|
||
|
return index;
|
||
|
}
|
||
|
|
||
|
/// <summary>[Pro-Only]
|
||
|
/// Determines the index where a new event with the specified `normalizedTime` should be added in order to
|
||
|
/// keep this list sorted, increases the <see cref="Count"/> by one, doubles the <see cref="Capacity"/> if
|
||
|
/// required, moves any existing events to open up the chosen index, and returns that index.
|
||
|
/// <para></para>
|
||
|
/// This overload starts searching for the desired index from the `hint`.
|
||
|
/// </summary>
|
||
|
private int Insert(int hint, float normalizedTime)
|
||
|
{
|
||
|
if (hint >= Count)
|
||
|
return Insert(normalizedTime);
|
||
|
|
||
|
if (_Events[hint].normalizedTime > normalizedTime)
|
||
|
{
|
||
|
while (hint > 0 && _Events[hint - 1].normalizedTime > normalizedTime)
|
||
|
hint--;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
while (hint < Count && _Events[hint].normalizedTime <= normalizedTime)
|
||
|
hint++;
|
||
|
}
|
||
|
|
||
|
Insert(hint);
|
||
|
return hint;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[Pro-Only]
|
||
|
/// Increases the <see cref="Count"/> by one, doubles the <see cref="Capacity"/> if required, and moves any
|
||
|
/// existing events to open up the `index`.
|
||
|
/// </summary>
|
||
|
private void Insert(int index)
|
||
|
{
|
||
|
Debug.Assert((uint)index <= (uint)Count, "index must be less than or equal to Count");
|
||
|
|
||
|
var capacity = _Events.Length;
|
||
|
if (Count == capacity)
|
||
|
{
|
||
|
if (capacity == 0)
|
||
|
{
|
||
|
_Events = new AnimancerEvent[DefaultCapacity];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
capacity *= 2;
|
||
|
if (capacity < DefaultCapacity)
|
||
|
capacity = DefaultCapacity;
|
||
|
|
||
|
var newEvents = new AnimancerEvent[capacity];
|
||
|
|
||
|
Array.Copy(_Events, 0, newEvents, 0, index);
|
||
|
if (Count > index)
|
||
|
Array.Copy(_Events, index, newEvents, index + 1, Count - index);
|
||
|
|
||
|
_Events = newEvents;
|
||
|
}
|
||
|
}
|
||
|
else if (Count > index)
|
||
|
{
|
||
|
Array.Copy(_Events, index, _Events, index + 1, Count - index);
|
||
|
}
|
||
|
|
||
|
Count++;
|
||
|
Version++;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[Pro-Only]
|
||
|
/// Removes the event at the specified `index` from this list by decrementing the <see cref="Count"/> and
|
||
|
/// copying all events after the removed one down one place.
|
||
|
/// </summary>
|
||
|
public void Remove(int index)
|
||
|
{
|
||
|
Debug.Assert((uint)index < (uint)Count, IndexTooHighError);
|
||
|
Count--;
|
||
|
if (index < Count)
|
||
|
Array.Copy(_Events, index + 1, _Events, index, Count - index);
|
||
|
_Events[Count] = default(AnimancerEvent);
|
||
|
Version++;
|
||
|
}
|
||
|
|
||
|
/// <summary>[Pro-Only]
|
||
|
/// Removes the `animancerEvent` from this list by decrementing the <see cref="Count"/> and copying all
|
||
|
/// events after the removed one down one place. Returns true if the event was found and removed.
|
||
|
/// </summary>
|
||
|
public bool Remove(AnimancerEvent animancerEvent)
|
||
|
{
|
||
|
var index = Array.IndexOf(_Events, animancerEvent);
|
||
|
if (index >= 0)
|
||
|
{
|
||
|
Remove(index);
|
||
|
return true;
|
||
|
}
|
||
|
else return false;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[Pro-Only]
|
||
|
/// Removes all events except the <see cref="endEvent"/>.
|
||
|
/// <seealso cref="Clear"/>
|
||
|
/// </summary>
|
||
|
public void RemoveAll()
|
||
|
{
|
||
|
Array.Clear(_Events, 0, Count);
|
||
|
Count = 0;
|
||
|
Version++;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Removes all events, including the <see cref="endEvent"/>.
|
||
|
/// <seealso cref="RemoveAll"/>
|
||
|
/// </summary>
|
||
|
public void Clear()
|
||
|
{
|
||
|
RemoveAll();
|
||
|
endEvent = new AnimancerEvent(float.NaN, null);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endregion
|
||
|
/************************************************************************************************************************/
|
||
|
#region On End
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Shorthand for the <c>endEvent.callback</c>. This callback is triggered when the animation passes the
|
||
|
/// <see cref="NormalizedEndTime"/> (not when the state is interrupted or exited for whatever reason).
|
||
|
/// <para></para>
|
||
|
/// Unlike regular events, this callback will be triggered every frame while it is past the end so if you
|
||
|
/// want to ensure that your callback only occurs once, you will need to clear it as part of that callback.
|
||
|
/// <para></para>
|
||
|
/// This callback is automatically cleared by <see cref="AnimancerState.Play"/>,
|
||
|
/// <see cref="AnimancerState.OnStartFade"/>, and <see cref="AnimancerState.Stop"/>.
|
||
|
/// </summary>
|
||
|
///
|
||
|
/// <example>
|
||
|
/// <code>
|
||
|
/// void PlayAnimation(AnimancerComponent animancer, AnimationClip clip)
|
||
|
/// {
|
||
|
/// var state = animancer.Play(clip);
|
||
|
/// state.Events.OnEnd = OnAnimationEnd;
|
||
|
/// state.Events.NormalizedEndTime = 0.75f;
|
||
|
///
|
||
|
/// // Or set the time and callback at the same time:
|
||
|
/// state.Events.endEvent = new AnimancerEvent(0.75f, OnAnimationEnd);
|
||
|
/// }
|
||
|
///
|
||
|
/// void OnAnimationEnd()
|
||
|
/// {
|
||
|
/// Debug.Log("Animation ended");
|
||
|
/// }
|
||
|
/// </code>
|
||
|
/// </example>
|
||
|
///
|
||
|
/// <remarks>
|
||
|
/// See the documentation for more information about
|
||
|
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/end">
|
||
|
/// End Events</see>.
|
||
|
/// </remarks>
|
||
|
public Action OnEnd
|
||
|
{
|
||
|
get { return endEvent.callback; }
|
||
|
set { endEvent.callback = value; }
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[Pro-Only]
|
||
|
/// Shorthand for <c>endEvent.normalizedTime</c>.
|
||
|
/// <para></para>
|
||
|
/// By default, this value will be <see cref="float.NaN"/> so that it can choose the correct value based on
|
||
|
/// the current play direction: forwards ends at 1 and backwards ends at 0.
|
||
|
/// <para></para>
|
||
|
/// Animancer Lite does not allow this value to be changed in a runtime build.
|
||
|
/// </summary>
|
||
|
///
|
||
|
/// <example>
|
||
|
/// <code>
|
||
|
/// void PlayAnimation(AnimancerComponent animancer, AnimationClip clip)
|
||
|
/// {
|
||
|
/// var state = animancer.Play(clip);
|
||
|
/// state.Events.OnEnd = OnAnimationEnd;
|
||
|
/// state.Events.NormalizedEndTime = 0.75f;
|
||
|
///
|
||
|
/// // Or set the time and callback at the same time:
|
||
|
/// state.Events.endEvent = new AnimancerEvent(0.75f, OnAnimationEnd);
|
||
|
/// }
|
||
|
///
|
||
|
/// void OnAnimationEnd()
|
||
|
/// {
|
||
|
/// Debug.Log("Animation ended");
|
||
|
/// }
|
||
|
/// </code>
|
||
|
/// </example>
|
||
|
///
|
||
|
/// <remarks>
|
||
|
/// See the documentation for more information about
|
||
|
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/end">
|
||
|
/// End Events</see>.
|
||
|
/// </remarks>
|
||
|
public float NormalizedEndTime
|
||
|
{
|
||
|
get { return endEvent.normalizedTime; }
|
||
|
set { endEvent.normalizedTime = value; }
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// The default <see cref="AnimancerState.NormalizedTime"/> for an animation to start at when playing
|
||
|
/// forwards is 0 (the start of the animation) and when playing backwards is 1 (the end of the animation).
|
||
|
/// <para></para>
|
||
|
/// `speed` 0 or <see cref="float.NaN"/> will also return 0.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// This method has nothing to do with events, so it is only here because of
|
||
|
/// <see cref="GetDefaultNormalizedEndTime"/>.
|
||
|
/// </remarks>
|
||
|
public static float GetDefaultNormalizedStartTime(float speed)
|
||
|
{
|
||
|
return speed < 0 ? 1 : 0;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// The default <see cref="normalizedTime"/> for an <see cref="endEvent"/> when playing forwards is 1 (the
|
||
|
/// end of the animation) and when playing backwards is 0 (the start of the animation).
|
||
|
/// <para></para>
|
||
|
/// `speed` 0 or <see cref="float.NaN"/> will also return 1.
|
||
|
/// </summary>
|
||
|
public static float GetDefaultNormalizedEndTime(float speed)
|
||
|
{
|
||
|
return speed < 0 ? 0 : 1;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endregion
|
||
|
/************************************************************************************************************************/
|
||
|
#region Copying
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Copies all the events from the `source` to replace the previous contents of this list.
|
||
|
/// </summary>
|
||
|
public void CopyFrom(Sequence source)
|
||
|
{
|
||
|
var sourceCount = source.Count;
|
||
|
|
||
|
if (Count > sourceCount)
|
||
|
Array.Clear(_Events, Count, sourceCount - Count);
|
||
|
else if (_Events.Length < sourceCount)
|
||
|
Capacity = sourceCount;
|
||
|
|
||
|
Count = sourceCount;
|
||
|
|
||
|
Array.Copy(source._Events, 0, _Events, 0, sourceCount);
|
||
|
|
||
|
endEvent = source.endEvent;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[<see cref="ICollection{T}"/>]
|
||
|
/// Copies all the events from this list into the `array`, starting at the `index`.
|
||
|
/// </summary>
|
||
|
public void CopyTo(AnimancerEvent[] array, int index)
|
||
|
{
|
||
|
Array.Copy(_Events, 0, array, index, Count);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endregion
|
||
|
/************************************************************************************************************************/
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|