城市环境车流规格
更新:2026-10-17。本文定义广州城市环境车流(类 GTA 的行驶车流)与共享单车的规则,包括第一阶段与第二阶段(信号灯、昼夜密度、共享单车)。按双宿主分工,完整车流先在 Godot 原生客户端实现;规则写成共享规格与测试用例,Web 只在需要展示时按同一份规则接入片段。
规则来源与对照方式
| 内容 | 位置 | 说明 |
|---|---|---|
| 规则数值 | packages/dayun-kicker-content/data/city-traffic-rules.json | 道路速度与权重、车队构成、生成半径、跟车 / 路口 / 踢车参数、洒水车与环卫车节奏 |
| 参考实现 | packages/dayun-kicker-content/src/city-traffic.ts | 车道图构建(构建期)与车流运行时(TypeScript 参考) |
| 共享测试用例 | packages/dayun-kicker-content/src/city-traffic-fixtures.ts | 在合成街区网格上运行参考实现,记录每一步的车辆状态与事件 |
| Godot 实现 | apps/dayun-kicker-godot/scripts/world/city_traffic.gd | 逐行移植的 GDScript 运行时 |
| 对照测试 | apps/dayun-kicker-godot/tests/traffic-contracts.gd | 回放上述用例,位置、速度和朝向允许 1e-4 的相对误差;对照信号相位与昼夜密度采样;并检查导出的广州车道图可以生成车流、有车道接入信号灯 |
| 闯红灯与处罚 | city-traffic-rules.json 的 violations 段、src/traffic-violations.ts | 停车线判定与记分 / 派单管控;用例在 city-traffic-fixtures.ts 的 trafficViolationFixtures,Godot 由 traffic-contracts.gd 回放、vehicle-ride-contracts.gd 验证会话行为 |
| 共享单车 | packages/dayun-kicker-content/data/shared-bike-rules.json、src/shared-bike.ts | 停放点规划、骑行参数与示意车费;Godot 由 tests/shared-bike-contracts.gd 验证解锁、骑行、还车与存档 |
两端用同一套可复现的运算:整数线性同余随机数(TS 用 Math.imul,GDScript 用 64 位整数取低 31 位)、sqrt(dx * dx + dz * dz) 而非 Math.hypot、1/30 秒固定子步长和按生成顺序遍历。修改规则时先改 JSON 和参考实现,再同步 GDScript,pnpm godot:test 会重新生成用例并回放。
洒水车 / 环卫车的节奏与提示曲同时供 Web 比赛车流使用(apps/dayun-kicker/src/game/serviceTraffic.ts 与音频目录);信号灯相位同时驱动 Web 街区路口的灯组与公交。两端只有一份数值。
车道图
构建期由 scripts/godot/chapter.ts 调用 buildTrafficLanes,从与导航相同的可骑行、陆上道路生成,写入 content.json 的 chapter.cityTraffic。
- 参与道路:
trunk / primary / secondary / tertiary(含_link)、unclassified、residential、living_street;排除人行道、服务道路、过街段、私有道路、隧道与地下层,宽度不足 4.5 米的路段不行车。带实测节点高度的高架 / 立交道路纳入车道并沿高度行驶;没有节点高度的普通桥梁(桥面由其他模型生成)仍不行车。车辆之间、车辆与骑手的跟车、踢车、撞车和水幕判定都要求高差不超过 3 米,高架上的车不会因为桥下的车或骑手而停下。全城共有 79 条高架车道,但目前第一章固定地图范围内没有连通的高架车道,要等车流覆盖全城(M4 分块世界)后才会出现。范围与章节地图的固定裁剪网格一致。 - 右侧通行:单向道路一个车道,位于道路中线;双向道路每个方向一个车道,向右偏移道路宽度的 1/4(1.1–3 米)。单向车道放在中线,避开新港中路公交的路缘车道。
- 路口:车道终点是路口节点。路口去向不含原路掉头;只有断头路才允许掉头。每条车道记录
stop:该路口最宽道路宽度的一半,用作停车线距离。 - 连通性:只保留最大的强连通分量,任何车辆都能一直开下去,不会困在地图边界。当前第一章地图导出 536 条车道。
生成与回收
以骑手为中心维持一个有上限的车流泡:
- 同时最多 16 辆。开局一次填满(最近 35 米);之后每 0.4 秒最多尝试补 1 辆,只在 110–190 米的环带内生成;离骑手超过 240 米即回收。
- 生成时随机选车道与位置,避开同车道 18 米内和任意车辆 10 米内,以及公交周围。
- 车队按权重抽取:小汽车 48、微面 14、面包车 12、厢式货车 14、洒水车 6、环卫车 6。洒水车和环卫车同时最多各 1 辆。
- 车流不写入存档;读档或重新进入后按同一规则重新生成。
行驶
速度单位为 m/s。车辆期望速度 = 道路速度 × 车型系数(小汽车 1、微面 0.95、面包车 0.92、货车 0.85、洒水车 0.55、环卫车 0.6)。道路速度:快速路 13、主干道 12、次干道 11、支路 9.5、无等级道路 7.5、居住区道路 6.5、生活街道 4。
- 加减速:加速度 2.2,跟车 / 停车制动 4.5,进路口制动 3;转弯连接段限速 5。
- 跟车与避让:在前方
8 + 速度 × 2.2米的车宽通道内,对前车、公交和骑手保持 2.5 米间隙。骑手在通道内时车辆停下等待,不会撞上骑手。 - 路口通行:车辆进入路口前 14 米开始检查。若路口已有车占用(转弯中或刚驶出 5 米内),或有 id 更小的横向来车也在接近,就在停车线(路口边缘 + 半车长 + 1 米)前停车。横向车辆只让行 id 更小的车,规则严格有序,不会互相等待成死锁;同向车辆由跟车规则处理。
- 转弯选择:按去向车道的道路权重 × 转向系数(直行 3、转弯 1.5、急转 0.4)随机选择。车辆在驶入一条车道时就选好下一条车道,信号灯据此区分直行、左转与右转。
信号灯
信号路口来自 Web 街区的道路设施(同一套路口、进口方向和灯组位置),构建期随美术一起导出;当前第一章地图有 12 个信号路口、188 个灯组,50 条车道接入信号进口。
- 相位:每个轴依次为直行绿灯 30 秒、黄灯 3 秒、全红 2 秒,再是左转箭头绿灯 12 秒、黄灯 3 秒、全红 2 秒;两个轴共 104 秒一周。Web 街区灯组、Web 公交和 Godot 车流使用同一个函数。
- 停车线:车道终点落在信号路口 26 米内、且行驶方向对准某个进口时,该车道受这个进口控制;停车线位于进口半径处。
- 通行:红灯在停车线前停车;黄灯时若已来不及停下则通过;直行看直行相位,左转看箭头相位,右转可在红灯时通过(仍遵守路口占用让行)。车头越过停车线 1 米后视为已进入该路口,不再受同一路口的灯约束。
- 灯组显示:Godot 用每种灯型一组实例化网格显示 Web 导出的灯组,按同一相位每 0.2 秒更新颜色;行人灯在剩余 5 秒内闪烁。
昼夜密度与声音
- 车流上限随时间变化:0 时 30%、5 时 25%、7 时 80%、9–18 时 100%、21 时 70%,其间线性插值。
- 最近的车辆在 38 米内发出低频行驶声,货车更低沉;洒水车提示曲与踢车、淋湿音效见下文。
多车道变道、桥梁和高架行车仍不在当前范围。
洒水车与环卫车
- 环卫车:11 秒一个周期,行驶 6.8 秒,1.2 秒平稳刹停,在收运点停 2.2 秒,再起步。每辆车的相位按 id 错开。
- 洒水车:车尾 7 米、左右各 1.55 米范围是水幕区。骑手第一次进入时提示「被洒水车淋湿」并播放水声,每辆车只淋一次,不影响分数、能量和速度。58 米内每 3.6 秒播放一次原创音乐盒提示曲,10 米内最响,随距离线性减弱并按左右方位声像偏移。
- Godot 中洒水车附带半透明水幕与湿路面模型,被踢飞时隐藏。
踢车
骑行时按左 / 右踢(Q / R,手柄 LB / RB),先判定章节感染者;没有命中时,踢出所在侧 3.6 米内、前后 3.2 米 + 半车长范围内最近的车辆。车辆获得侧向 9 m/s、向上 6.5 m/s 的初速度与翻滚,落地后摩擦减速,2.6 秒后移除。踢车有 0.45 秒冷却,只提示「踹飞路过车辆」,不计分、不写入存档。
美术与碰撞
车辆模型来自 Web 的程序模型(小汽车、微面、面包车、厢式货车、洒水车、环卫车),构建期按材质合并后导出 GLB(每辆 5–10 个网格),原点在车辆中心地面,-Z 为车头。行驶中的车辆作为动态碰撞盒交给原生 Rapier 查询世界;飞行中的车辆不参与碰撞。
共享单车
城市里有原创虚构品牌「云骑单车」的共享单车,作为步行之外的第三种出行方式。规则在 shared-bike-rules.json 与 shared-bike.ts,模型由 Web 程序模型 sharedBikeModel.ts 导出。
- 停放点:构建期在客村骑手站、公交站和章节地点附近寻找人行道位置:离车行道边缘 0.9–3.2 米、离建筑与小型街具(灯杆、树、候车亭)至少 0.9 米、不在水面上,相邻停放点相隔至少 55 米。每处并排 4 辆,车头朝向马路。当前第一章地图有 15 处、60 辆。
- 解锁与还车:下车步行后,靠近单车 2.4 米内会提示,停稳按 F 扫码解锁;电驴留在原地,单车骑行中再按 F 在当前位置还车,人站到车旁。附近有电驴时 F 优先上电驴。
- 骑行:最高 26 km/h、无加速冲刺,加速、刹车和滑行比电驴温和;草地上限 14 km/h。可以踢车和感染者,送餐配送照常进行。
- 车费:示意价格 1.5 元 15 分钟,之后每 15 分钟 1 元,只在还车时提示,不扣积分。
- 存档:记录正在骑的单车和离开原停放点的单车位置;读档后单车留在上次还车的地方,骑行中读档继续骑行。校验拒绝未知单车、步行时又在骑车、同一辆车既在骑又在停放等矛盾状态。回到检查点时单车停在当前位置。
驾驶街上的车辆
类似 GTA,下车步行后可以接管环境车流里的车辆自己开。规则在 city-traffic-rules.json 的 drive 段,driveTuning 由电驴参数派生各车型的驾驶参数并导出给 Godot。
- 上车:步行时按 F,按优先级依次是:3 米内的电驴、2.4 米内的共享单车、正在停靠的公交(见下节)、车身 2.2 米内的车辆。行驶中的车速度需低于 3 m/s(例如在红灯前停下)才能上车,司机让出车辆,车从车流中移除。
- 驾驶:各车型有自己的最高速度、加减速、轴距和转向角:小汽车 80 km/h、微面 70、面包车 65、厢式货车 55、洒水车与环卫车 45 km/h,均无冲刺;车越长转弯半径越大。车身用原生 Rapier 的车身盒扫掠与墙和其他车辆碰撞。驾驶时不能踢击;车流把你的车当作同尺寸障碍物跟车避让。开着洒水车时尾部同样喷水。
- 车灯:夜间车头灯跟随你正在驾驶 / 乘坐的车辆或骑行的单车,停在路边的电驴不再亮灯。
- 下车:停稳(低于 1.2 m/s)按 F,从驾驶侧(左侧)下车,左侧被挡时从右侧下车;车停在原地,按 F 可以再坐进去。最多保留最近 3 辆,更早的会被收走。
- 撞车:驾驶时速度达到 7 m/s,车头 1.2 米内碰到的车流车辆会被撞飞(沿行驶方向带走 80% 车速并向侧面推开,越快抛得越高),你的车同时减速 30%。规则在
kick.ram,运行时ram()与 GDScript 逐行对应并有回放用例。 - 存档:记录正在驾驶的车型和留在路边的车辆;读档后继续驾驶或在原处找到车辆。校验拒绝未知车型、步行时又在驾驶、超过上限的停放车辆。回到检查点时车辆停在当前位置。
坐公交
新港中路两条公交线可以作为乘客乘坐,规则在 data/transit-ride-rules.json:
- 上车:公交停靠在站点时,站在该站候车亭 9 米内、离车 14 米内按 F 上车,车门会多等 2 秒。电驴留在原地。
- 乘坐:镜头跟在公交后方;按住 Shift 以 4 倍速快进。公交按自己的时刻表行驶和停靠,到站时提示站名。
- 下车:只有公交停靠在站点时才能按 F 下车,乘客出现在该站候车亭,提示示意车费 2 元(不扣积分);行驶途中不能下车。
- 存档:乘车途中不单独存档,读档后在上车的站点步行开始。
- 公交和接驳车改用 Web 的公交模型导出(10.4 米公交、7.2 米接驳车),碰撞尺寸与模型一致。
出租车
车流中会出现原创虚构的「云的士」(奶油色车身、青绿腰线、车顶灯),权重与其他车型一起抽取,可以作为乘客乘坐。规则在 transit-ride-rules.json 的 taxi 段,车费函数 taxiFare 在 transit-ride.ts。
- 招手:先在地图上选一个 30 米以外的目的地(任务目标或自由骑行目的地),步行时出租车在车身 8.6 米内经过就按 F 招手,出租车靠边接你并离开车流。出租车不能被劫车。
- 乘坐:出租车沿导航路线以最高 12 m/s 行驶,接近终点平稳减速;它与同向前车保持 3 米间隙,在信号路口按同一相位遇红灯停在停车线前(直行相位判断)。横向车流把出租车当作障碍物避让,出租车只跟随同向车辆,因此两者不会互相等待;镜头跟车,按住 Shift 以 4 倍速快进。
- 下车:到达目的地自动下车;途中按 F 可随时靠边下车。车费按广州巡游出租车运价示意(见下节),按上车时刻计价,四舍五入到元,不扣积分。
参照广州交通规则
以下数值取自公开报道,只用来让游戏里的计价和提示更像广州,不构成法律或出行建议;罚款只出现在提示里,不扣积分。实际执行以主管部门最新公告为准。
| 规则 | 游戏实现 | 来源 |
|---|---|---|
| 巡游出租车:起步 12 元含 3 公里,之后每公里(续租价)2.6 元 | transit-ride-rules.json 的 taxi.fare,taxiFare(metres, hour) | 羊城晚报:下半年浮动运价维持不变 |
| 夜间服务费 23:00–次日 5:00 按续租价加收 30%;起步价、续租价参与浮动,夜间服务费不浮动 | nightStart / nightEnd / nightSurcharge | 羊城晚报:广州的士拟实行浮动运价 |
| 浮动运价:高峰 7–9 时、17–19 时上浮 10%,平峰 12–14 时下浮 10%;公告执行期为 2025-07-01 至 2025-12-31,之后的执行情况未核实 | peak / offPeak 与比例;运价调整时只改 JSON | 广州市政府:71 家巡游出租车企业运价浮动保持一致 |
| 机动车闯红灯记 6 分、罚款 200 元;记满 12 分暂停驾驶 | 开车闯红灯提示记分,累计 12 分后 120 秒内不能接管车辆(游戏缩放),随即清零 | 汽车之家:闯红灯处罚 |
| 《广州市电动自行车管理规定》(2024-12-30 起施行):不按信号或车道通行处警告或 50–200 元罚款 | 骑电驴或共享单车闯红灯提示警告或罚款 50–200 元 | 羊城晚报:电动自行车管理规定 |
| 外卖骑手一周内 3 次闯红灯、超速、逆行等严重违法,平台须限制派单 | 900 秒游戏时间内 3 次闯红灯,平台派单管控 120 秒,期间不能接单 | 澎湃新闻:骑手交通违法与派单管控 |
- 停车线判定:玩家骑行或驾驶时,沿某个信号路口的进口方向越过停车线(距路口中心为该进口半径、横向偏离不超过同一半径)记下此刻直行与左转箭头的灯色;离开路口时按离开方向判定直行、左转、右转或倒出。直行或左转时对应灯色为红才算闯红灯;黄灯、左转箭头绿灯、右转(规则允许红灯右转)和倒出路口都不算。高于垂直净空的高架上不判定,步行不判定。
- 记录:记分与派单管控只保存在本次会话,不写入存档;时间用车流时钟,乘坐快进时同样加速。HUD 读数显示驾照记分与剩余的禁驾 / 派单管控秒数,闯红灯时播放低音提示。
- 未采用:网上未能核实广州全城禁鸣区域与公交专用道时段,暂不实现;电动自行车非机动车道 15 km/h 限速、头盔与逆行判定留待后续。
- 存档:乘车途中不单独存档,读档后在上车处步行开始。
当前边界
- 已实现:驾驶接管的车辆与留车、撞飞车流车辆、驾驶时的夜间车灯、出租车(招手、按路线行驶、跟车、遇红灯停车、快进、到站或中途下车、按广州运价与时段计价)、闯红灯判定与记分 / 派单管控、带实测高度的高架车道规则、坐公交(上车、跟车镜头、快进、到站下车);Godot 原生客户端的车道图、车流泡、跟车、骑手避让、路口让行、信号灯(停车、黄灯判断、左转箭头、右转可红灯通过)与灯组动画、昼夜密度、车辆行驶声、洒水车 / 环卫车行为与音效、踢车、共享单车(停放点、解锁、骑行、还车、存档),以及共享规格与回放测试。
- 未实现:开车的乘客 / 合作多人共乘、公交线路选择与换乘、出租车左转箭头判断、第一章范围内的高架车流、多车道与变道、喇叭、车流车辆的夜间车灯光照、按区域调整密度、车辆避让行人、合作模式同步、Web 宿主接入、逆行与限速判定。这些按后续阶段推进。