## 《极限竞速:地平线》系列 UDP 数据包完整定义 ### 基础信息 | 属性 | 值 | |------|-----| | **协议** | UDP(单向发送,游戏不接收数据) | | **包大小** | **324 字节(FH6)/ 约 311 字节(FH5)** | | **发送频率** | 等于游戏帧率 | | **字节序** | **Little Endian**(小端序) | | **数据起始** | 玩家开始驾驶后发送,菜单/暂停/回放时不发送 | > FH6 和 FH5 的数据包基本兼容,主要差异是 FH6 新增了 `CarGroup`、`SmashableVelDiff`、`SmashableMass` 三个字段。 --- ### 类型符号说明 | 符号 | 含义 | 示例 | |------|------|------| | `S8` / `S32` | 有符号整数(signed) | S8 范围 -128~127 | | `U8` / `U16` / `U32` | 无符号整数(unsigned) | U8 范围 0~255 | | `F32` | 32位浮点数(float) | 小端序 | --- ### 完整数据包结构(按顺序) #### 第 1 部分:状态和时间(8 字节) | 偏移 | 大小 | 类型 | 字段 | 说明 | |------|------|------|------|------| | 0 | 4 | S32 | `IsRaceOn` | 1=比赛中,0=菜单/停止 | | 4 | 4 | U32 | `TimestampMS` | 时间戳(毫秒,会溢出归零) | #### 第 2 部分:引擎数据(12 字节) | 偏移 | 大小 | 类型 | 字段 | 说明 | |------|------|------|------|------| | 8 | 4 | F32 | `EngineMaxRpm` | 引擎最大转速 | | 12 | 4 | F32 | `EngineIdleRpm` | 引擎怠速转速 | | 16 | 4 | F32 | `CurrentEngineRpm` | 当前引擎转速 | #### 第 3 部分:加速度与速度(24 字节,车身坐标系 X=右, Y=上, Z=前) | 偏移 | 大小 | 类型 | 字段 | |------|------|------|------| | 20 | 4 | F32 | `AccelerationX` | | 24 | 4 | F32 | `AccelerationY` | | 28 | 4 | F32 | `AccelerationZ` | | 32 | 4 | F32 | `VelocityX` | | 36 | 4 | F32 | `VelocityY` | | 40 | 4 | F32 | `VelocityZ` | #### 第 4 部分:角速度与方向(24 字节) | 偏移 | 大小 | 类型 | 字段 | 说明 | |------|------|------|------|------| | 44 | 4 | F32 | `AngularVelocityX` | 俯仰(rad/s) | | 48 | 4 | F32 | `AngularVelocityY` | 偏航(rad/s) | | 52 | 4 | F32 | `AngularVelocityZ` | 侧倾(rad/s) | | 56 | 4 | F32 | `Yaw` | 偏航角(弧度) | | 60 | 4 | F32 | `Pitch` | 俯仰角(弧度) | | 64 | 4 | F32 | `Roll` | 侧倾角(弧度) | #### 第 5 部分:悬挂行程(16 字节,归一化 0=最大拉伸, 1=最大压缩) | 偏移 | 大小 | 类型 | 字段 | |------|------|------|------| | 68 | 4 | F32 | `NormalizedSuspensionTravelFrontLeft` | | 72 | 4 | F32 | `NormalizedSuspensionTravelFrontRight` | | 76 | 4 | F32 | `NormalizedSuspensionTravelRearLeft` | | 80 | 4 | F32 | `NormalizedSuspensionTravelRearRight` | #### 第 6 部分:轮胎滑移比(16 字节,0=100%抓地力,|ratio|>1=失去抓地力) | 偏移 | 大小 | 类型 | 字段 | |------|------|------|------| | 84 | 4 | F32 | `TireSlipRatioFrontLeft` | | 88 | 4 | F32 | `TireSlipRatioFrontRight` | | 92 | 4 | F32 | `TireSlipRatioRearLeft` | | 96 | 4 | F32 | `TireSlipRatioRearRight` | #### 第 7 部分:车轮转速(16 字节,弧度/秒) | 偏移 | 大小 | 类型 | 字段 | |------|------|------|------| | 100 | 4 | F32 | `WheelRotationSpeedFrontLeft` | | 104 | 4 | F32 | `WheelRotationSpeedFrontRight` | | 108 | 4 | F32 | `WheelRotationSpeedRearLeft` | | 112 | 4 | F32 | `WheelRotationSpeedRearRight` | #### 第 8 部分:车轮状态(16 字节,0/1布尔值) | 偏移 | 大小 | 类型 | 字段 | |------|------|------|------| | 116 | 4 | S32 | `WheelOnRumbleStripFrontLeft` | | 120 | 4 | S32 | `WheelOnRumbleStripFrontRight` | | 124 | 4 | S32 | `WheelOnRumbleStripRearLeft` | | 128 | 4 | S32 | `WheelOnRumbleStripRearRight` | #### 第 9 部分:涉水深度(16 字节,FH5 为深度值,FH6 为 0/1) | 偏移 | 大小 | 类型 | 字段 | |------|------|------|------| | 132 | 4 | S32/F32 | `WheelInPuddleFrontLeft` | | 136 | 4 | S32/F32 | `WheelInPuddleFrontRight` | | 140 | 4 | S32/F32 | `WheelInPuddleRearLeft` | | 144 | 4 | S32/F32 | `WheelInPuddleRearRight` | > ⚠️ FH5 为 F32(水深值),FH6 为 S32(0/1布尔值) #### 第 10 部分:路面震动反馈(16 字节) | 偏移 | 大小 | 类型 | 字段 | |------|------|------|------| | 148 | 4 | F32 | `SurfaceRumbleFrontLeft` | | 152 | 4 | F32 | `SurfaceRumbleFrontRight` | | 156 | 4 | F32 | `SurfaceRumbleRearLeft` | | 160 | 4 | F32 | `SurfaceRumbleRearRight` | #### 第 11 部分:轮胎滑移角(16 字节) | 偏移 | 大小 | 类型 | 字段 | |------|------|------|------| | 164 | 4 | F32 | `TireSlipAngleFrontLeft` | | 168 | 4 | F32 | `TireSlipAngleFrontRight` | | 172 | 4 | F32 | `TireSlipAngleRearLeft` | | 176 | 4 | F32 | `TireSlipAngleRearRight` | #### 第 12 部分:轮胎复合滑移(16 字节) | 偏移 | 大小 | 类型 | 字段 | |------|------|------|------| | 180 | 4 | F32 | `TireCombinedSlipFrontLeft` | | 184 | 4 | F32 | `TireCombinedSlipFrontRight` | | 188 | 4 | F32 | `TireCombinedSlipRearLeft` | | 192 | 4 | F32 | `TireCombinedSlipRearRight` | #### 第 13 部分:实际悬挂行程(16 字节,单位:米) | 偏移 | 大小 | 类型 | 字段 | |------|------|------|------| | 196 | 4 | F32 | `SuspensionTravelMetersFrontLeft` | | 200 | 4 | F32 | `SuspensionTravelMetersFrontRight` | | 204 | 4 | F32 | `SuspensionTravelMetersRearLeft` | | 208 | 4 | F32 | `SuspensionTravelMetersRearRight` | #### 第 14 部分:车辆信息(20 字节) | 偏移 | 大小 | 类型 | 字段 | 说明 | |------|------|------|------|------| | 212 | 4 | S32 | `CarOrdinal` | 车辆唯一ID | | 216 | 4 | S32 | `CarClass` | 等级 0(D) ~ 7(X) | | 220 | 4 | S32 | `CarPerformanceIndex` | 性能指数 100~999 | | 224 | 4 | S32 | `DrivetrainType` | 0=FWD, 1=RWD, 2=AWD | | 228 | 4 | S32 | `NumCylinders` | 引擎气缸数 | #### 第 15 部分:FH6 新增字段(12 字节)⚠️ | 偏移 | 大小 | 类型 | 字段 | 说明 | |------|------|------|------|------| | 232 | 4 | U32 | `CarGroup` | 车辆组标识(FH6新增) | | 236 | 4 | F32 | `SmashableVelDiff` | 可破坏物体碰撞速度损失(m/s) | | 240 | 4 | F32 | `SmashableMass` | 碰撞物体质量(kg) | > ⚠️ **FH5 没有这三个字段**,此位置为 12 字节的占位数据(`HorizonPlaceholder`) #### 第 16 部分:坐标与速度(16 字节) | 偏移 | 大小 | 类型 | 字段 | 说明 | |------|------|------|------|------| | 244 | 4 | F32 | `PositionX` | 世界坐标 X(米) | | 248 | 4 | F32 | `PositionY` | 世界坐标 Y(米) | | 252 | 4 | F32 | `PositionZ` | 世界坐标 Z(米) | | 256 | 4 | F32 | `Speed` | 速度(m/s) | #### 第 17 部分:动力与温度(24 字节) | 偏移 | 大小 | 类型 | 字段 | 说明 | |------|------|------|------|------| | 260 | 4 | F32 | `Power` | 功率(瓦特) | | 264 | 4 | F32 | `Torque` | 扭矩(牛·米) | | 268 | 4 | F32 | `TireTempFrontLeft` | 轮胎温度 | | 272 | 4 | F32 | `TireTempFrontRight` | 轮胎温度 | | 276 | 4 | F32 | `TireTempRearLeft` | 轮胎温度 | | 280 | 4 | F32 | `TireTempRearRight` | 轮胎温度 | #### 第 18 部分:增压与燃油(8 字节) | 偏移 | 大小 | 类型 | 字段 | 说明 | |------|------|------|------|------| | 284 | 4 | F32 | `Boost` | 涡轮增压值(PSI,高于大气压) | | 288 | 4 | F32 | `Fuel` | 燃油量(0.0~1.0) | #### 第 19 部分:里程与圈时(28 字节) | 偏移 | 大小 | 类型 | 字段 | 说明 | |------|------|------|------|------| | 292 | 4 | F32 | `DistanceTraveled` | 总行驶距离(米) | | 296 | 4 | F32 | `BestLap` | 最佳圈时(秒) | | 300 | 4 | F32 | `LastLap` | 上一圈用时(秒) | | 304 | 4 | F32 | `CurrentLap` | 当前圈用时(秒) | | 308 | 4 | F32 | `CurrentRaceTime` | 总比赛时间(秒) | #### 第 20 部分:比赛信息(6 字节) | 偏移 | 大小 | 类型 | 字段 | 说明 | |------|------|------|------|------| | 312 | 2 | U16 | `LapNumber` | 已完成圈数 | | 314 | 1 | U8 | `RacePosition` | 当前排名位置 | #### 第 21 部分:玩家输入(6 字节,值范围 0~255) | 偏移 | 大小 | 类型 | 字段 | 说明 | |------|------|------|------|------| | 315 | 1 | U8 | `Accel` | 油门(0~255) | | 316 | 1 | U8 | `Brake` | 刹车(0~255) | | 317 | 1 | U8 | `Clutch` | 离合器(0~255) | | 318 | 1 | U8 | `HandBrake` | 手刹(0~255) | | 319 | 1 | U8 | `Gear` | 当前档位 | #### 第 22 部分:转向与AI(3 字节) | 偏移 | 大小 | 类型 | 字段 | 说明 | |------|------|------|------|------| | 320 | 1 | S8 | `Steer` | 转向(-127左 ~ 0中 ~ 127右) | | 321 | 1 | S8 | `NormalizedDrivingLine` | 归一化赛道线(-127~127) | | 322 | 1 | S8 | `NormalizedAIBrakeDifference` | AI刹车差异(-127~127) | --- ### 快速参考:字段总数 - **FH6**:约 77 个字段,总计 **324 字节** - **FH5**:约 74 个字段(比 FH6 少 CarGroup/SmashableVelDiff/SmashableMass),总计 **约 311 字节** - **FH4**:和 FH5 基本一致 --- ## Gear 字段的完整取值范围 在您看到的包结构中(offset 319, 1字节),`Gear` 字段的实际取值范围比常见的 `1~6` 要大得多: | Gear 值 | 含义 | 出现场景 | |---------|------|---------| | **0** | **N 档(Neutral,空档)** | 启动引擎未挂档、手动切空档 | | **1~10** | 前进档 1~10 档 | 正常行驶 | | **11** | **R 档(Reverse,倒车档)** | 倒车时 | | **12~20** | **保留/未使用** | 极少数特殊场景 | | **21** | **P 档(Park,驻车档)** | 赛车完全静止时极罕见触发 | | **22+** | 理论上可能更高(如 D 档标记) | 极少见 | ### Gear 值说明 这个值在正常游戏中几乎不会出现,因为《极限竞速:地平线》本质上没有"驻车"机制——汽车要么在跑,要么完全静止时引擎仍在空档(Gear=0)。但极少数情况下(如某些特殊漂移状态下),游戏引擎会短暂报告 Gear=21。 ### N 档的显示逻辑 N 档(Gear=0)相对常见,但游戏中 **UI 通常不会显示 "N"**,而是在仪表盘上显示 **"--"** 或空白。只有一些第三方遥测仪表才将其明确标注为 "N"。 --- ## 完整 Gear 映射表(社区共识) | 原始值 (U8) | FH4/FH5 UI 显示 | FH6 UI 显示 | 实际含义 | |------------|----------------|------------|---------| | 0 | `N` 或 `--` | `N` 或 `--` | 空档 (Neutral) | | 1 | `1` | `1` | 1档 | | 2 | `2` | `2` | 2档 | | 3 | `3` | `3` | 3档 | | 4 | `4` | `4` | 4档 | | 5 | `5` | `5` | 5档 | | 6 | `6` | `6` | 6档 | | 7 | `7` | `7` | 7档 | | 8 | `8` | `8` | 8档 | | 9 | `9` | `9` | 9档(极少车辆有) | | 10 | `10` | `10` | 10档(极少车辆有) | | **11** | `R` | `R` | 倒车档 (Reverse) | | **21** | `P` | `P` | **驻车档 (Park)**(极罕见) | | 255 | `?` | `?` | 无效/初始化状态 | --- ## 为什么您很少见到 P 档? 原因有两个: 1. **FH 系列赛车游戏没有"驻车"机制**——汽车没有手刹锁止的"P"状态,手刹是 `HandBrake` 字段单独控制 2. **Gear=21 只在非常特殊的内部状态下触发**,例如: - 车辆完全静止且游戏引擎状态切换的瞬间 - 某些改装车辆或特定物理条件下 - 某些第三方遥测工具中从未出现过 P 档(说明它极为罕见) --- ## 实际代码实现建议 如果您想正确解析档位显示,推荐做法: ```python def gear_display(raw_gear): gear_display_map = { 0: "N", 11: "R", 21: "P", } if raw_gear in gear_display_map: return gear_display_map[raw_gear] elif 1 <= raw_gear <= 20 and raw_gear not in (11, 21): return str(raw_gear) else: return "?" ``` 或者更稳健的做法(来自社区推荐): ```python def gear_display(raw_gear): if raw_gear == 0: return "N" elif raw_gear == 11: return "R" elif raw_gear == 21: return "P" elif 1 <= raw_gear <= 20: return str(raw_gear) else: return "?" ```