namespace Fusion.Addons.AnimationController
{
using System;
using System.Collections.Generic;
using Unity.Profiling;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;
#pragma warning disable 0109
///
/// Animation controller component.
///
[DisallowMultipleComponent]
public partial class AnimationController : NetworkBehaviour, IAfterSpawned, IAfterClientPredictionReset, IAfterTick
{
// CONSTANTS
public const string LOGGING_SCRIPT_DEFINE = "ANIMATIONS_LOG";
public const string PROFILING_SCRIPT_DEFINE = "ANIMATIONS_PROFILE";
// PUBLIC MEMBERS
///
/// Evaluated playable graph.
///
public PlayableGraph Graph => _graph;
///
/// Main playable mixer which mixes outputs from animation layers.
///
public AnimationLayerMixerPlayable Mixer => _mixer;
///
/// List of animation layers.
///
public IList Layers => _layers;
///
/// Current animator.
///
public Animator Animator => _animator;
///
/// Controls whether update methods are driven by default Fusion methods or called manually using ManualFixedUpdate() and ManualRenderUpdate().
///
public bool HasManualUpdate => _hasManualUpdate;
///
/// Returns Runner.DeltaTime in fixed update and Time.deltaTime in render update.
///
public float DeltaTime => _deltaTime;
///
/// True if the animation prediction is enabled in fixed update.
///
[System.Obsolete("Use Object.IsInSimulation.")]
public bool IsPredictingInFixedUpdate => Object.IsInSimulation;
///
/// True if the animation interpolation is enabled in fixed update.
///
[System.Obsolete("Interpolation in fixed update has been removed.")]
public bool IsInterpolatingInFixedUpdate => false;
// PRIVATE MEMBERS
[SerializeField]
private Transform _root;
[SerializeField]
private Animator _animator;
private PlayableGraph _graph;
private AnimationLayerMixerPlayable _mixer;
private AnimationPlayableOutput _output;
private AnimationLayer[] _layers;
private bool _isSpawned;
private bool _hasManualUpdate;
private bool _evaluateOnResimulation;
private EvaluationSettings _fixedEvaluationSettings = new EvaluationSettings();
private EvaluationSettings _renderEvaluationSettings = new EvaluationSettings();
private float _deltaTime;
private static ProfilerMarker _fixedUpdateMarker = new ProfilerMarker("AnimationController.FixedUpdate");
private static ProfilerMarker _renderUpdateMarker = new ProfilerMarker("AnimationController.RenderUpdate");
private static ProfilerMarker _restoreStateMarker = new ProfilerMarker("AnimationController.RestoreState");
private static ProfilerMarker _afterTickMarker = new ProfilerMarker("AnimationController.AfterTick");
private static ProfilerMarker _interpolateMarker = new ProfilerMarker("AnimationController.Interpolate");
private static ProfilerMarker _evaluateMarker = new ProfilerMarker("AnimationController.Evaluate");
private static ProfilerMarker _evaluateGraphMarker = new ProfilerMarker("PlayableGraph.Evaluate");
// PUBLIC METHODS
///
/// Returns current evaluation mode for given target.
/// Evaluation target.
///
public EEvaluationMode GetEvaluationMode(EEvaluationTarget target)
{
switch (target)
{
case EEvaluationTarget.None: { return EEvaluationMode.None; }
case EEvaluationTarget.FixedUpdate: { return _fixedEvaluationSettings.Mode; }
case EEvaluationTarget.RenderUpdate: { return _renderEvaluationSettings.Mode; }
default:
{
throw new NotImplementedException($"{target}");
}
}
}
///
/// Set evaluation mode for given target.
/// Settings EEvaluationMode.Interlaced requires additional parameters and SetInterlacedEvaluation() must be called.
/// Settings EEvaluationMode.Periodic requires additional parameters and SetPeriodicEvaluation() must be called.
///
public void SetEvaluationMode(EEvaluationTarget target, EEvaluationMode mode)
{
if (target == EEvaluationTarget.None)
return;
if (mode == EEvaluationMode.Interlaced)
throw new NotSupportedException($"Parameters required, please use {nameof(AnimationController)}.{nameof(SetInterlacedEvaluation)}()");
if (mode == EEvaluationMode.Periodic)
throw new NotSupportedException($"Parameters required, please use {nameof(AnimationController)}.{nameof(SetPeriodicEvaluation)}()");
if (target == EEvaluationTarget.FixedUpdate)
{
_fixedEvaluationSettings.Mode = mode;
}
else if (target == EEvaluationTarget.RenderUpdate)
{
_renderEvaluationSettings.Mode = mode;
}
else
{
throw new NotImplementedException($"{target}");
}
}
///
/// Set interlaced evaluation for given target.
/// Evaluation target.
/// Evaluation runs once per [frames] fixed/render updates.
/// Initial frame offset to spread evaluation over multiple ticks/frames.
///
public void SetInterlacedEvaluation(EEvaluationTarget target, int frames, int seed)
{
if (frames < 0)
throw new ArgumentException(nameof(frames));
if (target == EEvaluationTarget.FixedUpdate)
{
_fixedEvaluationSettings.Mode = EEvaluationMode.Interlaced;
_fixedEvaluationSettings.Frames = frames;
_fixedEvaluationSettings.Seed = seed % frames;
}
else if (target == EEvaluationTarget.RenderUpdate)
{
_renderEvaluationSettings.Mode = EEvaluationMode.Interlaced;
_renderEvaluationSettings.Frames = frames;
_renderEvaluationSettings.Seed = seed % frames;
}
else
{
throw new NotImplementedException($"{target}");
}
}
///
/// Set periodic evaluation for given target.
/// Evaluation target.
/// Evaluation runs once per [period] seconds.
/// If true, the current offset will be reset to [initialOffset].
/// Initial time offset to start with.
///
public void SetPeriodicEvaluation(EEvaluationTarget target, float period, bool setInitialOffset = false, float initialOffset = 0.0f)
{
if (period <= 0.0f)
throw new ArgumentException(nameof(period));
if (setInitialOffset == true && initialOffset < 0.0f)
throw new ArgumentException(nameof(initialOffset));
if (target == EEvaluationTarget.FixedUpdate)
{
_fixedEvaluationSettings.Mode = EEvaluationMode.Periodic;
_fixedEvaluationSettings.Period = period;
if (setInitialOffset == true)
{
_fixedEvaluationSettings.Offset = initialOffset;
}
}
else if (target == EEvaluationTarget.RenderUpdate)
{
_renderEvaluationSettings.Mode = EEvaluationMode.Periodic;
_renderEvaluationSettings.Period = period;
if (setInitialOffset == true)
{
_renderEvaluationSettings.Offset = initialOffset;
}
}
else
{
throw new NotImplementedException($"{target}");
}
}
///
/// Enable/disable evaluation on resimulation. This is important if evaluated bones have direct impact on following logic.
/// If true, evaluation on resimulation will be enabled.
///
public void SetEvaluateOnResimulation(bool evaluateOnResimulation)
{
_evaluateOnResimulation = evaluateOnResimulation;
}
///
/// Set Animator target for evaluation.
///
public void SetAnimator(Animator animator)
{
_animator = animator;
if (_graph.IsValid() == false)
return;
if (_output.IsOutputValid() == true)
{
_graph.DestroyOutput(_output);
_output = default;
}
if (_animator != null)
{
_output = AnimationPlayableOutput.Create(_graph, name, _animator);
_output.SetSourcePlayable(_mixer);
}
}
///
/// Controls whether update methods are driven by default Fusion methods or called manually using ManualFixedUpdate() and ManualRenderUpdate().
///
public void SetManualUpdate(bool hasManualUpdate)
{
_hasManualUpdate = hasManualUpdate;
}
///
/// Manual fixed update execution, SetManualUpdate(true) must be called prior usage.
///
public void ManualFixedUpdate()
{
if (_isSpawned == false)
return;
if (_hasManualUpdate == false)
throw new InvalidOperationException("Manual update is not set!");
_fixedUpdateMarker.Begin();
OnFixedUpdateInternal();
_fixedUpdateMarker.End();
}
///
/// Manual render update execution, SetManualUpdate(true) must be called prior usage.
///
public void ManualRenderUpdate()
{
if (_isSpawned == false)
return;
if (_hasManualUpdate == false)
throw new InvalidOperationException("Manual update is not set!");
_renderUpdateMarker.Begin();
OnRenderUpdateInternal();
_renderUpdateMarker.End();
}
///
/// Returns first animation layer with the given name.
///
/// Name of the layer.
public AnimationLayer FindLayer(string layerName)
{
return FindLayer(layerName);
}
///
/// Returns first animation layer of type T.
///
public T FindLayer() where T : class
{
return FindLayer(default, out T layer) == true ? layer : default;
}
///
/// Returns first animation layer of type T with the given name.
///
/// Name of the layer.
public T FindLayer(string layerName) where T : class
{
return FindLayer(layerName, out T layer) == true ? layer : default;
}
///
/// Returns first animation layer of type T.
///
/// Reference to the layer.
public bool FindLayer(out T layer) where T : class
{
return FindLayer(default, out layer);
}
///
/// Returns first animation layer of type T with the given name.
///
/// Name of the layer.
/// Reference to the layer.
public bool FindLayer(string layerName, out T layer) where T : class
{
AnimationLayer[] layers = _layers;
for (int i = 0, count = layers.Length; i < count; ++i)
{
AnimationLayer animationLayer = layers[i];
if (animationLayer is T layerAsT && (layerName == default || string.CompareOrdinal(animationLayer.name, layerName) == 0))
{
layer = layerAsT;
return true;
}
}
layer = default;
return false;
}
///
/// Returns first animation state with the given name, using Breadth-First Search.
///
/// Name of the state.
public AnimationState FindState(string stateName)
{
return FindState(stateName, out AnimationState state) == true ? state : default;
}
///
/// Returns first animation state of type T, using Breadth-First Search.
///
public T FindState() where T : class
{
return FindState(default, out T state) == true ? state : default;
}
///
/// Returns first animation state of type T with the given name, using Breadth-First Search.
///
/// Name of the state.
public T FindState(string stateName) where T : class
{
return FindState(stateName, out T state) == true ? state : default;
}
///
/// Returns first animation state of type T, using Breadth-First Search.
///
/// Reference to the state.
public bool FindState(out T state) where T : class
{
return FindState(default, out state);
}
///
/// Returns first animation state of type T with the given name, using Breadth-First Search.
///
/// Name of the state.
/// Reference to the state.
public bool FindState(string stateName, out T state) where T : class
{
AnimationLayer[] layers = _layers;
for (int i = 0, count = layers.Length; i < count; ++i)
{
AnimationLayer layer = layers[i];
if (layer.FindState(stateName, out state, true) == true)
return true;
}
state = default;
return false;
}
///
/// Logs info message into console with frame/tick metadata.
///
/// Custom message objects.
public void Log(params object[] messages)
{
AnimationUtility.Log(this, default, EAnimationLogType.Info, messages);
}
///
/// Logs warning message into console with frame/tick metadata.
///
/// Custom message objects.
public void LogWarning(params object[] messages)
{
AnimationUtility.Log(this, default, EAnimationLogType.Warning, messages);
}
///
/// Logs error message into console with frame/tick metadata.
///
/// Custom message objects.
public void LogError(params object[] messages)
{
AnimationUtility.Log(this, default, EAnimationLogType.Error, messages);
}
// AnimationController INTERFACE
protected virtual void OnInitialize() {}
protected virtual void OnDeinitialize() {}
protected virtual void OnSpawned() {}
protected virtual void OnDespawned() {}
protected virtual void OnFixedUpdate() {}
protected virtual void OnInterpolate() {}
protected virtual void OnEvaluate() {}
// NetworkBehaviour INTERFACE
public override sealed int? DynamicWordCount => GetNetworkDataWordCount();
public override sealed void Spawned()
{
_isSpawned = true;
_deltaTime = Runner.DeltaTime;
_graph = PlayableGraph.Create(name);
_graph.SetTimeUpdateMode(DirectorUpdateMode.Manual);
_mixer = AnimationLayerMixerPlayable.Create(_graph);
_output = AnimationPlayableOutput.Create(_graph, name, _animator);
_output.SetSourcePlayable(_mixer);
if (Object.HasStateAuthority == false)
{
ReadNetworkData();
}
AnimationLayer[] layers = _layers;
for (int i = 0, count = layers.Length; i < count; ++i)
{
AnimationLayer layer = layers[i];
layer.Spawned();
}
OnSpawned();
}
public override sealed void Despawned(NetworkRunner runner, bool hasState)
{
_isSpawned = false;
OnDespawned();
AnimationLayer[] layers = _layers;
for (int i = 0, count = layers != null ? layers.Length : 0; i < count; ++i)
{
AnimationLayer layer = layers[i];
if (layer != null)
{
layer.Despawned();
}
}
if (_graph.IsValid() == true)
{
_graph.Destroy();
}
_hasManualUpdate = default;
_evaluateOnResimulation = default;
_fixedEvaluationSettings.Reset();
_renderEvaluationSettings.Reset();
}
public override sealed void FixedUpdateNetwork()
{
if (_hasManualUpdate == true)
return;
_fixedUpdateMarker.Begin();
OnFixedUpdateInternal();
_fixedUpdateMarker.End();
}
public override sealed void Render()
{
if (_hasManualUpdate == true)
return;
_renderUpdateMarker.Begin();
OnRenderUpdateInternal();
_renderUpdateMarker.End();
}
// IAfterSpawned INTERFACE
void IAfterSpawned.AfterSpawned()
{
if (Object.IsInSimulation == true)
{
WriteNetworkData();
}
}
// IAfterClientPredictionReset INTERFACE
void IAfterClientPredictionReset.AfterClientPredictionReset()
{
_restoreStateMarker.Begin();
ReadNetworkData();
_restoreStateMarker.End();
}
// IAfterTick INTERFACE
void IAfterTick.AfterTick()
{
_afterTickMarker.Begin();
WriteNetworkData();
_afterTickMarker.End();
}
// MonoBehaviour INTERFACE
protected virtual void Awake()
{
InitializeLayers();
InitializeNetworkProperties();
OnInitialize();
}
protected virtual void OnDestroy()
{
if (_isSpawned == true)
{
Despawned(null, false);
}
OnDeinitialize();
DeinitializeNetworkProperties();
DeinitializeLayers();
}
// PRIVATE METHODS
private void InitializeLayers()
{
if (_layers != null)
return;
List activeLayers = new List(8);
Transform root = _root;
for (int i = 0, count = root.childCount; i < count; ++i)
{
Transform child = root.GetChild(i);
AnimationLayer layer = child.GetComponentNoAlloc();
if (layer != null && layer.enabled == true && layer.gameObject.activeSelf == true)
{
activeLayers.Add(layer);
}
}
_layers = activeLayers.ToArray();
AnimationLayer[] layers = _layers;
for (int i = 0, count = layers.Length; i < count; ++i)
{
AnimationLayer layer = layers[i];
layer.Initialize(this);
}
}
private void DeinitializeLayers()
{
AnimationLayer[] layers = _layers;
for (int i = 0, count = layers != null ? layers.Length : 0; i < count; ++i)
{
AnimationLayer layer = layers[i];
if (layer != null)
{
layer.Deinitialize();
}
}
_layers = null;
}
private void OnFixedUpdateInternal()
{
if (Runner.Stage == default)
throw new InvalidOperationException();
_deltaTime = Runner.DeltaTime;
AnimationLayer[] layers = _layers;
for (int i = 0, count = layers.Length; i < count; ++i)
{
AnimationLayer layer = layers[i];
layer.ManualFixedUpdate();
}
OnFixedUpdate();
if (_fixedEvaluationSettings.Mode != EEvaluationMode.None && (_evaluateOnResimulation == true || Runner.IsResimulation == false))
{
bool evaluate = true;
if (_fixedEvaluationSettings.Mode == EEvaluationMode.Interlaced && _fixedEvaluationSettings.Frames > 1)
{
int targetSeed = Runner.Tick.Raw % _fixedEvaluationSettings.Frames;
if (_fixedEvaluationSettings.Seed != targetSeed)
{
evaluate = false;
}
}
else if (_fixedEvaluationSettings.Mode == EEvaluationMode.Periodic)
{
float deltaTime = Runner.DeltaTime;
float simulationTime = Runner.SimulationTime + _fixedEvaluationSettings.Offset;
float timeSincePeriod = simulationTime % _fixedEvaluationSettings.Period;
if (timeSincePeriod > deltaTime)
{
evaluate = false;
}
}
if (evaluate == true)
{
EvaluateInternal(false);
}
}
}
private void OnRenderUpdateInternal()
{
if (Runner.Stage != default)
throw new InvalidOperationException();
_deltaTime = Time.deltaTime;
if (_renderEvaluationSettings.Mode != EEvaluationMode.None)
{
bool evaluate = true;
if (_renderEvaluationSettings.Mode == EEvaluationMode.Interlaced && _renderEvaluationSettings.Frames > 1)
{
int targetSeed = Time.frameCount % _renderEvaluationSettings.Frames;
if (_renderEvaluationSettings.Seed != targetSeed)
{
evaluate = false;
}
}
else if (_renderEvaluationSettings.Mode == EEvaluationMode.Periodic)
{
_renderEvaluationSettings.Offset += Time.deltaTime;
if (_renderEvaluationSettings.Offset > _renderEvaluationSettings.Period)
{
_renderEvaluationSettings.Offset %= _renderEvaluationSettings.Period;
}
else
{
evaluate = false;
}
}
if (evaluate == true)
{
InterpolateInternal();
EvaluateInternal(true);
}
}
}
private void InterpolateInternal()
{
_interpolateMarker.Begin();
InterpolateNetworkData();
AnimationLayer[] layers = _layers;
for (int i = 0, count = layers.Length; i < count; ++i)
{
AnimationLayer layer = layers[i];
layer.Interpolate();
}
OnInterpolate();
_interpolateMarker.End();
}
private void EvaluateInternal(bool interpolated)
{
_evaluateMarker.Begin();
AnimationLayer[] layers = _layers;
for (int i = 0, count = layers.Length; i < count; ++i)
{
AnimationLayer layer = layers[i];
layer.SetPlayableInputWeights(interpolated);
}
_evaluateGraphMarker.Begin();
_graph.Evaluate();
_evaluateGraphMarker.End();
OnEvaluate();
_evaluateMarker.End();
}
private sealed class EvaluationSettings
{
public EEvaluationMode Mode = EEvaluationMode.Full;
public int Frames;
public int Seed;
public float Period;
public float Offset;
public void Reset()
{
Mode = EEvaluationMode.Full;
Frames = default;
Seed = default;
Period = default;
Offset = default;
}
}
}
}