Files
RebootKit/Runtime/Engine/Code/Animations/ReAnimator.cs
2025-08-08 23:37:33 +02:00

116 lines
3.8 KiB
C#

using System;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Assertions;
using UnityEngine.Playables;
using Logger = RebootKit.Engine.Foundation.Logger;
// @TODO:
// - avoid boxing values
// - think about refactoring in order to reduce gc allocs
namespace RebootKit.Engine.Animations {
public interface IReAnimatorNode {
string Name { get; }
void Tick(float deltaTime);
IPlayable Build(PlayableGraph graph);
bool TryFindChild(string name, out IReAnimatorNode node);
}
[Serializable]
class ReAnimatorLayer {
public string name;
public AvatarMask mask;
public bool isAdditive;
[Range(0.0f, 1.0f)] public float weight = 1.0f;
[SerializeReference] public IReAnimatorNode root;
}
[DefaultExecutionOrder(-100)]
public class ReAnimator : MonoBehaviour {
static readonly Logger s_Logger = new Logger(nameof(ReAnimator));
[SerializeField] Animator m_Animator;
[SerializeField] ReAnimatorLayer[] m_Layers;
PlayableGraph m_Graph;
AnimationPlayableOutput m_Output;
AnimationLayerMixerPlayable m_LayerMixer;
void Awake() {
Assert.IsNotNull(m_Animator, "Animator is not set");
Assert.IsTrue(m_Layers.Length > 0, "Atleast one layer must be defined!");
m_Animator.runtimeAnimatorController = null;
m_Graph = PlayableGraph.Create(name);
m_Output = AnimationPlayableOutput.Create(m_Graph, "Animation Output", m_Animator);
m_LayerMixer = AnimationLayerMixerPlayable.Create(m_Graph, m_Layers.Length);
m_Output.SetSourcePlayable(m_LayerMixer, 0);
for (int i = 0; i < m_Layers.Length; ++i) {
IPlayable playable = m_Layers[i].root.Build(m_Graph);
if (playable is AnimationMixerPlayable mixerPlayable) {
m_LayerMixer.ConnectInput(i, mixerPlayable, 0, m_Layers[i].weight);
} else if (playable is AnimationClipPlayable clipPlayable) {
m_LayerMixer.ConnectInput(i, clipPlayable, 0, m_Layers[i].weight);
}
m_LayerMixer.SetLayerAdditive((uint)i, m_Layers[i].isAdditive);
if (m_Layers[i].mask != null) {
m_LayerMixer.SetLayerMaskFromAvatarMask((uint)i, m_Layers[i].mask);
}
}
m_Graph.Play();
}
void OnDestroy() {
if (m_Graph.IsValid()) {
m_Graph.Destroy();
}
}
void Update() {
for (int i = 0; i < m_Layers.Length; i++) {
m_Layers[i].root.Tick(Time.deltaTime);
}
}
public void SetLayerWeight(int layer, float weight) {
m_Layers[layer].weight = weight;
m_LayerMixer.SetInputWeight(layer, weight);
}
public IReAnimatorNode FindNode(string nodeName) {
foreach (ReAnimatorLayer layer in m_Layers) {
if (layer.root.Name.Equals(nodeName, StringComparison.Ordinal)) {
return layer.root;
}
if (layer.root.TryFindChild(nodeName, out IReAnimatorNode childNode)) {
return childNode;
}
}
s_Logger.Error($"Couldn't find node with name: {nodeName}");
return null;
}
public TNode FindNode<TNode>(string nodeName) where TNode : class, IReAnimatorNode {
IReAnimatorNode node = FindNode(nodeName);
if (node is TNode tnode) {
return tnode;
}
s_Logger.Error($"Couldn't find node with name: {nodeName} of type {typeof(TNode).Name}");
return null;
}
}
}