1. 它是什么
遮挡墙的后缀文件是 .unr,它表示一个“动态不可达区域”。
它不是地图文件,也不是模型文件,而是一个动态碰撞区域模板。
简单理解:
.unr 文件 = 一堵墙的形状
DynamicRegion.txt = dataid 和 .unr 文件的对应关系
EnableDynamicRegion = 把这堵墙放到副本里的某个坐标和方向
例如:
9 1000cm_x_200cm.unr unr_01
10 1400cm_x_200cm.unr unr_01
11 2000cm_x_200cm.unr unr_01
12 3000cm_x_200cm.unr unr_01
表示:
dataid=9 使用 1000cm_x_200cm.unr
dataid=10 使用 1400cm_x_200cm.unr
dataid=11 使用 2000cm_x_200cm.unr
dataid=12 使用 3000cm_x_200cm.unr
名字里的 1000cm_x_200cm 只是方便人看,真正生效的是 .unr 文件内部的点数据。
2. 它怎么被使用
副本脚本里一般这样启用遮挡墙:
self:EnableDynamicRegion(index, dataid, posx, posz, dir)
参数含义:
index = 当前场景里的动态墙编号
dataid = DynamicRegion.txt 里的 DataId
posx = 墙中心点 X 坐标
posz = 墙中心点 Z 坐标
dir = 墙方向,弧度制
完整流程:
EnableDynamicRegion
↓
根据 dataid 查 DynamicRegion.txt
↓
找到对应 .unr 文件
↓
读取 .unr 里的 4 个相对点
↓
厘米转米
↓
按 dir 旋转
↓
加上 posx / posz
↓
得到真实副本里的四边形遮挡区域
3. DynamicRegion.txt 的作用
DynamicRegion.txt 是索引表。
格式:
DataId FileName Dyname
9 1000cm_x_200cm.unr unr_01
10 1400cm_x_200cm.unr unr_01
11 2000cm_x_200cm.unr unr_01
12 3000cm_x_200cm.unr unr_01
字段说明:
DataId 策划脚本里使用的 dataid
FileName 实际读取的 .unr 文件名
Dyname .unr 文件内部的区域名称
注意:
.unr 文件里可以理论上包含多个区域,Dyname 用来指定读取哪一个区域。当前常见情况都是 unr_01。
4. .unr 文件内部结构
.unr 是二进制文件,不是普通文本。
当前解析格式大概是:
char[16] "UnreachRegionNum"
uint32 region_count
重复 region_count 次:
uint32 name_len
char[] name
char[11] "RegionIndex"
uint32 region_index
char[14] "RegionPointNum"
uint32 point_num
char[14] "RegionPointPos"
重复 point_num 次:
float x_cm
float y_cm
float z_cm
重点:
- 数字是小端序。
- 坐标单位是厘米。
- 服务端只使用
x_cm 和 z_cm。
y_cm 是高度,当前遮挡墙逻辑一般不用。
- 当前服务端要求
RegionPointNum == 4,也就是 4 个点组成一个四边形。
5. 举例:1000cm_x_200cm.unr
这个文件解析出来类似:
点1:(-500, 0, 100) cm
点2:( 500, 0, 100) cm
点3:( 500, 0, -100) cm
点4:(-500, 0, -100) cm
服务端会厘米转米:
点1:(-5, 1)
点2:( 5, 1)
点3:( 5, -1)
点4:(-5, -1)
所以这个 .unr 实际表示一个:
长 10 米
宽 2 米
中心点在 0,0
的矩形遮挡区域。
6. 坐标转换原理
.unr 文件里的点都是“本地相对坐标”。
也就是说,它只描述:
这堵墙自身长什么样
它不知道这堵墙应该放在副本哪里。
真正放到副本中时,需要脚本传入:
posx
posz
dir
服务端转换公式是:
world_x = center_x + local_x * cos(dir) + local_y * sin(dir)
world_y = center_y - local_x * sin(dir) + local_y * cos(dir)
其中:
center_x / center_y = 副本里的墙中心点
local_x / local_y = .unr 里读出来的相对点
dir = 墙方向
world_x / world_y = 最终真实遮挡点
7. 策划怎么选择 dataid
如果只是想放一堵不同长度的墙,优先用已有 dataid。
常见例子:
9 1000cm_x_200cm.unr 长 10 米,宽 2 米
10 1400cm_x_200cm.unr 长 14 米,宽 2 米
11 2000cm_x_200cm.unr 长 20 米,宽 2 米
12 3000cm_x_200cm.unr 长 30 米,宽 2 米
脚本里:
{ dataid = 11, posx = 215, posz = 54, dir = 0.45 }
含义是:
使用 2000cm_x_200cm.unr
把一堵 20 米 × 2 米的墙
放到副本坐标 215,54
方向旋转 0.45 弧度
8. 常见问题
1. 为什么墙看得到,但玩家能穿过去?
通常有两种原因:
客户端显示墙了,但服务端没有保存动态墙状态
或者:
服务端没有用 .unr 算出来的四边形做移动拦截
只广播 GCSetDynamicRegion 不够,服务端也要保存并校验动态墙。
2. 为什么方向不对?
常见原因:
dir 填错
坐标轴理解错
.unr 点方向和 NPC 朝向不一致
dir 是弧度,不是角度。
常见换算:
0 = 0 度
1.57 = 90 度
3.14 = 180 度
4.71 = 270 度
6.28 = 360 度
3. 为什么墙范围变得特别大?
大概率是忘了厘米转米。
.unr 里是厘米,服务端场景坐标是米。
必须:
scene_x = x_cm * 0.01
scene_y = z_cm * 0.01
如果不乘 0.01,墙会放大 100 倍。
4. 为什么有的 .unr 文件解析失败?
检查:
文件开头是不是 UnreachRegionNum
Dyname 是否存在
RegionPointNum 是否为 4
DynamicRegion.txt 里的文件名是否正确
服务端 DynamicRegion 目录下是否有该文件
9. 调试建议
调试遮挡墙时,建议打印这些信息:
region_id
dataid
file_name
dyname
center_x
center_y
dir
4 个本地点
4 个世界点
这样可以直接判断:
是不是 dataid 找错
是不是 .unr 读错
是不是方向转错
是不是坐标放错
如果玩家反馈“某个位置还能穿”,就拿玩家移动线段和动态墙四边形做交叉判断,确认服务端是否真的拦截到了。
10. 总结
.unr 文件的本质是:
动态遮挡墙的碰撞模板
它只负责定义墙的形状,不负责决定墙放在哪里。
真正决定墙位置的是:
EnableDynamicRegion(id, dataid, x, z, dir)
所以完整理解应该是:
DynamicRegion.txt 决定 dataid 用哪个 .unr
.unr 决定墙的本地形状
副本脚本决定墙的位置和方向
服务端动态墙系统决定是否保存、同步、阻挡移动
只要这四部分一致,客户端显示、服务端拦截、重连补同步才会完整正常。