统一将车辆与道路对齐

Unity align vehicle to road WITH rotation

目标: 创建具有类似于 Mario Kart 8 的反重力模式或 f- 零的属性的车辆;在极端非水平表面上骑行的能力。

期望的行为: 除非摇杆或箭头键是 pressed/moved,否则车辆不应转弯;它必须保持直线运动,但相对于相机视图的滚动和垂直曲率除外。

实际行为: 车辆将缓慢(有时很快)偏离路线并保持弯曲,直到轨道停止弯曲。如果放置在面向内的圆柱体中并沿径向行驶,车辆将开始向全局 +z 或全局 +y 弯曲。

(无错误消息)

我尝试过的: -设置transform.up为表面法线然后绕法线为轴旋转 -使用 quaternion.euler(0, [所需角度], 0) 然后 fromToRotation

对齐和旋转代码:

transform.rotation = Quaternion.Euler(0, rotation, 0);
Quaternion tilt = Quaternion.FromToRotation(Vector3.up, localUp);
transform.rotation = tilt * transform.rotation;
transform.position += velocity * 1.1f;

整个脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using PhysicsExtensions;
using UnityEngine.Rendering.PostProcessing;

public class Cart : MonoBehaviour
{
Kartphysics inputActions;
public new Transform camera, camTarget, camTargetDrift, Visual;
public ShipType shipType;
public AudioSource Vroom;
public Vector3 localUp = Vector3.up;
Vector3 velocity, camUp, followPos;
public AnimationCurve SteeringControl;
public float steerAmount;
float rotation, rollTarget, roll, fovDifference, vroomPitch = 0, flameLength = 0;
public float normalFov, speedFov, Velocity, rollAmount, speedFactor, forcedAcceleration;
public GameObject[] ships;
public FlamingTrail[] flames;
public PostProcessProfile ppp;
Vector2 JoystickVal;
ChromaticAberration ca;
LensDistortion ld;
Vector3 LastForward;

private void Start()
{
    switch (shipType)
    {
        case ShipType.Carrier:
            {
                ships[0].SetActive(true);
                break;
            }
        case ShipType.Ram:
            {
                ships[1].SetActive(true);
                break;
            }
    }
    ca = ppp.GetSetting<ChromaticAberration>();
    ld = ppp.GetSetting<LensDistortion>();
}
private void Update()
{
    UpdateVisuals();
    UpdateCamera();
    Velocity = velocity.magnitude;
}
private void FixedUpdate()
{
    UpdateKart();
}
void SetFlames(float length)
{
    for(int i = 0; i < flames.Length; i++)
    {
        flames[i].length = length;
    }
}
void UpdateVisuals()
{
    ca.intensity.value = Mathf.Clamp01(forcedAcceleration) * 2;
    ld.intensity.value = Mathf.Lerp(0, -70f, Mathf.Clamp(forcedAcceleration, 0, 1));
    SetFlames(flameLength);
    Vroom.pitch = Mathf.Lerp(Vroom.pitch, vroomPitch, (speedFactor * 0.01f) * 10);
    Visual.position = Vector3.Lerp(Visual.position, transform.position, (speedFactor * 0.01f) * 30);
    Visual.rotation = Quaternion.Lerp(Visual.rotation, transform.rotation, (speedFactor * 0.01f) * 15);
    
}
void UpdateCamera()
{
    fovDifference = speedFov - normalFov;
    Camera.main.fieldOfView = speedFov - (fovDifference * (1 / Mathf.Clamp(velocity.magnitude + 1, 0, Mathf.Infinity)));
    camUp = Vector3.Lerp(camUp, localUp.normalized, (speedFactor * 0.01f) * (Vector3.Distance(camera.position, Vector3.Lerp(camTarget.position, camTargetDrift.position, transform.InverseTransformDirection(velocity).x)) + 3));
    camera.rotation = Quaternion.Slerp(camera.rotation, Quaternion.LookRotation((transform.position - (transform.right * transform.InverseTransformDirection(velocity).x * 5) + transform.up) - camera.position, camUp), (speedFactor * 0.01f) * 13);
    camera.position = Vector3.Lerp(camera.position, Vector3.Lerp(camTarget.position, camTargetDrift.position, transform.InverseTransformDirection(velocity).x), (speedFactor * 0.01f) * Vector3.Distance(camera.position, camTarget.position) * 20);
}
void UpdateKart()
{
    JoystickVal = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
    if (Input.GetAxis("Submit") > 0.5)
        JoystickVal = new Vector2(JoystickVal.x, 1);
    if (Input.GetAxis("Cancel") > 0.5)
        JoystickVal = new Vector2(JoystickVal.x, -1);
    if (JoystickVal.magnitude > 1)
    {
        JoystickVal.Normalize();
    }
    JoystickVal *= (speedFactor * 0.01f) * 0.2f;
    JoystickVal /= Mathf.Clamp(velocity.magnitude, 0.7f, Mathf.Infinity);
    velocity += ((transform.forward * JoystickVal.y) / Mathf.Clamp(Mathf.Abs(transform.InverseTransformDirection(velocity).x), 0.7f, Mathf.Infinity));
    
    rollTarget = Mathf.Clamp01(SteeringControl.Evaluate(velocity.magnitude)) * JoystickVal.x * rollAmount;
    roll = Mathf.MoveTowards(roll, rollTarget, (speedFactor * 0.01f) * 4);
    velocity -= localUp * (speedFactor * 0.01f) * 0.7f;
    velocity /= 1 + ((speedFactor * 0.01f) / 8);
    RaycastHit hit;
    CircleCastHit circleHit;
    if (Physics.Raycast(transform.position + transform.up, -transform.up + (velocity / 1), out hit))
    {

        if (hit.distance < 4)
        {
            transform.position -= hit.normal.normalized * (speedFactor * 0.01f);
            localUp = Vector3.MoveTowards(localUp, hit.normal, (speedFactor * 0.01f) * 9);
            if (hit.distance < 1.2f)
            {
                flameLength = Velocity * 2;
                if (hit.collider.tag == "SpeedPanel")
                    forcedAcceleration = 3f;
                rotation += SteeringControl.Evaluate(velocity.magnitude * 0.7f) * JoystickVal.x * (speedFactor * 0.01f) * 100 * steerAmount;
                transform.position += hit.normal.normalized * (1 - hit.distance);
                vroomPitch = velocity.magnitude * 1.5f;
                velocity += ((transform.forward * ((JoystickVal.y * 1.3f) + (forcedAcceleration / 100))) / Mathf.Clamp(Mathf.Abs(transform.InverseTransformDirection(velocity).x), 0.7f, Mathf.Infinity));
                rotation += SteeringControl.Evaluate((speedFactor * 0.01f) * velocity.magnitude * 50) * JoystickVal.x * 0.3f;
                velocity /= 1 + ((speedFactor * 0.01f));
                velocity -= transform.right * transform.InverseTransformDirection(velocity).x * 0.2f;
                Vector3 force = (hit.normal * -transform.InverseTransformDirection(velocity).y / Mathf.Clamp(hit.distance - 0.1f, 0.5f, 2)) * 1.1f;
                if (force.magnitude > 1)
                    force = force.normalized * 1;
                force /= 8;
                velocity += force;

            }
            else
            {
                vroomPitch = 0;
                flameLength = Mathf.MoveTowards(flameLength, 0, 0.03f);
            }

        }
        else
        {
            localUp = Vector3.MoveTowards(localUp, Vector3.up, (speedFactor * 0.01f) * 1.2f);
            vroomPitch = 0;
            transform.forward = velocity.normalized;
            flameLength = Mathf.MoveTowards(flameLength, 0, 0.03f);
        }
    }
    else
    {
        localUp = Vector3.MoveTowards(localUp, Vector3.up, (speedFactor * 0.01f) * 2);
        vroomPitch = 0;
        flameLength = Mathf.MoveTowards(flameLength, 0, 0.03f);
    }
    if (PhysicsII.CircleCast(transform.position + (transform.up * 0.5f), localUp, 0.7f, 8, out circleHit))
    {
        Debug.DrawRay(circleHit.nearestHit().point, circleHit.nearestHit().normal, Color.red, 0.1f);
        Debug.Log("HIT");
        velocity += (transform.position + (transform.up * 0.5f) - circleHit.nearestHit().point) / 3;
        if (circleHit.nearestHit().distance < 0.4f)
            velocity += (transform.position + (transform.up * 0.5f) - circleHit.nearestHit().point) / 7;
        if (circleHit.nearestHit().distance < 0.14f)
            velocity += (transform.position + (transform.up * 0.5f) - circleHit.nearestHit().point) / 7;

    }
    if(Physics.Raycast(transform.position + (transform.up * 0.8f) - velocity, velocity , out hit))
    {
        if(hit.distance < Velocity * 2)
            velocity /= 1 + ((speedFactor * 0.01f) * 2f);
        if (hit.distance < Velocity * 1.2f)
            velocity = Vector3.Reflect(velocity, hit.normal);
    }
    forcedAcceleration = Mathf.MoveTowards(forcedAcceleration, 0, 0.1f);

    transform.rotation = Quaternion.Euler(0, rotation, 0);
    Quaternion tilt = Quaternion.FromToRotation(Vector3.up, localUp);
    transform.rotation = tilt * transform.rotation;
    transform.position += velocity * 1.1f;

}
public enum ShipType
{
    Carrier = 0,
    Ram = 1
}
}

这是部分答案,因为我目前无法亲自测试它是否有效。看起来“roll”还没有用于任何事情(它是否意味着以某种方式改变变换的局部向上?)所以我不确定。

首先,您可以只使用 transform.forwardtransform.right 来实现这些目的,而不是保持浮动旋转来跟踪车辆的转向情况,并测量对它的修改每帧基础:

void UpdateKart()
{

    Vector3 newForward = transform.forward;  
    float turnAmount = 0f;

    // ...
            if (hit.distance < 1.2f)
            {
                flameLength = Velocity * 2;
                if (hit.collider.tag == "SpeedPanel")
                    forcedAcceleration = 3f;
                turnAmount += SteeringControl.Evaluate(velocity.magnitude * 0.7f) 
                        * JoystickVal.x * (speedFactor * 0.01f) * 100 * steerAmount;
                transform.position += hit.normal.normalized * (1 - hit.distance);
                vroomPitch = velocity.magnitude * 1.5f;
                velocity += /* too long to bother formatting */
                turnAmount += SteeringControl.Evaluate((speedFactor * 0.01f) 
                        * velocity.magnitude * 50) * JoystickVal.x * 0.3f;

    // ...

然后在实际调整旋转的时候,将围绕局部上轴的旋转量应用到当前局部向前的方向上。最后,设置变换的旋转,使其新的局部向上为 localUp 并使其局部向前尽可能保持恒定的方向(叉积后跟 Quaternion.LookRotation 可用于此):

    forcedAcceleration = Mathf.MoveTowards(forcedAcceleration, 0, 0.1f);

    Vector3 turnedForward = Quaternion.AngleAxis(turnAmount - 180, localUp) * 
            transform.forward; 

    Vector3 newRight = Vector3.Cross(turnedForward, localUp);
    if (newRight == Vector3.zero)
    {
       /* Ambiguous situation - maybe kart landed with its nose directly in the 
          direction of localUp or opposite direction. Possible solution: use 
          velocity as previous forward direction and recalculate, using a random 
          direction if that doesn't work 
       */

       newRight = Vector3.Cross(velocity, localUp); 

       if (newRight == Vector3.zero) 
       {    
           newRight = Vector3.ProjectOnPlane(Random.insideUnitSphere, 
                   localUp).normalized;
       }
    }

    Vector3 newForward = Vector3.Cross(newRight, localUp);

    transform.rotation = Quaternion.LookRotation(newForward, localUp);
    transform.position += velocity * 1.1f;

您之所以看到这样的结果是因为 FromToRotation 会给您“最小的”旋转,将一个向量移动到另一个向量。但是你更关心的是轮换,这将使局部前锋接近调整前的状态(很难解释为什么这不是一回事)。因此 Cross 东西。

正如我所说,这只是为了让您更接近的部分解决方案。但是,这可能就是您所需要的。请在评论中告诉我您的想法。