reanimator
This commit is contained in:
24
Runtime/Engine/Code/Animations/AnimationClipNode.cs
Normal file
24
Runtime/Engine/Code/Animations/AnimationClipNode.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Animations;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace RebootKit.Engine.Animations {
|
||||
public class AnimationClipNode : IReAnimatorNode {
|
||||
[field: SerializeField] public string Name { get; private set; }
|
||||
|
||||
public AnimationClip Clip;
|
||||
|
||||
public void Tick(float deltaTime) {
|
||||
}
|
||||
|
||||
public IPlayable Build(PlayableGraph graph) {
|
||||
AnimationClipPlayable playable = AnimationClipPlayable.Create(graph, Clip);
|
||||
return playable;
|
||||
}
|
||||
|
||||
public bool TryFindChild(string name, out IReAnimatorNode node) {
|
||||
node = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Runtime/Engine/Code/Animations/AnimationClipNode.cs.meta
Normal file
3
Runtime/Engine/Code/Animations/AnimationClipNode.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 784b3fdd05a24633992c1bb93b59e4d6
|
||||
timeCreated: 1754275137
|
||||
77
Runtime/Engine/Code/Animations/MixerNode.cs
Normal file
77
Runtime/Engine/Code/Animations/MixerNode.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Animations;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace RebootKit.Engine.Animations {
|
||||
[Serializable]
|
||||
public class MixerNode : IReAnimatorNode {
|
||||
[Serializable]
|
||||
struct InputState {
|
||||
[SerializeReference] public IReAnimatorNode node;
|
||||
|
||||
[Range(0.0f, 1.0f)] public float targetWeight;
|
||||
[NonSerialized] public float CurrentWeight;
|
||||
}
|
||||
|
||||
[field: SerializeField] public string Name { get; private set; }
|
||||
|
||||
AnimationMixerPlayable m_Mixer;
|
||||
|
||||
[SerializeField] float m_TransitionSpeed = 5.0f;
|
||||
[SerializeField] InputState[] m_Inputs;
|
||||
|
||||
public void Tick(float deltaTime) {
|
||||
for (int i = 0; i < m_Inputs.Length; ++i){
|
||||
if (m_TransitionSpeed > 0.0f) {
|
||||
m_Inputs[i].CurrentWeight = Mathf.MoveTowards(m_Inputs[i].CurrentWeight,
|
||||
m_Inputs[i].targetWeight,
|
||||
m_TransitionSpeed * deltaTime);
|
||||
} else {
|
||||
m_Inputs[i].CurrentWeight = m_Inputs[i].targetWeight;
|
||||
}
|
||||
|
||||
m_Mixer.SetInputWeight(i, m_Inputs[i].CurrentWeight);
|
||||
|
||||
m_Inputs[i].node.Tick(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public IPlayable Build(PlayableGraph graph) {
|
||||
m_Mixer = AnimationMixerPlayable.Create(graph, m_Inputs.Length);
|
||||
|
||||
for (int i = 0; i < m_Inputs.Length; ++i) {
|
||||
IPlayable playable = m_Inputs[i].node.Build(graph);
|
||||
|
||||
if (playable is AnimationMixerPlayable mixerPlayable) {
|
||||
m_Mixer.ConnectInput(i, mixerPlayable, 0, m_Inputs[i].targetWeight);
|
||||
} else if (playable is AnimationClipPlayable clipPlayable) {
|
||||
m_Mixer.ConnectInput(i, clipPlayable, 0, m_Inputs[i].targetWeight);
|
||||
}
|
||||
}
|
||||
|
||||
return m_Mixer;
|
||||
}
|
||||
|
||||
public bool TryFindChild(string name, out IReAnimatorNode node) {
|
||||
for (int i = 0; i < m_Inputs.Length; i++) {
|
||||
if (m_Inputs[i].node.Name.Equals(name, StringComparison.Ordinal)) {
|
||||
node = m_Inputs[i].node;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_Inputs[i].node.TryFindChild(name, out IReAnimatorNode childNode)) {
|
||||
node = childNode;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
node = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SetInputWeight(int index, float weight) {
|
||||
m_Inputs[index].targetWeight = weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Runtime/Engine/Code/Animations/MixerNode.cs.meta
Normal file
3
Runtime/Engine/Code/Animations/MixerNode.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a76aeb9f1d9542f6ba98ffec129beb0c
|
||||
timeCreated: 1754275958
|
||||
116
Runtime/Engine/Code/Animations/ReAnimator.cs
Normal file
116
Runtime/Engine/Code/Animations/ReAnimator.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Runtime/Engine/Code/Animations/ReAnimator.cs.meta
Normal file
3
Runtime/Engine/Code/Animations/ReAnimator.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 960522ea44ce4513aea34826f00bc19c
|
||||
timeCreated: 1754272717
|
||||
Reference in New Issue
Block a user