Unity中AnimationAndMovementController跳跃失效且重力异常问题排查
Unity角色控制问题:跳跃失效与重力异常
本人是Unity新手,正跟随教程学习角色控制。目前角色移动与跑步功能正常,但实现跳跃后出现重力失效、跳跃无法触发的问题。站定时按空格无跳跃反应,走下地面也不会下落。唯一与教程不同的是实现了基于相机的移动,以下是完整代码及问题演示:
using UnityEngine; using UnityEngine.InputSystem; public class AnimationAndMovementController : MonoBehaviour { private static readonly int IsWalking = Animator.StringToHash("isWalking"); private static readonly int IsRunning = Animator.StringToHash("isRunning"); private InputSystem_Actions playerInput; private CharacterController characterController; private Animator animator; private Camera mainCamera; private Vector2 currentMovementInput; private Vector3 currentMovement; private Vector3 currentRunMovement; private bool isMovementPressed; private bool isRunPressed; private float rotationFactorPerFrame = 15.0f; private float runMultiplier = 1.8f; private float speed = 3.0f; private int zero = 0; private bool isJumpPressed = false; private float initialJumpVelocity; private float maxJumpHeight = 1.0f; private float maxJumpTime = 0.75f; private bool isJumping = false; private float groundedGravity = -.05f; private float gravity = -9.8f; private void Awake() { playerInput = new InputSystem_Actions(); characterController = GetComponent<CharacterController>(); animator = GetComponent<Animator>(); mainCamera = Camera.main; playerInput.Player.Move.started += OnMovementInput; playerInput.Player.Move.canceled += OnMovementInput; playerInput.Player.Move.performed += OnMovementInput; playerInput.Player.Sprint.started += OnRun; playerInput.Player.Sprint.canceled += OnRun; playerInput.Player.Jump.started += OnJump; playerInput.Player.Jump.canceled += OnJump; SetupJumpVariables(); } private void SetupJumpVariables() { float timeToApex = maxJumpTime / 2; gravity = (-2 * maxJumpHeight) / Mathf.Pow(timeToApex, 2); initialJumpVelocity = (2 * maxJumpHeight) / timeToApex; } private void HandleJump() { if (characterController.isGrounded && isJumpPressed && !isJumping) { isJumping = true; currentMovement.y = initialJumpVelocity * .5f; currentRunMovement.y = initialJumpVelocity *.5f; } else if (isJumping && !isJumpPressed && characterController.isGrounded) { isJumping = false; } } private void OnJump(InputAction.CallbackContext context) { isJumpPressed = context.ReadValueAsButton(); } private void OnRun(InputAction.CallbackContext context) { isRunPressed = context.ReadValueAsButton(); } private void OnMovementInput(InputAction.CallbackContext context) { currentMovementInput = context.ReadValue<Vector2>(); isMovementPressed = currentMovementInput.x != 0 || currentMovementInput.y != 0; } private void UpdateMovement() { Vector3 forward = mainCamera.transform.forward; Vector3 right = mainCamera.transform.right; forward.y = 0f; right.y = 0f; forward.Normalize(); right.Normalize(); currentMovement = forward * currentMovementInput.y + right * currentMovementInput.x; currentRunMovement = currentMovement * runMultiplier; } private void HandleRotation() { Vector3 positionToLookAt; positionToLookAt.x = currentMovement.x; positionToLookAt.y = zero; positionToLookAt.z = currentMovement.z; Quaternion currentRotation = transform.rotation; if (isMovementPressed) { Quaternion targetRotation = Quaternion.LookRotation(positionToLookAt); transform.rotation = Quaternion.Slerp(currentRotation, targetRotation, rotationFactorPerFrame * Time.deltaTime); } } private void HandleGravity() { bool isFalling = currentMovement.y < 0.0f || !isJumpPressed; float fallMultiplier = 2.0f; if (characterController.isGrounded) { currentMovement.y = groundedGravity; currentRunMovement.y = groundedGravity; } else if (isFalling) { float previousY = currentMovement.y; float newY = currentMovement.y + (gravity * fallMultiplier * Time.deltaTime); float nextY = Mathf.Max((previousY + newY) * .5f, -20f); currentMovement.y += nextY; } else { float previousY = currentMovement.y; float newY = currentMovement.y + gravity * Time.deltaTime; float nextY = (previousY + newY) * .5f; currentMovement.y += nextY; currentRunMovement.y += nextY; } } private void HandleAnimation() { bool isWalking = animator.GetBool(IsWalking); bool isRunning = animator.GetBool(IsRunning); if (isMovementPressed && !isWalking) { animator.SetBool(IsWalking, true); } else if (!isMovementPressed && isWalking) { animator.SetBool(IsWalking, false); } if ((isMovementPressed && isRunPressed) && !isRunning) { animator.SetBool(IsRunning, true); } else if ((!isMovementPressed || !isRunPressed) && isRunning) { animator.SetBool(IsRunning, false); } } private void Update() { UpdateMovement(); HandleAnimation(); HandleRotation(); if (isRunPressed) { characterController.Move(currentRunMovement * (speed * Time.deltaTime)); } else { characterController.Move(currentMovement * (speed * Time.deltaTime)); } HandleGravity(); HandleJump(); } private void OnEnable() { playerInput.Player.Enable(); } private void OnDisable() { playerInput.Player.Disable(); } }

问题分析与修复
1. 核心问题点
- 执行顺序颠倒:
Update()中先执行角色移动,再处理重力和跳跃,导致当前帧的重力/跳跃力无法被应用,角色永远使用旧的Y轴数值移动。 - 重力计算逻辑错误:
HandleGravity()中错误地将计算出的Y轴值累加给currentMovement.y,导致数值持续异常增长,完全破坏重力逻辑。 - 跳跃状态重置不完善:仅在松开跳跃键且落地时重置
isJumping,按住跳跃键落地时会导致无法再次触发跳跃。
修复后的完整代码
using UnityEngine; using UnityEngine.InputSystem; public class AnimationAndMovementController : MonoBehaviour { private static readonly int IsWalking = Animator.StringToHash("isWalking"); private static readonly int IsRunning = Animator.StringToHash("isRunning"); private InputSystem_Actions playerInput; private CharacterController characterController; private Animator animator; private Camera mainCamera; private Vector2 currentMovementInput; private Vector3 currentMovement; private Vector3 currentRunMovement; private bool isMovementPressed; private bool isRunPressed; private float rotationFactorPerFrame = 15.0f; private float runMultiplier = 1.8f; private float speed = 3.0f; private int zero = 0; private bool isJumpPressed = false; private float initialJumpVelocity; private float maxJumpHeight = 1.0f; private float maxJumpTime = 0.75f; private bool isJumping = false; private float groundedGravity = -.05f; private float gravity = -9.8f; private void Awake() { playerInput = new InputSystem_Actions(); characterController = GetComponent<CharacterController>(); animator = GetComponent<Animator>(); mainCamera = Camera.main; playerInput.Player.Move.started += OnMovementInput; playerInput.Player.Move.canceled += OnMovementInput; playerInput.Player.Move.performed += OnMovementInput; playerInput.Player.Sprint.started += OnRun; playerInput.Player.Sprint.canceled += OnRun; playerInput.Player.Jump.started += OnJump; playerInput.Player.Jump.canceled += OnJump; SetupJumpVariables(); } private void SetupJumpVariables() { float timeToApex = maxJumpTime / 2; gravity = (-2 * maxJumpHeight) / Mathf.Pow(timeToApex, 2); initialJumpVelocity = (2 * maxJumpHeight) / timeToApex; } private void HandleJump() { if (characterController.isGrounded && isJumpPressed && !isJumping) { isJumping = true; currentMovement.y = initialJumpVelocity; currentRunMovement.y = initialJumpVelocity; } // 落地后直接重置跳跃状态,兼容按住跳跃键的情况 else if (characterController.isGrounded) { isJumping = false; } } private void OnJump(InputAction.CallbackContext context) { isJumpPressed = context.ReadValueAsButton(); } private void OnRun(InputAction.CallbackContext context) { isRunPressed = context.ReadValueAsButton(); } private void OnMovementInput(InputAction.CallbackContext context) { currentMovementInput = context.ReadValue<Vector2>(); isMovementPressed = currentMovementInput.x != 0 || currentMovementInput.y != 0; } private void UpdateMovement() { Vector3 forward = mainCamera.transform.forward; Vector3 right = mainCamera.transform.right; forward.y = 0f; right.y = 0f; forward.Normalize(); right.Normalize(); currentMovement = forward * currentMovementInput.y + right * currentMovementInput.x; currentRunMovement = currentMovement * runMultiplier; } private void HandleRotation() { Vector3 positionToLookAt; positionToLookAt.x = currentMovement.x; positionToLookAt.y = zero; positionToLookAt.z = currentMovement.z; Quaternion currentRotation = transform.rotation; if (isMovementPressed) { Quaternion targetRotation = Quaternion.LookRotation(positionToLookAt); transform.rotation = Quaternion.Slerp(currentRotation, targetRotation, rotationFactorPerFrame * Time.deltaTime); } } private void HandleGravity() { bool isFalling = currentMovement.y < 0.0f || !isJumpPressed; float fallMultiplier = 2.0f; if (characterController.isGrounded) { currentMovement.y = groundedGravity; currentRunMovement.y = groundedGravity; } else if (isFalling) { float newY = currentMovement.y + (gravity * fallMultiplier * Time.deltaTime); currentMovement.y = Mathf.Max(newY, -20f); currentRunMovement.y = currentMovement.y; } else { float newY = currentMovement.y + gravity * Time.deltaTime; currentMovement.y = newY; currentRunMovement.y = newY; } } private void HandleAnimation() { bool isWalking = animator.GetBool(IsWalking); bool isRunning = animator.GetBool(IsRunning); if (isMovementPressed && !isWalking) { animator.SetBool(IsWalking, true); } else if (!isMovementPressed && isWalking) { animator.SetBool(IsWalking, false); } if ((isMovementPressed && isRunPressed) && !isRunning) { animator.SetBool(IsRunning, true); } else if ((!isMovementPressed || !isRunPressed) && isRunning) { animator.SetBool(IsRunning, false); } } private void Update() { UpdateMovement(); HandleAnimation(); HandleRotation(); // 调整顺序:先处理重力和跳跃,再执行移动 HandleGravity(); HandleJump(); if (isRunPressed) { characterController.Move(currentRunMovement * (speed * Time.deltaTime)); } else { characterController.Move(currentMovement * (speed * Time.deltaTime)); } } private void OnEnable() { playerInput.Player.Enable(); } private void OnDisable() { playerInput.Player.Disable(); } }
额外优化说明
- 移除跳跃时的
*.5f系数,让跳跃高度匹配你设置的maxJumpHeight参数 - 同步
currentRunMovement.y与currentMovement.y的数值,避免跑步和走路时重力/跳跃状态不一致 - 简化重力计算逻辑,去掉错误的累加操作
内容的提问来源于stack exchange,提问作者Deagarys
相关产品推荐
相关产品推荐

