reanimator
This commit is contained in:
3
Runtime/Engine/Code/Animations.meta
Normal file
3
Runtime/Engine/Code/Animations.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3cf39a45a5774becbca8108fd7378be4
|
||||||
|
timeCreated: 1754272712
|
||||||
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
|
||||||
@@ -196,6 +196,12 @@ namespace RebootKit.Engine.Main {
|
|||||||
Network.SetCurrentWorld(worldID);
|
Network.SetCurrentWorld(worldID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Actor SpawnLocalOnlyActor(AssetReferenceGameObject assetReference,
|
||||||
|
Vector3 position,
|
||||||
|
Quaternion rotation) {
|
||||||
|
return Network.Actors.SpawnLocalOnlyActor(assetReference, position, rotation);
|
||||||
|
}
|
||||||
|
|
||||||
public static Actor SpawnActor(AssetReferenceGameObject assetReference,
|
public static Actor SpawnActor(AssetReferenceGameObject assetReference,
|
||||||
Vector3 position,
|
Vector3 position,
|
||||||
Quaternion rotation) {
|
Quaternion rotation) {
|
||||||
|
|||||||
@@ -250,6 +250,8 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal bool IsLocalOnly;
|
||||||
|
|
||||||
[SerializeField] internal Rigidbody actorRigidbody;
|
[SerializeField] internal Rigidbody actorRigidbody;
|
||||||
[InfoBox("If empty, will use GetComponentsInChildren<Collider>() to find colliders.")]
|
[InfoBox("If empty, will use GetComponentsInChildren<Collider>() to find colliders.")]
|
||||||
[SerializeField] Collider[] m_OverrideActorColliders;
|
[SerializeField] Collider[] m_OverrideActorColliders;
|
||||||
@@ -346,7 +348,7 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void MountTo(Actor actor, string slotName) {
|
public void MountTo(Actor actor, string slotName) {
|
||||||
if (!RR.IsServer()) {
|
if (!RR.IsServer() && !IsLocalOnly) {
|
||||||
s_ActorLogger.Error($"Only the server can mount actors. Actor: {name} (ID: {ActorID})");
|
s_ActorLogger.Error($"Only the server can mount actors. Actor: {name} (ID: {ActorID})");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -370,7 +372,7 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void UnMount() {
|
public void UnMount() {
|
||||||
if (!RR.IsServer()) {
|
if (!RR.IsServer() && !IsLocalOnly) {
|
||||||
s_ActorLogger.Error($"Only the server can unmount actors. Actor: {name} (ID: {ActorID})");
|
s_ActorLogger.Error($"Only the server can unmount actors. Actor: {name} (ID: {ActorID})");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,6 +188,35 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
actor.ActorID = actorID;
|
actor.ActorID = actorID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Actor SpawnLocalOnlyActor(AssetReferenceGameObject assetReference, Vector3 position, Quaternion rotation) {
|
||||||
|
if (!assetReference.RuntimeKeyIsValid()) {
|
||||||
|
s_Logger.Error("Trying to spawn an actor with an invalid asset reference.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryGenerateNextActorID(out ushort actorID)) {
|
||||||
|
s_Logger.Error("Cannot spawn actor: Failed to generate next actor ID.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameObject actorObject = assetReference.InstantiateAsync(position, rotation).WaitForCompletion();
|
||||||
|
Actor actor = actorObject.GetComponent<Actor>();
|
||||||
|
if (actor is null) {
|
||||||
|
s_Logger.Error($"GameObject {actorObject.name} does not have an Actor component.");
|
||||||
|
Object.Destroy(actorObject);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
actor.Manager = this;
|
||||||
|
actor.IsLocalOnly = true;
|
||||||
|
actor.SourceActorPath = assetReference.AssetGUID;
|
||||||
|
actor.ActorID = actorID;
|
||||||
|
actor.Data = actor.InternalCreateActorData();
|
||||||
|
// m_SpawnedActors.Add(actor);
|
||||||
|
|
||||||
|
return actor;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// @MARK: Common API
|
// @MARK: Common API
|
||||||
|
|||||||
Reference in New Issue
Block a user