如何实现Unity角色蹲伏/站起的平滑慢速过渡?
解决Unity角色蹲伏/站起过渡速度过快的问题
我想让角色蹲伏和站起的过程更平滑缓慢,但不管是直接修改transform.localScale还是后来用Mathf.Lerp调整CharacterController的height和center,过渡速度都太快,求解决办法。
初始核心代码
private void Start() { startYScale = transform.localScale.y; mainCamera = Camera.main; } private void PlayerMovement() { currentSpeed = moveSpeed; move = controls.Player.Movement.ReadValue<Vector2>(); movement = (move.y * transform.forward) + (move.x * transform.right); if (isRunning) { currentSpeed = runSpeed; Vector3 running = (move.y * transform.forward) + (move.x * transform.right); controller.Move(running * currentSpeed * Time.deltaTime); } if (crouching) { currentSpeed = crouchSpeed; isRunning = false; transform.localScale = new Vector3(transform.localScale.x, crouchYScale, transform.localScale.z); } else { currentSpeed = moveSpeed; transform.localScale = new Vector3(transform.localScale.x, startYScale, transform.localScale.z); } controller.Move(movement * currentSpeed * Time.deltaTime); }
尝试优化后的代码
private void FixedUpdate() { var desierdHeight = crouching ? crouchYScale : startYScale; if (controller.height != desierdHeight) { AdjustHeight(desierdHeight); playerCamera.transform.localPosition = new Vector3(0,controller.height, 0); } } private void AdjustHeight(float height) { float center = height / 2; controller.height = Mathf.Lerp(controller.height, height, crouchSpeed); controller.center = Vector3.Lerp(controller.center, new Vector3(0, center,0), crouchSpeed); }
问题根源分析
- 复用移动速度作为插值比例:你用了
crouchSpeed(移动速度参数,通常远大于1)作为Mathf.Lerp的第三个参数,而这个参数要求是0-1之间的插值比例,大于1会直接跳到目标值,根本没有过渡效果。 - 未加入时间增量:Lerp计算没乘以
Time.fixedDeltaTime,导致过渡速度随帧率波动,且每帧变化幅度过大。 - 浮点精度判断问题:直接用
controller.height != desierdHeight判断,会因为浮点精度误差一直触发调整,还可能导致抖动。
具体解决方案
1. 新增独立过渡参数
不要复用移动速度,单独定义控制过渡快慢的变量,可在Inspector面板可视化调整:
[SerializeField] private float crouchTransitionSpeed = 2f; // 值越小过渡越慢 [SerializeField] private float heightThreshold = 0.01f; // 浮点精度阈值 private float startHeight; private float crouchHeight;
2. 修正Lerp逻辑,加入时间增量
保证过渡速度帧率无关,同时用阈值判断结束过渡:
private void Start() { controller = GetComponent<CharacterController>(); playerCamera = Camera.main; // 记录CharacterController初始高度,不再依赖localScale startHeight = controller.height; crouchHeight = startHeight * 0.5f; // 自定义蹲伏高度比例 } private void FixedUpdate() { float targetHeight = crouching ? crouchHeight : startHeight; // 用阈值判断是否接近目标,避免浮点精度问题 if (Mathf.Abs(controller.height - targetHeight) > heightThreshold) { AdjustHeight(targetHeight); // 同步平滑调整相机位置 Vector3 targetCamPos = new Vector3(0, targetHeight, 0); playerCamera.transform.localPosition = Vector3.Lerp( playerCamera.transform.localPosition, targetCamPos, crouchTransitionSpeed * Time.fixedDeltaTime ); } else { // 直接设置为目标值,结束过渡 controller.height = targetHeight; controller.center = new Vector3(0, targetHeight / 2, 0); playerCamera.transform.localPosition = new Vector3(0, targetHeight, 0); } } private void AdjustHeight(float targetHeight) { float targetCenter = targetHeight / 2; float lerpFactor = crouchTransitionSpeed * Time.fixedDeltaTime; controller.height = Mathf.Lerp(controller.height, targetHeight, lerpFactor); controller.center = Vector3.Lerp(controller.center, new Vector3(0, targetCenter, 0), lerpFactor); }
3. 移除localScale修改逻辑
直接修改transform.localScale会导致碰撞体缩放异常,容易出现穿墙、碰撞检测失效等问题,用CharacterController的height和center控制高度是更规范的做法。
4. 进阶:用SmoothDamp实现更自然的过渡
如果需要带加速度/减速度的顺滑效果,可替换Mathf.Lerp为Mathf.SmoothDamp:
private float heightVelocity; private float centerVelocity; private void AdjustHeight(float targetHeight) { float targetCenter = targetHeight / 2; // 0.2f为平滑时间,值越大过渡越慢 controller.height = Mathf.SmoothDamp(controller.height, targetHeight, ref heightVelocity, 0.2f); controller.center = Vector3.SmoothDamp(controller.center, new Vector3(0, targetCenter, 0), ref centerVelocity, 0.2f); }
内容的提问来源于stack exchange,提问作者Okashi
相关产品推荐
相关产品推荐

