using System; using System.Runtime.CompilerServices; using Animancer; using RebootReality.jelycho.Items; using Unity.Mathematics; using UnityEngine; using UnityEvent = UnityEngine.Events.UnityEvent; namespace RebootReality.jelycho.Player { public struct PlayerLocomotionAnimatorParams { public float VelocityForwardNormalized; public float VelocityRightNormalized; public float TurnVelocity; public bool IsGrounded; } public class PlayerHandsAnimator { enum State { None, Idle, QuickAttack, Charging, ChargedIdle, Charged, ChargedUse } AnimancerLayer m_AnimancerLayer; AnimancerState m_AnimancerState; ItemHandsAnimationClipsSet m_ClipsSet; State m_State; public event Action OnQuickAttackAnimationFinished = delegate { }; public event Action OnCharged = delegate { }; public PlayerHandsAnimator(AnimancerLayer layer) { m_AnimancerLayer = layer; } public void Tick(float deltaTime) { if (m_AnimancerState == null) { return; } switch (m_State) { case State.QuickAttack: { if (IsCurrentClipFinished()) { // m_AnimancerState.Stop(); m_AnimancerState = null; OnQuickAttackAnimationFinished?.Invoke(); } break; } case State.Charging: { if (IsCurrentClipFinished()) { SetChargedIdle(); OnCharged?.Invoke(); } break; } case State.ChargedUse: { if (IsCurrentClipFinished()) { SetIdle(); } break; } } } 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_AnimancerState = m_AnimancerLayer.Play(clip, 0.1f); m_State = State.QuickAttack; } public void SetIdle() { if (m_ClipsSet == null) { return; } m_AnimancerState = m_AnimancerLayer.Play(m_ClipsSet.idle, 0.1f); m_State = State.Idle; } public void SetCharging() { if (m_ClipsSet == null) { return; } m_AnimancerState = m_AnimancerLayer.Play(m_ClipsSet.charging, 0.1f); m_State = State.Charging; } public void SetChargedIdle() { if (m_ClipsSet == null) { return; } m_AnimancerState = m_AnimancerLayer.Play(m_ClipsSet.chargedIdle, 0.1f); m_State = State.ChargedIdle; } public void PlayChargedUse() { if (m_ClipsSet == null) { return; } m_AnimancerState = m_AnimancerLayer.Play(m_ClipsSet.chargedUse, 0.1f); m_State = State.ChargedUse; } [MethodImpl(MethodImplOptions.AggressiveInlining)] bool IsCurrentClipFinished() { return m_AnimancerState.NormalizedTime >= 1.0f; } } public class PlayerAnimator : MonoBehaviour { [SerializeField] AnimancerComponent m_Animancer; [SerializeField] AnimationClip m_IdleClip; [SerializeField] TransitionAsset m_GroundLocomotion; [SerializeField] StringAsset m_GroundLocomotionPropertyX; [SerializeField] StringAsset m_GroundLocomotionPropertyY; SmoothedVector2Parameter m_SmoothLocomotionDirection; [SerializeField] TransitionAsset m_GroundTurn; [SerializeField] StringAsset m_GroundTurnProperty; SmoothedFloatParameter m_SmoothGroundTurn; [SerializeField] AnimationClip m_InAirClip; [SerializeField] AnimationClip m_KickClip; [SerializeField] AvatarMask m_HandsMask; [SerializeField] AvatarMask m_LegsMask; AnimancerLayer m_BaseLayer; AnimancerLayer m_LocomotionLegsLayer; AnimancerLayer m_HandsLayer; AnimancerLayer m_FullBodyLayer; PlayerHandsAnimator m_Hands; bool m_IsPlayingKickAnimation; // @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_BaseLayer = m_Animancer.Layers[0]; m_BaseLayer.SetDebugName("Base Layer"); m_BaseLayer.Play(m_IdleClip); m_LocomotionLegsLayer = m_Animancer.Layers[1]; m_LocomotionLegsLayer.SetDebugName("Locomotion Legs"); m_LocomotionLegsLayer.Mask = m_LegsMask; m_SmoothLocomotionDirection = new SmoothedVector2Parameter(m_Animancer, m_GroundLocomotionPropertyX, m_GroundLocomotionPropertyY, 0.1f); m_LocomotionLegsLayer.Play(m_GroundLocomotion); m_SmoothGroundTurn = new SmoothedFloatParameter(m_Animancer, m_GroundTurnProperty, 0.1f); m_HandsLayer = m_Animancer.Layers[2]; m_HandsLayer.SetDebugName("Hands"); m_HandsLayer.Mask = m_HandsMask; m_HandsLayer.SetWeight(0.0f); m_Hands = new PlayerHandsAnimator(m_HandsLayer); m_Hands.OnQuickAttackAnimationFinished += () => onQuickAttackFinished.Invoke(); m_Hands.OnCharged += () => onChargeReady.Invoke(); m_FullBodyLayer = m_Animancer.Layers[3]; m_FullBodyLayer.SetDebugName("Full body"); m_FullBodyLayer.SetWeight(0.0f); m_IsReady = true; } void OnDestroy() { m_SmoothLocomotionDirection.Dispose(); } void Update() { float dt = Time.deltaTime; m_Hands.Tick(dt); if (m_IsPlayingKickAnimation && m_FullBodyLayer.CurrentState.NormalizedTime >= 1.0f) { m_FullBodyLayer.SetWeight(0.0f); m_IsPlayingKickAnimation = false; } } public void SetLocomotionParams(PlayerLocomotionAnimatorParams locomotionParams) { if (!m_IsReady) { return; } if (!locomotionParams.IsGrounded) { m_LocomotionLegsLayer.Play(m_InAirClip, 0.1f); return; } if (math.abs(locomotionParams.TurnVelocity) > 0.1f) { m_SmoothGroundTurn.TargetValue = locomotionParams.TurnVelocity; m_LocomotionLegsLayer.Play(m_GroundTurn, 0.1f); } else { var groundBlendDirection = new float2(locomotionParams.VelocityRightNormalized, locomotionParams.VelocityForwardNormalized); m_SmoothLocomotionDirection.TargetValue = groundBlendDirection; m_LocomotionLegsLayer.Play(m_GroundLocomotion, 0.1f); } } public void SetHandsAnimationSet(ItemHandsAnimationClipsSet clipsSet) { if (clipsSet == null) { m_HandsLayer.SetWeight(0.0f); return; } m_HandsLayer.SetWeight(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(); } public void PlayKickAnimation() { if (m_IsPlayingKickAnimation) { return; } m_FullBodyLayer.Play(m_KickClip, 0.1f).Time = 0.0f; m_IsPlayingKickAnimation = true; } } }