Unity角色蹲伏恢复高度卡顿问题求助
Unity角色蹲伏恢复高度卡顿问题修复
问题现象
- 角色蹲伏时高度过渡流畅,但松开左Ctrl恢复原高度时,静止状态下会出现明显卡顿
- 仅当角色移动时,蹲伏和恢复高度的双向过渡都能保持平滑
- 当前使用的蹲伏逻辑代码片段:
if (Input.GetKey(KeyCode.LeftControl)) { characterController.height = Mathf.Lerp(characterController.height, 0.5f, 10f * Time.deltaTime); canRun = false; canJump = false; walkingSpeed = 2f; } else { characterController.height = Mathf.Lerp(characterController.height, 2.0f, 10f * Time.deltaTime); canRun = true; canJump = true; walkingSpeed = 4f; }
问题原因
卡顿的核心原因是CharacterController的isGrounded检测机制:
- 当角色静止且处于蹲伏状态时,CharacterController底部几乎贴合地面,松开左Ctrl恢复高度时,
isGrounded会瞬间从true变为false,重力瞬间作用产生向下位移,打断高度平滑过渡的视觉效果 - 角色移动时,CharacterController会持续检测地面并调整自身位置,抵消了重力带来的突兀位移,因此过渡看起来平滑
解决方案
方案1:调整CharacterController的Skin Width
在Inspector面板中,给CharacterController的Skin Width设置一个较小的非零值(比如0.01)。这个参数会让CharacterController在碰撞检测时保留微小缓冲空间,避免恢复高度时isGrounded瞬间切换,减少重力带来的突兀位移。
方案2:恢复高度时临时抵消重力影响
修改Update中的蹲伏逻辑,在恢复高度过程中,针对静止状态临时抵消重力,直到高度恢复完成:
// 新增变量跟踪目标高度 private float targetHeight = 2.0f; void Update() { // ... 原有其他代码 ... // 蹲伏逻辑重构 if (Input.GetKey(KeyCode.LeftControl)) { targetHeight = 0.5f; canRun = false; canJump = false; walkingSpeed = 2f; } else { targetHeight = 2.0f; canRun = true; canJump = true; walkingSpeed = 4f; } // 平滑过渡高度 characterController.height = Mathf.Lerp(characterController.height, targetHeight, 10f * Time.deltaTime); // 静止恢复高度时临时抵消重力 if (targetHeight == 2.0f && Mathf.Abs(characterController.height - 2.0f) < 0.1f && Mathf.Abs(moveDirection.x) < 0.1f && Mathf.Abs(moveDirection.z) < 0.1f) { moveDirection.y = 0; } else if (!characterController.isGrounded) { moveDirection.y -= gravity * Time.deltaTime; } // ... 原有其他代码 ... }
方案3:调整Controller的Center偏移替代直接修改Height
通过调整CharacterController的center而非直接修改height,可以避免碰撞检测异常,过渡更稳定:
private Vector3 originalCenter; private const float originalHeight = 2.0f; private const float crouchHeight = 0.5f; void Start() { characterController = GetComponent<CharacterController>(); originalCenter = characterController.center; // ... 原有其他代码 ... } void Update() { // ... 原有其他代码 ... if (Input.GetKey(KeyCode.LeftControl)) { // 计算目标center偏移:高度减少量的一半向下偏移 float targetY = originalCenter.y - (originalHeight - crouchHeight) / 2; characterController.center = Vector3.Lerp(characterController.center, new Vector3(0, targetY, 0), 10f * Time.deltaTime); characterController.height = Mathf.Lerp(characterController.height, crouchHeight, 10f * Time.deltaTime); canRun = false; canJump = false; walkingSpeed = 2f; } else { characterController.center = Vector3.Lerp(characterController.center, originalCenter, 10f * Time.deltaTime); characterController.height = Mathf.Lerp(characterController.height, originalHeight, 10f * Time.deltaTime); canRun = true; canJump = true; walkingSpeed = 4f; } // ... 原有其他代码 ... }
完整修复后的FirstPersonController代码
using System.Collections; using System.Collections.Generic; using UnityEngine; [RequireComponent(typeof(CharacterController))] public class FirstPersonController : MonoBehaviour { public float walkingSpeed = 4f; public float runningSpeed = 5.5f; public float jumpSpeed = 4.0f; public float gravity = 15.0f; public Camera playerCamera; public CharacterController Player; public float lookSpeed = 2.0f; public float lookXLimit = 90.0f; public static bool canRun = true; public static bool running = false; CharacterController characterController; Vector3 moveDirection = Vector3.zero; float rotationX = 0; [HideInInspector] public bool canMove = true; public bool canJump = true; // 蹲伏相关变量 private float targetHeight = 2.0f; private const float originalHeight = 2.0f; private const float crouchHeight = 0.5f; void Start() { characterController = GetComponent<CharacterController>(); targetHeight = originalHeight; // Lock cursor Cursor.lockState = CursorLockMode.Locked; Cursor.visible = false; } void Update() { Vector3 forward = transform.TransformDirection(Vector3.forward); Vector3 right = transform.TransformDirection(Vector3.right); bool isRunning = Input.GetKey(KeyCode.LeftShift) && canRun == true; float curSpeedX = canMove ? (isRunning ? runningSpeed : walkingSpeed) * Input.GetAxis("Vertical") : 0; float curSpeedY = canMove ? (isRunning ? runningSpeed : walkingSpeed) * Input.GetAxis("Horizontal") : 0; float movementDirectionY = moveDirection.y; if (isRunning) { moveDirection = Vector3.ClampMagnitude((forward * curSpeedX) + (right * curSpeedY), 5.5f); running = true; if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.D)) { Camera.main.fieldOfView = Mathf.Lerp(Camera.main.fieldOfView, 65f, 10f * Time.deltaTime); } } else { moveDirection = Vector3.ClampMagnitude((forward * curSpeedX) + (right * curSpeedY), 4.0f); running = false; Camera.main.fieldOfView = Mathf.Lerp(Camera.main.fieldOfView, 60f, 10f * Time.deltaTime); } if (Input.GetButton("Jump") && canMove && characterController.isGrounded && canJump) { moveDirection.y = jumpSpeed; } else { moveDirection.y = movementDirectionY; } // 蹲伏逻辑 if (Input.GetKey(KeyCode.LeftControl)) { targetHeight = crouchHeight; canRun = false; canJump = false; walkingSpeed = 2f; } else { targetHeight = originalHeight; canRun = true; canJump = true; walkingSpeed = 4f; } // 平滑过渡高度 characterController.height = Mathf.Lerp(characterController.height, targetHeight, 10f * Time.deltaTime); // 静止恢复高度时临时抵消重力 if (targetHeight == originalHeight && Mathf.Abs(characterController.height - originalHeight) < 0.1f && Mathf.Abs(moveDirection.x) < 0.1f && Mathf.Abs(moveDirection.z) < 0.1f) { moveDirection.y = 0; } else if (!characterController.isGrounded) { moveDirection.y -= gravity * Time.deltaTime; } characterController.Move(moveDirection * Time.deltaTime); if (canMove) { rotationX += -Input.GetAxis("Mouse Y") * lookSpeed; rotationX = Mathf.Clamp(rotationX, -lookXLimit, lookXLimit); playerCamera.transform.localRotation = Quaternion.Euler(rotationX, 0, 0); transform.rotation *= Quaternion.Euler(0, Input.GetAxis("Mouse X") * lookSpeed, 0); } } }
内容的提问来源于stack exchange,提问作者lunixd
相关产品推荐
相关产品推荐

