1. 项目概述为什么选择Unity做交通仿真如果你对游戏开发或者3D可视化感兴趣大概率听说过Unity。但你可能不知道它早已不只是个游戏引擎在工业仿真、数字孪生、教育培训等领域Unity正扮演着越来越重要的角色。交通仿真就是其中一个典型应用场景。相比于专业的交通仿真软件如VISSIM、SUMO用Unity来做这件事最大的优势在于所见即所得的可视化效果和极低的原型开发门槛。你不需要去啃复杂的交通流理论模型API用最熟悉的C#配合Unity直观的编辑器就能快速搭建出一个动态、直观、可交互的十字路口红绿灯系统。这个项目要解决的就是一个最基础的交通控制单元十字路口。我们将模拟现实中红绿灯的周期性切换比如东西向绿灯30秒黄灯3秒红灯33秒南北向则相反并让虚拟的车辆能够根据信号灯的指示做出“停下”或“通行”的决策。这听起来简单但背后涉及到Unity的几个核心模块GameObject与组件化设计、C#脚本的驱动逻辑、物理系统用于车辆移动与停止以及UI系统用于信号灯状态显示。通过完成这个项目你不仅能掌握一个仿真demo更能深刻理解Unity中“数据驱动行为”和“状态机”的设计思想这是开发更复杂AI或交互系统的基石。我选择从十字路口红绿灯入手是因为它要素齐全、边界清晰非常适合Unity新手作为第一个“非游戏”的仿真项目来练手。无论你是学生想做个课程设计还是开发者想拓展Unity的应用边界这个项目都能给你一套可直接运行、易于扩展的代码框架。接下来我会带你从场景搭建开始一步步实现信号灯控制逻辑、车辆AI并分享我在调试过程中踩过的坑和总结的优化技巧。2. 核心思路与系统架构设计在动手写代码之前我们先花点时间把整个系统的设计思路理清楚。一个可运行、易维护的仿真系统绝对不是一堆脚本的胡乱堆砌。好的架构能让后续添加新功能比如左转专用灯、行人过街信号变得轻而易举。2.1 以“状态”为核心的设计哲学交通信号灯的本质是一个状态机。它总是在“红灯”、“绿灯”、“黄灯”这几个有限的状态之间按既定周期循环切换。我们的核心任务就是用代码精准地描述这个状态机。在Unity中实现状态机有多种方式比如用枚举Enum定义状态用switch语句或委托Delegate来处理不同状态下的行为。对于这个入门项目我们将采用最清晰易懂的枚举协程Coroutine方案。为什么用协程因为信号灯的切换是典型的基于时间的延时行为。“绿灯亮30秒”意味着在绿灯状态下等待30秒然后切换到黄灯状态。用协程的yield return new WaitForSeconds(seconds);可以非常优雅地实现这种“等待一段时间后执行下一步”的逻辑避免了在Update里写复杂计时器代码的混乱。2.2 系统模块划分我们将整个系统拆解为三个相对独立的模块通过松耦合的方式组织起来交通信号灯控制器 (TrafficLightController)这是系统的大脑。它是一个C#脚本挂载在一个空GameObject上比如命名为“IntersectionManager”。它的职责是定义路口各方向如东西直行、南北直行的信号灯组。维护当前全局的灯色状态例如Phase_A_Green。使用协程控制整个信号周期的循环。在状态改变时通知所有相关的信号灯模型和车辆控制器。信号灯视觉表现器 (TrafficLightVisual)这是系统的“脸”。每个具体的红绿灯模型一个由红、黄、绿三个灯珠组成的3D模型或UI元素都会挂载这个脚本。它的职责很简单接收控制器的状态变更指令然后改变自身对应灯珠的材质颜色如将绿色灯珠的材质从黑色变为发光绿色来做出视觉反馈。这里的关键是表现与逻辑分离控制器只关心“东西向现在是绿灯”而不关心具体是哪个模型来表现视觉器只关心“我被通知要亮绿灯”然后执行亮灯操作。这样我们更换红绿灯的3D模型甚至换成2D Sprite都无需修改核心控制逻辑。车辆智能体 (VehicleAI)这是系统的“参与者”。我们将为每一辆预制车辆挂载这个脚本。它的核心逻辑是持续检测其行驶方向前方的信号灯状态。根据信号灯状态和自身与停车线的距离决定是继续前进还是减速刹车。实现简单的加速、匀速、减速运动逻辑。这三个模块通过Unity内置的事件Event或简单的公共方法调用进行通信构成了我们仿真系统的基础骨架。这种设计确保了代码的清晰度和可扩展性。3. 场景搭建与资源准备理论说再多不如动手做。我们首先在Unity中搭建一个最基本的十字路口场景。3.1 创建基础场景与道路打开Unity创建一个新的3D项目。在场景中我们可以使用简单的立方体Cube来搭建道路。创建四个细长的Cube摆成一个“十”字形调整它们的缩放Scale比如X: 20, Y: 0.2, Z: 4作为道路的主体。再创建四个小Cube作为路缘石放在道路两侧。为了更美观你可以从Unity Asset Store免费资源中搜索“Road”或“Simple Road”导入一些现成的道路模型这会让你的场景瞬间专业起来。但对于本教程使用Cube足以说明问题。在四条道路的入口和出口方向创建一些简单的立方体或圆柱体作为建筑物或树木以丰富场景避免看起来过于空旷。3.2 制作红绿灯模型红绿灯模型可以直接用组合的立方体来制作这最能体现Unity组件化思想的精髓。创建一个空GameObject命名为“TrafficLight_NS”表示南北方向。在这个空对象下创建三个子Cube分别命名为“RedLight”、“YellowLight”、“GreenLight”。将它们竖直排列。为每个灯珠Cube创建并指定材质Material。创建三个新材质分别命名为“Mat_Red_Off”、“Mat_Yellow_Off”、“Mat_Green_Off”将它们的颜色Albedo设为深灰色或黑色模拟熄灭状态。再创建三个材质“Mat_Red_On”、“Mat_Yellow_On”、“Mat_Green_On”颜色设为饱和的红色、黄色和绿色。为了有发光效果可以将材质的“Emission”属性勾选并赋予相同的颜色并适当调整强度。将“_Off”系列的材质分别赋给对应的灯珠Cube。将这个“TrafficLight_NS”拖入项目窗口的Assets文件夹生成一个预制体Prefab。然后复制三份分别旋转并放置到路口的四个角落代表四个方向的行车信号灯。注意使用预制体是Unity最佳实践之一。这样你只需要修改一个预制体所有场景中它的实例都会同步更新极大提高了制作效率。3.3 设置车辆与路径车辆也可以用Cube制作或者从免费资源中找一个简单的汽车模型。创建一个Cube调整大小命名为“Vehicle”为其添加一个材质。同样将其制成预制体。我们需要为车辆定义行驶路径。一个简单的方法是使用空对象作为路径点Waypoint。在每条车道的起点、停车线前、路口中心、出口处分别创建空对象命名为“WP_Start”、“WP_StopLine”、“WP_Intersection”、“WP_End”。车辆AI的逻辑就是沿着这些路径点依次移动。你可以将这些路径点按顺序放入一个数组或列表中。至此一个简陋但结构清晰的十字路口场景就准备好了。接下来我们将注入灵魂——C#脚本。4. 核心代码实现信号灯控制器这是整个系统最核心的脚本。我们创建一个名为TrafficLightController的C#脚本。4.1 定义状态与公共接口using System.Collections; using System.Collections.Generic; using UnityEngine; public class TrafficLightController : MonoBehaviour { // 使用枚举清晰定义信号灯组的状态 public enum LightPhase { Phase1_NS_Green_EW_Red, // 南北绿灯东西红灯 Phase2_NS_Yellow_EW_Red, // 南北黄灯东西红灯 Phase3_NS_Red_EW_Green, // 南北红灯东西绿灯 Phase4_NS_Red_EW_Yellow // 南北红灯东西黄灯 } [Header(信号灯时间配置秒)] public float greenDuration 30.0f; public float yellowDuration 3.0f; // 红灯时间由对方方向的绿灯黄灯时间决定这里我们计算得出 [Header(当前状态只读)] public LightPhase currentPhase; // 存储所有需要控制的红绿灯视觉器引用 [Header(信号灯视觉器引用)] public ListTrafficLightVisual northSouthLights; // 南北方向灯组 public ListTrafficLightVisual eastWestLights; // 东西方向灯组 // 一个简单的委托事件用于通知车辆等系统状态已改变 public delegate void OnPhaseChanged(LightPhase newPhase); public event OnPhaseChanged PhaseChanged; void Start() { if (northSouthLights.Count 0 || eastWestLights.Count 0) { Debug.LogError(请将场景中的TrafficLightVisual组件拖拽到对应的列表中); return; } // 启动信号灯循环协程 StartCoroutine(TrafficLightCycle()); } }代码解析LightPhase枚举定义了四个明确的相位这是典型的两相位信号控制。清晰的状态定义是后续逻辑无误的基础。将时间参数greenDuration和yellowDuration暴露为公共变量并加上[Header]属性这样在Unity Inspector面板中会显示为友好的分组标题方便运行时动态调整这是快速调试和平衡仿真效果的关键。使用ListTrafficLightVisual来管理视觉器。这样一个方向如南北可以有多组信号灯如主灯、辅助灯控制器可以统一管理它们。定义了一个OnPhaseChanged事件。这是实现模块间松耦合的关键。车辆AI可以订阅这个事件而不是每帧去查询控制器状态效率更高。4.2 实现信号周期协程这是控制器的核心逻辑我们实现TrafficLightCycle协程。IEnumerator TrafficLightCycle() { Debug.Log(交通信号灯系统启动。); while (true) // 无限循环模拟持续运行 { // 相位1南北绿灯东西红灯 currentPhase LightPhase.Phase1_NS_Green_EW_Red; UpdateAllLights(); // 更新所有灯视觉状态 PhaseChanged?.Invoke(currentPhase); // 发出事件通知 Debug.Log($切换到相位: {currentPhase}, 持续时间: {greenDuration}秒); yield return new WaitForSeconds(greenDuration); // 相位2南北黄灯东西红灯 currentPhase LightPhase.Phase2_NS_Yellow_EW_Red; UpdateAllLights(); PhaseChanged?.Invoke(currentPhase); Debug.Log($切换到相位: {currentPhase}, 持续时间: {yellowDuration}秒); yield return new WaitForSeconds(yellowDuration); // 相位3南北红灯东西绿灯 currentPhase LightPhase.Phase3_NS_Red_EW_Green; UpdateAllLights(); PhaseChanged?.Invoke(currentPhase); Debug.Log($切换到相位: {currentPhase}, 持续时间: {greenDuration}秒); yield return new WaitForSeconds(greenDuration); // 相位4南北红灯东西黄灯 currentPhase LightPhase.Phase4_NS_Red_EW_Yellow; UpdateAllLights(); PhaseChanged?.Invoke(currentPhase); Debug.Log($切换到相位: {currentPhase}, 持续时间: {yellowDuration}秒); yield return new WaitForSeconds(yellowDuration); // 循环回到相位1 } }代码解析while (true)循环确保了信号灯系统永不停止。每个相位严格按照“改变状态 - 更新视觉 - 发出通知 - 等待时长”的顺序执行。顺序很重要必须保证视觉和逻辑状态同步更新后再通知其他系统。PhaseChanged?.Invoke(currentPhase)是C# 6.0引入的空条件运算符的简化写法等同于if (PhaseChanged ! null) PhaseChanged(currentPhase)。安全地触发事件。在每个相位切换时输出Log这在调试阶段非常有用可以清晰地在Console窗口看到状态流转。4.3 更新视觉状态的辅助方法void UpdateAllLights() { bool nsGreen (currentPhase LightPhase.Phase1_NS_Green_EW_Red); bool nsYellow (currentPhase LightPhase.Phase2_NS_Yellow_EW_Red); bool nsRed (currentPhase LightPhase.Phase3_NS_Red_EW_Green) || (currentPhase LightPhase.Phase4_NS_Red_EW_Yellow); bool ewGreen (currentPhase LightPhase.Phase3_NS_Red_EW_Green); bool ewYellow (currentPhase LightPhase.Phase4_NS_Red_EW_Yellow); bool ewRed (currentPhase LightPhase.Phase1_NS_Green_EW_Red) || (currentPhase LightPhase.Phase2_NS_Yellow_EW_Red); // 更新南北方向灯组 foreach (var light in northSouthLights) { light.SetLightState(nsRed, nsYellow, nsGreen); } // 更新东西方向灯组 foreach (var light in eastWestLights) { light.SetLightState(ewRed, ewYellow, ewGreen); } }这个方法根据当前的currentPhase计算出南北和东西方向各自的红、黄、绿灯布尔状态然后遍历对应的视觉器列表调用其SetLightState方法进行更新。逻辑清晰将状态判断集中在一处。将TrafficLightController脚本挂载到场景中的“IntersectionManager”空对象上。然后在Inspector面板中将场景中四个方向的TrafficLightVisual组件分别拖拽到northSouthLights和eastWestLights列表中。运行游戏你应该能在Console窗口看到状态循环的日志输出但红绿灯模型还没有变化因为我们还没实现视觉器。5. 核心代码实现信号灯视觉器现在创建TrafficLightVisual脚本并将其挂载到我们之前制作的每一个红绿灯预制体实例上。using UnityEngine; public class TrafficLightVisual : MonoBehaviour { [Header(灯珠物体引用)] public GameObject redLightObject; public GameObject yellowLightObject; public GameObject greenLightObject; [Header(点亮时的材质)] public Material redOnMat; public Material yellowOnMat; public Material greenOnMat; [Header(熄灭时的材质)] public Material redOffMat; public Material yellowOffMat; public Material greenOffMat; // 内部组件引用缓存 private Renderer _redRenderer; private Renderer _yellowRenderer; private Renderer _greenRenderer; void Start() { // 获取灯珠物体的渲染器组件 if (redLightObject ! null) _redRenderer redLightObject.GetComponentRenderer(); if (yellowLightObject ! null) _yellowRenderer yellowLightObject.GetComponentRenderer(); if (greenLightObject ! null) _greenRenderer greenLightObject.GetComponentRenderer(); // 初始状态设为全灭 SetLightState(false, false, false); } // 外部控制器调用的方法设置当前灯色 public void SetLightState(bool redOn, bool yellowOn, bool greenOn) { if (_redRenderer ! null) _redRenderer.material redOn ? redOnMat : redOffMat; if (_yellowRenderer ! null) _yellowRenderer.material yellowOn ? yellowOnMat : yellowOffMat; if (_greenRenderer ! null) _greenRenderer.material greenOn ? greenOnMat : greenOffMat; } }代码解析与实操要点公共变量赋值在Unity Inspector中你必须将红绿灯模型下对应的三个灯珠子物体RedLight, YellowLight, GreenLight拖拽到脚本的redLightObject等字段中。同样需要将之前创建的“_On”和“_Off”材质球拖拽到对应的材质字段。这一步是连接逻辑与视觉的关键务必仔细操作。缓存Renderer在Start方法中获取Renderer组件并缓存起来比在每次SetLightState调用时通过GetComponent获取要高效得多。这是一个微小的但很重要的性能优化习惯。三元运算符redOn ? redOnMat : redOffMat这行代码是C#的三元条件运算符它等价于一个if-else语句但写起来更简洁。意思是如果redOn为真使用redOnMat否则使用redOffMat。完成脚本挂载和参数赋值后运行游戏。现在你应该能看到路口的红绿灯按照设定的周期30秒绿3秒黄交替变化了恭喜你系统的大脑和脸已经能够协同工作了。6. 核心代码实现车辆AI与交通规则有了会变色的红绿灯我们需要让车辆能看懂这些信号。这就是VehicleAI脚本的任务。6.1 基础移动与路径跟随我们先实现车辆沿着预设路径点移动的基本功能。using System.Collections.Generic; using UnityEngine; public class VehicleAI : MonoBehaviour { [Header(路径点)] public ListTransform waypoints; // 按顺序存放路径点Transform public float arrivalThreshold 0.5f; // 判定到达路径点的距离阈值 [Header(运动参数)] public float maxSpeed 10.0f; public float acceleration 5.0f; public float brakeForce 15.0f; private int _currentWaypointIndex 0; private Rigidbody _rb; private float _currentSpeed 0f; private bool _isStopped false; void Start() { _rb GetComponentRigidbody(); if (_rb null) { Debug.LogError(VehicleAI 需要挂载在带有Rigidbody组件的物体上); return; } if (waypoints null || waypoints.Count 0) { Debug.LogError(请为VehicleAI分配路径点); } } void FixedUpdate() { if (waypoints null || waypoints.Count 0 || _currentWaypointIndex waypoints.Count) return; Transform targetWaypoint waypoints[_currentWaypointIndex]; Vector3 direction (targetWaypoint.position - transform.position).normalized; float distanceToWaypoint Vector3.Distance(transform.position, targetWaypoint.position); // 基础移动逻辑朝向目标点加速 if (!_isStopped) { _currentSpeed Mathf.MoveTowards(_currentSpeed, maxSpeed, acceleration * Time.fixedDeltaTime); } else { // 如果被要求停止则减速 _currentSpeed Mathf.MoveTowards(_currentSpeed, 0f, brakeForce * Time.fixedDeltaTime); } // 应用速度 Vector3 newPosition transform.position direction * _currentSpeed * Time.fixedDeltaTime; _rb.MovePosition(newPosition); // 旋转朝向移动方向平滑过渡 if (direction ! Vector3.zero) { Quaternion targetRotation Quaternion.LookRotation(direction); transform.rotation Quaternion.Slerp(transform.rotation, targetRotation, Time.fixedDeltaTime * 5f); } // 检查是否到达当前路径点 if (distanceToWaypoint arrivalThreshold) { _currentWaypointIndex; if (_currentWaypointIndex waypoints.Count) { // 到达终点销毁或重置车辆 Debug.Log(${gameObject.name} 已到达路线终点。); Destroy(gameObject); // 简单处理销毁车辆 // 更复杂的仿真中这里可以将其放回起点或对象池 } } } }代码解析FixedUpdate是Unity中处理物理更新的函数比Update更适用于涉及Rigidbody的运动能保证在不同帧率下物理行为一致。使用Rigidbody.MovePosition来移动物体这是一种“运动学”移动方式比直接修改Transform.position更符合物理模拟能避免穿墙等问题虽然本demo中不一定有碰撞。Mathf.MoveTowards函数是一个非常好用的线性插值工具可以平滑地将当前值向目标值改变我们用它来实现加速和减速过程。当车辆到达最后一个路径点后我们简单地将其销毁。在一个完整的仿真中你应该使用对象池Object Pooling来重复利用车辆对象避免频繁的实例化和销毁造成的性能开销。6.2 集成信号灯检测与响应现在让车辆能感知红绿灯。我们需要知道车辆当前在哪条车道以及它对应的信号灯是什么状态。public class VehicleAI : MonoBehaviour { // ... 之前的变量 ... [Header(信号灯检测)] public TrafficLightController intersectionController; // 关联的路口控制器 public Transform stopLinePoint; // 该车道对应的停车线位置路径点之一 public float stopDistance 5.0f; // 看到红灯时开始减速的距离 public float slowDownDistance 15.0f; // 看到黄灯时开始减速的距离 private bool _isFacingRedOrYellow false; void Start() { // ... 之前的Start代码 ... // 订阅信号灯状态改变事件 if (intersectionController ! null) { intersectionController.PhaseChanged OnTrafficLightPhaseChanged; } } void OnDestroy() { // 取消订阅防止内存泄漏 if (intersectionController ! null) { intersectionController.PhaseChanged - OnTrafficLightPhaseChanged; } } void OnTrafficLightPhaseChanged(TrafficLightController.LightPhase newPhase) { // 这是一个简化判断我们需要根据车辆所在车道和新的信号灯相位判断该车道是红灯/黄灯还是绿灯 // 实际情况更复杂需要知道车辆属于“南北向”还是“东西向” // 这里我们假设脚本上有一个public变量来手动指定车辆方向或者通过计算车辆与路口中心的位置关系来判断 // 为了简化我们假设每个VehicleAI都手动指定了它关心的信号灯状态是看NS灯还是EW灯 // 并在外部根据相位计算_isFacingRedOrYellow。这里我们用一个更直接的方法 // 方法在FixedUpdate中实时检测距离和信号灯状态 } void FixedUpdate() { // ... 之前的路径跟随代码 ... // --- 新增信号灯响应逻辑 --- bool shouldStop CheckIfShouldStop(); _isStopped shouldStop; // 更新停止状态影响移动逻辑中的速度计算 // --- 结束新增 --- // ... 后续的移动和应用速度代码 ... } bool CheckIfShouldStop() { if (intersectionController null || stopLinePoint null) return false; // 计算车辆到停车线的距离在前进方向上的投影距离更准确这里简化处理 float distanceToStopLine Vector3.Distance(transform.position, stopLinePoint.position); // 获取当前信号灯相位 var currentPhase intersectionController.currentPhase; // 假设此车辆是南北方向行驶的。你需要根据车辆实际方向来写判断逻辑。 // 这里仅作示例 bool isRedLightAhead (currentPhase TrafficLightController.LightPhase.Phase3_NS_Red_EW_Green || currentPhase TrafficLightController.LightPhase.Phase4_NS_Red_EW_Yellow); bool isYellowLightAhead (currentPhase TrafficLightController.LightPhase.Phase2_NS_Yellow_EW_Red); if (isRedLightAhead) { // 红灯在一定距离外就准备停车 if (distanceToStopLine stopDistance) return true; } else if (isYellowLightAhead) { // 黄灯如果距离停车线很近尝试通过如果较远则稳妥停车 if (distanceToStopLine slowDownDistance distanceToStopLine arrivalThreshold) return true; // 黄灯且距离适中选择停车 // 如果已经非常接近停车线distanceToStopLine arrivalThreshold则继续通过 } // 绿灯不停车 return false; } }代码解析与避坑指南事件订阅与取消在Start中订阅PhaseChanged事件在OnDestroy中取消订阅。这是一个非常重要的好习惯可以防止游戏对象被销毁后控制器仍持有对它的引用而导致错误或内存泄漏。复杂的车道-信号灯映射上面代码中的CheckIfShouldStop方法是一个极度简化的示例。它假设车辆是南北向的。在实际项目中你需要建立一个更可靠的映射关系。有几种方法标签法给车辆和信号灯控制器打上标签如“NS_Vehicle, EW_Vehicle”在判断时检查标签。区域检测法在路口每个方向设置一个触发区域Trigger Collider车辆进入时记录自己当前所处的方向。路径信息法在路径点数据中加入该点所属方向的信息。 我个人推荐路径信息法因为它最精确。你可以在stopLinePoint这个路径点对象上挂载一个脚本里面定义一个public TrafficLightController.LightPhase blockingPhase变量。当信号灯处于blockingPhase时停在这个点的车辆就需要停下。这样只需在CheckIfShouldStop中比较当前相位和stopLinePoint上的blockingPhase即可逻辑清晰且可配置。黄灯逻辑黄灯的处理是交通仿真真实性的一个体现。上面的逻辑是一个简单策略“安全第一”在黄灯亮起时如果距离停车线较远就刹车如果已经很近了就通过。你可以尝试实现更复杂的策略比如根据当前速度计算能否在停车线前安全刹停。将VehicleAI脚本挂载到车辆预制体上并为其分配好路径点序列将场景中的路径点空对象拖拽到waypoints列表关联intersectionController并指定stopLinePoint通常是waypoints列表中停车线那个点。复制几辆车放置在不同车道的起点。运行游戏你应该能看到车辆在红灯前停下绿灯时通行。7. 调试、优化与功能扩展基础功能跑通后我们来看看如何让这个仿真系统更健壮、更真实、更可用。7.1 可视化调试工具在开发阶段将关键信息可视化能极大提升效率。我们可以使用Unity的Gizmos来绘制调试图形。在TrafficLightController脚本中添加void OnDrawGizmos() { if (!Application.isPlaying) return; // 在Scene视图中绘制当前相位信息 GUIStyle style new GUIStyle(); style.normal.textColor Color.white; style.fontSize 20; style.fontStyle FontStyle.Bold; UnityEditor.Handles.Label(transform.position Vector3.up * 5, $当前相位: {currentPhase}, style); }在VehicleAI脚本的CheckIfShouldStop方法附近添加void OnDrawGizmosSelected() { if (stopLinePoint ! null) { Gizmos.color Color.yellow; Gizmos.DrawLine(transform.position, stopLinePoint.position); Gizmos.DrawWireSphere(stopLinePoint.position, 1f); // 绘制减速和停止区域 Gizmos.color Color.red; Gizmos.DrawWireSphere(stopLinePoint.position, stopDistance); Gizmos.color new Color(1f, 0.5f, 0f); // 橙色 Gizmos.DrawWireSphere(stopLinePoint.position, slowDownDistance); } }OnDrawGizmos和OnDrawGizmosSelected可以让开发者在Scene视图中直观地看到信号灯状态、车辆的检测距离等信息无需依赖Console Log。7.2 性能优化初步对象池管理车辆在Start中预生成一定数量的车辆预制体设为未激活状态。需要发车时从池中取一个激活并设置位置。车辆到达终点后不是Destroy而是将其设回未激活并放回池中。这能有效减少GC垃圾回收压力。减少不必要的计算在VehicleAI的FixedUpdate中CheckIfShouldStop每帧都在计算距离。如果车辆离路口很远这个计算是浪费的。可以添加一个“激活距离”只有当车辆进入路口一定范围内才开始进行信号灯检测。使用Layer进行碰撞过滤为车辆、信号灯、道路等设置不同的Layer并在物理设置中合理配置碰撞矩阵避免不必要的物理计算。7.3 功能扩展思路一个基础的十字路口红绿灯系统已经完成。你可以在此基础上尝试以下扩展让仿真更丰满多相位信号灯现实中的十字路口常有左转专用相位。你可以扩展LightPhase枚举增加Phase_NS_LeftTurn_Green等状态并修改控制器协程和车辆路径逻辑。行人系统添加行人预制体和人行横道。创建PedestrianAI脚本行人等待“行人绿灯”信号可由同一个TrafficLightController管理绿灯时沿人行横道移动。车流生成器创建一个VehicleSpawner脚本在每条车道起点按一定时间间隔如泊松分布实例化车辆并为其分配预设好的路径。这能模拟持续的车流。数据统计在TrafficLightController中记录每个相位通过的车辆数量、平均等待时间等并在UI上实时显示。这是仿真系统价值的重要体现。UI控制面板使用Unity的UI系统UGUI或UI Toolkit创建一个控制面板可以实时调整绿灯、黄灯时长甚至手动切换相位方便演示和调试。导入真实地图使用OpenStreetMap等开源地图数据结合一些Unity插件如“OSM to Unity”可以将真实的道路网络导入Unity在此基础上部署你的交通信号控制系统瞬间提升项目的专业度和复杂度。8. 常见问题与解决方案实录在实际操作中你几乎一定会遇到下面这些问题。这里是我踩过坑后总结的解决方案。问题1车辆在红灯前停不下来直接穿过了停车线。可能原因1CheckIfShouldStop方法中的distanceToStopLine计算不准确。车辆可能从侧面接近停车线而Vector3.Distance计算的是直线距离。应该计算车辆前进方向上到停车线平面的距离。解决方案使用向量点积进行计算。Vector3.Dot(transform.forward, (stopLinePoint.position - transform.position))可以获取车辆前方到停车线的投影距离。只有当这个投影距离为正且小于阈值时才触发停车。可能原因2刹车力brakeForce太小或者车辆速度太快在给定的stopDistance内无法减速到0。解决方案增大brakeForce或增大stopDistance。更真实的做法是根据当前速度动态计算所需刹车距离requiredBrakingDistance (currentSpeed * currentSpeed) / (2 * brakeForce)然后在CheckIfShouldStop中比较这个动态距离。问题2多辆车在红灯前排队时会挤在一起甚至重叠。可能原因车辆AI只检测信号灯没有检测前车。解决方案在VehicleAI中添加前车检测。使用Physics.Raycast或Physics.OverlapSphere在车辆前方检测同车道车辆。如果检测到前车则保持安全车距其优先级甚至高于红绿灯。这涉及到更复杂的“跟驰模型”是交通仿真的核心之一。问题3调整Inspector中的时间参数如greenDuration后运行中的信号灯周期没有立即改变。可能原因协程TrafficLightCycle中的WaitForSeconds使用的是创建该指令时的greenDuration值。后续修改公共变量不会影响已经开始的等待。解决方案不要直接在协程里使用yield return new WaitForSeconds(greenDuration)。改为使用一个计时器在Update中累加时间并检查是否到达设定的时长。这样每帧都可以读取最新的greenDuration值。或者在时间参数改变时停止旧的协程用新的参数重新启动协程。问题4车辆转弯时动作生硬像“瞬移”一样切方向。可能原因在FixedUpdate中直接使用Quaternion.LookRotation(direction)设置旋转没有平滑过渡。解决方案代码中已经使用了Quaternion.Slerp进行球形插值但插值速度代码中的5f可能不够。可以增加这个系数或者使用Quaternion.RotateTowards。对于路径转弯更好的办法是使用贝塞尔曲线计算路径点之间的平滑路径让车辆沿着曲线行驶而不是直线连接路径点。问题5运行一段时间后感觉游戏变卡。可能原因车辆不断被实例化和销毁Destroy产生了内存碎片和GC开销。解决方案如前所述必须实现对象池。这是Unity性能优化中针对频繁创建销毁场景的黄金法则。创建一个VehiclePool管理器来统一管理所有车辆的生成和回收。这个从零搭建的Unity交通仿真十字路口红绿灯项目虽然基础但完整地走通了从场景搭建、逻辑设计、代码实现到调试优化的全流程。它像一颗种子你可以根据自己的兴趣向其中添加更复杂的交通流模型、更美观的3D资源、更智能的决策AI甚至连接外部数据接口让它成长为一个功能强大的专业仿真工具。编程和仿真的乐趣就在于这种从无到有、从简单到复杂的创造过程。希望这份详细的指南和源码能成为你探索Unity更多可能性的坚实起点。