You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 22:04:55