ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Unity赛车源码怎么用:从WheelCollider到手感调校的完整拆解

Unity赛车源码怎么用:从WheelCollider到手感调校的完整拆解 简介面向Unity开发者的完整3D赛车游戏源码项目支持Unity 5.5及以上版本覆盖从场景搭建、车辆控制到任务关卡与移动端UI交互的完整框架。游戏内置越南、香港、东京、纽约等多个城市赛道收录10辆超级跑车玩家通过左右倾斜操控赛车躲避障碍并收集金币、氮气提供快速比赛与生涯模式两种模式快速比赛进一步细分为普通、检查站、计时赛共36个任务关卡。资源包共2000个文件以C#逻辑脚本、prefab预制体、材质贴图与Unity场景文件为主体辅以XML配置、安卓安装包和少量Java原生文件压缩后大小约330MB文件分类细致可直接导入工程使用。目前已有298人学习/下载。项目内置广告集成与IAP支付模块便于开发者直接移植到新游戏同时完整UI界面和多样化城市赛道场景无论是学习赛车操控、任务系统还是商业化打包都能找到对应模块非常适合作为毕业设计或商业原型起点。1. 为什么一份Unity赛车源码值得你花时间读从“转圈Demo”到可玩赛车的距离打开一个名为 King Of Racing 3D 的 Unity 赛车项目源码第一次按下 Play 键时大多数人的反应是“就这不就一辆车在环形赛道上来回跑吗”。但当你把场景里的车换成自己的模型、把物理参数随手一改却发现车辆要么原地打转、要么过弯就翻、要么刹车时像踩了块冰这时候才会意识到一份能稳定发售的赛车游戏源码真正值钱的不是 3D 模型和赛道摆件而是那几十个 C# 脚本里围绕 WheelCollider、刚体、相机和粒子特效做的手感调校。这篇文章就从这套源码出发讲清楚它拆开以后有哪些模块、怎么在自己机器上跑通、调参时该动哪里以及哪些坑是一线项目里反复出现的血泪经验。适合手里拿着 Unity 源码但不知道从哪下手的初学者也适合想快速搭一套可复用赛车框架的熟手。2. 拆解 King Of Racing 3D 的项目结构用 C# 脚本看清一套赛车游戏的骨架拿到任何游戏源码第一步不是急着打开场景看效果而是先在 Project 面板里把 Assets 目录过一遍。这套赛车项目的目录安排非常典型基本是“场景、脚本、预置体、美术资源、音频”五大块。搞清楚它们之间的关系后面改任何东西都不会迷路。2.1 先看目录Assets 下的文件夹决定了项目的“性格”我一般会按这个顺序去读一个 Unity 赛车源码的目录先找 Assets/Scenes看里面有几个可用场景这决定了项目是单关卡演示还是完整可循环的玩法闭环再找 Assets/Scripts看 C# 脚本的组织方式是按功能拆分还是全堆在一个叫 GameManager 的文件里最后看 Assets/Prefabs里面有多少个可复用的车辆预制体。King Of Racing 3D 这类项目的标准结构长这样Assets/ Scenes/ 主场景、菜单场景、赛道场景 Scripts/ Car、Camera、UI、GameManager、Track 等子目录 Prefabs/ 赛车、轮胎、检查点、粒子特效的预制体 Models/ 车身、赛道、场景道具的 fbx 模型 Materials/ 车漆、赛道路面、天空盒材质 Textures/ 贴图 Audio/ 引擎音效、碰撞音效、背景音乐这套划分方式的好处是职责清晰车辆相关脚本不会和 UI 脚本互相引用得到处都是预制体里保存好的物理参数也让每次调整不需要重新拖场景物体。我拿到源码后会先做一件事把主场景里的赛车层级结构展开看它挂了哪些组件。如果一辆车上同时挂了 Rigidbody、WheelCollider 列表、AudioSource、ParticleSystem那这辆车的核心逻辑大概率就在一个大型的 CarController 类里这也是多数赛车 Demo 的常见写法。2.2 CarControllerWheelCollider 才是赛车手感的物理基础赛车游戏和普通第三人称动作游戏最大的区别在于车辆的移动不能靠直接修改 Transform 位置来实现。如果你在一个赛车源码里看到代码里写transform.Translate()那这辆车基本告别真实物理了。正规做法是让 Rigidbody 承担车身受力让四个轮子各自挂一个 WheelCollider由引擎扭矩和刹车力矩驱动整体运动。这个项目里最核心的脚本通常叫 CarController.cs它的骨架长这样using UnityEngine; public class CarController : MonoBehaviour { [Header(轮子配置)] public WheelCollider frontLeft; public WheelCollider frontRight; public WheelCollider rearLeft; public WheelCollider rearRight; [Header(动力参数)] public float motorTorque 1500f; // 电机扭矩 public float brakeTorque 3000f; // 刹车扭矩 public float maxSteerAngle 30f; // 最大转向角 private Rigidbody rb; void Start() { rb GetComponentRigidbody(); // 赛车类刚体必须压低重心否则过弯必翻 rb.centerOfMass new Vector3(0f, -0.5f, 0f); } void FixedUpdate() { float steer Input.GetAxis(Horizontal); float throttle Input.GetAxis(Vertical); // 前轮转向四轮驱动或后轮驱动按项目设定 frontLeft.steerAngle steer * maxSteerAngle; frontRight.steerAngle steer * maxSteerAngle; // 电机扭矩施加到驱动轮 rearLeft.motorTorque throttle * motorTorque; rearRight.motorTorque throttle * motorTorque; // 按下空格或倒车时刹车 if (Input.GetKey(KeyCode.Space)) { ApplyBrakes(brakeTorque); } } void ApplyBrakes(float torque) { frontLeft.brakeTorque torque; frontRight.brakeTorque torque; rearLeft.brakeTorque torque; rearRight.brakeTorque torque; } }这段脚本的逻辑说明FixedUpdate 是物理帧回调所有和 WheelCollider 相关的输入都必须放在这里因为 WheelCollider 的受力计算依赖固定时间步长刚体中心下沉是关键如果不改 centerOfMass所有赛车过弯都会像砖头一样侧翻。参数方面motorTorque 决定了直线加速的猛不猛maxSteerAngle 决定低速弯和高速弯的响应差异brakeTorque 调太大时轻点空格就会抱死甩尾这是后面调手感时最常动的三个值。2.3 输入层从旧输入管理器到新输入系统的兼容问题很多 Unity 老项目用的是默认的 Input Manager代码里写Input.GetAxis(Horizontal)拿方向键输入。King Of Racing 3D 这种带 3D 赛车玩法、又强调移植到移动端的项目往往会在旧输入系统和新 Input System 之间纠结。如果源码是 2019 或 2020 年附近做的大概率还是旧输入系统。这里有一个实用判断方法打开 Project Settings → Player → Active Input Handling如果显示的是 Input Manager (Old)那所有Input.GetAxis都能直接跑如果项目升级到了新版 Unity 并且切成了 Input System Package (New)那你需要在Project Settings → Player → Active Input Handling里选择 Both否则项目会在运行时直接抛InvalidOperationException方向盘完全不动。我自己拿到源码后习惯先确认这一点因为“车不动”这个问题有一半原因不是脚本逻辑而是输入系统切换导致的事件分发失效。另外移动端源码里常会看到Input.touchCount和虚拟摇杆脚本这类代码在 PC 上测试时要额外接一套模拟输入否则你点鼠标是没有反应的。2.4 代码里的“黑匣子”GameManager、检查点与圈速计时除开车辆本身赛车游戏源码里最容易被忽略的是 GameManager 和 TrackManager。它们负责从车辆脚本手里拿数据、判断圈数、计时、显示 UI。你看一辆车跑起来很简单但“过了一个检查点、再过一个检查点、最后回到起点算一圈”这套逻辑才是从技术 Demo 走向可玩赛车游戏的分水岭。常见实现方式是每个检查点是一个触发器 Collider车辆进入时把当前 CheckpointIndex 加一当车辆以正确顺序经过全部检查点并再次撞到起点线时圈数加一。这里有个关键细节检查点列表的顺序和方向必须正确否则车辆第一次出发就算跑完整圈圈数计数器也直接卡住。using UnityEngine; public class CheckpointManager : MonoBehaviour { public Transform[] checkpoints; // 按顺序排列的检查点 public int totalLaps 3; private int currentCheckpoint 0; private int currentLap 0; void OnTriggerEnter(Collider other) { if (!other.CompareTag(Player)) return; // 应用了“必须按顺序通过”的约束 if (transform checkpoints[currentCheckpoint]) { currentCheckpoint; if (currentCheckpoint checkpoints.Length) { currentCheckpoint 0; currentLap; Debug.Log(完成圈数 currentLap / totalLaps); if (currentLap totalLaps) { Debug.Log(比赛结束); } } } } }这段脚本的注意点每个检查点物体都要单独挂一个同名脚本并且把checkpoints数组统一指向同一个检查点序列对象。常见的错误是检查点顺序反向导致玩家跑完一圈后没有触发完成判定另一个坑是玩家抄近路跳过检查点直接冲向终点线所以完整项目里往往会记录“当前必须到达的下一个检查点”而不是简单地判断“进过任何一个检查点都算数”。3. 把源码跑起来版本选型、场景导入与最小可玩链路有了上一章的结构认知现在把项目真正打开。这一步最大的障碍反而不是代码而是 Unity 编辑器版本和项目管线不匹配。一旦编辑器给出几百条编译错误新手很容易怀疑源码本身有问题其实大多数时候只是版本不对。3.1 用哪个 Unity 版本打开内置管线优先别一上来就升级King Of Racing 3D 这类 3D 赛车项目的源码最稳的打开方式是选择 Unity 2020.3 LTS 或 2021.3 LTS。原因有三第一内置渲染管线Built-in Render Pipeline在这两个版本里还在绝对主流位置项目里默认的 Standard 材质不会出现一片粉色的情况第二C# 脚本 API 兼容性最好WheelCollider、ParticleSystem的 API 没有被高版本打上过时标签第三旧版 Input Manager 在 2020/2021 里开箱即用不用处理 Input System 的迁移。如果手头只有 Unity 2022 或 Unity 6也不是不能打开但要做好几件事先在菜单栏Edit → Project Settings → Player → Active Input Handling设置为 Both遇到材质显示异常把 Standard Shader 换成 URP 对应版本遇到OnCollisionEnter等生命周期方法报错大概率是从 Unity 5 时代升级过来的格式问题重新保存脚本就能触发编译器刷新。3.2 导入后的第一个动作先跑原版场景再谈改代码拿到源码后不要直接拿自己的车模型替换进去。正确顺序是首次打开场景后完完整整地按下 Play 键用默认键位开几圈记录这辆车的原始手感——加速度、极速、过弯姿态、漂移幅度。这一步叫“基线测试”后续你调的每一个参数都是在和这个基线作对比。跑原版场景时要注意观察三类对象车辆轮子的转速和转向是否正常、相机跟随是否平滑、粒子特效出现在哪些时机。如果车辆在某条默认赛道上开得非常顺说明物理参数和赛道摩擦配置是配套的当你换了赛道后手感突变问题往往不出在车而出在赛道路面的 Physic Material 摩擦系数。3.3 最小可玩链路新建一个空场景把一辆车从零拼出来如果原版场景太复杂或者你想把车辆脚本套用到自己的测试赛道上最建议的做法是新建一个空场景从 Prefabs 里拖出一辆车再手动搭建一个最简单的平面赛道跑通“起步—加速—转弯—刹车”的最小闭环。using UnityEngine; // 最小测试脚本挂到任意空物体上用键盘控制一辆带 Rigidbody 的简易小车 public class MinimalDriver : MonoBehaviour { public WheelCollider frontLeft; public WheelCollider frontRight; public WheelCollider rearLeft; public WheelCollider rearRight; public float moveSpeed 500f; public float turnSpeed 25f; void FixedUpdate() { float v Input.GetAxis(Vertical); // 前进后退 float h Input.GetAxis(Horizontal); // 左右转向 frontLeft.steerAngle h * turnSpeed; frontRight.steerAngle h * turnSpeed; rearLeft.motorTorque v * moveSpeed; rearRight.motorTorque v * moveSpeed; } }逻辑说明这是一个不依赖 CarController 完整逻辑的“冒烟测试”脚本专门用来验证车辆预制体的物理配置是否有效。把这个脚本挂到一辆车的父物体上并且手动把四个 WheelCollider 拖进对应槽位播放时车就能动。参数说明moveSpeed是电机扭矩受车辆质量影响极大。一辆质量为 1000kg 的车500 扭矩起步会比较肉适合用来测试物理配置如果车重达到 2000kg需要将扭矩提到 1000 以上才能达到“赛车”的体感。而turnSpeed对应的是转向角角度太大高速过弯时后轮会抓不住地。3.4 场景搭建时的三个隐藏依赖灯光、相机和赛车刚体很多新手把车从 Prefabs 拖进空场景后发现画面里全是灰色或黑色第一反应是模型坏了。其实是因为新场景里没有正确配置灯光和相机。Unity 默认的新场景带了一个方向和一支相机但如果你从别的项目拷贝过设置这些基础对象可能被删掉了。最少需要的三样东西一盏 Directional Light 用来照亮场景一辆有 Rigidbody 的车一架使用 SmoothFollow 脚本的相机。其中相机脚本是赛车项目最容易翻车的地方——如果相机跟随脚本直接把位置锁死在车身坐标那车辆漂移、侧滑时画面会像钉住了一样僵硬。下一章专门讲相机的调校。另外还有个容易忽略的地方车辆刚体的质量分布。默认 Rigidbody 的中心在模型几何中心而赛车车身低矮几何中心偏高过弯必然侧倾严重。上一章 CarController 里那一行rb.centerOfMass new Vector3(0f, -0.5f, 0f)就是对这个问题的修正。你在自己的场景里拖车出来时一定要确认这行代码被执行了或者手动在 Rigidbody 的 Center Of Mass 属性里填一个偏下的值。4. 驾驶手感调优把物理参数、相机与粒子特效从“能开”变成“好开”跑通源码只是第一步。接下来这段是最吃经验的部分同样是四个轮子、一辆车、一条直线赛道为什么别人的演示视频里车贴地飞行而自己跑起来像在冰面上旋转答案全在 WheelCollider 的参数组合和相机响应曲线里。这部分的调参过程非常像玄学但只要把握住受力分析大部分问题都能收敛。4.1 必调参数表WheelCollider 的五个关键值WheelCollider 不是简单给车轮一个碰撞体它内部模拟了悬架、轮胎弹性和地面摩擦。只改其中一个参数往往没有效果必须组合调整。我把最常用的参数整理成一张表方便对照项目情况来改参数作用手感影响刹车/起步时的变化Spring悬架弹簧车身弹跳刚度数值低则车身软过弯侧倾大起步抬头、刹车点头明显Damper悬架阻尼抑制弹簧余震太高则车身硬太低则车身颠簸阻尼不足时刹停后车身上下晃Forward Friction前向摩擦轮胎加速和刹车的抓地力数值低则起步打滑、刹车距离长踩满油门时轮胎空转拉烟Sideways Friction侧向摩擦抵抗横向滑动数值低则过弯甩尾数值高则推头高速弯道中容易测出差别Motor Torque电机扭矩驱动轮上的发力决定加速推背感和极速配合车轮转速决定是否烧胎调参时的推荐顺序先把 Spring 和 Damper 调到车身不上下晃动再调 forwardFriction 让直线加速不打滑最后调 sidewaysFriction 控制过弯姿态。这三个过程都会直接影响“这辆车是不是赛车”的体感。4.2 车轮摩擦曲线为什么默认值总是让车“太滑”WheelCollider 里的摩擦不是用一个数字表达的而是一张“速度—摩擦系数”曲线。Unity 默认的前向摩擦曲线在速度极低时摩擦系数几乎为 0这就解释了为什么很多源码刚打开时低速起步车会打滑、掉头时像抹了黄油。调整方式有两种一种是把曲线上的关键点全部拉平让低速和高速的摩擦系数趋于一致另一种是使用WheelFrictionCurve直接写脚本设定适合想精确控制手感、不需要在 Inspector 里反复拖曲线的开发者。using UnityEngine; public class TireFrictionSetup : MonoBehaviour { public WheelCollider targetWheel; void Awake() { WheelFrictionCurve fFriction targetWheel.forwardFriction; fFriction.extremumSlip 0.4f; // 达到最大抓地力时的滑移率 fFriction.extremumValue 1.2f; // 最大抓地力系数 fFriction.asymptoteSlip 0.8f; // 抓地力开始衰减的滑移率 fFriction.asymptoteValue 0.8f; // 衰减后的抓地力 fFriction.stiffness 1.0f; // 整体刚度倍率 targetWheel.forwardFriction fFriction; WheelFrictionCurve sFriction targetWheel.sidewaysFriction; sFriction.extremumSlip 0.2f; sFriction.extremumValue 1.0f; sFriction.asymptoteSlip 0.5f; sFriction.asymptoteValue 0.6f; sFriction.stiffness 1.0f; targetWheel.sidewaysFriction sFriction; } }代码说明extremumSlip是轮胎开始发挥最大抓地力的滑移率数值越小说明轮胎越“早”进入抓地状态extremumValue直接决定抓地力峰值asymptoteSlip和asymptoteValue则是超过极限后衰减的数据。stiffness是整体倍率乘以所有曲线值适合做“雨地打滑”等全局变种手感。如果要做一个偏娱乐向的漂移赛车可以把 Sideways Friction 的 extremumValue 降到 0.6 左右让车在高速急弯时后轮主动侧滑如果要做拟真向这个值要提高到 1.5 上下否则赛道竞技玩家会觉得车“抓不住地”。4.3 相机跟随从“钉死”到“有呼吸感”的平滑跟随脚本相机是赛车手感里最容易被低估的部分。一辆车物理调得再好如果相机死板地锁在车尾玩家会感觉整个画面像被固定住速度感完全消失。常见做法是使用带阻尼的平滑跟随让相机有“追”车的感觉而不是“焊”在车上。using UnityEngine; public class SmoothFollowCamera : MonoBehaviour { public Transform target; // 要跟随的赛车 public float distance 8f; // 相机距离车辆的水平距离 public float height 3f; // 相机高度 public float smoothTime 0.3f; // 平滑时间越大越钝 private Vector3 velocity Vector3.zero; void LateUpdate() { Vector3 targetPos target.position - target.forward * distance Vector3.up * height; transform.position Vector3.SmoothDamp(transform.position, targetPos, ref velocity, smoothTime); transform.LookAt(target.position Vector3.up * 1.2f); } }逻辑说明SmoothDamp相当于一个带阻尼的弹簧smoothTime越大相机越追不上车画面在急加速时会有短暂但舒服的滞后感。LookAt看向略高于车顶的位置避免画面中心总是对着车尾保险杠。一个非常常见的翻车现象是相机穿模车辆漂移后车身横过来相机镜头直接穿进车门。解决办法是在相机和车辆之间打一条射线当中间有障碍物时把相机拉近到障碍物之前。这个技巧在复杂赛道场景里几乎是必需品。4.4 粒子特效与音效把“炫酷”做出来还不掉帧King Of Racing 3D 之所以看起来“炫酷”很大程度靠的是氮气加速时的尾焰粒子、漂移时的轮胎烟雾、碰撞时的火花。这些 ParticleSystem 脚本看起来简单实际运行时有一个隐患如果在每次碰撞或漂移时都动态Instantiate一套粒子系统而粒子系统的Stop没有配合Clear它会持续占用资源直到粒子完全衰减这在移动端尤其明显跑完一局后会感到帧率断崖式下跌。正确做法是对象池启动时预先创建好若干套粒子系统并藏在一个空物体下需要触发时就近取一套播放播放完毕后再归还池子。另外要给主粒子组件设置合适的maxParticles上限尾焰控制在 50 以内烟雾控制在 100 以内火花控制在 30 以内整体特效数目的浮动范围会稳定很多这也是我在粒子特效上最想强调的一点项目的卡顿往往不是模型精度而是粒子系统数量失控。5. 赛车源码的五个常见坑现象、原因与处理办法这部分记录的是我在导入与调优过程中反复踩过的坑全部按“现象 → 原因 → 解决”的结构来写。每一条背后都对应一个真实开发场景新人遇到时可以直接对号入座。5.1 场景打开后一片黑但编辑器能选中物体现象按下 Play 后画面全黑场景里的车和地图看不到但 Hierarchy 面板物体都在。原因最常见的是渲染管线和光照不匹配。这个项目如果使用 Built-in 渲染管线制作在新版本中被打包为 URP 项目时Standard Shader 会无法显示表现为模型呈暗红色或全黑或者烘焙贴图丢失。另一个原因是场景里没有 Directional Light新手从默认场景创建新场景时误删了灯光。解决先把 Light 组件补上确认 Watch 窗口里能看到光照强度再把项目的渲染管线切换为 Built-in。具体操作是Edit → Graphics → Scriptable Render Pipeline Settings置为 None。如果不想切换管线就给所有材质重新指定 URP/Lit Shader。5.2 方向盘不响应但脚本没有报错现象Play 后按下方向键轮子不转向车辆也不动Console 面板干净得让人怀疑人生。原因输入系统被切换成了新的 Input System而代码用的还是Input.GetAxis。在新输入系统激活状态下旧的Input静态方法默认不工作不会报错只是所有输入值为 0。解决打开Project Settings → Player → Active Input Handling改成 Input Manager (Old) 或 Both然后重启编辑器。这一步是对旧源码兼容性影响最大的一处几乎每台机器第一次打开时都要做。5.3 车辆过弯直接翻车或一直原地抖个不停现象直线行驶正常一到弯道就侧翻停车状态下车身不停上下抖动像在“抽搐”。原因前者通常是centerOfMass没有压低或者Spring调太高、刚体质量没设置导致抗侧倾能力不足后者是悬架弹簧和阻尼比例不匹配Spring 过大而 Damper 过小车体像一个没有阻尼的弹簧在那里来回振荡。解决先确认 Rigidbody 的centerOfMass低于模型底部 0.30.5 个单位再把 Spring 和 Damper 按“弹簧数值约为阻尼的 23 倍”的区间调整。如果抖动只发生在静止时还需要检查四个轮子的 WheelCollider 是否都在同一平面高度地面动态摩擦是否突然变化。5.4 导入后报数百条编译错误WheelCollider方法找不到现象打开工程后 Console 里刷出大量编译错误错误信息集中在WheelCollider、ParticleSystem相关 API 上代码里还有new WheelCollider()之类的写法。原因源码可能是用很老的 Unity 5.x 或 2017 写的API 已经变了也可能是项目文件里的.meta文件缺失或损坏导致脚本和预制体关联错乱。解决先把编译错误逐条分类。如果是 API 迁移问题用Obsolete建议替换成新写法如果是.meta丢失导致的 GUID 冲突常见做法是删掉 Assets 目录下所有.meta文件让 Unity 重新生成。这个操作会丢失场景里的预制体引用关系强烈建议在改之前先备份整个项目。5.5 换电脑或换系统后贴图和材质全部丢失现象项目在 A 电脑打开正常拷贝到 B 电脑后模型变成灰色或粉色贴图全没了。原因美术资源的引用是相对路径 GUID 的。直接拷贝项目文件夹一般没问题但如果你只拷贝了部分文件夹比如只拷贝了 Assets 而忘了 Packages 和 ProjectSettingsUnity 重新生成后会因找不到 GUID 而丢失所有引用。解决拷贝项目时整个根目录一起拷不要只拷 Assets。如果是用 Git 管理检查.gitignore是否把Library/排除在外首次打开时会重新构建 Library耗时较长但不会丢资源。遇到材质丢失时也可以直接把材质和贴图重新拖拽到模型的对应槽位上比重做整个场景更快。6. 进阶把源码改装成自己的作品——AI 对手、圈速记录与移动端收尾这一章做三件源码里最常见也最费精力的扩展给赛道加 AI 赛车让 AI 有基本的避让逻辑做圈速排名用 PlayerPrefs 保存成绩最后把粒子上限、画质档位和 Draw Call 调到一个移动端可运行的水平。6.1 给赛道加一个能跑的 AI 对手多数赛车源码里只有一个玩家车辆如果想加 AI 对手最稳妥的不是给车辆写一套复杂的寻路系统而是让 AI 车走“检查点列表 前方射线检测”的简化方案。让 AI 沿着检查点数组里的位置逐点移动检测到前方有障碍物时自动减速。using UnityEngine; public class AIDriver : MonoBehaviour { public Transform[] waypoints; // 复用人赛道上的检查点 public WheelCollider frontLeft; public WheelCollider frontRight; public WheelCollider rearLeft; public WheelCollider rearRight; public float maxSpeed 80f; private int next 0; private Rigidbody rb; void Start() { rb GetComponentRigidbody(); } void FixedUpdate() { Transform target waypoints[next]; Vector3 dir target.position - transform.position; dir.y 0; // 根据目标点偏移计算转向 float steer Vector3.SignedAngle(transform.forward, dir, Vector3.up); steer Mathf.Clamp(steer, -30f, 30f) / 30f; frontLeft.steerAngle steer * 30f; frontRight.steerAngle steer * 30f; // 前进时限制速度避免AI全程满油门冲出赛道 float forwardSpeed Vector3.Dot(rb.linearVelocity, transform.forward); float throttle forwardSpeed maxSpeed ? 1f : 0f; rearLeft.motorTorque throttle * 800f; rearRight.motorTorque throttle * 800f; // 到达检查点附近则切换下一个 if (Vector3.Distance(transform.position, target.position) 8f) { next (next 1) % waypoints.Length; } } }这个脚本说明AI 不是用物理寻路而是把赛道检查点当作一条“轨道路径”车辆在运行中不断把车头转向下一个目标点。Vector3.SignedAngle得到带符号的转向角正值右转、负值左转。forwardSpeed是车辆在前进方向上的速度分量防止 AI 在弯道内全油门冲出赛道。这套方案在不超过 5 辆 AI 车的场景里表现不错虽然不会攻击玩家但能形成基本的比赛压力。6.2 圈速记录用 PlayerPrefs 保存最佳成绩赛车项目非常依赖圈速。圈速数据的写入时机是车辆经过终点线完成一整圈时把总时间记录下来。这里有一个很容易踩的坑如果直接用Time.time计时在场景暂停、切换或时间缩放时数据会被污染。所以进阶做法是用一个独立计时器不受Time.timeScale影响。using UnityEngine; public class LapTimer : MonoBehaviour { public float currentLapTime 0f; public float bestLapTime 0f; void FixedUpdate() { currentLapTime Time.fixedDeltaTime; // 不受 timeScale 影响 } void OnTriggerEnter(Collider other) { if (other.CompareTag(Player)) { // 触发终点线时把当前圈速写入存档 if (bestLapTime 0 || currentLapTime bestLapTime) { bestLapTime currentLapTime; PlayerPrefs.SetFloat(BestLap, bestLapTime); PlayerPrefs.Save(); } currentLapTime 0f; } } }Time.fixedDeltaTime在物理帧上是恒定的不受Time.timeScale影响适合做比赛计时。而PlayerPrefs只适合存少量数据存圈速这种标量足够如果要做排行榜或者回放数据就要用 JSON 或 SQLite 了。我把bestLapTime和当前对比后写入存档这样重启游戏后仍能看到最快圈速。6.3 移动端收尾把粒子上限、画质和 Draw Call 控制住最后说移动端。King Of Racing 3D 这种项目拿到手机上看效果首先要做的是为移动端准备一套低配预制体。我会把粒子系统的最大粒子数限制在 100 以内关闭碰撞体积关闭所有实时阴影改用烘焙光贴图。画质档位上在菜单中提供一个简单的分级选项避免在低端机上直接进最高画质帧率崩成幻灯片。车辆和场景的材质用尽量少的材质球数同一个车身贴图尽量共用。Draw Call 是最容易忽视的优化点一辆车轮子模型如果是独立材质一个轮子一个 Draw Call四个轮子加车漆加玻璃就是 6 个以上的 Draw Call这么一辆车在全屏粒子下会让低端机非常吃力。常见做法是把车漆和玻璃合并到一张贴图上轮毂用共享材质场景道具用Static Batching来合并。6.4 我的收尾习惯先跑 10 分钟热循环验证再提交这是我个人的习惯也是踩过太多次热循环内存泄漏的教训总结。任何项目在提交前我都会开着编辑器跑一个 10 分钟的自动循环测试让车辆沿着赛道自动行驶持续触发碰撞、漂移和氮气观察内存曲线是否持续上涨。如果每次漂移都动态生成粒子系统内存曲线会在 3 分钟内明显上扬那就要回去整理粒子池了。如果你拿到这份源码后只做一件事我建议也是跑一遍热循环测试亲自看着内存曲线在 10 分钟内保持水平再往它的代码里加自己的内容。这套源码最值得参考的价值就是把赛车游戏里最容易失控的物理和特效做成了可改的参数骨架。你不需要把整个项目理解透才能开始用先让车跑起来再用自己的美术和赛道逐步替换慢慢就会摸清每个值背后对应到方向盘上的感受。希望这些从一线项目里磨出来的经验能帮你少走几段弯路把这份源码改造成真正属于你的赛车作品。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进