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

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>());
    }
}

配置说明

  1. 在Inspector面板给moveSpeedCurve配置曲线,曲线的横坐标0对应运动开始,1对应运动结束,纵坐标0对应速度为0,1对应最大速度
  2. 调整followOffset可以设置玩家最终停在滚动物体的哪个方位
  3. 如果你的Animator移动逻辑依赖自身坐标,只需要保证player的forward和滚动方向同步即可,无需修改其他动画逻辑

内容的提问来源于stack exchange,提问作者Daniel Lip

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.10 19:48:03