362 lines
12 KiB
C#
362 lines
12 KiB
C#
using System;
|
|
using System.Runtime.CompilerServices;
|
|
using Animancer;
|
|
using RebootKit.Engine.Animations;
|
|
using RebootReality.jelycho.Items;
|
|
using Unity.Mathematics;
|
|
using UnityEngine;
|
|
using UnityEngine.Animations;
|
|
using UnityEngine.Playables;
|
|
using UnityEvent = UnityEngine.Events.UnityEvent;
|
|
|
|
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);
|
|
}
|
|
|
|
float turnWeight = math.clamp(math.abs(m_Turning), 0.0f, 1.0f);
|
|
|
|
if (m_Turning > 0.1f) {
|
|
m_Mixer.SetInputWeight(5, turnWeight);
|
|
} else if (m_Turning < -0.1f) {
|
|
m_Mixer.SetInputWeight(6, turnWeight);
|
|
}
|
|
|
|
m_Mixer.SetInputWeight(0, 1.0f - turnWeight);
|
|
|
|
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);
|
|
|
|
var idlePlayable = AnimationClipPlayable.Create(graph, m_IdleClip);
|
|
var runForwardPlayable = AnimationClipPlayable.Create(graph, m_RunForwardClip);
|
|
var runBackwardsPlayable = AnimationClipPlayable.Create(graph, m_RunBackwardsClip);
|
|
var strafeRightPlayable = AnimationClipPlayable.Create(graph, m_StrafeRightClip);
|
|
var strafeLeftPlayable = AnimationClipPlayable.Create(graph, m_StrafeLeftClip);
|
|
var turnRightPlayable = AnimationClipPlayable.Create(graph, m_TurnRightClip);
|
|
var 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 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()) {
|
|
Debug.Log("QUICK ATTACK FINISHEDDD");
|
|
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);
|
|
|
|
m_State = State.QuickAttack;
|
|
}
|
|
|
|
public void SetIdle() {
|
|
if (m_ClipsSet == null) {
|
|
return;
|
|
}
|
|
|
|
m_AnimancerState = m_AnimancerLayer.Play(m_ClipsSet.idle);
|
|
m_State = State.Idle;
|
|
}
|
|
|
|
public void SetCharging() {
|
|
if (m_ClipsSet == null) {
|
|
return;
|
|
}
|
|
|
|
m_AnimancerState = m_AnimancerLayer.Play(m_ClipsSet.charging);
|
|
m_State = State.Charging;
|
|
}
|
|
|
|
public void SetChargedIdle() {
|
|
if (m_ClipsSet == null) {
|
|
return;
|
|
}
|
|
|
|
m_AnimancerState = m_AnimancerLayer.Play(m_ClipsSet.chargedIdle);
|
|
m_State = State.ChargedIdle;
|
|
}
|
|
|
|
public void PlayChargedUse() {
|
|
if (m_ClipsSet == null) {
|
|
return;
|
|
}
|
|
|
|
m_AnimancerState = m_AnimancerLayer.Play(m_ClipsSet.chargedUse);
|
|
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] AvatarMask m_HandsMask;
|
|
|
|
AnimancerLayer m_LocomotionLayer;
|
|
AnimancerLayer m_HandsLayer;
|
|
PlayerHandsAnimator 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_LocomotionLayer = m_Animancer.Layers[0];
|
|
m_HandsLayer = m_Animancer.Layers[1];
|
|
|
|
m_LocomotionLayer.SetDebugName("Locomotion");
|
|
m_HandsLayer.SetDebugName("Hands");
|
|
m_HandsLayer.Mask = m_HandsMask;
|
|
|
|
m_Hands = new PlayerHandsAnimator(m_HandsLayer);
|
|
m_Hands.OnQuickAttackAnimationFinished += () => onQuickAttackFinished.Invoke();
|
|
m_Hands.OnCharged += () => onChargeReady.Invoke();
|
|
|
|
m_SmoothLocomotionDirection = new SmoothedVector2Parameter(m_Animancer,
|
|
m_GroundLocomotionPropertyX,
|
|
m_GroundLocomotionPropertyY,
|
|
0.1f);
|
|
m_LocomotionLayer.Play(m_GroundLocomotion);
|
|
m_HandsLayer.SetWeight(0.0f);
|
|
|
|
m_IsReady = true;
|
|
}
|
|
|
|
void OnDestroy() {
|
|
m_SmoothLocomotionDirection.Dispose();
|
|
}
|
|
|
|
void Update() {
|
|
float dt = Time.deltaTime;
|
|
m_Hands.Tick(dt);
|
|
}
|
|
|
|
public void SetLocomotionParams(PlayerLocomotionAnimatorParams locomotionParams) {
|
|
if (!m_IsReady) {
|
|
return;
|
|
}
|
|
|
|
//m_LegsLayerMixer.SetLayerWeight(1, locomotionParams.IsGrounded ? 0.0f : 1.0f);
|
|
|
|
var groundBlendDirection = new float2(locomotionParams.VelocityRightNormalized,
|
|
locomotionParams.VelocityForwardNormalized);
|
|
//m_GroundBlendTree.SetInput(groundBlendDirection, locomotionParams.TurnVelocity);
|
|
|
|
m_SmoothLocomotionDirection.TargetValue = groundBlendDirection;
|
|
}
|
|
|
|
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) {
|
|
// int index = combo % m_ItemHandsClips.quickAttacks.Length;
|
|
// AnimancerState state = m_HandsLayer.Play(m_ItemHandsClips.quickAttacks[index]);
|
|
// state.Events(this).OnEnd ??= OnQuickAttackAnimationEnd;
|
|
m_Hands.PlayQuickAttack(combo);
|
|
}
|
|
|
|
// void OnQuickAttackAnimationEnd() {
|
|
// PlayHandsIdle();
|
|
// onQuickAttackFinished?.Invoke();
|
|
// }
|
|
|
|
public void PlayHandsIdle() {
|
|
m_Hands.SetIdle();
|
|
}
|
|
|
|
public void PlayCharging() {
|
|
m_Hands.SetCharging();
|
|
}
|
|
|
|
public void PlayChargedUse() {
|
|
m_Hands.PlayChargedUse();
|
|
}
|
|
|
|
public void PlayKickAnimation() {
|
|
// m_LegsLayerMixer.SetLayerWeight(2, 1.0f);
|
|
|
|
// m_QuickKickNode.PlayOnceWithCallback(() => {
|
|
// m_LegsLayerMixer.SetLayerWeight(2, 0.0f);
|
|
// });
|
|
}
|
|
}
|
|
} |