☰
Unity3D小车驾驶模拟:灯光阴影、车辆控制与碰撞检测
2026/10/9 2:10:47 网站建设 项目流程

简介:面向Unity3D学习者和课程设计人群的小车驾驶模拟系统完整工程,涵盖三维场景搭建、车辆控制、UI交互与光照设置等模块,可作为课设作业或入门练手的直观范例。压缩包内含课程报告Word、演示视频、可运行源文件及项目截图,便于对照学习与二次开发。资源共2000个文件,主要包含C#脚本、场景与预制体(asset/prefab)、模型贴图(fbx/png/mat)、音频(wav)及说明文档(docx/pdf/md)等,整个压缩包约127.23MB。实验基于Directional Light(定向光)实现场景照明,并采用Soft Shadow软阴影提升真实感;场景由简单多边形搭建地面、建筑、道路与红绿灯,从光源设置到场景布局覆盖了Unity基础制作流程,对于理解光照系统、材质贴图和游戏逻辑较有帮助。目前已有1017人学习下载,适合作为Unity课程设计、游戏开发入门及驾驶模拟类项目的参考模板。

1. 基于Unity3D的小车驾驶模拟系统:一个能交差也能扩展的课设骨架

第一次打开这份“基于Unity3D的小车驾驶模拟系统”时,我心里预期是一个几十G的庞大工程,解压之后发现恰恰相反——一个地面、几栋建筑、几条路、几个红绿灯,场景里没有过多模型资产,却把unity3D小车游戏该有的东西都串全了。后来我理解了这类驾驶模拟系统的定位:它不是商业游戏,而是专门为“课程设计”准备的骨架,重点在于把场景搭建、光照阴影、车辆控制、碰撞与视角跟随这条链路理顺,让演示视频和课程报告都有内容可写。无论你是想直接拿它交差,还是想在此基础上改成带计分、多关卡的小车驾驶项目,这个资源包都能当底座。

2. 灯光链路:Directional Light 与 Shadow 的取舍逻辑

2.1 Directional Light 为什么是驾驶场景的默认选项

这个场景里只用了一盏 Directional Light,没有补光也没有环境光探头。对很多第一次做小车驾驶模拟的开发者来说,可能觉得不够“高级”,但方向光的特性决定了它在这种露天场景里是最稳的选择。Directional Light 的定位是无限远光源,它不会因为物体离得近而变亮或变暗,也不会因为摆放位置不同而改变照射角度,真正决定光照方向的只有它的 Rotation 参数。也就是说,你把这盏灯放在 (0, 10, 0) 还是 (100, 100, 100),只要旋转角度一样,画面结果就完全一样。

所以我在调整这类场景灯光时,第一件事不是看位置,而是看旋转角度。常见做法是把灯旋转到类似 (50, -30, 0) 的角度,这个角度能让地面得到一个偏斜的光照方向,建筑能产生侧面阴影,而不是正午那种完全垂直的顶光。顶光最大的问题在于道路和建筑侧面缺乏立体感,整个画面会显得“平”,课程设计视频里看起来也会缺少层次。

Intensity 通常给到 0.8 到 1.2 就够了。如果场景里的物体都是浅色材质,0.8 会比较柔和;如果地面颜色偏深,可以提到 1.2。超过 1.5 之后再调高,画面会泛白,阴影和亮部同时丢失细节,属于这条链路上最普遍的误操作。

2.2 Hard Shadow 还是 Soft Shadow:按性能边界选,不按肉眼喜恶选

Shadow 属性是这个场景的第二个关键节点。Unity 给 Shadow 提供了 Hard Shadow 和 Soft Shadow 两个选项。Hard Shadow 的阴影边缘锐利,像素感强,每个阴影边界都像被刀切过;Soft Shadow 则会在阴影边缘做一层模糊过渡,视觉上更接近真实环境光下的阴影。单看截图,几乎所有人都会觉得 Soft Shadow 更“真实”,这也是这个项目最终选择 Soft Shadow 的原因。

但我得提醒一句:Soft Shadow 的消耗比 Hard Shadow 高不少,尤其是场景里有多栋建筑、多个红绿灯同时投射阴影时。如果目标平台是 PC 或演示用笔记本,Soft Shadow 没有问题;如果你后面把场景搬到移动端,或者在地图上加了大量小物件,第一件要牺牲的就是 Soft Shadow 分辨率,而不是光源数量。课程演示场景里物体数量在几十个以内,Soft Shadow 是全场景统一阴影最合适的档位。

另外要注意 Shadow Type 下面的 Strength 和 Resolution。Strength 默认是 1,阴影非常黑,配合 Unity 自带的 Ambient Color(环境色)略微提高可以缓解。这个项目里没有刻意为每个物体做过多的阴影设置,所以整条链路其实是“一盏方向光 + 一套全局阴影 + 阴影距离调大”的极简组合。

2.3 灯光参数速查表:直接抄也能用的档位

为了方便按这个项目搭场景,我把常见参数整理成一张速查表。这里的数值不是死规范,而是我在复现时验证过的起步档位。

参数推荐值说明
Light TypeDirectional场景光源唯一选择,位置不影响效果
Rotation(50, -30, 0)决定侧向阴影,避免纯顶光
Intensity0.8 ~ 1.2超过1.5画面泛白
Shadow TypeSoft Shadows演示效果优先
Shadow Strength0.7 ~ 1.01.0最黑,配合环境色微降
Shadow Distance80 ~ 120必须大于场景最长对角线的一半

Shadow Distance 经常被忽略。它决定“多远的物体还在渲染阴影”,如果场景地面铺到了 60 米宽,Shadow Distance 只给 20 米,你会看到远处建筑完全没影子,像悬浮在场景里。这个参数在 Lighting 窗口(Window > Rendering > Lighting)的 Scene 选项卡下,通常默认 50 左右,1×1 的小场景没问题,拉大地面后必须同步调大。

为了配合 Soft Shadow,我一般会把 Window > Rendering > Lighting Environment 里的 Ambient Mode 从 Skybox 切成 Color,颜色取深灰蓝 (45, 52, 60) 左右。这样做的原因是场景里的天空盒如果不匹配,方向光打出来的阴影会偏蓝或偏脏,而单独切环境色能让阴影里带一抹冷色,视觉更干净。操作路径在不同 Unity 版本里略有差异,但关键词是 Environment Lighting。

2.4 灯光验收的三个自检点:影子方向、亮度和过曝

灯光搭完先别急着做车,先在 Game 视图切到 16:9,从俯视 45 度角扫一遍场景。我每次必看三个点:第一,地面亮度是否均匀,如果一块亮一块暗,说明场景里混进了其它光源;第二,所有建筑的阴影方向是否一致,方向光只有一个旋转角,理论上全部朝一个方向,出现多方向阴影就是有额外光源在捣乱;第三,路面有没有过曝,过曝时道路看起来是一块纯白地面,需要把 Intensity 降 0.1 再试。这三个自检点做完,灯光链路基本就稳了。方向光阴影距离这种看不见摸不着的参数,最容易成为最先踩进去的玄学问题。

3. 场景搭建:地面、道路、红绿灯与建筑的组装顺序

3.1 先铺地面再摆模型:Plane 尺寸与坐标归零

搭建场景的第一原则是先地面后建筑。项目里的地面是一张巨大的 Plane,不是 Terrain。Plane 的好处在于网格简单、像素对齐、生成阴影稳定,适合城市道路这类几何风格的场景。Terrain 虽然能做地形起伏,但小车驾驶模拟里大部分路面是平的,用 Terrain 反而引入大量不必要的刷地形工作。

Plane 默认尺寸是 10×10 米,在 Scale 里直接改成 X=8、Z=8 可以达到 80×80 米的有效地面。注意此时 Plane 的 Inspector 中 Mesh Collider 要保留,它是小车不穿透地面的第一道屏障。常见做法是单独建一个空物体命名为 Ground,挂 Mesh Collider,再让平面的 Renderer 保持默认,这样即便后续要对地面做特殊碰撞区域,可以不用重新建平面。

地面坐标尽量归零,Position 的 Y 保持 0。这样做的原因是后续所有建筑、红绿灯都要按某个基准面摆放,如果地面在 Y=0,建筑基座高度直接取偶数值即可,排除一大堆坐标计算误差。坐标归零这个习惯,能直接减少后面“物体悬在半空”的返工次数。

3.2 从 Cube 到建筑群:道路宽度与碰撞几何的匹配

建筑不需要复杂模型,所有立方体基本都由 Cube 拉伸而成。窗和门通过材质贴图解决,而不是用额外模型。一个常见结构是:

  • 建筑主体:Cube,Scale 设为 6, 8, 6;
  • 房顶:Cube 压扁,Scale 设为 6.2, 0.4, 6.2,比主体大一圈,防止边缘缝透光;
  • 如果需要阳台,可以再叠一层小 Cube,但注意数量控制,否则阴影消耗会上去。

道路的做法通常是一条横向 Cube。路面宽 4 米,长度跟随场景跨度,Scale 设为 40, 0.2, 4,颜色用深灰材质。把路面铺到建筑之间时,我习惯先按建筑群的网格位置把路网摆出来,再回过头去调建筑位置,而不是在建筑上死磕坐标。

红绿灯是多边形素材里一个典型组合:一个圆柱(Cylinder)做灯柱,两个 Cube 一横一竖做横臂和灯箱底座,三个小球(Sphere)分别代表红、黄、绿。三个球体用三种自发光材质,或者用标准材质加 Emission 通道,都可以。这种细节很值得抄:用 Unlit 材质可以避免灯光把灯体颜色带偏,画面更干净。

因为灯光是方向光,灯箱上的小球在光照下会产生自己的阴影,如果想让车在红灯前有停车判断,还需要在小球模型内部或旁边挂一个碰撞盒子。碰撞盒用 Box Collider,Size 填小球直径,方便代码层判断撞到了哪个灯。

3.3 空物体分组:让 Hierarchy 不再变成黑匣子

搭建过程里一定要开启空物体分组。我观察这个项目场景时发现,它的 Hierarchy 层级很干净,所有建筑、道路、红绿灯都有父物体。这一点在课程设计中经常被低估:如果不分组,后面挂脚本、调材质、统一做阴影排除时会非常痛苦。

建议这样分组:

Scene_Root Ground Buildings Building_01 Building_02 Roads Road_01 Road_02 TrafficLights TrafficLight_01 RedSphere YellowSphere GreenSphere Post BoxCollider Props Trees Streetlights

每新增一个物体就拖进对应层级,而不是散落在根目录。这样做最大的好处是:当你要删除一整片区域时,只需要删除对应父物体,不需要逐个选中;当你要给所有建筑同时换材质时,也只需要展开父物体批量改。课程设计做到后期,Hierarchy 就是你的记忆地图,分组越清晰,改东西越快。

3.4 贴图与材质:避免 UV 拉伸的几个尺寸习惯

多边形素材的贴图是最后一道工序。很多人搭完模型后随手把贴图拖到材质球上,结果墙面的窗户被拉成一道斜线。这里的关键是理解 Unity 的 Cube UV 映射:Cube 的每个面默认映射整张贴图,如果贴图本身比例不合适,透视会变形。

经验做法是:墙体贴图用 2:1 或 1:1 的画布比例,路面贴图用长条形,比如 512×128,并平铺成 Tiling X=2、Y=1。常见做法是在材质球上只调 Tiling,不调 Offset,这样不仅避免拉伸,也让道路中线之类的标记能重复出现。对于灯柱这种细节圆柱,干脆用纯色材质,不为了一点点细节引入一张会被大幅拉伸的纹理。

地面的大平面最容易翻车。一张 1024×1024 的沥青纹理平铺到一个 80×80 米平面上,纹理密度会低到看不出细节,这时把 Tiling 从 1,1 调到 4,4 或 8,8 就可以获得更密的路面颗粒感,而不需要换更大的贴图。简单说,模型越大的物体,Tiling 越大,这是多边形场景贴图最实用的经验。

4. 小车驾驶逻辑:车辆控制脚本、碰撞检测与摄像机跟随参数

4.1 车辆控制脚本:WASD 输入、速度上限与转向节流

场景是骨架,车是灵魂。这个项目里的小车控制应该是最常见的 Kit 思路:键盘 WASD 控制前后左右。我也看到过很多课程设计把车写成 Transform.Translate 直接移动,那会使车毫无物理感。更稳妥的方案是给车挂 Rigidbody,再配合一个 C# 脚本去控制速度。

下面是复现场景时我整理的一份最小可跑脚本(放在车体根物体上):

using UnityEngine; public class PlayerCarController : MonoBehaviour { [Header("速度参数")] public float maxForwardSpeed = 25f; // 前进速度上限(米/秒) public float maxReverseSpeed = 10f; // 倒车速度上限 public float acceleration = 12f; // 加速度 public float brakeDeceleration = 20f; // 刹车减速度 public float turnSpeed = 90f; // 转向角速度(度/秒) private Rigidbody rb; private float currentSpeed = 0f; void Start() { rb = GetComponent<Rigidbody>(); // 锁住 X/Z 旋转,防止碰撞后车辆翻滚失控 rb.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ; } void Update() { float horizontal = Input.GetAxis("Horizontal"); // 转向只在车有一定速度时生效,否则原地打转 if (Mathf.Abs(currentSpeed) > 0.2f) { transform.Rotate(0f, horizontal * turnSpeed * Time.deltaTime, 0f); } } void FixedUpdate() { float vertical = Input.GetAxis("Vertical"); float targetSpeed = vertical > 0f ? vertical * maxForwardSpeed : vertical * maxReverseSpeed; // 用 Lerp 逼近目标速度,产生加速度感 currentSpeed = Mathf.Lerp(currentSpeed, targetSpeed, acceleration * Time.fixedDeltaTime); // 注意:Unity 6 以后属性名改为 rb.linearVelocity,旧版本用 rb.velocity Vector3 velocity = transform.forward * currentSpeed; velocity.y = rb.velocity.y; // 保留刚体自身的垂直速度(重力) rb.velocity = velocity; } }

这个脚本的逻辑分三层:Update 里读取输入并处理转向,FixedUpdate 里计算物理速度,Start 里锁旋转。车辆不会因为撞到路边而乱翻滚,也不会因为按一下 W 就瞬间飞到高速——速度变化由 Lerp 控制,加速度的参数决定手感。turnSpeed 的单位是“度/秒”,90 意味着按住方向键一秒转 90 度,车在路口转弯时基本只用按 0.5 秒左右。

如果你发现车在转弯时身体发飘,第一反应不要加力,而是检查 Rigidbody 的 Drag 和 Angular Drag。Drag 默认是 0,我一般会给到 0.05 到 0.1;Angular Drag 保持默认 0.05,不然转弯后车头会持续旋转。质量保持默认 1 即可,这台车没有载重概念。

4.2 碰撞检测:Box Collider 与 OnCollisionEnter 回调

脚本控制的是速度,真正阻挡车辆的还是碰撞体。车体本身至少要有两个 Collider:一个 Box Collider 包住车身,另一个 Capsule Collider 或 Sphere Collider 做保险杠,或者直接用一个 Box Collider 把车身包住。我一般会把 Box Collider 的 Center 设在车壳模型 Y=1 左右,Size 略小于模型边缘,避免模型已经碰墙而碰撞盒还没接触。

路面和建筑也应该挂 Mesh Collider 或 Box Collider。Plane 挂 Mesh Collider 是最省事的方式,但如果地面很大会增加网格碰撞计算,直接用一个 Box Collider 把地面包住也可以。建筑如果都是 Cube 拉伸,直接保留 Box Collider 自动生成,碰撞形状天然是准确的长方体。

碰撞反馈在脚本里通过 OnCollisionEnter 实现:

void OnCollisionEnter(Collision collision) { if (collision.gameObject.CompareTag("Building")) { // 撞上建筑时立即把水平速度清零,车停下来 currentSpeed = 0f; rb.velocity = Vector3.zero; // Unity 6 中可换成 rb.linearVelocity } }

tag 是 Unity 里的字符串标签,需要在 Inspector 里给所有建筑父物体下的根物体打上 Building 标签。注意 OnCollisionEnter 只会响应挂在 Rigidbody 的物体与 Collider 之间的碰撞,如果地面只挂了 Collider 而车没有 Rigidbody,这个回调不会触发。这也是最常踩的坑之一:脚本明明写好了,车还是穿模,原因就是车少了 Rigidbody 或者碰撞体缺失。

很多课程设计会额外给每个红灯模型加一个 Box Collider,配合 tag 判断“红灯区”。做法是给灯的碰撞物体打上 RedLight 标签,在 OnCollisionEnter 里判断 tag 后减速停车。这种写法简单,比去解析红绿灯状态机更适合短周期课设。

4.3 摄像机跟随:LateUpdate 与平滑插值

最后一块拼图是摄像机。场景里默认的 Main Camera 如果放在固定机位,无法跟随小车移动。比较标准的做法是给摄像机挂一个跟随脚本,用 LateUpdate 做位置更新。LateUpdate 的时序在 Update 之后、渲染之前,适合所有跟车视角,因为车辆先动,摄像机再追,就不会出现画面滞后一帧的感觉。

using UnityEngine; public class CameraFollow : MonoBehaviour { public Transform target; // 小车根物体 public Vector3 offset = new Vector3(0f, 6f, -7f); // 相对小车的偏移 public float smoothSpeed = 5f; // 越大约追得越快 void LateUpdate() { if (target == null) return; Vector3 desiredPos = target.position + target.TransformDirection(offset); Vector3 smoothedPos = Vector3.Lerp(transform.position, desiredPos, smoothSpeed * Time.deltaTime); transform.position = smoothedPos; transform.LookAt(target.position + Vector3.up * 1.5f); } }

CameraFollow 的核心是把 offset 相对目标物体的朝向做一次 TransformDirection,车头转时,摄像机也会跟着转到车后。smoothSpeed 控制追尾滞后:5 左右是比较舒适的跟车感,给 10 则摄像机几乎贴在车尾,转急弯时画面会晃;给 2 则像行车记录仪,滞后明显,但对新手友好。LookAt 看向点加了一个 Vector3.up * 1.5,让镜头盯着车身上半部而不是车底,视野里会带出更多路面,演示视频里效果更好。

5. 避坑记录:灯光阴影、碰撞体、贴图拉伸与坐标漂移的五个现场

5.1 阴影突然整个消失

现象:场景搭完,建筑有模型有贴图,但地板上完全看不到影子,好像所有物体都悬浮着。

原因:最常见的是 Shadow Distance 值太小。Unity 的阴影裁剪距离默认在 50 左右,如果地面 80×80,镜头拉高后建筑距离超出阴影裁剪范围,影子就没了。另一种可能是在 Lighting 面板里误关了阴影。

解决:打开 Directional Light 面板,把 Shadow Type 从 No Shadow 改回 Soft Shadows,再打开 Lighting 窗口(Window > Rendering > Lighting)或 Project Settings > Quality 里的 Shadow Distance 调到 100 或更大。如果还不行,检查 Quality Settings(Edit > Project Settings > Quality)里的 Shadows 是否被设置成 Disable。课程设计在两个地方都可能翻车,排查顺序是先 Light、后 Quality。

5.2 小车起步就掉穿地面

现象:点击 Play 后小车直接从路面掉下去,悬在空中或落到地面以下。

原因:车有 Rigidbody,但地面没有 Collider。Plane 的默认 Inspector 里其实自带 Mesh Collider,可一旦你在场景里重新建了一个 Cube 拉伸当马路,它默认是没有碰撞体的,只是视觉地面。小车一启动,重力把它往下拽。

解决:给所有路面和地板统一补碰撞体。最省事的方法是在地面物体上添加 Mesh Collider(把 Mesh 选成 Plane 或 Cube 的网格),或者像前面说的加一个 Box Collider 当碰撞层。记住一条规则:凡是你希望车能开上去的物体,至少有一个 Collider。

5.3 贴到建筑上的图拉伸成花脸

现象:墙体正面贴图看着正常,侧面却像被撕裂了一样,窗框拉成斜线,分辨率特别低。

原因:Cube 的 UV 映射是六面展开,Scale 被拉长或压扁后,贴图没有按新比例平铺。尤其是把一栋 6×8×6 的 Cube 拉伸成 12×16×2 时,画面比例完全变了。

解决:优先用标准材质控制 Tiling,不换贴图。选中材质球,把 Tiling 从 1,1 调到 2,1 或 1,2,让纹理按墙面尺寸重复。如果贴图本身分辨率只有 256×256,直接换 512 或 1024 的版本,比在画图软件里硬拉更省时间。重要提醒:如果要拉伸单面模型(比如立面墙),考虑用 Plane 代替 Cube,单面模型永远不会出现六面比例打架。

5.4 转向方向反了,倒车影像镜像

现象:按左方向键,车往右转;倒车时画面像镜像一样乱。

原因:模型自身的正方向轴不是 Unity 默认的蓝色前轴,或者脚本里把 horizontal 的正负号搞反了。很多免费模型的前进方向是红色轴或绿色轴,直接套 transform.forward 就会反向。

解决:先选中小车,在 Scene 视图里开启 Local 坐标显示,看蓝色轴指向哪。如果蓝色轴指向车尾,旋转模型让它指向车头,或者在脚本里把前进方向改写成 transform.right。最稳妥的办法是在车辆根物体下再包一层空物体,用空物体的蓝色轴作为前进方向,控制脚本挂在空物体上,模型作为子物体旋转校正一次。

5.5 演示视频录制时画面卡顿

现象:录出来的视频一卡一顿,画面掉帧,车转弯时镜头明显拖尾。

原因:录制软件抓的是 Unity 渲染帧,如果场景里同时开着 Game 视图、Scene 视图,或者开着多盏投影仪,编辑器性能会被分摊;还有一种情况是垂直同步没开,帧率虚高但每帧时间不稳定。

解决:先把 Game 视图分辨率固定为 1280×720,关掉 Scene 视图或缩小窗口,再打开 Quality 里 VSync 为 1。如果仍然卡,顺手加一个 Time.timeScale = 0.5f,同一个驾驶演示用 2 倍慢速录,素材拿到剪辑软件里再倍速,画质反而更稳。这一步放在最后一章细说。

6. 进阶验收:用时间缩放与固定机位拍出稳定演示视频

课程设计能不能拿高分,一半看画面稳不稳。你可能很难相信,那些观感很流畅的驾驶演示视频,很多不是现场一遍过的,而是开着 0.5 倍速录制,后期再拉回 1 倍速。Unity 的 Time.timeScale 可以直接控制整套模拟系统的运行节奏,乘上 0.5 之后,车子的加速度、转向速度、碰撞响应全部等比例变慢,录制时每一帧的画面信息量不变,但机器有更充裕的时间去渲染。

具体做法是在 Play 模式下先暂停,再在 Console 窗口的输入框里执行一句脚本,或者直接给车辆控制脚本加两个调试键。我习惯用 F1/F2 切换,因为不用打断播放:

if (Input.GetKeyDown(KeyCode.F1)) { Time.timeScale = 0.5f; // 半速运行,适合录演示 } if (Input.GetKeyDown(KeyCode.F2)) { Time.timeScale = 1f; // 恢复正常速度 }

录制之前固定机位也很重要。直接把 Main Camera 切到车辆上方俯视角度,然后固定不动,用鼠标在 Scene 视图里从远处拉近,录制一段“绕车一周 + 上路行驶”的组合。更建议的是把 Game 视图的纵横比锁成 16:9,不要用自由纵横比,不然录出来的视频两侧黑边不一样。剪辑时把 0.5 倍速片段统一提高速度到 1 倍,音频如果用了引擎声,记得同步变速,否则声音和画面会对不上。

演示视频之外的部分,这个资源包已经把关键步骤固化成可直接对照的演示视频和实验报告,你只需要补上自己的改动说明。把它们和这套链路对照着看,就能看出每个参数对应在哪个环节。从那以后,我每做完一个模拟类课设都会强制走一遍同样的流程:先搭地面、再布置方向光、挂好车体脚本、最后录半速演示。这套习惯帮我少改了大量报告配图,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询