坐标系
背景
navmesh 升级后,导航数据改由新版 Recast Navigation 工具链生成。这里需要区分两个概念:
- NavMesh 源模型、生成工具和底层 Detour 使用的坐标约定。
KBEEntity 通过客户端协议同步的位置和方向。
NavMesh 升级不会改变 Entity 协议中的位置顺序。KBE 仍然使用 Y 轴表示高度、XZ 作为水平面,位置按照 (x, y, z) 传入 Detour,路径点也按照 (x, y, z) 返回。
客户端是否需要转换位置,取决于客户端引擎自身的坐标定义。默认前向为 +Z 或 -Z 主要影响模型朝向,不能据此直接交换位置的 Y/Z 轴。
基本约定
KBE世界坐标使用 Y-up,水平面为 XZ。- Entity 位置字段顺序为
(x, y, z),NavMesh 升级不会自动改变该顺序。 - 位置和朝向必须分开处理。位置轴一致时直接使用;模型默认前向不一致时只调整朝向。
旋转字段中:
x = rolly = pitchz = yaw
当前大部分客户端场景中,主要关注的是 yaw 的转换。
接入原则
- 先确认客户端引擎的向上轴、水平轴、默认前向和长度单位。
- 出生、普通位置更新、平滑移动、传送、服务端纠正和客户端回传必须使用相同规则。
- 客户端回传服务端时执行下发映射的反向操作。
- 模型朝向存在固定偏差时,优先调整模型或其渲染子节点;确需在代码中补偿时,保持下发和回传对称。
各客户端坐标转换
以下公式以 KBE Entity 坐标为输入,转换到当前配套 Demo 使用的客户端坐标。
Unity
Unity 侧需要对 x 轴做翻转。
位置:
text
x' = -x
y' = y
z' = z朝向:
text
yaw' = -yaw说明:
- 由于协议中的
z表示yaw,也可以写成:yaw = -z。
Cocos Creator
Cocos Creator 3.x 与 KBE 都使用 Y 轴表示高度,位置直接使用,不翻转 X,也不交换 Y/Z。
位置:
text
x' = x
y' = y
z' = z朝向:
text
yaw' = yaw + 180当前 Demo 中可以直接写:
ts
const targetPos = new Vec3(
this.position.x,
this.position.y,
this.position.z
);
this.renderObj.setPosition(targetPos);这里的 yaw + 180 用于适配 Demo 模型的默认前向,不属于位置转换。如果模型资源本身已经朝向正确,不应重复增加偏移。
Godot
Godot 4 与 KBE 都使用 Y 轴表示高度,位置同样直接使用。
位置:
text
x' = x
y' = y
z' = z朝向:
text
yaw' = yaw + 180当前 C# Demo 中的位置更新写法为:
csharp
((Node3D)this.renderObj).GlobalPosition = new Vector3(
position.x,
position.y + 1.0f,
position.z
);部分角色控制器会在 Y 轴增加角色高度偏移。该偏移需要在客户端回传时从 Y 轴减掉,它不是坐标轴转换。
Unreal Engine
UE 除了轴映射外,还需要注意长度单位差异。KBE 常用米制逻辑单位,而 UE 默认使用厘米,因此位置需要乘以 100。
位置:
text
x' = x * 100
y' = z * 100
z' = y * 100朝向:
text
yaw' = yaw + 90对照汇总
| 客户端 | 位置转换 | 朝向转换 |
|---|---|---|
| Unity | (-x, y, z) | yaw = -yaw |
| Cocos Creator | (x, y, z) | 当前 Demo 模型:yaw = yaw + 180 |
| Godot | (x, y, z) | 当前 Demo 模型:yaw = yaw + 180 |
| Unreal Engine | (x * 100, z * 100, y * 100) | yaw = yaw + 90 |
注意事项
- 如果客户端还存在“客户端坐标回传服务端”的逻辑,必须实现反向转换,不能只做单向适配。
yaw + 180、yaw + 90后,建议统一做角度归一化,例如限制到[-180, 180)或[0, 360)。- 不要把
+Z/-Z默认前向差异直接理解成位置必须交换 Y/Z。 - Cocos Creator 或 Godot 若使用
(-x, z, y),会把 Y 高度错误地换到 Z 轴,导致角色高度和水平位置异常。 - NavMesh 二进制格式已经升级,旧
.navmesh不能通过改名复用,必须用当前工具从场景源模型重新生成。
