Unity物理驱动3D汽车实现:从刚体控制到参数调优

1. 项目概述:为什么从一辆小汽车开始?

如果你刚打开Unity,面对一个空荡荡的3D场景,可能会有点不知所措。网上教程要么是“Hello World”级别的UI点击,要么是直接跳到复杂的角色控制器或网络同步,中间仿佛缺了一大块。今天,我们就来填补这块空白,用一个最经典、最直观的物理对象——一辆3D小汽车,作为你Unity和C#编程的“第一块里程碑”。

为什么是汽车?因为它完美融合了Unity引擎的几个核心模块: 3D模型与场景搭建 刚体物理(Rigidbody) 碰撞体(Collider) 力的施加与控制 ,以及 通过C#脚本实现的玩家输入逻辑 。完成这个项目,你不仅能收获一个可以在地图上跑来跑去、能撞墙、能刹停的玩具,更重要的是,你能透彻理解游戏对象(GameObject)如何通过组件(Component)协同工作,以及如何用代码驱动物理行为。这比单纯背API要有用得多。

我见过很多新手卡在“我的物体为什么不受力?”或者“代码写了怎么没反应?”这类问题上。这个教程会手把手带你走过每一个坑,从创建空物体、挂组件、写第一行脚本,到调试物理参数,最终让你获得一辆操控感扎实的小汽车。所有用到的C#脚本都会完整提供,并逐行解释其作用,确保你知其然,更知其所以然。

2. 核心思路与物理方案选型

在Unity里让一个物体动起来,有好几种“流派”。对于一辆汽车,我们需要做出关键选择,这直接决定了最终的手感和实现复杂度。

2.1 方案对比:Transform位移 vs. 物理引擎驱动

最直接的想法可能是用 Transform.Translate 或直接修改 Transform.position 。写个脚本,按W键就让位置Z轴增加,按A键就让旋转Y轴变化。这种方法简单粗暴,但会带来一个严重问题: 它完全无视物理法则 。你的车会穿墙而过(因为没有碰撞反馈),会在斜坡上如履平地(因为没有重力),停下来也没有惯性。这做出来的更像一个“幽灵车”,而不是有质量的实体。

因此,我们必须选择第二条路: 利用Unity的物理引擎(PhysX)来驱动 。这意味着我们要给汽车 GameObject 添加一个 Rigidbody 组件。 Rigidbody 让Unity将其视为一个具有质量、阻力、并受重力影响的物理实体。然后,我们不再直接修改位置,而是通过脚本向这个 Rigidbody 施加力(Force)或扭矩(Torque) ,由物理引擎来计算最终的位移和旋转。这样,碰撞、摩擦力、惯性全都自然实现了。

2.2 力的施加方式:AddForce vs. 直接修改速度

即使用 Rigidbody ,也有不同的发力方式。 Rigidbody.AddForce 是最符合物理直觉的,它模拟了一个持续的推力。但对于汽车控制,尤其是转向,单纯用 AddForce 在局部坐标轴可能会让操控变得非常笨重和反直觉,因为力作用在质心上,容易导致车辆打转(就像你从侧面推一个箱子)。

经过多次尝试,一个对新手更友好、效果也更接近街机赛车手感的方法是: 混合使用 AddForce 驱动前进后退,以及直接修改 Rigidbody.angularVelocity 来控制转向

  • 驱动 :在车辆的前进方向(通常是其Z轴正方向)上施加力。这样,无论车头朝向哪里,力总是推着它向前跑。
  • 转向 :通过修改绕Y轴旋转的角速度,让车辆产生转向效果。这比施加扭矩更容易控制转向的灵敏度和回正。

这个方案在保证物理交互(碰撞、惯性)真实性的同时,赋予了我们对操控感的精细控制,非常适合新手入门和实现“能跑能停”的目标。

3. 环境与资源准备

在开始写代码之前,我们需要在Unity中搭建一个最基本的测试舞台。

3.1 创建项目与基本场景

打开Unity Hub,创建一个新的 3D (Core) 项目,命名为“MyFirstCar”。进入项目后,你会看到一个默认场景,包含一个主摄像机和一个平行光。

首先,我们需要一个地面。在Hierarchy窗口右键 -> 3D Object -> Plane,创建一个平面。将其Scale调整为(5, 1, 5),让它足够大,作为我们的跑道。可以给它创建一个简单的材质,比如颜色设为浅灰色,方便观察。

接着,创建我们的“汽车”。由于重点是逻辑而非美术,我们用一个简单的立方体(Cube)来代表车体。在Hierarchy窗口右键 -> 3D Object -> Cube,重命名为“PlayerCar”。将其位置Y轴设为0.5,这样它刚好“站”在地面上。

注意 :使用基本几何体进行原型开发是游戏开发中的标准做法。它能让你快速验证核心玩法逻辑,而不被复杂的美术资源分散注意力。逻辑跑通后,再用精美的3D模型替换这个Cube是分分钟的事。

3.2 为汽车装配物理组件

选中“PlayerCar”对象,我们需要为其添加两个核心物理组件:

  1. Rigidbody :点击Inspector窗口下方的“Add Component”,搜索并添加“Rigidbody”。这是让物体受物理引擎控制的灵魂组件。
  2. Collider :Cube默认自带一个“Box Collider”组件,这很好。碰撞体定义了物体的物理轮廓,用于检测与其他碰撞体的接触。

现在,你的“PlayerCar” Inspector视图应该类似这样:

  • Transform
  • Mesh Filter
  • Mesh Renderer
  • Box Collider
  • Rigidbody

暂时不要修改Rigidbody的默认参数,我们稍后会在脚本中调整。

3.3 设置摄像机跟随

为了让游戏体验更好,我们需要让主摄像机跟着汽车跑。一个简单的方法是让摄像机成为汽车的子物体。在Hierarchy中,将“Main Camera”拖拽到“PlayerCar”对象上,使其成为后者的子级。然后,调整摄像机相对于汽车的位置和角度,比如将位置设为(0, 3, -5),旋转设为(25, 0, 0),形成一个经典的第三人称追尾视角。

实操心得 :将摄像机设为子物体是最快的跟随方案,但并非最佳。因为摄像机会继承汽车的所有旋转,当汽车转向时,画面会剧烈晃动。对于正式项目,建议编写独立的摄像机跟随脚本,使用插值平滑跟随。但作为第一个项目,子物体方案足够简单直观。

4. 核心脚本解析与逐行实现

现在进入最核心的部分:编写控制汽车的C#脚本。我们将创建一个名为 CarController 的脚本。

4.1 创建脚本与变量声明

在Project窗口中,右键 -> Create -> C# Script,命名为“CarController”。双击在Visual Studio或你喜欢的编辑器中打开。

首先,我们需要定义一些可调节的参数,这样无需修改代码就能改变汽车的手感。在 CarController 类内部, Start Update 方法之上,声明以下公共变量:

using UnityEngine;

public class CarController : MonoBehaviour
{
    // 公开变量,方便在Unity编辑器中实时调整
    [Header("移动参数")]
    public float motorForce = 1000f;      // 发动机力,决定加速快慢
    public float brakeForce = 3000f;      // 制动力,决定刹车强度
    public float maxSpeed = 20f;          // 最大速度限制

    [Header("转向参数")]
    public float steerAngle = 30f;        // 最大转向角度(影响角速度)
    public float turnSpeed = 100f;        // 转向灵敏度

    [Header("物理参数")]
    public float dragOnGround = 3f;       // 地面阻力
    public float dragInAir = 0.1f;        // 空中阻力(较小)

    // 私有变量,用于内部引用和计算
    private Rigidbody rb;
    private float horizontalInput; // 水平输入(A/D, Left/Right)
    private float verticalInput;   // 垂直输入(W/S, Up/Down)
    private bool isBraking;        // 是否正在刹车
}

变量解析

  • motorForce brakeForce :这是两个关键力值。你可以把它们想象成油门和刹车踏板的深度。值越大,加速或减速越猛。
  • maxSpeed :一个安全阀,防止车辆因持续加速而失控。
  • steerAngle turnSpeed :共同控制转向行为。 steerAngle 是目标转向角度, turnSpeed 是达到这个角度的快慢。
  • dragOnGround dragInAir :阻力系数。给 Rigidbody 设置不同的阻力,可以模拟车辆在地面有摩擦力和在空中几乎无阻力的状态,这对防止车辆滑行过头很有用。
  • rb :用于缓存对自身 Rigidbody 组件的引用,这是一种优化做法,避免在每帧都调用 GetComponent<Rigidbody>()

4.2 初始化与输入获取

Start 方法中,我们获取 Rigidbody 组件的引用,并进行一些初始设置。

void Start()
{
    // 获取附加在同一GameObject上的Rigidbody组件
    rb = GetComponent<Rigidbody>();

    // 设置Rigidbody的初始属性,优化车辆物理表现
    if (rb != null)
    {
        rb.centerOfMass = new Vector3(0, -0.5f, 0); // 将质心降低,增加稳定性,防止翻车
        rb.drag = dragOnGround; // 初始化地面阻力
    }
}

关键点 centerOfMass (质心)是控制车辆稳定性的魔法参数。默认质心在物体中心,对于立方体来说就是几何中心。将其在Y轴上降低(例如-0.5),相当于给车辆配了“配重块”,能极大减少转弯时侧翻的几率。这是实现“能跑”且“不易翻”的实用技巧。

Update 方法中,我们处理每帧的玩家输入。 Update 与帧率相关,适合处理输入。

void Update()
{
    // 获取玩家输入,范围在[-1, 1]之间
    horizontalInput = Input.GetAxis("Horizontal"); // A/D 或 左右箭头
    verticalInput = Input.GetAxis("Vertical");     // W/S 或 上下箭头

    // 检测空格键是否被按下,用于刹车
    isBraking = Input.GetKey(KeyCode.Space);
}

Input.GetAxis 返回一个平滑的值,而不是简单的0或1,这使得操控(尤其是转向)更加平滑。

4.3 物理驱动与转向逻辑

真正的物理计算和力的施加,必须放在 FixedUpdate 方法中。 FixedUpdate 的调用频率是固定的(默认每秒50次,即0.02秒一次),与物理引擎的更新步频同步,在这里处理力和速度能保证物理模拟的稳定性和可预测性。

void FixedUpdate()
{
    // 1. 处理驱动(前进/后退)
    HandleMotor();

    // 2. 处理转向
    HandleSteering();

    // 3. 处理刹车
    HandleBraking();

    // 4. 限制最大速度
    LimitMaxSpeed();

    // 5. 根据是否在地面调整阻力
    AdjustDrag();
}

接下来,我们逐一实现这些方法。

HandleMotor() - 驱动处理

private void HandleMotor()
{
    // 计算当前的前进方向(物体的Z轴正方向在世界空间中的向量)
    Vector3 forwardForce = transform.forward * verticalInput * motorForce;

    // 只有当有输入(verticalInput != 0)且速度未达上限时,才施加力
    if (verticalInput != 0 && rb.velocity.magnitude < maxSpeed)
    {
        rb.AddForce(forwardForce, ForceMode.Acceleration);
    }
}
  • transform.forward 获取的是该GameObject的蓝色箭头(Z轴)所指的方向。无论车头朝向哪里,力都沿着这个方向施加。
  • ForceMode.Acceleration 表示添加一个持续的加速度,忽略物体的质量。这比 ForceMode.Force (受质量影响)更容易调出稳定的手感。

HandleSteering() - 转向处理

private void HandleSteering()
{
    // 计算目标转向角速度:输入 * 最大角度 * 转向灵敏度
    float targetTurnSpeed = horizontalInput * steerAngle * turnSpeed;

    // 获取当前的角速度
    Vector3 currentAngularVelocity = rb.angularVelocity;

    // 只修改绕Y轴(上下)旋转的角速度,保持X和Z轴(侧翻、俯仰)的物理稳定性
    // Mathf.Lerp用于平滑过渡,避免转向突变
    currentAngularVelocity.y = Mathf.Lerp(currentAngularVelocity.y, targetTurnSpeed, Time.fixedDeltaTime * 5f);

    // 将修改后的角速度赋值回去
    rb.angularVelocity = currentAngularVelocity;
}
  • 这里没有使用 AddTorque (添加扭矩),而是直接设置了 angularVelocity.y 。这提供了更直接、更线性的转向控制,类似于直接设定方向盘转角。
  • Mathf.Lerp 是线性插值函数,让角速度从当前值平滑地变化到目标值,参数 Time.fixedDeltaTime * 5f 控制了平滑的速度。这是避免转向“生硬”的关键。

HandleBraking() - 刹车处理

private void HandleBraking()
{
    if (isBraking)
    {
        // 计算一个与当前速度方向相反的力(即刹车力)
        Vector3 brakeDirection = -rb.velocity.normalized; // 速度的反方向
        Vector3 brakeForceVector = brakeDirection * brakeForce;

        // 施加刹车力
        rb.AddForce(brakeForceVector, ForceMode.Acceleration);

        // 同时,立即将阻力设为很大,模拟刹车片的摩擦力,让车快速停下
        rb.drag = dragOnGround * 5f;
    }
    else
    {
        // 松开刹车时,恢复普通地面阻力
        rb.drag = dragOnGround;
    }
}
  • 刹车逻辑分为两部分:一是施加一个反向力来抵消动量;二是瞬间增大阻力 ( drag ),这能产生非常迅速的减速效果,模拟急刹车。

LimitMaxSpeed() - 速度限制

private void LimitMaxSpeed()
{
    // 如果当前速度大小超过了最大速度限制
    if (rb.velocity.magnitude > maxSpeed)
    {
        // 将速度向量归一化(方向不变),然后缩放到最大速度
        rb.velocity = rb.velocity.normalized * maxSpeed;
    }
}

这是一个简单的速度钳制,防止车辆无限加速。

AdjustDrag() - 阻力调整(进阶)

private void AdjustDrag()
{
    // 这是一个简单的接地检测:从车底向下发射一条短射线
    if (Physics.Raycast(transform.position, Vector3.down, 0.6f))
    {
        // 如果射线碰到了地面,使用地面阻力
        rb.drag = dragOnGround;
    }
    else
    {
        // 如果悬空(比如飞坡后),使用较小的空中阻力
        rb.drag = dragInAir;
    }
}

这个方法通过射线检测来判断车辆是否在地面上。在空中时,阻力变小,车辆会保持动量滑行更远,落地时阻力恢复,增加真实感。 0.6f 这个距离需要根据你的车体高度(Cube的Scale.y=1,中心在0.5,所以0.6刚好比底盘低一点)进行调整。

4.4 挂载脚本与基础测试

保存脚本,回到Unity编辑器。将 CarController 脚本从Project窗口拖拽到Hierarchy中的“PlayerCar”对象上。你会在Inspector中看到脚本组件,以及所有我们声明的公共变量。

现在,点击Unity顶部的播放按钮。你应该可以用W/A/S/D键控制立方体前进、后退、左右转向,用空格键刹车。恭喜你,你的第一辆物理驱动小车已经能跑能停了!

5. 参数调优与手感打磨

脚本能运行只是第一步,让汽车开起来“舒服”才是难点。下面是一些关键的调参经验和技巧。

5.1 动力与制动平衡

选中“PlayerCar”,在 CarController 组件中,你会看到我们定义的所有公共变量。这是我们的调试面板。

  1. 加速感 ( motorForce ) :初始值1000可能偏大或偏小。如果按下W键车像火箭一样窜出去,就调小(如500);如果感觉加速绵软无力,就调大(如1500)。理想的感受是:轻按有反应,深踩能持续加速。
  2. 刹车力度 ( brakeForce ) :它需要能与 motorForce 抗衡。如果刹车时感觉停不下来,就增大 brakeForce (如5000)。一个好的基准是:在中等速度下,按住空格键大约1-2秒内能完全刹停。
  3. 极速 ( maxSpeed ) :限制最高速度不仅能防止失控,也是塑造车辆性格的一部分。一辆笨重的卡车可能 maxSpeed=15 ,而跑车可以设为 30

实操心得 :调参时,不要一次性改多个变量。先调 motorForce 获得满意的加速,再调 brakeForce 获得满意的制动,最后用 maxSpeed 收尾。每次修改后,在Play模式下直接拖动Inspector中的数值滑块,效果是实时生效的,这是Unity编辑器调试的便利之处。

5.2 转向灵敏度与稳定性

转向是手感的核心,也是最容易调“怪”的地方。

  1. 转向角度 ( steerAngle ) :这个值决定了方向盘打满时,车辆角速度的目标值。值越大,转弯越急。对于街机手感,可以设大一些(如45);追求模拟真实,可以设小一些(如20)。
  2. 转向速度 ( turnSpeed ) :它决定了车辆从直行状态达到目标转向角速度的快慢。值越大,转向响应越“跟手”,但也可能越“贼”。通常 turnSpeed 在50-150之间尝试。
  3. 质心 ( centerOfMass ) :这是防止翻车的终极法宝。如果你发现车辆容易在转弯时侧翻,将脚本中 rb.centerOfMass 的Y值进一步降低,比如从 -0.5f 改为 -0.8f 。你会发现车辆像吸在地上一样稳定。

一个经典的调试场景 :创建一个弯道(可以用多个Cube拼成围墙)。以不同速度过弯,感受车辆是推头(转向不足)还是甩尾(转向过度)。推头就适当增大 steerAngle turnSpeed ;甩尾就降低这两个值,或者更激进地降低质心。

5.3 阻力与空中姿态

dragOnGround dragInAir 影响车辆的“滑溜”程度。

  • dragOnGround :地面阻力。值越大,松开油门后减速越快。设置为3-5,可以模拟较好的路面摩擦,避免车辆无限滑行。
  • dragInAir :空中阻力。通常设为很小的值(如0.1),这样车辆飞坡时会有漂亮的抛物线轨迹,而不是突然停滞。

你可以制作一个小坡道(用一个细长的Cube,旋转一下),开车冲上去,观察空中滑行距离,来调整 dragInAir

6. 从方块到汽车:模型与碰撞体优化

用Cube做原型没问题,但最终我们想要一辆像样的车。这里涉及模型导入和碰撞体匹配。

6.1 导入3D汽车模型

你可以在许多免费资源网站(如Unity Asset Store的免费部分、Sketchfab等)找到低多边形(Low Poly)的汽车模型。下载FBX格式的文件。将其拖入Unity项目的Assets文件夹。然后,将这个模型从Project窗口拖到Hierarchy中,最好作为“PlayerCar”的子物体,或者直接用它替换掉原来的Cube。

关键步骤

  1. 确保新模型的轴心正确。导入后,在Project窗口选中模型文件,在Inspector的“Model”标签页下,检查“Bake Axis Conversion”或调整“Rotation”,确保模型的蓝色箭头(前向轴)指向车头。
  2. 调整模型的Scale和Position,使其与原来的Cube大致重合。

6.2 配置更精确的碰撞体

Cube自带的Box Collider是一个方盒子,套在汽车模型上会非常不准确,导致视觉上车身离墙还很远,物理上却已经撞上了。

我们需要一个 复合碰撞体 。删除汽车模型自带的或旧的Box Collider。然后:

  1. 选中“PlayerCar”(或你的汽车模型父物体)。
  2. 点击“Add Component”,搜索并添加“Mesh Collider”。
  3. 在Mesh Collider组件的“Mesh”属性中,指定为你的汽车模型网格。
  4. 重要 :勾选“Convex”选项。只有凸包(Convex)的Mesh Collider才能与动态刚体正常作用,并且性能更好。

但是,一个复杂的Mesh Collider(即使勾选Convex)可能仍然不够精确或性能不佳。 更优的做法是使用多个简单碰撞体(如Box Collider, Capsule Collider)来近似拟合汽车形状 。例如:

  • 用一个长的Box Collider作为车身。
  • 用四个小的Capsule Collider或Box Collider作为四个车轮的碰撞区域。

这需要手动添加和调整,但能获得更好的性能和更可控的物理反馈。

6.3 添加车轮视觉效果(简单版)

为了让车看起来在转,我们可以让车轮子模型旋转。假设你的汽车模型四个轮子是独立的子物体(名为Wheel_FR, Wheel_FL等)。

创建一个新的脚本 WheelVisual ,挂载到每个轮子对象上:

public class WheelVisual : MonoBehaviour
{
    public bool isSteeringWheel = false; // 是否是转向轮(前轮)
    private CarController carController;
    private float initialRotationX;

    void Start()
    {
        carController = GetComponentInParent<CarController>();
        initialRotationX = transform.localEulerAngles.x;
    }

    void Update()
    {
        // 1. 根据车速旋转(模拟滚动)
        // 获取汽车刚体的速度在自身前向方向上的分量
        float carSpeed = carController.GetComponent<Rigidbody>().velocity.magnitude;
        float rotationSpeed = carSpeed * 50f; // 调节系数,让滚动看起来自然
        float deltaRotation = rotationSpeed * Time.deltaTime;

        // 绕轮子的本地X轴旋转(假设轮子模型X轴指向侧面)
        transform.Rotate(deltaRotation, 0, 0, Space.Self);

        // 2. 如果是转向轮,根据输入旋转(模拟转向)
        if (isSteeringWheel && carController != null)
        {
            // 这里需要从CarController脚本中获取转向输入值,可能需要将其设为public
            // 假设我们通过一个公共方法获取
            float steerInput = carController.GetSteerInput(); // 你需要先在CarController里添加这个方法
            float targetSteerAngle = steerInput * 30f; // 最大转向视觉角度
            Quaternion targetRotation = Quaternion.Euler(transform.localEulerAngles.x, targetSteerAngle, 0);
            transform.localRotation = Quaternion.Lerp(transform.localRotation, targetRotation, Time.deltaTime * 10f);
        }
    }
}

这个脚本让轮子根据车速滚动,并让前轮根据转向输入左右转动。你需要先在 CarController 中添加一个 public float GetSteerInput() { return horizontalInput; } 方法以供调用。

7. 常见问题与调试技巧实录

在实现过程中,你几乎一定会遇到下面这些问题。这里是我的踩坑记录和解决方案。

7.1 车辆原地打转或侧翻

症状 :一按转向键,车就在原地疯狂旋转甚至翻车。 原因

  1. 质心太高或转向力/扭矩过大。
  2. 可能错误地在 FixedUpdate 中使用了 Transform.Rotate 来转向,与物理系统冲突。 排查与解决
  • 首先,确保你使用的是我们方案中的 rb.angularVelocity 修改方式,而不是直接旋转Transform。
  • 其次,在 Start 方法中,务必设置 rb.centerOfMass 为一个较低的负值,这是最有效的防翻车手段。
  • 降低 steerAngle turnSpeed 的值。

7.2 刹车后车辆向后滑行或行为怪异

症状 :按下空格刹车,车停住后,会自己慢慢向后滑,或者刹车力方向不对。 原因 HandleBraking 方法中的刹车力方向计算依赖于 rb.velocity.normalized 。当车速极低时,这个向量可能不稳定,或者当车辆刚好停下时(速度为零), normalized 会得到一个零向量,导致后续计算出错。 优化代码

private void HandleBraking()
{
    if (isBraking)
    {
        // 只有当速度大于一个极小值时,才计算反向刹车力
        if (rb.velocity.magnitude > 0.1f)
        {
            Vector3 brakeDirection = -rb.velocity.normalized;
            Vector3 brakeForceVector = brakeDirection * brakeForce;
            rb.AddForce(brakeForceVector, ForceMode.Acceleration);
        }
        else
        {
            // 如果速度已经很小,直接施加一个反向于当前运动趋势的力(如果有的话),或者直接清零速度
            rb.velocity = Vector3.zero;
            rb.angularVelocity = Vector3.zero;
        }
        rb.drag = dragOnGround * 5f;
    }
    else
    {
        rb.drag = dragOnGround;
    }
}

7.3 车辆上坡无力或下坡失控

症状 :遇到斜坡时,感觉动力不足爬不上去,或者下坡时速度激增刹不住。 原因 :我们的 HandleMotor 中,力是沿物体自身前向施加的。在坡道上,这个力需要分解为克服重力的分力和前进的分力。同时,下坡时重力会做功加速。 解决方案 :这是一个更接近真实物理的进阶话题。一个简化的改进是,在施加驱动力时,考虑坡道的影响。但更简单有效的方法是 调整场景坡度 (不要做得太陡)和 调整车辆的马力与重量 。你可以尝试增加 motorForce ,或者减少 Rigidbody Mass (质量)来让爬坡轻松些。下坡失控则需要更强的 brakeForce dragOnGround

7.4 转向有延迟或不够跟手

症状 :按下转向键后,车要过一会儿才转,或者转向过程不线性。 原因 Mathf.Lerp 的平滑系数可能太小,或者 turnSpeed 值太低。 解决

  • 增大 HandleSteering 方法中 Mathf.Lerp 的第三个参数(即 Time.fixedDeltaTime * 5f 中的 5f ),比如改成 10f 15f ,会让转向响应更快。
  • 直接增大 turnSpeed 值。
  • 如果你想追求极其直接的响应,可以不用 Lerp ,直接赋值: currentAngularVelocity.y = targetTurnSpeed; 。但这可能会让转向感觉过于生硬。

7.5 性能问题与优化提示

  • 复杂的Mesh Collider :如果为复杂汽车模型添加了非凸包的Mesh Collider,在移动时会产生巨大的性能开销。 务必使用凸包(Convex)或简单碰撞体组合
  • 过多的刚体 :如果你的场景中有很多静止的物体(如围墙、建筑),确保它们没有 Rigidbody ,或者如果有,将其设置为 Is Kinematic (运动学)或 Static (静态),这样物理引擎就不会浪费资源去计算它们的运动。
  • 脚本效率 :确保 GetComponent<>() 这类调用只在 Start Awake 中执行一次并缓存结果,而不是在 Update FixedUpdate 中每帧调用。

8. 功能扩展与创意发挥

基础功能实现后,你可以以此为骨架,添加更多功能,让你的小汽车游戏化。

8.1 添加速度表与UI

创建一个Canvas和UI Text,显示当前速度。 在 CarController 脚本中添加:

public Text speedText; // 在Inspector中关联你的UI Text

void Update()
{
    // ... 原有的输入获取代码 ...

    // 更新UI:速度大小,并转换为整数和公里/小时单位(假设1单位=1米,*3.6转为km/h)
    if (speedText != null)
    {
        int speedKPH = (int)(rb.velocity.magnitude * 3.6f);
        speedText.text = "Speed: " + speedKPH + " km/h";
    }
}

8.2 实现简单的氮气加速

添加一个氮气槽和爆发加速功能。 在 CarController 中新增变量:

public float nitroForceMultiplier = 2.0f; // 氮气动力倍增
public float maxNitro = 100f;
private float currentNitro;
public bool isNitroActive = false;

Start 中初始化 currentNitro = maxNitro; 。 在 Update 中检测按键(例如左Shift)来激活氮气:

if (Input.GetKeyDown(KeyCode.LeftShift) && currentNitro > 0)
{
    isNitroActive = true;
}
if (Input.GetKeyUp(KeyCode.LeftShift) || currentNitro <= 0)
{
    isNitroActive = false;
}

修改 HandleMotor 方法,在施加力时判断氮气状态:

float currentMotorForce = motorForce;
if (isNitroActive && currentNitro > 0)
{
    currentMotorForce *= nitroForceMultiplier;
    currentNitro -= Time.fixedDeltaTime * 20f; // 每秒消耗20单位氮气
}
Vector3 forwardForce = transform.forward * verticalInput * currentMotorForce;
// ... 后续施加力的代码不变 ...

8.3 制作一个简单的赛道并添加检查点

用Cube和斜坡搭建一个环形赛道。创建空物体作为“检查点”(Checkpoint),为其添加一个带有 Is Trigger 勾选的碰撞体(如Box Collider)。 创建一个 CheckpointManager 脚本管理赛道逻辑,并在 CarController 中检测触发:

void OnTriggerEnter(Collider other)
{
    if (other.CompareTag("Checkpoint"))
    {
        Debug.Log("Passed Checkpoint: " + other.name);
        // 这里可以通知CheckpointManager,更新圈数或计时
    }
}

通过这些扩展,你的项目就从一辆能跑能停的汽车,进化成了一个有UI、有技能、有赛道目标的迷你赛车游戏原型。这整个过程,正是Unity游戏开发从核心机制到外围系统搭建的典型缩影。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值