using System; using System.ComponentModel.DataAnnotations.Schema; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using RebootKit.Engine.Animations; using RebootReality.jelycho.Items; using Unity.Mathematics; using UnityEditor.Search; using UnityEngine; using UnityEngine.Animations; using UnityEngine.Events; using UnityEngine.Playables; namespace RebootReality.jelycho.Player { public struct PlayerLocomotionAnimatorParams { public float VelocityForwardNormalized; public float VelocityRightNormalized; public float TurnVelocity; public bool IsGrounded; } [Serializable] public class BasicCharacterLocomotionReAnimatorNode : IReAnimatorNode { [field: SerializeField] public string Name { get; private set; } [SerializeField] AnimationClip m_IdleClip; [SerializeField] AnimationClip m_RunForwardClip; [SerializeField] AnimationClip m_RunBackwardsClip; [SerializeField] AnimationClip m_StrafeRightClip; [SerializeField] AnimationClip m_StrafeLeftClip; [SerializeField] AnimationClip m_TurnRightClip; [SerializeField] AnimationClip m_TurnLeftClip; [SerializeField] float m_TransitionSpeed = 5.0f; [SerializeField, Range(0.0f, 1.0f)] float m_ForceIdleMagnitudeThreshold = 0.2f; AnimationMixerPlayable m_Mixer; float2 m_TargetInput; float2 m_CurrentInput; float m_Turning; public void Tick(float deltaTime) { if (m_TransitionSpeed > 0.0f) { m_CurrentInput = Vector2.MoveTowards(m_CurrentInput, m_TargetInput, m_TransitionSpeed * deltaTime); } else { m_CurrentInput = m_TargetInput; } if (math.length(m_CurrentInput) <= m_ForceIdleMagnitudeThreshold) { for (int i = 0; i < 7; ++i) { m_Mixer.SetInputWeight(i, 0.0f); } if (m_Turning > 0.1f) { m_Mixer.SetInputWeight(5, 1.0f); } else if (m_Turning < -0.1f) { m_Mixer.SetInputWeight(6, 1.0f); } else { m_Mixer.SetInputWeight(0, 1.0f); } return; } float inputMagnitude = math.length(m_CurrentInput); float2 inputNormalized = math.normalizesafe(m_CurrentInput); inputMagnitude = math.min(1.0f, inputMagnitude); float forwardWeight = math.max(0.0f, math.dot(inputNormalized, new float2(0, 1)) * inputMagnitude); float backwardsWeight = math.max(0.0f, math.dot(inputNormalized, new float2(0, -1)) * inputMagnitude); float rightWeight = math.max(0.0f, math.dot(inputNormalized, new float2(1, 0)) * inputMagnitude); float leftWeight = math.max(0.0f, math.dot(inputNormalized, new float2(-1, 0)) * inputMagnitude); float totalWeight = forwardWeight + backwardsWeight + rightWeight + leftWeight; if (totalWeight > 1.0f) { forwardWeight /= totalWeight; backwardsWeight /= totalWeight; rightWeight /= totalWeight; leftWeight /= totalWeight; totalWeight = 1.0f; } float idleWeight = math.max(0.0f, 1.0f - totalWeight); m_Mixer.SetInputWeight(0, idleWeight); m_Mixer.SetInputWeight(1, forwardWeight); m_Mixer.SetInputWeight(2, backwardsWeight); m_Mixer.SetInputWeight(3, rightWeight); m_Mixer.SetInputWeight(4, leftWeight); m_Mixer.SetInputWeight(5, 0.0f); m_Mixer.SetInputWeight(6, 0.0f); } public IPlayable Build(PlayableGraph graph) { m_Mixer = AnimationMixerPlayable.Create(graph, 7); AnimationClipPlayable idlePlayable = AnimationClipPlayable.Create(graph, m_IdleClip); AnimationClipPlayable runForwardPlayable = AnimationClipPlayable.Create(graph, m_RunForwardClip); AnimationClipPlayable runBackwardsPlayable = AnimationClipPlayable.Create(graph, m_RunBackwardsClip); AnimationClipPlayable strafeRightPlayable = AnimationClipPlayable.Create(graph, m_StrafeRightClip); AnimationClipPlayable strafeLeftPlayable = AnimationClipPlayable.Create(graph, m_StrafeLeftClip); AnimationClipPlayable turnRightPlayable = AnimationClipPlayable.Create(graph, m_TurnRightClip); AnimationClipPlayable turnLeftPlayable = AnimationClipPlayable.Create(graph, m_TurnLeftClip); m_Mixer.ConnectInput(0, idlePlayable, 0, 1.0f); m_Mixer.ConnectInput(1, runForwardPlayable, 0, 0.0f); m_Mixer.ConnectInput(2, runBackwardsPlayable, 0, 0.0f); m_Mixer.ConnectInput(3, strafeRightPlayable, 0, 0.0f); m_Mixer.ConnectInput(4, strafeLeftPlayable, 0, 0.0f); m_Mixer.ConnectInput(5, turnRightPlayable, 0, 0.0f); m_Mixer.ConnectInput(6, turnLeftPlayable, 0, 0.0f); return m_Mixer; } public bool TryFindChild(string name, out IReAnimatorNode node) { node = null; return false; } public void SetInput(float2 input, float turning) { m_TargetInput = input; m_Turning = turning; } } public class CharacterHandsReAnimatorNode : IReAnimatorNode { enum State { None, Idle, QuickAttack, Charging, ChargedIdle, Charged, ChargedUse } [field: SerializeField] public string Name { get; private set; } PlayableGraph m_Graph; AnimationMixerPlayable m_Mixer; AnimationClipPlayable m_CurrentPlayable; ItemHandsAnimationClipsSet m_ClipsSet; State m_State; public event Action OnQuickAttackAnimationFinished = delegate { }; public event Action OnCharged = delegate { }; public void Tick(float deltaTime) { switch (m_State) { case State.QuickAttack: { if (IsCurrentClipFinished() && m_CurrentPlayable.GetPlayState() == PlayState.Playing) { m_CurrentPlayable.Pause(); OnQuickAttackAnimationFinished?.Invoke(); } break; } case State.Charging: { if (IsCurrentClipFinished()) { SetChargedIdle(); OnCharged?.Invoke(); } break; } case State.ChargedUse: { if (IsCurrentClipFinished()) { SetIdle(); } break; } } } public IPlayable Build(PlayableGraph graph) { m_Graph = graph; m_Mixer = AnimationMixerPlayable.Create(graph, 1); return m_Mixer; } public bool TryFindChild(string name, out IReAnimatorNode node) { node = null; return false; } public void UpdateClips(ItemHandsAnimationClipsSet clipsSet) { m_ClipsSet = clipsSet; if (clipsSet == null) { m_State = State.None; return; } SetIdle(); } public void PlayQuickAttack(int combo) { if (m_ClipsSet == null) { return; } AnimationClip clip = m_ClipsSet.quickAttacks[combo % m_ClipsSet.quickAttacks.Length]; m_CurrentPlayable = AnimationClipPlayable.Create(m_Graph, clip); m_Mixer.DisconnectInput(0); m_Mixer.ConnectInput(0, m_CurrentPlayable, 0, 1.0f); m_State = State.QuickAttack; m_CurrentPlayable.Play(); } public void SetIdle() { if (m_ClipsSet == null) { return; } m_CurrentPlayable = AnimationClipPlayable.Create(m_Graph, m_ClipsSet.idle); m_Mixer.DisconnectInput(0); m_Mixer.ConnectInput(0, m_CurrentPlayable, 0, 1.0f); m_CurrentPlayable.Play(); m_State = State.Idle; } public void SetCharging() { if (m_ClipsSet == null) { return; } m_CurrentPlayable = AnimationClipPlayable.Create(m_Graph, m_ClipsSet.charging); m_Mixer.DisconnectInput(0); m_Mixer.ConnectInput(0, m_CurrentPlayable, 0, 1.0f); m_CurrentPlayable.Play(); m_State = State.Charging; } public void SetChargedIdle() { if (m_ClipsSet == null) { return; } m_CurrentPlayable = AnimationClipPlayable.Create(m_Graph, m_ClipsSet.chargedIdle); m_Mixer.DisconnectInput(0); m_Mixer.ConnectInput(0, m_CurrentPlayable, 0, 1.0f); m_CurrentPlayable.Play(); m_State = State.ChargedIdle; } public void PlayChargedUse() { if (m_ClipsSet == null) { return; } m_CurrentPlayable = AnimationClipPlayable.Create(m_Graph, m_ClipsSet.chargedUse); m_Mixer.DisconnectInput(0); m_Mixer.ConnectInput(0, m_CurrentPlayable, 0, 1.0f); m_CurrentPlayable.Play(); m_State = State.ChargedUse; } [MethodImpl(MethodImplOptions.AggressiveInlining)] bool IsCurrentClipFinished() { return m_CurrentPlayable.GetTime() >= m_CurrentPlayable.GetAnimationClip().length; } } public class PlayerAnimator : MonoBehaviour { [SerializeField] ReAnimator m_ReAnimator; MixerNode m_LocomotionRootMixer; BasicCharacterLocomotionReAnimatorNode m_GroundBlendTree; CharacterHandsReAnimatorNode m_Hands; // @TODO: for some reason `SetLocomotionParams` is called before awake bool m_IsReady = false; public UnityEvent onQuickAttackFinished = new UnityEvent(); public UnityEvent onChargeReady = new UnityEvent(); void Awake() { m_LocomotionRootMixer = m_ReAnimator.FindNode("locomotion_root"); m_LocomotionRootMixer.SetInputWeight(0, 1.0f); m_LocomotionRootMixer.SetInputWeight(1, 0.0f); m_GroundBlendTree = m_ReAnimator.FindNode("locomotion_ground"); m_GroundBlendTree.SetInput(new float2(0, 0), 0.0f); m_Hands = m_ReAnimator.FindNode("hands"); m_Hands.OnQuickAttackAnimationFinished += () => { onQuickAttackFinished?.Invoke(); }; m_Hands.OnCharged += () => { onChargeReady?.Invoke(); }; m_IsReady = true; } void Update() { } public void SetLocomotionParams(PlayerLocomotionAnimatorParams locomotionParams) { if (!m_IsReady) { return; } m_LocomotionRootMixer.SetInputWeight(0, locomotionParams.IsGrounded ? 1.0f : 0.0f); m_LocomotionRootMixer.SetInputWeight(1, locomotionParams.IsGrounded ? 0.0f : 1.0f); float2 groundBlendDirection = new float2(locomotionParams.VelocityRightNormalized, locomotionParams.VelocityForwardNormalized); m_GroundBlendTree.SetInput(groundBlendDirection, locomotionParams.TurnVelocity); } public void SetHandsAnimationSet(ItemHandsAnimationClipsSet clipsSet) { if (clipsSet == null) { m_ReAnimator.SetLayerWeight(1, 0.0f); return; } m_ReAnimator.SetLayerWeight(1, 1.0f); m_Hands.UpdateClips(clipsSet); } public void PlayQuickAttack(int combo) { m_Hands.PlayQuickAttack(combo); } public void PlayHandsIdle() { m_Hands.SetIdle(); } public void PlayCharging() { m_Hands.SetCharging(); } public void PlayChargedUse() { m_Hands.PlayChargedUse(); } } }