reanimator

This commit is contained in:
2025-08-08 23:37:33 +02:00
parent 369f7099ad
commit 2d06552025
10 changed files with 268 additions and 2 deletions

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3cf39a45a5774becbca8108fd7378be4
timeCreated: 1754272712

View 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;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 784b3fdd05a24633992c1bb93b59e4d6
timeCreated: 1754275137

View 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;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a76aeb9f1d9542f6ba98ffec129beb0c
timeCreated: 1754275958

View 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;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 960522ea44ce4513aea34826f00bc19c
timeCreated: 1754272717

View File

@@ -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) {

View File

@@ -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;
} }

View File

@@ -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