基于unity交通仿真软件开发
摘要:基于Unity实现交通系统仿真功能,开发一电脑端与安卓手机端软件,设计了一个包含道路、车辆和交通信号灯等元素的场景,确保车辆按照规定的交通规则行驶,实现了最基本、最核心的固定周期信号灯变换和车量运行逻辑功能。对该软件的架构设计、技术细节、应用前景进行了讨论,并提出了一些改进和扩展的方向,以进一步提升系统的功能和性能。
关键词:信号灯、车辆运行、UI、数据保存
1背景
交通仿真是一种基于计算机模拟的方法,通过建立交通系统的数学模型和仿真环境,模拟和分析交通流量、车辆行为、道路网络等因素对交通系统运行的影响。通过对不同交通策略和控制方法的仿真实验,可以评估其对交通系统性能的影响,并提供优化方案。
交通仿真背后的核心理论包括车辆行为模型、交通流模型和道路网络模型等。车辆行为模型描述了车辆在不同情况下的加速、减速、换道等行为规则。交通流模型则描述了车辆在道路上的流动规律,包括车辆密度、速度和流量等指标。道路网络模型则描述了道路网络的拓扑结构和连接关系。
通过交通仿真,我们可以模拟不同交通场景下的交通流量、交通拥堵、交通事故等情况,并评估不同交通策略的效果。这些信息可以帮助交通规划者和决策者做出科学合理的决策,优化交通系统的运行效率,提高交通安全性。
2 制作过程
2.1 素材准备
2.1.1 需要资源
网格(fbx),材质(material),贴图(textyre),预制体(prefab),粒子特效(particle),特定插件从UnityStore下载和使用专业软件进行处理。
2.1.2 完整开发流程使用软件
用PS 处理图片,提供贴图。
用Blender或3Dmax等建模软件,使用自带着色器算法对贴图和颜色进行处理,为模型添加材质,设置合适的模型原点坐标,将方向设为Y轴向上后,导出为fbx格式到unity。导入过程出现贴图丢失属于正常现象,unity不支持内嵌贴图,先将fbx导入,再将对应texture导入同一文件夹下。
Vistual Studio与unity关联,用于更改插件,编程。
用unity编辑粒子特效,发布exe,打包apk。
2.2 建立模型
为了节约时间成本,降低电脑GPU渲染压力,提升程序运行性能。本研究信号灯等模型采用unity自带原生物体(圆柱、正方体、球体……)简单缩放旋转后建模,然后添加材质,其它均从Unitystore下载。未严格按照实际模型比例模型精确到0.01m并在专业工业建模软件UG、solidworks建模,未从blender,3dmax等建模软件建模。
采用非写实建模风格。
2.2.1信号灯
整体结构:
默认情况下Gleftturn不显示。
左转:
采用4个长方体和1个圆柱组成来表示左转信号。用该物体显示与隐藏表示亮灭,不采用点光源阵列来表示是为了节约性能。
2.2.2道路
在unity中为平面(qued),添加贴图,设置图片拉伸效果tiling。
2.2.3 车辆、建筑、花草树木、路灯……
从Unitystore导入。
2.2.4 组合
y轴基准高度均设成0。
2.3基本和核心功能
2.3.1十字路口路口信号灯
交通规则:1绿灯时间+黄灯时间+红灯时间=周期。
2同一个红绿灯直行与左转的周期相同。
2.3.1.1 直行信号灯
思路:为每一个方向的信号灯赋予给一个数字,设置参数Whichlight,x-、x+、z-、z+方向信号灯挂载脚本设置0、1、2、3,进行区分,如果不添加存储功能次参数无效。设置延迟时间,用Invoke函数嵌套进行循环,周期性改变参数i,i的值意义0为绿,1为黄,2为红,改变颜色模拟灯的亮灭。一个路口4个方向信号灯不存在关联,独立运行。
2.3.1.2 左转信号灯
同理,用隐藏显示模拟灯达到亮灭。
2.3.1.3 参数表
见源程序
2.3.2 T字路口信号灯
同理十字路口,适当删减,改变参数。
2.3.3 车辆
最核心的功能,每辆车相当于小的智能体。
2.3.3.1 直行车辆生成功能
在道路上选择0-n个点作为生成点位,采用Instantitate函数生成预制体,同时初始化车辆参数。
在生成点位旁加基于碰撞检测的范围检测,在有车占用范围内时段不生成。
2.3.3.2 直行车辆遇到红灯停车
方法1:车辆获取最近路口对应方向信号灯参数i判断信号状态。如果无人驾驶普及,路口可以只放一个信号发生器,不需要灯,让车辆智能系统判断。
方法2:判断该方向最近信号灯的颜色,本研究采用此方法。
车辆通过查询标签(tag)方式寻找最近路口。
2.3.3.3直行车辆避让前方直行车辆减速
采用射线检测返回前车距离,小于临界值进行减速直到速度为0。
2.3.3.4 直行车辆加速
周期性采用射线检测返回前方车距离,距离减少进行加速直至速度为最大值。
2.3.3.5 直行车辆避让左转车辆停车
采用射线检测返回前车距离,小于临界值进行减速直到速度为0。可以加一定偏移量,本研究为了方便调试,偏移量设为0。
2.3.3.6 直行车辆销毁
判断与最近路口的距离,超出边界范围后销毁。
2.3.3.6 左转车辆红灯停车
左转车辆设为循环动画,停车原理同直行车辆。
2.3.3.7 左转车辆避让直行车辆
基于碰撞检测的范围检测,左转车辆在左转过程中,范围内存在直行车辆时停止运动。
2.4绿波带仿真
拼接多个路口,每个路口设置成相同周期,设置不同的延迟时间,错开的时差用路口距离除车速度计算。
2.5 UI设计
以上所有仿真功能在unity中均可正常运行。为了方便用户体验获得直观感受,增加添加部分模型功能,调整部分参数功能。UI的设计原则是不同界面之间不干涉,基于项目自身功能进行设置,具有不可借鉴性。UI有固定程序、界面模板,根据实际需求改变内容与程序框架。
2.6 数据保存
类中声明属性,将各个参数转为列表。采用序列化,反序列化的方对列表进行读写。电脑端用Newtonsoft.json插件将列表数据保存到路径Streaming Asset中json文件下。安卓手机端使用ios插件保存到路径Application.persistentDataPath + "/SaveData.json"。
2.7 Camera设置
SRcamare插件导入设置参数。
3 改进思路
曲线道路的建模,车辆沿着曲线道路行驶。实际往往只存在连续3-4个直线十字路口。现实中没有笔直的的道路,不会都是是井子形状设计。
车辆变道功能实现,没有任何一辆车在起始点与终点间不转弯,不会只在直行左转车道上。
4 发展前景
如果有验证信号灯算法的需求,本程序可以导入信号灯算法,调整算法参数,更改车流量,用最短时间,最直观的方式,多种测试来展现结果。比实际路口连续多天实验时间不断调整参数时间成本短。
5其他说明
上传的源程序中脚本GYRlight1,TurnLeftLight1中lighttime[0]、Turnlefttime[0]没有减3。上传不易,哎嘿,特此声明一下。
如果设置自适应算法,根据车流量改变信号灯分配时长。参考该代码,把源码中GYRlight1更新成这个代码,设置一下碰撞体的范围就可以了。不设置范围没有影响,意味着没有开启调节算法。一个路口只设置红绿灯杆x0中的直行就可以,不需要4个方向都设置。算法可以根据需要自行调整。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
public class GYRlight1 : MonoBehaviour
{
//传值x00x11
public int whichlight;
[Tooltip("延迟开始")]
public float delaytime;
[Tooltip("第几个灯")]
public int i;
[Tooltip("灯的秒数的数组")]
public int[] Lighttime = new int[] {4,1,5};
//[Tooltip("判断是否为X方向")]
//public bool x=false;
public static GYRlight1 instance;
//[Tooltip("汽车点位")]
//public Transform[] carCreatpoints;
[Tooltip("灯的颜色初始化设置")]
public int initialsetting;
public int T,gt,rt;
[Tooltip("碰撞检测范围")]
public Vector3 offset, CheckCarNumsize ;
[Tooltip("参数改变的脚本引用")]
public GYRlight1 x0, x1, z0, z1;
public TurnLeftLight1 leftx0, leftx1, leftz0, leftz1;
RYBpoleBase rYBpoleBase;
private RYBpole RYBpole;
private void Awake()
{
instance = this;
}
void Start()
{
if (transform.parent.parent.GetComponent<RYBpoleBase>() != null)
{
rYBpoleBase = transform.parent.parent.GetComponent<RYBpoleBase>();
//放在start里面初始化值awake太早调用其他脚本
RYBpole = rYBpoleBase.RYBpole;
if (RYBpole != null)
{
print(transform.parent.parent.name + "绿灯是" + RYBpole.LightStraight_gt[whichlight]);
T = RYBpole.LightCycle;
gt =Lighttime[0] = RYBpole.LightStraight_gt[whichlight];
rt=Lighttime[2] = T - gt-3;
i = RYBpole.i[whichlight];
delaytime = RYBpole.dt[whichlight];
initialsetting = RYBpole.sc[whichlight];
}
}
else
{
gt = Lighttime[0];
rt = Lighttime[2];
}
if (initialsetting == 2)
{
transform.GetChild(2).GetComponent<MeshRenderer>().material.color = Color.red;
}
if (initialsetting == 1)
{
transform.GetChild(1).GetComponent<MeshRenderer>().material.color = Color.yellow;
}
if (initialsetting == 0)
{
transform.GetChild(0).GetComponent<MeshRenderer>().material.color = Color.green;
}
//第一周期加速
//StartCoroutine(SkipTime());
Invoke(nameof(Changecolor), delaytime);
}
private void Update()
{
if(Input.GetKeyDown(KeyCode.Alpha3))
{
Time.timeScale = 20;
}
if (Input.GetKeyDown(KeyCode.Alpha2))
{
Time.timeScale = 2;
}
if(Input.GetKeyDown(KeyCode.Alpha1))
{
Time.timeScale = 1;
}
}
/// <summary>
/// 跳过第一周期
/// </summary>
private IEnumerator SkipTime()
{
Time.timeScale = 20; // 设置时间缩放为2倍
yield return new WaitForSecondsRealtime((gt+rt+3)/20-1); // 延迟10秒
Time.timeScale = 1; // 恢复时间缩放为正常速度
}
void Changecolor()
{
//Debug.Log(i);
//根据i的值决定颜色
//绿灯
if (i == 0)
{
transform.GetChild(0).GetComponent<MeshRenderer>().material.color = Color.green;
}
else
{
transform.GetChild(0).GetComponent<MeshRenderer>().material.color = Color.white;
}
//黄灯
if (i == 1)
{
transform.GetChild(1).GetComponent<MeshRenderer>().material.color = Color.yellow;
}
else
{
transform.GetChild(1).GetComponent<MeshRenderer>().material.color = Color.white;
}
//红灯
if (i == 2)
{
transform.GetChild(2).GetComponent<MeshRenderer>().material.color = Color.red;
}
else
{
transform.GetChild(2).GetComponent<MeshRenderer>().material.color = Color.white;
}
//改变i的大小
//绿灯
if (i == 0)
{
//绿灯前检测
//if (whichlight == 0)
//{
// CheckCarNum();
//}
Invoke(nameof(Changecolor), Lighttime[0]);
}
//黄灯
else if (i == 1)
{
Invoke(nameof(Changecolor), Lighttime[1]);
//黄灯时y检测
if (whichlight == 0)
{
CheckCarNum();
}
}
//红灯
else if (i == 2)
{
Invoke(nameof(Changecolor), Lighttime[2]);
红灯时检测
//if (whichlight == 0)
//{
// CheckCarNum();
//}
}
i++;
if (i == 3) i = 0;
}
/// <summary>
/// 改变周期、绿灯、红灯参数
/// </summary>
void CheckCarNum()
{
//归零
x0.Lighttime[0] = x0.gt;
x0.Lighttime[2] = x0.rt;
x1.Lighttime[0] = x1.gt;
x1.Lighttime[2] = x1.rt;
z0.Lighttime[0] = z0.gt;
z0.Lighttime[2] = z0.rt;
z1.Lighttime[0] = z1.gt;
z1.Lighttime[2] = z1.rt;
//函数
Collider[] hitcolliders = Physics.OverlapBox(transform.parent.parent.position + offset, CheckCarNumsize, Quaternion.identity, LayerMask.GetMask("ground"));
int targercount = hitcolliders.Length;
if (targercount>=4)
{
print("触发调节");
x0.Lighttime[0] += 3;
x0.Lighttime[2] -= 3;
//leftx0.Turnlefttime[2] += 3;
//leftx0.delaytime+=3;
x1.Lighttime[0] += 3;
x1.Lighttime[2] -= 3;
//leftx1.Turnlefttime[2] += 3;
//leftx1.delaytime += 3;
z0.Lighttime[0] -= 3;
z0.Lighttime[2] += 3;
//z0.delaytime += 3;
//leftz0.delaytime += 3;
z1.Lighttime[0] -= 3;
z1.Lighttime[2] += 3;
//z1.delaytime += 3;
//leftz1.delaytime += 3;
}
}
private void OnDrawGizmos()
{
if (whichlight == 0)
{
Gizmos.color = UnityEngine.Color.green;
Gizmos.DrawWireCube(transform.parent.parent.position + offset, CheckCarNumsize);
}
}
}