汽车的行驶以及等红绿灯的场景进行模拟,使用了pixi
和webgl
去实现的。
webgl 主要是用在 pixi 中 spite 的着色器
的编写。
github 预览地址
gitee 预览地址
网速慢的慢的话建议gitee 预览地址
,效果会更好
如果感觉不错的话,给 star
右侧区域:
左侧区域: 汽车的模式目前是有四种:原始模式
、多彩模式
、多彩闪光模式
、简笔画模式
。
多彩模式
是可以让原始的汽车颜色进行更换多种颜色的更换多彩闪光模式
是让汽车颜色一直随机变简笔画模式
是让汽车是简笔画的形态 这三部分是通过pixi
的sprite
用的webgl
来写了一部分sharder
完成的。车辆管理区域:
目前就两款车型,可以控制道路上行驶的车辆类型,禁用
可以让该类型的车辆不在道路上行驶,启用
则是相反,允许该车辆行驶。
首先车辆的行驶方向是上下左右四个方向,这四个方向的车辆我这里是采用了链表
的数据结构。
为什么要使用链表这种数据结构呢?
回答:老子乐意!!!
其实是平时只有刷算法题的时候才用到链表,平时工作用不到,所以就想用用链表来实现。当然用数组也能实现
。由于车辆要不断地删除添加的操作所以链表的效率会更高一些。这个项目中用到的链表也挺不难,就是链表的添加和删除,会这两个就能进行车辆的添加和删除。
车辆是pixijs
的sprite
,每种类型的车辆都是分为上下左右
四张图片。
添加车辆就是在链表的最后添加上 sprite ,存储数据是 carData
,它是个 useRef 。是分为left right top bottom
四个字段,代表四个方向,每个字段都是一个链表,是每个方向的车辆。根据每个方向要对 sprite 进行 x ,y 点进行初始化,根据前一个链表节点的 x 和 y ,计算添加车辆的 x 和 y 。
// direction: 方向 WIDTH 画布的宽度 HEIGHT 画布的高度 ROADWIDTH 道路的宽度 // 该链表如果存在节点最后添加节点,没有节点直接放入 if (carData.current[direction]) { let current = carData.current[direction]!; // 拿到最后一个节点 while (current.next) { current = current.next!; } // 根据最后一个节点计算出要添加节点的位置 switch (direction) { case 'left': sprite.y = (HEIGHT - ROADWIDTH) / 2 + ROADWIDTH / 4; if (current.val.x >= WIDTH - CARLENGTH / 2) { sprite.x = current.val.x + CARLENGTH * 1.5; } else { sprite.x = WIDTH - CARLENGTH / 2; } break; case 'right': sprite.y = HEIGHT / 2 + ROADWIDTH / 4; if (current.val.x <= -CARLENGTH / 2) { sprite.x = current.val.x - CARLENGTH * 1.5; } else { sprite.x = -CARLENGTH / 2; } break; case 'top': sprite.x = WIDTH / 2 + ROADWIDTH / 4; if (current.val.y >= HEIGHT + CARLENGTH / 2) { sprite.y = current.val.y + CARLENGTH * 1.5; } else { sprite.y = HEIGHT + CARLENGTH / 2; } break; case 'bottom': sprite.x = WIDTH / 2 - ROADWIDTH / 4; if (current.val.y <= -CARLENGTH / 2) { sprite.y = current.val.y - CARLENGTH * 1.5; } else { sprite.y = -CARLENGTH / 2; } break; } current.next = new ListNode(sprite); } else { if (direction === 'left') { // 向左行驶的车辆位置 sprite.y = (HEIGHT - ROADWIDTH) / 2 + ROADWIDTH / 4; sprite.x = WIDTH - CARLENGTH / 2; } else if (direction === 'right') { // 向右行驶的车辆位置 sprite.y = HEIGHT / 2 + ROADWIDTH / 4; sprite.x = -CARLENGTH / 2; } else if (direction === 'top') { // 向上行驶的车辆位置 sprite.x = WIDTH / 2 + ROADWIDTH / 4; sprite.y = HEIGHT + CARLENGTH / 2; } else if (direction === 'bottom') { // 向下行驶的车辆位置 sprite.x = WIDTH / 2 - ROADWIDTH / 4; sprite.y = -CARLENGTH / 2; } carData.current[direction] = new ListNode(sprite); }
车辆行驶需要考虑几点:
下面一一解答:
阈值 min
,当后车与前车的距离小于这个min
的时候,需要让后车相对于前车速度进行减速,如果减速还是会小于这个min
的话,说明前车在等红绿灯,这时候后车的位置等于这个min
就行了,保证不小于这个min
。阈值 min
,是为了让车减速,加速则是要设置车距之间的最大阈值 max
,车距超过这个max
就进行加速操作。因为pixijs
默认是使用webgl
去进行渲染的,sprite
是支持使用 webgl 编写着色器的
。根据选择的不同模式进行着色器 sharder 的编写完成的。
多彩模式
:生成一个随机的 rgb
三个通道的值,然后到通过uniform
传入到片元着色器与当前的色值进行相乘
,深色部分打算是保留下来的,所以设置一个阈值
,超过这个阈值代表浅色
,浅色部分会去跟随机 rgb 进行相乘。
多彩闪光模式
:和多彩模式实现是一样的,区别是动画每帧都会使用多彩,所以有了多彩闪光的一个效果。
简笔画模式
:这个是我写多彩模式的 sharder 无意间实现出来的,原理也是很简单的,将浅色部分都变成白色,只留下深色部分,就是简笔画的效果。
/** * 汽车颜色滤镜 * @param sprite */ export const carFilterColor = (sprite: Sprite, type?: number) => { const fragStr = ` varying vec2 vTextureCoord; uniform sampler2D uSampler; uniform vec2 size; uniform vec3 secondaryColor; uniform float type; void main(void){ vec2 uv = size; vec4 color = texture2D(uSampler, vTextureCoord); // 阈值 float num = 0.3; if(type != 0.0){ if(color.r > num || color.g >= num || color.b >= num){ if(type == 1.0 || type == 2.0 ){ / 多彩或多彩闪光模式 color.rgb *= secondaryColor; }else if (type == 3.0){ // 简笔画模式 color.rgb = vec3(1.0); } } color.rgb = clamp(vec3(0.0),color.rgb,vec3(1.0)); } gl_FragColor = color; } `; let filter = new PIXI.Filter(undefined, fragStr, { secondaryColor: [ Math.random() + 0.6, Math.random() + 0.6, Math.random() + 0.6, ], type: type || 0, }); sprite.filters = [filter]; };
还有一些细节点就不一一赘述了,如:交通灯的设计和控制、车辆类型的控制等等。
项目的GitHub地址忘了发出来了, https://github.com/dearDreamWeb/traffic_simulator.github.io
喜欢的话,请给个star