Unity中如何基于时间而非固定位置实现Vector3 Lerp控制玩家运动
基于时间的Vector3插值实现滚动物体跟随方案
核心逻辑
你不需要提前设置固定目标位置,只需要将插值对象从「位置」改为「速度倍率」,配合滚动物体的实时移动方向计算玩家位移即可,整个移动过程完全由时间控制:
- 把整个运动周期(加速+匀速+减速)的时长作为总插值区间
- 用归一化的时间值读取动画曲线得到当前的速度倍率
- 速度倍率乘以滚动物体的实时移动方向,就是玩家当前帧的位移矢量
可直接使用的修改方案
修改你的Rolling脚本,替换或新增以下内容即可:
using System.Collections; using System.Collections.Generic; using UnityEngine; using Cinemachine; using FIMSpace.FLook; public class Rolling : MonoBehaviour { [Header("基础设置")] public GameObject mainCam; public CinemachineFreeLook freeLookCamGameplay; public CinemachineFreeLook freeLookCamPickUp; public Transform player; public Transform crate; public Transform cube; public Animation anim; public UnlockCrate unlockCrate; public float timeToStopRolling; public float speed; public float waitBeforeSlowdown; public float startDrag; public float endDrag = 50; public float rotationSpeed; public static bool hasStopped = false; // 新增玩家跟随相关设置 [Header("玩家跟随设置")] public float playerMaxSpeed = 6f; public AnimationCurve moveSpeedCurve; // 自行在Inspector配置曲线:上升段=加速,水平段=匀速,下降段=减速 public Vector3 followOffset = new Vector3(1.2f, 0f, 0f); // 最终停在滚动物体旁的偏移量 public float directionSmooth = 8f; // 玩家转向平滑度 private bool crateOpenOnce = false; private bool alreadyRolling; private Rigidbody rb; private Quaternion defaultRotation; private Animator animator; private bool startSpeedUp = true; public float lerpTimeMultiplicator = 0.25f; private float totalMoveDuration; private float currentMoveTime; private Vector3 currentRollDir; private Vector3 targetStopPos; private void Start() { rb = GetComponent<Rigidbody>(); defaultRotation = rb.rotation; animator = player.GetComponent<Animator>(); // 预计算总运动时长:滚动时间+等待减速时间+减速阶段预估时间 totalMoveDuration = timeToStopRolling + waitBeforeSlowdown + (1 / lerpTimeMultiplicator); } private void Update() { var distance = Vector3.Distance(crate.position, player.position); if (distance < 1.7f && crateOpenOnce == false && unlockCrate.HasOpened()) { rb.isKinematic = false; crateOpenOnce = true; } if (alreadyRolling && !hasStopped) { currentMoveTime += Time.deltaTime; float normalizedTime = Mathf.Clamp01(currentMoveTime / totalMoveDuration); float speedMulti = moveSpeedCurve.Evaluate(normalizedTime); // 计算玩家位移 Vector3 moveDir = currentRollDir.normalized; player.position += moveDir * playerMaxSpeed * speedMulti * Time.deltaTime; // 平滑转向移动方向 player.forward = Vector3.Lerp(player.forward, moveDir, directionSmooth * Time.deltaTime); // 同步Animator参数 animator.SetFloat("Forward", speedMulti); } // 物体停止后修正玩家位置到偏移点 if (hasStopped) { player.position = Vector3.Lerp(player.position, targetStopPos, 5f * Time.deltaTime); if (Vector3.Distance(player.position, targetStopPos) < 0.1f) { animator.SetFloat("Forward", 0f); } } } private void OnCollisionEnter(Collision collision) { if (collision.gameObject.CompareTag("Crate")) // 建议把木箱设为Crate标签 { if (crateOpenOnce) { rb.drag = 0f; if (!alreadyRolling) StartCoroutine(RollingRoutine()); } } } private void OnCollisionExit(Collision collision) { if (collision.gameObject.CompareTag("Crate")) { var brain = mainCam.GetComponent<CinemachineBrain>(); brain.m_DefaultBlend.m_Time = 1f; freeLookCamGameplay.enabled = false; freeLookCamPickUp.enabled = true; } } private Vector3 GetRandomDirection() { var rnd = Random.insideUnitSphere; rnd.y = 0; return rnd.normalized; } private IEnumerator RollingRoutine() { if (alreadyRolling) yield break; alreadyRolling = true; currentMoveTime = 0; currentRollDir = GetRandomDirection(); for (var timePassed = 0f; timePassed < timeToStopRolling; timePassed += Time.deltaTime) { yield return new WaitForFixedUpdate(); rb.AddForce(currentRollDir * speed * Time.deltaTime); } yield return new WaitForSeconds(waitBeforeSlowdown); var dragLerpFactor = 0f; var defaultDrag = rb.drag; while (!Mathf.Approximately(rb.velocity.sqrMagnitude, 0) || rb.rotation != defaultRotation) { yield return new WaitForFixedUpdate(); dragLerpFactor += Time.deltaTime * lerpTimeMultiplicator; rb.drag = Mathf.Lerp(startDrag, endDrag, dragLerpFactor); rb.MoveRotation(Quaternion.RotateTowards(rb.rotation, defaultRotation, rotationSpeed * Time.deltaTime)); } rb.rotation = defaultRotation; rb.drag = defaultDrag; rb.velocity = Vector3.zero; targetStopPos = transform.position + followOffset; hasStopped = true; Destroy(transform.GetComponent<Rigidbody>()); Destroy(transform.GetComponent<SphereCollider>()); } }
配置说明
- 在Inspector面板给
moveSpeedCurve配置曲线,曲线的横坐标0对应运动开始,1对应运动结束,纵坐标0对应速度为0,1对应最大速度 - 调整
followOffset可以设置玩家最终停在滚动物体的哪个方位 - 如果你的Animator移动逻辑依赖自身坐标,只需要保证player的forward和滚动方向同步即可,无需修改其他动画逻辑
内容的提问来源于stack exchange,提问作者Daniel Lip
相关产品推荐
相关产品推荐

