DetailLevel 详情级别
DetailLevel 是 EntityDef 提供的属性同步分级机制。它根据观察者与目标 Entity 的水平距离,将关系划分为 NEAR、MEDIUM、FAR 或超出全部级别,并决定目标 Entity 的哪些属性需要继续同步给观察者客户端。
典型用途包括:
- 近距离同步战斗姿态、动作阶段和精细外观;
- 中距离同步名称、阵营、状态和简化外观;
- 远距离只同步客户端仍需持续更新的基础属性;
- 在大 View、高密度 AOI 中减少属性变更产生的网络包量和序列化开销。
DetailLevel 只是一种同步优化,不是权限控制。敏感数据不应依赖距离隐藏,而应使用正确的属性 Flags,或根本不把数据暴露给客户端。
工作边界
在使用之前,需要先区分它与其他几个概念:
| 机制 | 控制内容 |
|---|---|
| View | Entity 是否处于某个客户端的 AOI 视野中 |
DetailLevels | 已在 View 中的目标 Entity,哪些属性继续向其他客户端同步 |
Volatile | 位置和方向字段是否同步,以及字段自身的距离范围 |
witness_volatile_lod | CellApp 在高密度同屏时降低位置、方向的发送频率 |
逻辑层通常只需要在 EntityDef 中使用 DetailLevels 和属性的 DetailLevel。witness_volatile_lod 是 kbengine.xml 中的底层 CellApp 调优项,不应写入 EntityDef,也不需要 Python 逻辑主动控制。
完整配置示例
以下示例可以放在拥有 Cell 部分的 EntityDef 中,例如 Avatar.def:
<root>
<DetailLevels>
<NEAR>
<radius>10</radius>
<hyst>2</hyst>
</NEAR>
<MEDIUM>
<radius>30</radius>
<hyst>3</hyst>
</MEDIUM>
<FAR>
<radius>60</radius>
<hyst>5</hyst>
</FAR>
</DetailLevels>
<Properties>
<combatAction>
<Type>UINT8</Type>
<Flags>ALL_CLIENTS</Flags>
<Default>0</Default>
<DetailLevel>NEAR</DetailLevel>
</combatAction>
<name>
<Type>UNICODE</Type>
<Flags>ALL_CLIENTS</Flags>
<Default></Default>
<DatabaseLength>64</DatabaseLength>
<DetailLevel>MEDIUM</DetailLevel>
</name>
<modelID>
<Type>UINT32</Type>
<Flags>ALL_CLIENTS</Flags>
<Default>0</Default>
<DetailLevel>FAR</DetailLevel>
</modelID>
</Properties>
</root>NEAR、MEDIUM 和 FAR 必须同时存在,每一级都必须配置 radius 和 hyst。数值必须是有限的非负数,并满足:
NEAR.radius <= MEDIUM.radius <= FAR.radius不满足约束时,EntityDef 加载失败,相关服务端组件不会正常启动。
radius 是绝对距离
三个 radius 都是以目标 Entity 为中心的绝对水平距离,不是上一档距离的增量。上面的配置表示:
| 观察距离 | 当前关系级别 | 可持续接收的属性级别 |
|---|---|---|
0 - 10 | NEAR | NEAR、MEDIUM、FAR |
10 - 30 | MEDIUM | MEDIUM、FAR |
30 - 60 | FAR | FAR |
大于 60 | NONE | 不再接收受 DetailLevel 控制的属性更新 |
距离使用观察者与目标 Entity 的 XZ 平面坐标计算,Y 轴高度不参与 DetailLevel 判定。因此多层建筑、空中单位和地下空间如果在 XZ 上接近,仍可能被判定为近距离。需要按楼层或空间隔离数据时,应使用 Space、View、业务分区或独立属性,而不是依赖 DetailLevel。
属性级别的含义
属性的 <DetailLevel> 表示该属性允许同步到的最远详情等级:
NEAR:只在近距离持续同步;MEDIUM:在近距离和中距离持续同步;FAR:在近、中、远距离持续同步。
属性未写 <DetailLevel> 时默认为 FAR。为了让配置意图清晰,建议对需要距离分级的属性显式填写;不需要分级的普通属性可以保持默认值。
只有属性标签还不够
如果属性写了 <DetailLevel>,但 Entity 没有配置完整的 <DetailLevels>,三个范围会保持无限,引擎只输出警告,不会产生预期的距离过滤。需要分级时必须同时配置两部分。
哪些 Flags 会受到影响
当前 DetailLevel 判定作用于 Cell Entity 向其他客户端广播属性的路径,主要对应:
ALL_CLIENTS;OTHER_CLIENTS。
对于 ALL_CLIENTS,其他观察者客户端会经过距离过滤,但 Entity 自己的客户端仍按 ALL_CLIENTS 的原有语义接收属性更新,不受自身与自身距离的影响。
以下数据流不应依赖 DetailLevel:
BASE、CELL_PRIVATE:本来就不发送给客户端;CELL_PUBLIC:Cell 与 Ghost 之间的同步不按客户端 DetailLevel 过滤;OWN_CLIENT、BASE_AND_CLIENT:只面向自己的客户端;CELL_PUBLIC_AND_OWN:面向 Ghost 和自己的客户端。
因此,先根据数据所有权和接收对象选择正确的 Flags,再用 DetailLevel 优化面向其他客户端的广播范围。不要为了获得距离过滤而把本应私有的属性改成 ALL_CLIENTS。
hyst 防止边界抖动
hyst 是向外离开当前等级时使用的滞回距离。它可以避免两个 Entity 在等级边界附近小幅移动时,反复切换等级并重复补发属性。
例如:
<NEAR>
<radius>10</radius>
<hyst>2</hyst>
</NEAR>首次判定或从外侧向内移动时,进入 NEAR 仍以 10 为界;已经处于 NEAR 后向外移动,则可以保持到约 12,超过后才离开近距离等级。
hyst 应略大于常见的位置抖动和单次移动误差,但不宜过大。过大的滞回区会让远离中的观察者继续接收高细节更新,削弱带宽优化效果。
属性同步生命周期
DetailLevel 控制的是属性的后续同步,而不是客户端 Entity 对象的创建和销毁。
首次进入 View
目标 Entity 首次进入客户端 View 时,引擎会创建客户端 Entity,并发送它面向其他客户端的初始属性。当前实现的初始属性流只按 Flags 判断是否面向其他客户端,不按当前 DetailLevel 过滤,因此 NEAR、MEDIUM 和 FAR 属性都会先发送一次。
这里需要区分两种等级:
- 观察关系等级为
FAR:表示观察者当前位于目标 Entity 的远距离区间; - 属性等级为
FAR:表示该属性允许在近、中、远三个距离区间持续同步。
按照正常的后续更新规则,观察关系处于 FAR 时,只会持续接收属性等级为 FAR 的更新,不会接收属性等级为 NEAR 或 MEDIUM 的更新。
初始属性流是当前实现中的例外:如果观察者第一次看到目标 Entity 时,观察关系已经处于 FAR,客户端仍会先收到该 Entity 的 NEAR、MEDIUM 和 FAR 属性初始值。完成初始同步后,后续属性变更才按当前观察关系等级过滤。
从远处靠近
关系从 FAR 进入 MEDIUM,或从 MEDIUM 进入 NEAR 时,引擎会补发新等级内可见属性的当前值。客户端不需要主动向服务端查询这些属性。
例如 name 的属性等级为 MEDIUM:观察关系处于 FAR 时,客户端不会持续收到它的修改;当观察者进入 MEDIUM 区间后,引擎会补发此刻最新的 name,而不会回放中间的变化。首次进入 View 则不同,初始属性流会先发送所有面向其他客户端的属性,不受当前观察关系等级限制。
从近处远离
关系向外切换时,引擎停止继续发送已超出范围的高细节属性,但不会向客户端发送“删除属性”或“恢复默认值”消息。客户端对象中可能继续保留最后一次收到的旧值。
因此:
- 不要把客户端缓存的近距离属性当成始终实时的数据;
- 不要仅根据某个属性是否存在来判断当前 DetailLevel;
- 如果客户端必须在离开范围时立即隐藏 UI 或清理表现,应使用客户端距离、AOI 事件或明确的业务状态驱动;
- 权威判定必须留在服务端,不能依赖客户端缓存值。
离开 View
目标 Entity 离开 View 后,客户端按正常 AOI 生命周期销毁对应客户端 Entity。这个阶段由 View 管理,而不是由 DetailLevel 管理。
Python 逻辑层如何使用
Python 逻辑不需要计算观察者距离,也不需要手动选择接收客户端。属性仍按普通 Entity 属性赋值:
import KBEngine
class Avatar(KBEngine.Entity):
def setCombatAction(self, action_id):
"""更新权威战斗动作,广播范围由 EntityDef DetailLevel 决定。"""
self.combatAction = action_idCellApp 在属性变化时检查每个 Witness 与目标 Entity 的当前关系级别,只向满足条件的其他客户端发送更新。逻辑层不应遍历 entitiesInView 后自行广播同一份属性,否则会重复实现底层已有机制,并增加 Python Tick、消息数量和维护成本。
适合放在 NEAR 的通常是高频且只影响近距离表现的数据,例如攻击阶段、局部特效状态或精细姿态。名称、阵营等低频识别数据更适合 MEDIUM 或 FAR。决定级别时应同时考虑变更频率、序列化大小、同屏 Entity 数量和客户端是否真的需要实时值。
EntityComponent
EntityComponent 的客户端 Cell 属性也可以声明 <DetailLevel>。当观察者进入更近等级时,引擎只补发组件中刚变为可见的子属性,不会为了补发一个字段而重新发送整个组件,也不会把组件中的 OWN_CLIENT 或服务端私有字段带给其他客户端。
组件示例:
<Properties>
<weaponState>
<Type>UINT8</Type>
<Flags>ALL_CLIENTS</Flags>
<Default>0</Default>
<DetailLevel>NEAR</DetailLevel>
</weaponState>
</Properties>组件类型仍应根据既有 EntityComponent 规则声明和挂载,详见 EntityComponent。
性能影响
DetailLevel 的主要收益是减少不必要的属性序列化和网络发送,尤其适合以下场景:
- 单个 Witness 的 View 中存在大量 Entity;
- Entity 包含多个面向其他客户端的属性;
- 近距离属性变化频繁;
- 属性类型较大,例如字符串、数组、字典或复杂组件字段。
它不会减少 AOI 中的 Entity 数量,也不会替代 View 范围控制。每个 Witness 仍需维护可见关系和详情级别,并在观察者或目标移动时更新关系。若大量 Entity 都处于近距离,或绝大多数属性都配置为 FAR,收益会很有限。
优化时应综合观察:
- CellApp Tick 和 Witness 序列化耗时;
- 客户端属性更新包量与字节数;
- 单个 Witness 的可见 Entity 数量;
- 属性变更频率和数据大小;
- 边界附近是否因
hyst太小发生频繁切换; - 客户端是否错误使用了停止同步后的旧值。
不要只根据“距离越远数据越少”设置级别。关键状态如果客户端在远处仍需要实时使用,就应保留为 FAR,或设计更小的远距离摘要属性。
与 witness_volatile_lod 的区别
witness_volatile_lod 位于 kbengine.xml 的 CellApp coordinate_system 配置中。它只在同屏 Entity 达到阈值后,按距离把位置和方向更新安排为每 1、2、4 等多个 Tick 发送一次;Enter、Leave、普通属性和方法消息仍按原有路径处理。
两者可以同时使用:
- EntityDef
DetailLevels减少远距离普通属性更新; witness_volatile_lod减少高密度同屏时远距离位置和方向更新频率。
逻辑层教程中的 <DetailLevels> 不需要与底层 nearDistance、mediumDistance 设置成相同数值。它们服务于不同的数据类型,应根据业务属性与移动表现分别压测。
验证步骤
建议使用两个客户端验证配置:一个客户端控制观察者,另一个客户端控制目标 Entity。
- 为目标 Entity 配置明确的
NEAR、MEDIUM、FAR属性; - 在服务端定时或通过调试命令修改三个属性;
- 让观察者依次停留在近、中、远和
FAR.radius之外; - 记录客户端属性回调,确认各距离只持续收到对应级别;
- 从远处向内移动,确认新可见属性收到当前值;
- 从近处向外移动,确认属性停止更新且边界不会频繁抖动;
- 调整 View 半径,确认 DetailLevel 不会替代 Entity 的进出 View 生命周期。
正式调整前应使用接近真实业务的 Entity 数量、属性变更频率和网络条件进行 Bots 或多客户端压测。只用两个静止 Entity 验证功能正确,无法代表高密度 AOI 下的实际收益。
常见错误
只写了属性 DetailLevel
现象是启动日志出现 uses DetailLevel but has no DetailLevels,所有范围仍然无限。为该 Entity 配置完整的 NEAR、MEDIUM、FAR。
把 radius 写成增量
例如 10、20、30 表示绝对半径 10、20、30,不是三段分别宽 10、20、30。需要远距离到 60 时,应把 FAR.radius 直接写成 60。
radius 顺序错误
NEAR.radius > MEDIUM.radius 或 MEDIUM.radius > FAR.radius 会导致 EntityDef 加载失败。三个半径必须非递减。
用 DetailLevel 隐藏敏感属性
首次进入 View 可能收到初始值,客户端也可能保留停止更新前的旧值。敏感属性必须使用私有 Flags 或完全不进入客户端协议。
期望远离后属性被清空
DetailLevel 不发送清理消息。需要隐藏表现时,由客户端根据距离或明确业务事件处理,但不能据此做权威游戏判定。
把 DetailLevel 当成 View
超过 FAR.radius 不等于离开 AOI。Entity 是否仍存在于客户端由 View 半径和滞回区决定,详见 View视图。
