using UnityEngine; namespace Invector.vCharacterController { using vEventSystems; public class vThirdPersonAnimator : vThirdPersonMotor { #region Variables public float randomIdleCount; public const float walkSpeed = 0.5f; public const float runningSpeed = 1f; public const float sprintSpeed = 1.5f; public Vector3 lastCharacterPosition { get; protected set; } #endregion #if UNITY_EDITOR [ContextMenu("Enable AnimatorStateInfos Debug")] void EnableAnimatorStateInfosDebug() { if (animatorStateInfos != null) { animatorStateInfos.debug = true; } } [ContextMenu("Disable AnimatorStateInfos Debug")] void DisableAnimatorStateInfosDebug() { if (animatorStateInfos != null) { animatorStateInfos.debug = false; } } #endif protected override void Start() { base.Start(); RegisterAnimatorStateInfos(); } protected virtual void RegisterAnimatorStateInfos() { animatorStateInfos = new vAnimatorStateInfos(GetComponent()); animatorStateInfos.RegisterListener(); } protected override void OnEnable() { base.OnEnable(); if (animatorStateInfos.animator != null) { animatorStateInfos.RegisterListener(); } } protected override void OnDisable() { base.OnDisable(); animatorStateInfos.RemoveListener(); } public virtual void UpdateAnimator() { if (animator == null || !animator.enabled) { return; } AnimatorLayerControl(); ActionsControl(); TriggerRandomIdle(); CalculateRotationMagnitude(); UpdateAnimatorParameters(); DeadAnimation(); } public virtual void AnimatorLayerControl() { baseLayerInfo = animator.GetCurrentAnimatorStateInfo(baseLayer); underBodyInfo = animator.GetCurrentAnimatorStateInfo(underBodyLayer); rightArmInfo = animator.GetCurrentAnimatorStateInfo(rightArmLayer); leftArmInfo = animator.GetCurrentAnimatorStateInfo(leftArmLayer); upperBodyInfo = animator.GetCurrentAnimatorStateInfo(upperBodyLayer); fullBodyInfo = animator.GetCurrentAnimatorStateInfo(fullbodyLayer); } public virtual void ActionsControl() { // to have better control of your actions, you can assign bools to know if an animation is playing or not // this way you can use this bool to create custom behavior for the controller // identify if the rolling animations is playing isRolling = IsAnimatorTag("IsRolling"); // identify if a turn on spot animation is playing isTurningOnSpot = IsAnimatorTag("TurnOnSpot"); // locks player movement while a animation with tag 'LockMovement' is playing lockAnimMovement = IsAnimatorTag("LockMovement"); // locks player rotation while a animation with tag 'LockRotation' is playing lockAnimRotation = IsAnimatorTag("LockRotation"); // ! -- you can add the Tag "CustomAction" into a AnimationState and the character will not perform any Melee action -- ! customAction = IsAnimatorTag("CustomAction"); // identify if the controller is airborne isInAirborne = IsAnimatorTag("Airborne"); } public virtual void UpdateAnimatorParameters() { if (disableAnimations) { return; } animator.SetBool(vAnimatorParameters.IsStrafing, isStrafing); animator.SetBool(vAnimatorParameters.IsSprinting, isSprinting); animator.SetBool(vAnimatorParameters.IsSliding, isSliding && !isRolling); animator.SetBool(vAnimatorParameters.IsCrouching, isCrouching); animator.SetBool(vAnimatorParameters.IsGrounded, isGrounded); animator.SetBool(vAnimatorParameters.IsDead, isDead); animator.SetFloat(vAnimatorParameters.GroundDistance, groundDistance); animator.SetFloat(vAnimatorParameters.GroundAngle, GroundAngleFromDirection()); if (!isGrounded) { animator.SetFloat(vAnimatorParameters.VerticalVelocity, verticalVelocity); } //if (!lockAnimMovement) { if (isStrafing) { animator.SetFloat(vAnimatorParameters.InputHorizontal, horizontalSpeed, strafeSpeed.animationSmooth, Time.fixedDeltaTime); animator.SetFloat(vAnimatorParameters.InputVertical, verticalSpeed, strafeSpeed.animationSmooth, Time.fixedDeltaTime); } else { animator.SetFloat(vAnimatorParameters.InputVertical, verticalSpeed, freeSpeed.animationSmooth, Time.fixedDeltaTime); animator.SetFloat(vAnimatorParameters.InputHorizontal, 0, freeSpeed.animationSmooth, Time.fixedDeltaTime); } inputMagnitude = Mathf.LerpUnclamped(inputMagnitude, 0f, stopMoveWeight); animator.SetFloat(vAnimatorParameters.InputMagnitude, inputMagnitude, isStrafing ? strafeSpeed.animationSmooth : freeSpeed.animationSmooth, Time.fixedDeltaTime); animator.SetFloat(vAnimatorParameters.RotationMagnitude, rotationMagnitude); } } protected virtual void UpdateRotationMagnitude(float deltaTime) { bool moving = inputMagnitude >= 0.1f; if (!(moving ? useLeanMovementAnim : useTurnOnSpotAnim)) { rotationMagnitudeSmoothVelocity = 0f; rotationMagnitude = 0f; return; } float target = rawRotationMagnitude; float dampTime = moving ? leanSmooth : turnOnSpotSmooth; if (dampTime <= 0f || deltaTime <= 0f) { rotationMagnitudeSmoothVelocity = 0f; rotationMagnitude = target; return; } rotationMagnitude = Mathf.SmoothDamp(rotationMagnitude, target, ref rotationMagnitudeSmoothVelocity, dampTime, Mathf.Infinity, deltaTime); } /// /// Advances the rotationMagnitude damping for one rendered frame. Called from /// , before the animation pose for the frame is sampled. /// public virtual void UpdateRotationMagnitudePerFrame() { if (animator == null || !animator.enabled) { return; } UpdateRotationMagnitude(Time.deltaTime); } public virtual void SetAnimatorMoveSpeed(vMovementSpeed speed) { Vector3 relativeInput = transform.InverseTransformDirection(moveDirection); verticalSpeed = relativeInput.z; horizontalSpeed = relativeInput.x; var newInput = new Vector2(verticalSpeed, horizontalSpeed); if (speed.walkByDefault || alwaysWalkByDefault) { inputMagnitude = Mathf.Clamp(newInput.magnitude, 0, isSprinting ? runningSpeed : walkSpeed); } else { var mag = newInput.magnitude; sprintWeight = Mathf.Lerp(sprintWeight, isSprinting ? 1f : 0f, (isStrafing ? strafeSpeed.movementSmooth : freeSpeed.movementSmooth) * Time.fixedDeltaTime); inputMagnitude = Mathf.Clamp(Mathf.Lerp(mag, mag + (newInput.magnitude > 0.1f ? 0.5f : 0), sprintWeight), 0, isSprinting ? sprintSpeed : runningSpeed); } } public virtual void SetInputDirection(Vector3 direction) { float movementDirection = direction.magnitude >= .1 ? transform.forward.AngleFormOtherDirection(direction).y : 0; if (vAnimatorParameters.InputDirection != -1) animator.SetFloat(vAnimatorParameters.InputDirection, movementDirection); } public virtual void ResetInputAnimatorParameters() { animator.SetBool(vAnimatorParameters.IsSprinting, false); animator.SetBool(vAnimatorParameters.IsSliding, false); animator.SetBool(vAnimatorParameters.IsCrouching, false); animator.SetBool(vAnimatorParameters.IsGrounded, true); animator.SetFloat(vAnimatorParameters.GroundDistance, 0f); animator.SetFloat("InputHorizontal", 0); animator.SetFloat("InputVertical", 0); animator.SetFloat("InputMagnitude", 0); } protected virtual void TriggerRandomIdle() { if (input != Vector3.zero || customAction) { return; } if (randomIdleTime > 0) { if (input.sqrMagnitude == 0 && !isCrouching && _capsuleCollider.enabled && isGrounded) { randomIdleCount += Time.fixedDeltaTime; if (randomIdleCount > 6) { randomIdleCount = 0; animator.SetTrigger(vAnimatorParameters.IdleRandomTrigger); animator.SetInteger(vAnimatorParameters.IdleRandom, Random.Range(1, 4)); } } else { randomIdleCount = 0; animator.SetInteger(vAnimatorParameters.IdleRandom, 0); } } } protected virtual void DeadAnimation() { if (!isDead) { return; } // death by animation if (deathBy == DeathBy.Animation) { if (TryGetDeadStateProgress(out float normalizedTime) && normalizedTime >= 0.99f && groundDistance <= 0.15f) { RemoveComponents(); } } // death by animation & ragdoll after a time else if (deathBy == DeathBy.AnimationWithRagdoll) { if (TryGetDeadStateProgress(out float normalizedTime) && normalizedTime >= 0.8f) { onActiveRagdoll.Invoke(null); } } // death by ragdoll else if (deathBy == DeathBy.Ragdoll) { onActiveRagdoll.Invoke(null); } } /// /// Normalized progress of the death animation on whichever backend is driving the character, /// excluding transitions. False while no death state is playing. /// protected virtual bool TryGetDeadStateProgress(out float normalizedTime) { normalizedTime = 0f; #if INVECTOR_PLAYABLESANIMATOR if (UsingPlayablesBackend) { return !playablesAnimator.IsInTransition(0) && playablesAnimator.IsPlayingState("Dead", out normalizedTime, 0); } #endif var info = animatorStateInfos.GetStateInfoUsingTag("Dead"); if (info == null || animator.IsInTransition(baseLayer)) { return false; } normalizedTime = info.normalizedTime; return true; } #region Generic Animations Methods public virtual void SetActionState(int value) { animator.SetInteger(vAnimatorParameters.ActionState, value); } public virtual bool IsAnimatorTag(string tag) { #if INVECTOR_PLAYABLESANIMATOR if (playablesAnimator != null && playablesAnimator.isActiveAndEnabled) { return playablesAnimator.HasTag(tag); } #endif if (animator == null) { return false; } if (animatorStateInfos != null) { if (animatorStateInfos.HasTag(tag)) { return true; } } if (baseLayerInfo.IsTag(tag)) { return true; } if (underBodyInfo.IsTag(tag)) { return true; } if (rightArmInfo.IsTag(tag)) { return true; } if (leftArmInfo.IsTag(tag)) { return true; } if (upperBodyInfo.IsTag(tag)) { return true; } if (fullBodyInfo.IsTag(tag)) { return true; } return false; } public virtual void MatchTarget(Vector3 matchPosition, Quaternion matchRotation, AvatarTarget target, MatchTargetWeightMask weightMask, float normalisedStartTime, float normalisedEndTime) { if (animator.isMatchingTarget || animator.IsInTransition(0)) { return; } float normalizeTime = Mathf.Repeat(animator.GetCurrentAnimatorStateInfo(0).normalizedTime, 1f); if (normalizeTime > normalisedEndTime) { return; } animator.MatchTarget(matchPosition, matchRotation, target, weightMask, normalisedStartTime, normalisedEndTime); } #endregion } public static partial class vAnimatorParameters { public static int InputHorizontal = Animator.StringToHash("InputHorizontal"); public static int InputVertical = Animator.StringToHash("InputVertical"); public static int InputMagnitude = Animator.StringToHash("InputMagnitude"); public static int RotationMagnitude = Animator.StringToHash("RotationMagnitude"); public static int TurnOnSpotDirection = Animator.StringToHash("TurnOnSpotDirection"); public static int ActionState = Animator.StringToHash("ActionState"); public static int ResetState = Animator.StringToHash("ResetState"); public static int IsDead = Animator.StringToHash("isDead"); public static int IsGrounded = Animator.StringToHash("IsGrounded"); public static int IsCrouching = Animator.StringToHash("IsCrouching"); public static int IsStrafing = Animator.StringToHash("IsStrafing"); public static int IsSprinting = Animator.StringToHash("IsSprinting"); public static int IsSliding = Animator.StringToHash("IsSliding"); public static int GroundDistance = Animator.StringToHash("GroundDistance"); public static int GroundAngle = Animator.StringToHash("GroundAngle"); public static int VerticalVelocity = Animator.StringToHash("VerticalVelocity"); public static int IdleRandom = Animator.StringToHash("IdleRandom"); public static int IdleRandomTrigger = Animator.StringToHash("IdleRandomTrigger"); public static int InputDirection = Animator.StringToHash("InputDirection"); } }