// Animancer // Copyright 2020 Kybernetik //
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.Playables;
using Object = UnityEngine.Object;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Animancer
{
///
/// Base class for all states in an graph.
/// Each state is a wrapper for a in the .
///
/// This class can be used as a custom yield instruction to wait until the animation either stops playing or reaches its end.
///
///
/// There are various different ways of getting a state:
///
/// -
/// Use one of the state's constructors. Generally the first parameter is a layer or mixer which will be used as
/// the state's parent. If not specified, you will need to call SetParent manually. Also note than an
/// AnimancerComponent can be implicitly cast to its first layer.
///
/// -
/// AnimancerController.CreateState creates a new ClipState. You can optionally specify a custom `key` to
/// register it in the dictionary instead of the default (the `clip` itself).
///
/// -
/// AnimancerController.GetOrCreateState looks for an existing state registered with the specified `key` and only
/// creates a new one if it doesn’t already exist.
///
/// -
/// AnimancerController.GetState returns an existing state registered with the specified `key` if there is one.
///
/// -
/// AnimancerController.TryGetState is similar but returns a bool to indicate success and returns the `state`
/// as an out parameter.
///
/// -
/// AnimancerController.Play and CrossFade also return the state they play.
///
///
///
/// Note that when inheriting from this class, the field must be assigned in the
/// constructor to avoid throwing s throughout the system.
///
public abstract partial class AnimancerState : AnimancerNode, IAnimationClipCollection
{
/************************************************************************************************************************/
#region Hierarchy
/************************************************************************************************************************/
/// The object which receives the output of the .
public override IPlayableWrapper Parent { get { return _Parent; } }
private AnimancerNode _Parent;
///
/// Connects this state to the `parent` mixer at the specified `index`.
///
/// See also to connect a state to an available port on a
/// layer.
///
public void SetParent(AnimancerNode parent, int index)
{
if (_Parent != null)
_Parent.OnRemoveChild(this);
Index = index;
_Parent = parent;
if (parent != null)
{
SetWeightDirty();
parent.OnAddChild(this);
}
}
/// [Internal]
/// Called by if the specified
/// port is already occupied so it can be cleared without triggering any other calls.
///
internal void ClearParent()
{
Index = -1;
_Parent = null;
}
/************************************************************************************************************************/
///
/// The of this state multiplied by the of each of
/// its parents down the hierarchy to determine how much this state affects the final output.
///
/// Thrown if this state has no .
public float EffectiveWeight
{
get
{
var weight = Weight;
var parent = _Parent;
while (parent != null)
{
weight *= parent.Weight;
parent = parent.Parent as AnimancerNode;
}
return weight;
}
}
/************************************************************************************************************************/
// Layer.
/************************************************************************************************************************/
/// The root which this state is connected to.
public override AnimancerLayer Layer { get { return _Parent.Layer; } }
///
/// The index of the this state is connected to (determined by the
/// ).
///
public int LayerIndex
{
get { return _Parent.Layer.Index; }
set
{
if (_Parent != null && LayerIndex == value)
return;
Root.Layers[value].AddChild(this);
}
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Key and Clip
/************************************************************************************************************************/
internal object _Key;
///
/// The object used to identify this state in the root dictionary.
/// Can be null.
///
public object Key
{
get { return _Key; }
set
{
Root.States.Unregister(this);
Root.States.Register(value, this);
}
}
/************************************************************************************************************************/
/// The which this state plays (if any).
///
/// Thrown if this state type doesn't have a clip and you try to set it.
///
public virtual AnimationClip Clip
{
get { return null; }
set { throw new NotSupportedException(GetType() + " does not support setting the Clip."); }
}
/// The main object to show in the Inspector for this state (if any).
///
/// Thrown if this state type doesn't have a main object and you try to set it.
///
///
/// Thrown if you try to assign something this state can't use.
///
public virtual Object MainObject
{
get { return null; }
set { throw new NotSupportedException(GetType() + " does not support setting the MainObject."); }
}
/************************************************************************************************************************/
/// The average velocity of the root motion caused by this state.
public virtual Vector3 AverageVelocity
{
get { return default(Vector3); }
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Playing
/************************************************************************************************************************/
/// Is the automatically advancing?
private bool _IsPlaying = true;
///
/// Has changed since it was last applied to the .
///
///
/// Playables start playing by default so we start dirty to pause it during the first update (unless
/// is set to true before that).
///
private bool _IsPlayingDirty;
/************************************************************************************************************************/
/// Is the automatically advancing?
///
///
///
/// void IsPlayingExample(AnimancerComponent animancer, AnimationClip clip)
/// {
/// var state = animancer.States.GetOrCreate(clip);
///
/// if (state.IsPlaying)
/// Debug.Log(clip + " is playing");
/// else
/// Debug.Log(clip + " is paused");
///
/// state.IsPlaying = false;// Pause the animation.
///
/// state.IsPlaying = true;// Unpause the animation.
/// }
///
///
public virtual bool IsPlaying
{
get { return _IsPlaying; }
set
{
if (_IsPlaying == value)
return;
_IsPlaying = value;
// If it was already dirty then we just returned to the previous state so it is no longer dirty.
if (_IsPlayingDirty)
{
_IsPlayingDirty = false;
}
else
{
_IsPlayingDirty = true;
Root.RequireUpdate(this);
}
}
}
/************************************************************************************************************************/
///
/// Returns true if this state is playing and is at or fading towards a non-zero
/// .
///
public bool IsActive
{
get
{
return
_IsPlaying &&
TargetWeight > 0;
}
}
/************************************************************************************************************************/
///
/// Returns true if this state is not playing and is at 0 .
///
public bool IsStopped
{
get
{
return
!_IsPlaying &&
Weight == 0;
}
}
/************************************************************************************************************************/
///
/// Updates the for fading, applies it to this state's port on the parent
/// mixer, and plays or pauses the if its state is dirty.
///
/// If the 's is set to false, this
/// method will also connect/disconnect this node from the in the playable graph.
///
protected internal override void Update(out bool needsMoreUpdates)
{
base.Update(out needsMoreUpdates);
if (_IsPlayingDirty)
{
_IsPlayingDirty = false;
if (_IsPlaying)
{
#if UNITY_2017_3_OR_NEWER
_Playable.Play();
#else
_Playable.SetPlayState(PlayState.Playing);
#endif
}
else
{
#if UNITY_2017_3_OR_NEWER
_Playable.Pause();
#else
_Playable.SetPlayState(PlayState.Paused);
#endif
}
}
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Timing
/************************************************************************************************************************/
// Time.
/************************************************************************************************************************/
///
/// The current time of the , retrieved by whenever the
/// is different from the .
private float _Time;
///
/// The from when the was last retrieved from the
/// .
///
private uint _TimeFrameID;
/************************************************************************************************************************/
///
/// The number of seconds that have passed since the start of this animation.
///
/// This value will continue increasing after the animation passes the end of its while
/// the animated object either freezes in place or starts again from the beginning according to whether it is
/// looping or not.
///
/// Animancer Lite does not allow this value to be changed in a runtime build (except resetting it to 0).
///
///
///
///
/// void PlayAnimation(AnimancerComponent animancer, AnimationClip clip)
/// {
/// var state = animancer.Play(clip);
///
/// // Skip 0.5 seconds into the animation:
/// state.Time = 0.5f;
///
/// // Skip 50% of the way through the animation (0.5 in a range of 0 to 1):
/// state.NormalizedTime = 0.5f;
///
/// // Skip to the end of the animation and play backwards.
/// state.NormalizedTime = 1;
/// state.Speed = -1;
/// }
///
///
///
///
/// This property internally uses whenever the value is out of date or gets changed.
///
public float Time
{
get
{
var frameID = Root.FrameID;
if (_TimeFrameID != frameID)
{
_TimeFrameID = frameID;
_Time = NewTime;
}
return _Time;
}
set
{
if (_TimeFrameID == Root.FrameID)
{
if (_Time == value)
return;
}
else
{
_TimeFrameID = Root.FrameID;
}
Debug.Assert(!float.IsNaN(value), "Time must not be NaN");
_Time = value;
NewTime = value;
}
}
/************************************************************************************************************************/
///
/// The internal implementation of which actually gets and sets the underlying value.
///
///
/// Setting this value actually calls twice to ensure that animation
/// events aren't triggered incorrectly. Calling it only once would trigger any animation events between the
/// previous time and the new time. So if an animation plays to the end and you set the time back to 0 (such as
/// by calling or playing a different animation), the next time that animation played it
/// would immediately trigger all of its events, then play through and trigger them normally as well.
///
protected virtual float NewTime
{
get { return (float)_Playable.GetTime(); }
set
{
var time = (double)value;
_Playable.SetTime(time);
_Playable.SetTime(time);
if (_EventUpdatable != null)
_EventUpdatable.OnTimeChanged();
}
}
/************************************************************************************************************************/
///
/// The of this state as a portion of the animation's , meaning the
/// value goes from 0 to 1 as it plays from start to end, regardless of how long that actually takes.
///
/// This value will continue increasing after the animation passes the end of its while
/// the animated object either freezes in place or starts again from the beginning according to whether it is
/// looping or not.
///
/// The fractional part of the value (NormalizedTime % 1) is the percentage (0-1) of progress in the
/// current loop while the integer part ((int)NormalizedTime) is the number of times the animation has
/// been looped.
///
/// Animancer Lite does not allow this value to be changed to a value other than 0 in a runtime build.
///
///
///
///
/// void PlayAnimation(AnimancerComponent animancer, AnimationClip clip)
/// {
/// var state = animancer.Play(clip);
///
/// // Skip 0.5 seconds into the animation:
/// state.Time = 0.5f;
///
/// // Skip 50% of the way through the animation (0.5 in a range of 0 to 1):
/// state.NormalizedTime = 0.5f;
///
/// // Skip to the end of the animation and play backwards.
/// state.NormalizedTime = 1;
/// state.Speed = -1;
/// }
///
///
public float NormalizedTime
{
get
{
var length = Length;
if (length != 0)
return Time / Length;
else
return 0;
}
set { Time = value * Length; }
}
/************************************************************************************************************************/
// Duration.
/************************************************************************************************************************/
///
/// The number of seconds the animation will take to play fully at its current
/// .
///
/// Setting this value modifies the , not the .
/// Animancer Lite does not allow this value to be changed in a runtime build.
///
/// For the time remaining from now until it reaches the end, use instead.
///
///
///
///
/// void PlayAnimation(AnimancerComponent animancer, AnimationClip clip)
/// {
/// var state = animancer.Play(clip);
///
/// state.Duration = 1;// Play fully in 1 second.
/// state.Duration = 2;// Play fully in 2 seconds.
/// state.Duration = 0.5f;// Play fully in half a second.
/// state.Duration = -1;// Play backwards fully in 1 second.
/// state.NormalizedTime = 1; state.Duration = -1;// Play backwards from the end in 1 second.
/// }
///
///
public float Duration
{
get
{
var speed = Speed;
if (speed == 0)
return float.PositiveInfinity;
else
return Length / Math.Abs(speed);
}
set
{
if (value == 0)
Speed = float.PositiveInfinity;
else
Speed = Length / value;
}
}
///
/// The number of seconds the animation will take to reach the end at its current .
///
/// Setting this value modifies the , not the .
/// Animancer Lite does not allow this value to be changed in a runtime build.
///
/// For the time it would take to play fully from the start, use instead.
///
///
///
///
/// void PlayAnimation(AnimancerComponent animancer, AnimationClip clip)
/// {
/// var state = animancer.Play(clip);
///
/// state.RemainingDuration = 1;// Play from the current time to the end in 1 second.
/// state.RemainingDuration = 2;// Play from the current time to the end in 2 seconds.
/// state.RemainingDuration = 0.5f;// Play from the current time to the end in half a second.
/// state.RemainingDuration = -1;// Play backwards from the current time to the end in 1 second.
/// }
///
///
public float RemainingDuration
{
get
{
var speed = Speed;
if (speed == 0)
return float.PositiveInfinity;
var length = Length;
if (_EventUpdatable != null)
{
if (speed > 0)
length *= _EventUpdatable.Events.NormalizedEndTime;
else
length *= 1 - _EventUpdatable.Events.NormalizedEndTime;
}
var time = Time;
if (IsLooping)
time = Mathf.Repeat(time, length);
return (length - time) / Math.Abs(speed);
}
set
{
if (value == 0)
throw new ArgumentException("Duration cannot be set to 0 because that would require infinite speed.");
var length = Length;
if (_EventUpdatable != null)
{
if (value > 0)
length *= _EventUpdatable.Events.NormalizedEndTime;
else
length *= 1 - _EventUpdatable.Events.NormalizedEndTime;
}
var time = Time;
if (IsLooping)
time = Mathf.Repeat(time, length);
Speed = (length - time) / value;
}
}
/************************************************************************************************************************/
// Length.
/************************************************************************************************************************/
/// The total time this state takes to play in seconds (when Speed = 1).
public abstract float Length { get; }
///
/// Indicates whether this state will loop back to the start when it reaches the end.
///
public virtual bool IsLooping { get { return false; } }
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Inverse Kinematics
/************************************************************************************************************************/
///
/// Determines whether OnAnimatorIK(int layerIndex) will be called on the animated object.
/// The initial value is determined by .
///
/// This is equivalent to the "IK Pass" toggle in Animator Controller layers, except that due to limitations in
/// the Playables API the layerIndex will always be zero.
///
/// It requires Unity 2018.1 or newer, however 2018.3 or newer is recommended because a bug in earlier versions
/// of the Playables API caused this value to only take effect while a state was at
/// == 1 which meant that IK would not work while fading between animations.
///
/// Returns false and does nothing if this state does not support IK.
///
public virtual bool ApplyAnimatorIK
{
get { return false; }
set { }
}
///
/// Indicates whether this state is applying IK to the character's feet.
/// The initial value is determined by .
///
/// This is equivalent to the "Foot IK" toggle in Animator Controller states.
///
/// Returns false and does nothing if this state does not support IK.
///
public virtual bool ApplyFootIK
{
get { return false; }
set { }
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Methods
/************************************************************************************************************************/
/// Constructs a new .
public AnimancerState(AnimancerPlayable root) : base(root)
{
IsPlaying = false;
}
/************************************************************************************************************************/
///
/// Plays this animation immediately, without any blending.
/// Sets = true, = 1, and clears the
/// .
///
/// This method does not change the so it will continue from its current value.
///
public void Play()
{
IsPlaying = true;
Weight = 1;
EventUpdatable.TryClear(_EventUpdatable);
}
/************************************************************************************************************************/
///
/// Stops the animation and makes it inactive immediately so it no longer affects the output.
/// Sets = 0, = false, = 0, and
/// clears the .
///
/// If you only want to freeze the animation in place, you can set = false instead. Or
/// to freeze all animations, you can call .
///
public override void Stop()
{
base.Stop();
IsPlaying = false;
Time = 0;
EventUpdatable.TryClear(_EventUpdatable);
}
/************************************************************************************************************************/
///
/// Called by . Clears the .
///
protected internal override void OnStartFade()
{
EventUpdatable.TryClear(_EventUpdatable);
}
/************************************************************************************************************************/
/// Destroys the .
public virtual void Destroy()
{
GC.SuppressFinalize(this);
if (_Parent != null)
_Parent.OnRemoveChild(this);
Index = -1;
EventUpdatable.TryClear(_EventUpdatable);
Root.States.Unregister(this);
// For some reason this is slightly faster than _Playable.Destroy().
if (_Playable.IsValid())
Root._Graph.DestroyPlayable(_Playable);
}
/************************************************************************************************************************/
/// []
/// Gathers all the animations in this state.
///
public virtual void GatherAnimationClips(ICollection clips)
{
clips.Gather(Clip);
}
/************************************************************************************************************************/
///
/// Returns true if the animation is playing and has not yet passed the
/// .
///
/// This method is called by so this object can be used as a custom yield
/// instruction to wait until it finishes.
///
protected internal override bool IsPlayingAndNotEnding()
{
if (!IsPlaying)
return false;
var speed = EffectiveSpeed;
if (speed > 0)
{
float endTime;
if (_EventUpdatable != null)
{
endTime = _EventUpdatable.Events.endEvent.normalizedTime;
if (float.IsNaN(endTime))
endTime = Length;
else
endTime *= Length;
}
else endTime = Length;
return Time <= endTime;
}
else if (speed < 0)
{
float endTime;
if (_EventUpdatable != null)
{
endTime = _EventUpdatable.Events.endEvent.normalizedTime;
if (float.IsNaN(endTime))
endTime = 0;
else
endTime *= Length;
}
else endTime = 0;
return Time >= endTime;
}
else return true;
}
/************************************************************************************************************************/
#region Descriptions
/************************************************************************************************************************/
#if UNITY_EDITOR
/// [Editor-Only] Returns a custom drawer for this state.
protected internal virtual Editor.IAnimancerNodeDrawer GetDrawer()
{
return new Editor.AnimancerStateDrawer(this);
}
#endif
/************************************************************************************************************************/
///
/// Called by to append the details of this node.
///
protected override void AppendDetails(StringBuilder text, string delimiter)
{
base.AppendDetails(text, delimiter);
text.Append(delimiter).Append("IsPlaying: ").Append(IsPlaying);
text.Append(delimiter).Append("Time (Normalized): ").Append(Time);
text.Append(" (").Append(NormalizedTime).Append(')');
text.Append(delimiter).Append("Length: ").Append(Length);
text.Append(delimiter).Append("IsLooping: ").Append(IsLooping);
if (_Key != null)
text.Append(delimiter).Append("Key: ").Append(_Key);
if (_EventUpdatable != null && _EventUpdatable.Events != null)
_EventUpdatable.Events.endEvent.AppendDetails(text, "EndEvent", delimiter);
var clip = Clip;
if (clip != null)
{
#if UNITY_EDITOR
text.Append(delimiter).Append("AssetPath: ").Append(AssetDatabase.GetAssetPath(clip));
#endif
}
}
/************************************************************************************************************************/
/// Returns the hierarchy path of this state through its s.
public string GetPath()
{
if (_Parent == null)
return null;
var path = new StringBuilder();
AppendPath(path, _Parent);
AppendPortAndType(path);
return path.ToString();
}
/// Appends the hierarchy path of this state through its s.
private static void AppendPath(StringBuilder path, AnimancerNode parent)
{
var parentState = parent as AnimancerState;
if (parentState != null && parentState._Parent != null)
{
AppendPath(path, parentState._Parent);
}
else
{
path.Append("Layers[")
.Append(parent.Layer.Index)
.Append("].States");
return;
}
var state = parent as AnimancerState;
if (state != null)
{
state.AppendPortAndType(path);
}
else
{
path.Append(" -> ")
.Append(parent.GetType());
}
}
/// Appends "[Index] -> GetType().Name".
private void AppendPortAndType(StringBuilder path)
{
path.Append('[')
.Append(Index)
.Append("] -> ")
.Append(GetType().Name);
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Transition
/************************************************************************************************************************/
///
/// Base class for serializable s which can create a particular type of
/// when passed into .
///
///
/// Unfortunately the tool used to generate this documentation does not currently support nested types with
/// identical names, so only one Transition class will actually have a documentation page.
///
/// Even though it has the , this class won't actually get serialized
/// by Unity because it's generic and abstract. Each child class still needs to include the attribute.
///
[Serializable]
public abstract class Transition : ITransitionDetailed where TState : AnimancerState
{
/************************************************************************************************************************/
[SerializeField, Tooltip(Strings.ProOnlyTag + "The amount of time the transition will take (in seconds)")]
private float _FadeDuration = AnimancerPlayable.DefaultFadeDuration;
/// [] The amount of time the transition will take (in seconds).
/// Thrown when setting the value to a negative number.
public float FadeDuration
{
get { return _FadeDuration; }
set
{
if (value < 0)
throw new ArgumentOutOfRangeException("value", "must not be negative");
_FadeDuration = value;
}
}
/************************************************************************************************************************/
/// []
/// Indicates what the value of will be for the created state.
/// Returns false unless overridden.
///
public virtual bool IsLooping { get { return false; } }
/// []
/// Determines what to start the animation at.
/// Returns unless overridden.
///
public virtual float NormalizedStartTime { get { return float.NaN; } set { } }
/// []
/// Determines how fast the animation plays (1x = normal speed).
/// Returns 1 unless overridden.
///
public virtual float Speed { get { return 1; } set { } }
/// []
/// The maximum amount of time the animation is expected to take (in seconds).
///
public abstract float MaximumDuration { get; }
/************************************************************************************************************************/
[SerializeField, Tooltip(Strings.ProOnlyTag + "Events which will be triggered as the animation plays")]
private AnimancerEvent.Sequence.Serializable _Events;
/// [] Events which will be triggered as the animation plays.
public AnimancerEvent.Sequence.Serializable Events
{
get { return _Events; }
set { _Events = value; }
}
/************************************************************************************************************************/
///
/// The state that was created by this object. Specifically, this is the state that was most recently
/// passed into (usually by ).
///
/// You can use or
/// to get or create the state for a
/// specific object.
///
/// is simply a shorthand for casting this to .
///
public AnimancerState BaseState { get; private set; }
/************************************************************************************************************************/
private TState _State;
///
/// The state that was created by this object. Specifically, this is the state that was most recently
/// passed into (usually by ).
///
/// You can use or
/// to get or create the state for a
/// specific object.
///
/// This property is shorthand for casting the to .
///
///
/// Thrown if the is not actually a . This should only
/// happen if a different type of state was created by something else and registered using the
/// , causing this to pass that
/// state into instead of calling to make the correct type of
/// state.
///
public TState State
{
get
{
if (_State == null)
_State = (TState)BaseState;
return _State;
}
protected set
{
BaseState = _State = value;
}
}
/************************************************************************************************************************/
///
/// The which the created state will be registered with.
///
/// By default, a transition is used as its own , but this property can be overridden.
///
public virtual object Key { get { return this; } }
///
/// When a transition is passed into , this property
/// determines which will be used.
///
public virtual FadeMode FadeMode { get { return FadeMode.FixedSpeed; } }
///
/// Creates and returns a new connected to the `layer`.
///
public abstract TState CreateState(AnimancerLayer layer);
///
/// Creates and returns a new connected to the `layer`.
///
AnimancerState ITransition.CreateState(AnimancerLayer layer)
{
return CreateState(layer);
}
/************************************************************************************************************************/
/// []
/// Called by to set the
/// and apply any other modifications to the `state`.
///
///
/// This method also clears the if necessary, so it will re-cast the
/// when it gets accessed again.
///
public virtual void Apply(AnimancerState state)
{
state.Events = _Events;
BaseState = state;
if (_State != state)
_State = null;
}
/************************************************************************************************************************/
#if UNITY_EDITOR
/************************************************************************************************************************/
/// [Editor-Only] Don't use Inspector Gadgets Nested Object Drawers.
private const bool NestedObjectDrawers = false;
/************************************************************************************************************************/
/// [Editor-Only] Adds context menu functions for this transition.
void ITransitionDetailed.AddItemsToContextMenu(GenericMenu menu, SerializedProperty property,
Editor.Serialization.PropertyAccessor accessor)
{
AddItemsToContextMenu(menu, property, accessor);
}
/// [Editor-Only] Adds context menu functions for this transition.
protected virtual void AddItemsToContextMenu(GenericMenu menu, SerializedProperty property,
Editor.Serialization.PropertyAccessor accessor)
{
var transition = (Transition)accessor.GetValue(property);
const string EventsPrefix = "Transition Event Details/";
int timeCount, callbackCount;
AnimancerEvent.Sequence.Serializable.GetDetails(transition._Events, out timeCount, out callbackCount);
menu.AddDisabledItem(new GUIContent(EventsPrefix + "Serialized Time Count: " + timeCount));
menu.AddDisabledItem(new GUIContent(EventsPrefix + "Serialized Callback Count: " + callbackCount));
Editor.Serialization.AddPropertyModifierFunction(menu, property, "Reset Transition", Reset);
}
/************************************************************************************************************************/
private static void Reset(SerializedProperty property)
{
var transition = Editor.Serialization.GetValue(property);
if (transition == null)
return;
const System.Reflection.BindingFlags Bindings =
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance;
var type = transition.GetType();
var constructor = type.GetConstructor(Bindings, null, Type.EmptyTypes, null);
if (constructor == null)
{
Debug.LogError("Parameterless constructor not found in " + type);
return;
}
Editor.Serialization.RecordUndo(property);
constructor.Invoke(transition, null);
Editor.Serialization.OnPropertyChanged(property);
}
/************************************************************************************************************************/
#endif
/************************************************************************************************************************/
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
}
}