Skip to content

DetailLevel 详情级别

DetailLevel 是 EntityDef 提供的属性同步分级机制。它根据观察者与目标 Entity 的水平距离,将关系划分为 NEARMEDIUMFAR 或超出全部级别,并决定目标 Entity 的哪些属性需要继续同步给观察者客户端。

典型用途包括:

  • 近距离同步战斗姿态、动作阶段和精细外观;
  • 中距离同步名称、阵营、状态和简化外观;
  • 远距离只同步客户端仍需持续更新的基础属性;
  • 在大 View、高密度 AOI 中减少属性变更产生的网络包量和序列化开销。

DetailLevel 只是一种同步优化,不是权限控制。敏感数据不应依赖距离隐藏,而应使用正确的属性 Flags,或根本不把数据暴露给客户端。

工作边界

在使用之前,需要先区分它与其他几个概念:

机制控制内容
ViewEntity 是否处于某个客户端的 AOI 视野中
DetailLevels已在 View 中的目标 Entity,哪些属性继续向其他客户端同步
Volatile位置和方向字段是否同步,以及字段自身的距离范围
witness_volatile_lodCellApp 在高密度同屏时降低位置、方向的发送频率

逻辑层通常只需要在 EntityDef 中使用 DetailLevels 和属性的 DetailLevelwitness_volatile_lodkbengine.xml 中的底层 CellApp 调优项,不应写入 EntityDef,也不需要 Python 逻辑主动控制。

完整配置示例

以下示例可以放在拥有 Cell 部分的 EntityDef 中,例如 Avatar.def

xml
<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>

NEARMEDIUMFAR 必须同时存在,每一级都必须配置 radiushyst。数值必须是有限的非负数,并满足:

text
NEAR.radius <= MEDIUM.radius <= FAR.radius

不满足约束时,EntityDef 加载失败,相关服务端组件不会正常启动。

radius 是绝对距离

三个 radius 都是以目标 Entity 为中心的绝对水平距离,不是上一档距离的增量。上面的配置表示:

观察距离当前关系级别可持续接收的属性级别
0 - 10NEARNEARMEDIUMFAR
10 - 30MEDIUMMEDIUMFAR
30 - 60FARFAR
大于 60NONE不再接收受 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:

  • BASECELL_PRIVATE:本来就不发送给客户端;
  • CELL_PUBLIC:Cell 与 Ghost 之间的同步不按客户端 DetailLevel 过滤;
  • OWN_CLIENTBASE_AND_CLIENT:只面向自己的客户端;
  • CELL_PUBLIC_AND_OWN:面向 Ghost 和自己的客户端。

因此,先根据数据所有权和接收对象选择正确的 Flags,再用 DetailLevel 优化面向其他客户端的广播范围。不要为了获得距离过滤而把本应私有的属性改成 ALL_CLIENTS

hyst 防止边界抖动

hyst 是向外离开当前等级时使用的滞回距离。它可以避免两个 Entity 在等级边界附近小幅移动时,反复切换等级并重复补发属性。

例如:

xml
<NEAR>
    <radius>10</radius>
    <hyst>2</hyst>
</NEAR>

首次判定或从外侧向内移动时,进入 NEAR 仍以 10 为界;已经处于 NEAR 后向外移动,则可以保持到约 12,超过后才离开近距离等级。

hyst 应略大于常见的位置抖动和单次移动误差,但不宜过大。过大的滞回区会让远离中的观察者继续接收高细节更新,削弱带宽优化效果。

属性同步生命周期

DetailLevel 控制的是属性的后续同步,而不是客户端 Entity 对象的创建和销毁。

首次进入 View

目标 Entity 首次进入客户端 View 时,引擎会创建客户端 Entity,并发送它面向其他客户端的初始属性。当前实现的初始属性流只按 Flags 判断是否面向其他客户端,不按当前 DetailLevel 过滤,因此 NEARMEDIUMFAR 属性都会先发送一次。

这里需要区分两种等级:

  • 观察关系等级为 FAR:表示观察者当前位于目标 Entity 的远距离区间;
  • 属性等级为 FAR:表示该属性允许在近、中、远三个距离区间持续同步。

按照正常的后续更新规则,观察关系处于 FAR 时,只会持续接收属性等级为 FAR 的更新,不会接收属性等级为 NEARMEDIUM 的更新。

初始属性流是当前实现中的例外:如果观察者第一次看到目标 Entity 时,观察关系已经处于 FAR,客户端仍会先收到该 Entity 的 NEARMEDIUMFAR 属性初始值。完成初始同步后,后续属性变更才按当前观察关系等级过滤。

从远处靠近

关系从 FAR 进入 MEDIUM,或从 MEDIUM 进入 NEAR 时,引擎会补发新等级内可见属性的当前值。客户端不需要主动向服务端查询这些属性。

例如 name 的属性等级为 MEDIUM:观察关系处于 FAR 时,客户端不会持续收到它的修改;当观察者进入 MEDIUM 区间后,引擎会补发此刻最新的 name,而不会回放中间的变化。首次进入 View 则不同,初始属性流会先发送所有面向其他客户端的属性,不受当前观察关系等级限制。

从近处远离

关系向外切换时,引擎停止继续发送已超出范围的高细节属性,但不会向客户端发送“删除属性”或“恢复默认值”消息。客户端对象中可能继续保留最后一次收到的旧值。

因此:

  • 不要把客户端缓存的近距离属性当成始终实时的数据;
  • 不要仅根据某个属性是否存在来判断当前 DetailLevel;
  • 如果客户端必须在离开范围时立即隐藏 UI 或清理表现,应使用客户端距离、AOI 事件或明确的业务状态驱动;
  • 权威判定必须留在服务端,不能依赖客户端缓存值。

离开 View

目标 Entity 离开 View 后,客户端按正常 AOI 生命周期销毁对应客户端 Entity。这个阶段由 View 管理,而不是由 DetailLevel 管理。

Python 逻辑层如何使用

Python 逻辑不需要计算观察者距离,也不需要手动选择接收客户端。属性仍按普通 Entity 属性赋值:

python
import KBEngine


class Avatar(KBEngine.Entity):
    def setCombatAction(self, action_id):
        """更新权威战斗动作,广播范围由 EntityDef DetailLevel 决定。"""
        self.combatAction = action_id

CellApp 在属性变化时检查每个 Witness 与目标 Entity 的当前关系级别,只向满足条件的其他客户端发送更新。逻辑层不应遍历 entitiesInView 后自行广播同一份属性,否则会重复实现底层已有机制,并增加 Python Tick、消息数量和维护成本。

适合放在 NEAR 的通常是高频且只影响近距离表现的数据,例如攻击阶段、局部特效状态或精细姿态。名称、阵营等低频识别数据更适合 MEDIUMFAR。决定级别时应同时考虑变更频率、序列化大小、同屏 Entity 数量和客户端是否真的需要实时值。

EntityComponent

EntityComponent 的客户端 Cell 属性也可以声明 <DetailLevel>。当观察者进入更近等级时,引擎只补发组件中刚变为可见的子属性,不会为了补发一个字段而重新发送整个组件,也不会把组件中的 OWN_CLIENT 或服务端私有字段带给其他客户端。

组件示例:

xml
<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> 不需要与底层 nearDistancemediumDistance 设置成相同数值。它们服务于不同的数据类型,应根据业务属性与移动表现分别压测。

验证步骤

建议使用两个客户端验证配置:一个客户端控制观察者,另一个客户端控制目标 Entity。

  1. 为目标 Entity 配置明确的 NEARMEDIUMFAR 属性;
  2. 在服务端定时或通过调试命令修改三个属性;
  3. 让观察者依次停留在近、中、远和 FAR.radius 之外;
  4. 记录客户端属性回调,确认各距离只持续收到对应级别;
  5. 从远处向内移动,确认新可见属性收到当前值;
  6. 从近处向外移动,确认属性停止更新且边界不会频繁抖动;
  7. 调整 View 半径,确认 DetailLevel 不会替代 Entity 的进出 View 生命周期。

正式调整前应使用接近真实业务的 Entity 数量、属性变更频率和网络条件进行 Bots 或多客户端压测。只用两个静止 Entity 验证功能正确,无法代表高密度 AOI 下的实际收益。

常见错误

只写了属性 DetailLevel

现象是启动日志出现 uses DetailLevel but has no DetailLevels,所有范围仍然无限。为该 Entity 配置完整的 NEARMEDIUMFAR

把 radius 写成增量

例如 10、20、30 表示绝对半径 10、20、30,不是三段分别宽 10、20、30。需要远距离到 60 时,应把 FAR.radius 直接写成 60

radius 顺序错误

NEAR.radius > MEDIUM.radiusMEDIUM.radius > FAR.radius 会导致 EntityDef 加载失败。三个半径必须非递减。

用 DetailLevel 隐藏敏感属性

首次进入 View 可能收到初始值,客户端也可能保留停止更新前的旧值。敏感属性必须使用私有 Flags 或完全不进入客户端协议。

期望远离后属性被清空

DetailLevel 不发送清理消息。需要隐藏表现时,由客户端根据距离或明确业务事件处理,但不能据此做权威游戏判定。

把 DetailLevel 当成 View

超过 FAR.radius 不等于离开 AOI。Entity 是否仍存在于客户端由 View 半径和滞回区决定,详见 View视图