You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Unity新样条曲线玩家移动系统:曲线内外速度逻辑不符合赛车离心效果的修正求助

Unity新样条曲线玩家移动系统:曲线内外速度逻辑不符合赛车离心效果的修正求助

我现在用Unity的新样条系统做了一个玩家移动系统,样条定义了前进方向,玩家可以相对样条自由左右、上下移动,相机跟随玩家。目前遇到的问题是:

  • 玩家在曲线内侧时会减速
  • 在曲线外侧时会加速
  • 居中时保持正常速度

但我想要的是完全相反的赛车手感:内侧加速、外侧减速、居中维持原速,模拟离心力带来的自然驾驶感受。

以下是我当前的PlayerMovement脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
using Unity.Cinemachine;
using UnityEngine.Rendering.PostProcessing;
using UnityEngine.InputSystem.Controls;

public class PlayerMovement : MonoBehaviour
{
    private Transform playerModel;
    private InputManager inputManager;

    [Header("Settings")]
    public bool joystick = true;

    [Header("Parameters")]
    public float xySpeed = 18;
    public float lookSpeed = 340;
    public float forwardSpeed = 60f;
    public float autodolly = 1f;
    [SerializeField] private Vector3 moveVelocity;
    [SerializeField] private float inertia = 0.2f;

    [Header("Public References")]
    public Transform aimTarget;
    public CinemachineSplineCart dolly;
    public Transform cameraParent;

    [Header("Particles")]
    public ParticleSystem trail;
    public ParticleSystem circle;
    public ParticleSystem barrel;
    public ParticleSystem stars;

    private float elapsedTime = 0f;
    private float timeSpeedMultiplier = 1f;
    private float originalSpeed = 60;

    void Start()
    {
        var autodollyMethod = dolly.AutomaticDolly.Method as SplineAutoDolly.FixedSpeed;
        if (autodollyMethod != null)
        {
            autodollyMethod.Speed = forwardSpeed;
            originalSpeed = forwardSpeed;
        }
        inputManager = GetComponent<InputManager>();
        playerModel = transform.GetChild(0);
        SetSpeed(forwardSpeed);
    }

    void Update()
    {
        if (dolly != null)
        {
            dolly.SplinePosition += autodolly * Time.deltaTime;
        }
        elapsedTime += Time.deltaTime;
        timeSpeedMultiplier = 1f + (elapsedTime / 20f);

        float t = dolly.SplinePosition;
        float adjustedSpeed = CalculateSpeedFromCurve(t) * timeSpeedMultiplier;
        Debug.Log($"t={t:F3}, Adjusted Speed: {adjustedSpeed}");
        SetSpeed(adjustedSpeed);

        Vector2 moveInput = inputManager.Move;
        float h = Physics.Raycast(transform.position, (transform.right * moveInput.x).normalized, 4) ? 0 : moveInput.x;
        float v = Physics.Raycast(transform.position, (Vector3.up * moveInput.y).normalized, 4) ? 0 : moveInput.y;

        LocalMove(h, v, xySpeed);
        RotationLook(h, v, lookSpeed);
        HorizontalLean(playerModel, h, 60, 0.10f);
        //VerticalLean(playerModel, v, 20, 0.1f);

        if (inputManager.IsSprinting)
        {
            Boost(true);
        }
        else Boost(false);
    }

    float CalculateSpeedFromCurve(float t)
    {
        var curvePoints = FindFirstObjectByType<SplineCurveCalc>().points;
        SplineCurveCalc.Evaluation closest = null;
        float closestDistance = float.MaxValue;

        // Find the closest point on the curve
        foreach (var point in curvePoints)
        {
            float dist = Mathf.Abs(point.t - t);
            if (dist < closestDistance)
            {
                closestDistance = dist;
                closest = point;
            }
        }

        // Log tangent and acceleration for debugging
        Debug.Log($"t={t:F3} | Tangent={closest.tangent} | Acceleration={closest.acceleration} | Position={closest.position}");

        // 限制Z轴加速度,只使用X/Y分量
        Vector3 acceleration = closest.acceleration;
        acceleration.z = 0f;

        // 根据加速度调整速度
        float curveSharpness = acceleration.magnitude;
        float maxSharpness = 1000f;
        curveSharpness = Mathf.Min(curveSharpness, maxSharpness);

        float minSpeed = 20f;
        float maxSpeed = 60f;
        float normalized = Mathf.InverseLerp(0f, maxSharpness, curveSharpness);
        float speed = Mathf.Lerp(maxSpeed, minSpeed, normalized);

        speed = Mathf.Max(speed, 0f);
        speed = Mathf.Clamp(speed, minSpeed, maxSpeed);

        Debug.Log($"Adjusted Speed: {speed}");
        return speed;
    }

    void SetSpeed(float speed)
    {
        var autodolly = dolly.AutomaticDolly.Method as SplineAutoDolly.FixedSpeed;
        if (autodolly != null)
        {
            autodolly.Speed = speed;
        }
    }

    void LocalMove(float x, float y, float speed)
    {
        Vector3 moveDirection = new Vector3(x, y, 0) * speed;
        moveVelocity = Vector3.Lerp(moveVelocity, moveDirection, inertia);
        transform.localPosition += moveVelocity * Time.deltaTime;

        Debug.Log($"Move Speed: {speed}, Move Velocity: {moveVelocity}, Position: {transform.position}, Rotation: {transform.rotation}");
        ClampPosition();
    }

    void ClampPosition()
    {
        // 原代码此处截断,推测是限制玩家在样条的可移动范围内
        // 可根据需求补充完整逻辑
    }

    // 省略RotationLook、HorizontalLean、Boost等未完整提供的方法
}

问题分析

你当前的CalculateSpeedFromCurve方法只根据曲线的加速度大小(曲线尖锐程度)调整速度:曲线越急(加速度越大),速度越低。但完全没有考虑玩家相对于曲线的偏移位置(内侧还是外侧),所以自然达不到离心力模拟的效果——赛车游戏里的离心力逻辑是:内侧抓地力更强,允许更快速度;外侧容易打滑,需要限制速度。

解决方案

要实现预期手感,我们需要:

  1. 计算当前样条点的内侧法向量(指向曲线弯曲的内侧方向)
  2. 计算玩家相对于当前样条点的偏移向量
  3. 通过点积判断玩家在曲线的内侧/外侧/居中
  4. 结合曲线尖锐程度和偏移方向,调整速度:内侧加速、外侧减速、居中维持原速

修改后的核心代码

下面是调整后的CalculateSpeedFromCurve方法,以及配套的辅助逻辑:

// 获取当前绑定的样条引用
private Spline GetCurrentSpline()
{
    return dolly.Spline;
}

float CalculateSpeedFromCurve(float t)
{
    Spline spline = GetCurrentSpline();
    if (spline == null)
    {
        Debug.LogWarning("未找到样条引用!");
        return originalSpeed;
    }

    // 获取当前样条点的位置、切线、向上向量
    SplineUtility.Evaluate(spline, t, out Vector3 splinePos, out Vector3 tangent, out Vector3 up);
    // 计算指向曲线内侧的法向量(方向不对的话,调换tangent和up的顺序即可)
    Vector3 curveInnerNormal = Vector3.Cross(up, tangent).normalized;

    // 计算玩家相对于样条点的左右偏移向量(忽略前进方向的偏移)
    Vector3 playerOffset = transform.position - splinePos;
    playerOffset = Vector3.ProjectOnPlane(playerOffset, tangent);

    // 通过点积判断位置:正=内侧,负=外侧,0=居中
    float offsetDot = Vector3.Dot(playerOffset.normalized, curveInnerNormal);
    // 归一化偏移程度(根据你的可移动范围调整)
    float offsetIntensity = Mathf.Clamp01(playerOffset.magnitude / 4f); // 假设左右最大偏移是4单位

    // 保留原逻辑的曲线尖锐程度计算
    var curvePoints = FindFirstObjectByType<SplineCurveCalc>().points;
    SplineCurveCalc.Evaluation closest = null;
    float closestDistance = float.MaxValue;
    foreach (var point in curvePoints)
    {
        float dist = Mathf.Abs(point.t - t);
        if (dist < closestDistance)
        {
            closestDistance = dist;
            closest = point;
        }
    }
    Vector3 acceleration = closest.acceleration;
    acceleration.z = 0f;
    float curveSharpness = Mathf.Min(acceleration.magnitude, 1000f);
    float normalizedSharpness = Mathf.InverseLerp(0f, 1000f, curveSharpness);

    // 速度调整逻辑:曲线越急、偏移越明显,调整幅度越大
    float speedAdjustRate = normalizedSharpness * offsetIntensity * 0.4f; // 最大40%的速度调整幅度
    float speedMultiplier = 1f + (offsetDot * speedAdjustRate);

    // 限制速度范围,避免手感失控
    float minSpeed = originalSpeed * 0.7f;
    float maxSpeed = originalSpeed * 1.3f;
    float finalSpeed = Mathf.Clamp(originalSpeed * speedMultiplier * timeSpeedMultiplier, minSpeed, maxSpeed);

    Debug.Log($"t={t:F3} | 偏移方向: {offsetDot:F2} | 速度倍率: {speedMultiplier:F2} | 最终速度: {finalSpeed:F2}");
    return finalSpeed;
}

注意事项

  1. 法向量方向:如果内侧外侧判断反了,把Vector3.Cross(up, tangent)改成Vector3.Cross(tangent, up)即可
  2. 调整幅度speedAdjustRate里的0.4是最大速度调整比例,可根据手感改成0.3或0.5
  3. 偏移范围playerOffset.magnitude / 4f中的4是假设的玩家最大左右偏移距离,要和你的实际可移动范围对应
  4. 速度限制minSpeedmaxSpeed可以根据游戏节奏调整,避免速度波动过大

现在测试一下,应该就能实现曲线内侧加速、外侧减速的真实赛车手感了!

内容来源于stack exchange

火山引擎 最新活动