using UnityEngine; using Fusion; namespace OnlyScove.Scripts { public class PlayerAnimationHandler : NetworkBehaviour { [Header("Animator Settings")] [SerializeField] private string speedParamName = "Speed"; [SerializeField] private string velocityXParamName = "Velocity X"; [SerializeField] private string velocityZParamName = "Velocity Z"; [SerializeField] private string groundedParamName = "Grounded"; [SerializeField] private float animationDamping = 0.2f; [Header("Visual Correction")] [SerializeField] private float visualOffsetY = 0f; [SerializeField] private Transform modelTransform; private CharacterController controller; private Animator anim; private int speedHash; private int velocityXHash; private int velocityZHash; private int groundedHash; private readonly System.Collections.Generic.HashSet parameterHashes = new System.Collections.Generic.HashSet(); public void Initialize(Animator animator) { this.anim = animator; this.controller = GetComponentInParent(); // Auto-assign modelTransform if null if (modelTransform == null && anim != null) modelTransform = anim.transform; if (anim != null) { Debug.Log($"[AnimationHandler] Animator found on: {anim.gameObject.name}"); parameterHashes.Clear(); foreach (AnimatorControllerParameter param in anim.parameters) { parameterHashes.Add(param.nameHash); } int speedHashCheck = Animator.StringToHash(speedParamName); if (!parameterHashes.Contains(speedHashCheck) && parameterHashes.Contains(Animator.StringToHash("Blend"))) { speedParamName = "Blend"; speedHash = Animator.StringToHash(speedParamName); Debug.Log($"[AnimationHandler] 'Speed' not found, using 'Blend' instead."); } } else { Debug.LogError("[AnimationHandler] FAILED to find Animator! Please check your Prefab."); } speedHash = Animator.StringToHash(speedParamName); velocityXHash = Animator.StringToHash(velocityXParamName); velocityZHash = Animator.StringToHash(velocityZParamName); groundedHash = Animator.StringToHash(groundedParamName); } private bool wasGroundedInAnimator; public void UpdateAnimator(float speed, Vector2 moveInput, bool isGrounded, float deltaTime) { if (anim == null) return; // Snap to zero if speed is very low to force Idle float targetSpeed = speed < 0.05f ? 0f : speed; float damping = (targetSpeed == 0f || targetSpeed > 0.9f) ? 0f : animationDamping; if (parameterHashes.Contains(speedHash)) anim.SetFloat(speedHash, targetSpeed, damping, deltaTime); if (parameterHashes.Contains(velocityXHash)) anim.SetFloat(velocityXHash, moveInput.x * targetSpeed, damping, deltaTime); if (parameterHashes.Contains(velocityZHash)) anim.SetFloat(velocityZHash, moveInput.y * targetSpeed, damping, deltaTime); // Quan trọng: Cập nhật biến Grounded cho Animator if (parameterHashes.Contains(groundedHash)) anim.SetBool(groundedHash, isGrounded); // Nếu đang ở trên mặt đất, đảm bảo reset các trạng thái nhảy/rơi if (isGrounded) { SafeSetBool("IsJumping", false); SafeSetBool("IsFalling", false); var stateInfo = anim.GetCurrentAnimatorStateInfo(0); if (stateInfo.IsName("Jump") || stateInfo.IsName("Fall") || stateInfo.IsName("Airborne")) { anim.CrossFadeInFixedTime("Locomotion Ground", 0.1f); } } wasGroundedInAnimator = isGrounded; } public void SafeSetTrigger(string name) { if (anim == null) return; int hash = Animator.StringToHash(name); if (parameterHashes.Contains(hash)) anim.SetTrigger(hash); } public void SafeSetTrigger(int hash) { if (anim == null) return; if (parameterHashes.Contains(hash)) anim.SetTrigger(hash); } public void SafeResetTrigger(string name) { if (anim == null) return; int hash = Animator.StringToHash(name); if (parameterHashes.Contains(hash)) anim.ResetTrigger(hash); } public void SafeResetTrigger(int hash) { if (anim == null) return; if (parameterHashes.Contains(hash)) anim.ResetTrigger(hash); } public void SafeSetBool(string name, bool value) { if (anim == null) return; int hash = Animator.StringToHash(name); if (parameterHashes.Contains(hash)) anim.SetBool(hash, value); } public void SafeSetFloat(int hash, float value, float dampTime, float deltaTime) { if (anim == null) return; if (parameterHashes.Contains(hash)) anim.SetFloat(hash, value, dampTime, deltaTime); } public void SetSpeed(float speed) { if (anim != null && parameterHashes.Contains(speedHash)) anim.SetFloat(speedHash, speed); } public void ForceLocomotion() { if (anim != null) anim.CrossFadeInFixedTime("Locomotion Ground", 0.1f); } private void LateUpdate() { if (modelTransform != null && controller != null) { // Automatically snap mesh to the bottom of the CharacterController capsule // This fixes hovering/tiptoeing issues regardless of animation offsets Vector3 targetPos = modelTransform.localPosition; float bottomY = controller.center.y - (controller.height / 2f); targetPos.y = bottomY + visualOffsetY; modelTransform.localPosition = targetPos; } } } }