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”对象,我们需要为其添加两个核心物理组件:
- Rigidbody :点击Inspector窗口下方的“Add Component”,搜索并添加“Rigidbody”。这是让物体受物理引擎控制的灵魂组件。
- 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
组件中,你会看到我们定义的所有公共变量。这是我们的调试面板。
-
加速感 (
motorForce) :初始值1000可能偏大或偏小。如果按下W键车像火箭一样窜出去,就调小(如500);如果感觉加速绵软无力,就调大(如1500)。理想的感受是:轻按有反应,深踩能持续加速。 -
刹车力度 (
brakeForce) :它需要能与motorForce抗衡。如果刹车时感觉停不下来,就增大brakeForce(如5000)。一个好的基准是:在中等速度下,按住空格键大约1-2秒内能完全刹停。 -
极速 (
maxSpeed) :限制最高速度不仅能防止失控,也是塑造车辆性格的一部分。一辆笨重的卡车可能maxSpeed=15,而跑车可以设为30。
实操心得 :调参时,不要一次性改多个变量。先调
motorForce获得满意的加速,再调brakeForce获得满意的制动,最后用maxSpeed收尾。每次修改后,在Play模式下直接拖动Inspector中的数值滑块,效果是实时生效的,这是Unity编辑器调试的便利之处。
5.2 转向灵敏度与稳定性
转向是手感的核心,也是最容易调“怪”的地方。
-
转向角度 (
steerAngle) :这个值决定了方向盘打满时,车辆角速度的目标值。值越大,转弯越急。对于街机手感,可以设大一些(如45);追求模拟真实,可以设小一些(如20)。 -
转向速度 (
turnSpeed) :它决定了车辆从直行状态达到目标转向角速度的快慢。值越大,转向响应越“跟手”,但也可能越“贼”。通常turnSpeed在50-150之间尝试。 -
质心 (
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。
关键步骤 :
- 确保新模型的轴心正确。导入后,在Project窗口选中模型文件,在Inspector的“Model”标签页下,检查“Bake Axis Conversion”或调整“Rotation”,确保模型的蓝色箭头(前向轴)指向车头。
- 调整模型的Scale和Position,使其与原来的Cube大致重合。
6.2 配置更精确的碰撞体
Cube自带的Box Collider是一个方盒子,套在汽车模型上会非常不准确,导致视觉上车身离墙还很远,物理上却已经撞上了。
我们需要一个 复合碰撞体 。删除汽车模型自带的或旧的Box Collider。然后:
- 选中“PlayerCar”(或你的汽车模型父物体)。
- 点击“Add Component”,搜索并添加“Mesh Collider”。
- 在Mesh Collider组件的“Mesh”属性中,指定为你的汽车模型网格。
- 重要 :勾选“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 车辆原地打转或侧翻
症状 :一按转向键,车就在原地疯狂旋转甚至翻车。 原因 :
- 质心太高或转向力/扭矩过大。
-
可能错误地在
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游戏开发从核心机制到外围系统搭建的典型缩影。

3251

被折叠的 条评论
为什么被折叠?



