技能系统中的标签机制

技能系统中的标签机制

前言


对于一个大规模系统的建模 一定离不开常见的分类手段, 而分类要根据系统中的元素以及元素之间的关系来作为划分依据, 从而采用不同策略
技能系统中的配置以’技能’和’效果’作为完整的配置单位, 所有的技能和效果之间的关系则是多维的图的拓扑关系的, 例如:

眩晕BUFF: 禁止普通移动, 禁止常规技能释放,
定身BUFF: 禁止普通移动
金身BUFF: 禁止普通移动, 禁止所有技能释放, 不可被眩晕/禁止所有效果(受击, 伤害, debuff/buff等)
冰冻BUFF: 禁止普通移动, 禁止所有技能释放除了解控技能, 可能禁止受击
沉默BUFF: 禁止所有(部分)技能释放
无敌BUFF: 不接受任何伤害
霸体BUFF: 打断受击状态并免控
灯笼BUFF: 可困住霸体BUFF单位
解控技能: 接触被控状态但无法接触部分例如灯笼BUFF
净化技能: 移除DEBUFF
破盾技能: 移除带有指定盾效果的BUFF
致命一击: 突破所有防御包括部分无敌BUFF .
引导技能A: 可被强控技能打断
引导技能B: 可被移动取消
连招技能: 需要前置技能成功释放完成
千斤坠技能: 需要飞行状态可释放

几乎所有的技能和buff之间都需要定义好他们的交互关系, 包括依赖条件, 禁止状态, 打断逻辑, 替换规则, 触发节奏, 否则在不断的引入新的技能时, 很容易导致已有的系统出现局部甚至整体的错误流程和功能表现.

而作为一个灵动丰富的多样性的技能系统, 在具体的状态关系上往往不会是简单的禁止打断逻辑, 而是分梯度, 计数, 浮动值的不同而有不同的分支策略, 需要对这样的一个规模和结构复杂的系统进行井井有条的管理和维护, 则需要一套完善灵活的拆分理念和管理机制.

常见系统的拓扑关系和拆分方式

  • 水平聚合 –> 分组
  • 垂直聚合 –> 分层
  • 分形聚合 –> 分维
  • 树形(水平+垂直) –> 按照继承关系逐层分组
  • 有向图 –> 按阶段划分
  • 图 –> 系统分析

技能系统的拓扑关系基本分析梳理与划分方法

  • 根据上层表现进行逻辑拆解, 分类, 分组, 分优先级直到不可继续拆分

    自定向下拆分
    不可拆分的定义应该由实现者参与和辅助

  • 根据上层表现的总量分布, 重新划分定义好相应的组,类, 层级关系, 来方便上层表现的组装

    自底向上重组
    在这个阶段奠定基础的组,类,层级之间的关系, 例如打断的基本关系, 禁止的基本关系, 依赖的基本关系.

  • 构建新的系统模型

    因为是网络图, 因此无论任何系统模型都可能存在表达的边界 对于这个问题尝试以下几个方法解决或者规避

    • 可以尝试砍部分需求来简化系统模型 如果这些需求本身存在逻辑上矛盾, 计算的边界等硬伤问题
    • 实现者提供尽可能的开放拓扑关系的制定, 提供更为松散的框架进行兜底设计, 或者说提供一套图灵完备的设计, 让设计者直接面对复杂度来达到对脑洞的克制目的.

提供特征的编码和分类机制, 把定义抛给设计者

通常来讲, 类型划分中, 类和事物的关系是 IS-A的关系, 是一种强的分类定义, 但是假设能按照类型进行划分和分类, 其构建出来的系统也更为简洁.
例如: 我们对所有生物进行了IS-A的划分, 并且该类型是存在继承(上下)关系的阶元系统.

作为技能系统, 和生物群体类似, 每个技能都有丰富的特征和效果, 问题是谁可以去定义类型, 依据什么特征进行划分, 这种划分的依据是否会频繁变更导致整个系统持续的动态调整.

针对这个问题, 我们采用另外一套方法, 即标签机制, 标签是和事物的特征进行映射, 从分类视角来看 标签和事物的关系是 HAS-A的关系, 标签提供了类型划分的依据, 也因此只要做好了标签相关的机制, 即可把分类的定义抛给设计者灵活设计.
即本文介绍的核心主旨:

标签机制提供了分类的依据但不定义分类本身, 把定义交给设计者, 尽量减少和避免开发人员的持续跟进开发

标签机制在技能中的设计


资源标签配置

无论是技能还是效果的配置, 都存在一个标示这个配置包含哪些标签, 例如:

某buff需要免疫某种类型的伤害(效果): 伤害效果可能携带 魔法 物理
某buff需要沉默某种类型的技能: 技能可能携带 普攻 刺杀 强交互 重击
某技能需要清除某些负面效果: BUFF可能携带 \Debuff 沉默 禁止移动 无敌

对于持续性的技能和效果, 存在额外的track标签, 与持续阶段, 时间点相关 例如:

攻击技能持续2秒 划分为三个阶段, 前摇和攻击阶段不可普通移动, 后摇可移动取消, 那么禁止普通移动的标签只能贴在前两个阶段
攻击过程中有大约5帧时间会有霸体效果, 那么可以拖一个5帧时长的标签来标识这个效果开启和截止时间点

战斗中的标签检测

在战斗中的事件节点中, 如果涉及到技能和效果, 则可以通过查找配置, 根据事件中的配置的标签进行分支决策, 例如:

在被攻击事件中, 检测攻击来源所属的技能配置是否包含’物理’标签, 如果当前状态免疫物理攻击, 则一旦查询到有该标签则免疫掉本次攻击的效果
在被攻击事件中, 检测自身是否有技能或者buff存在’霸体’标签, 如果有并且该标签的影响范围覆盖当前时间点, 则免疫掉掉本次攻击的受击效果
在攻击事件中, 检测目标是否存在’霸体’标签, 如果存在且覆盖当前时间点, 则主动取消掉受击效果. (该配置同上, 但属于主动检测)

标签机制在技能中的实现


运行时的记录(作为状态存在的标签集合):

为了实现快速查询, 减少遍历的性能开销, 在涉及到持续存在的标签部分都会记录到战斗单位的属性中, 并进行计数.

  • 固定标签编号的范围, 以数组的形式存储计数信息

    例如: 标签的范围是200, 同时存在的技能和buff等影响计数的持续状态不超过50个, 单个技能或者buff的标签不超过3个, 那么我们就可以用200个字节来存储计数.
    放宽范围的话可能需要ushort大小

  • 数组计数

    所有标签信息生效时均在战斗单位对应的属性内存段的标签位进行计数++
    在标签信息失效时在战斗单位对应的属性内存段的标签位进行计数–

  • bitmap优化判定, CPUcache友好

    标签范围是200, 则只需要4个uint64来存储, 非常高效快速
    bitmap只包含0或者1, 0-1的切换由计数模块负责
    复杂度为O(1) index计算+位的与运算即可

  • 标签的同步

    直接全量BITMAP(脏同步/实时同步)给观察者
    进出视野, 标签变化等时机

  • 全量模式下的变更对比

    通过异或检测是否发生变更并记录到老数据中
    通过bsr扫描变更的位置, 与新数据对比确认变更方向
    所有变更对比完后使用新数据覆盖

  • 分全量模式下的变更对比

    把变更的部分已数组的形式下发
    遍历该数据即可

基于标签的资源分类和定义

类型总是拆解成具体的标签, 类型的划分依据标签, 类型的定义也由标签实现.

  • 扩展定义

    指定的类型本质上是一个和普通标签不冲突的标签编号, 单独配置

  • 聚合定义

    指定的类型本质上是多个普通标签+类型标签聚合而成, 单独配置并解析为具体的标签

  • 继承定义

    指定的类型和层级关系由多个普通标签+多层类型标签聚合而成, 单独配置并解析为具体的标签

其他分类

  • 连招组
  • 公共CD组