XML 使用说明 - 定义行为树

在游戏配置文件中,您可以使用 <BehaviorTreeDef> 元素来定义角色的行为树逻辑。每个 <BehaviorTreeDef> 元素都必须包含在顶层的 <Define> 元素之内,并遵循其通用的属性定义规则。

功能介绍

<BehaviorTreeDef> 用于构建复杂的AI行为逻辑,通过树状结构组织一系列行为节点。每个节点代表一个决策或行动,它们可以嵌套形成复杂的判断和执行流程。行为树支持多种定义方式,包括传统定义和更简洁的节点定义。行为树的根节点默认是 ThinkNode_Selector

参数说明

所有的参数都必须通过嵌套元素的方式进行赋值。

  • <defName> (必需,字符串)

    • 该行为树定义的唯一标识符。在整个游戏配置中,此名称必须是全局唯一的,除非您有意覆盖现有定义。
    • 作为匿名定义时,此字段可以省略。
  • <label> (可选,字符串)

    • 提供一个人类可读的标签,用于更好地理解该行为树定义的用途。
  • <description> (可选,字符串)

    • 提供更详细的文本描述,解释该行为树定义的具体内容或特殊用途。
  • <childTree> (可选,行为树数组)

    • 包含当前行为树节点的子节点列表。每个子节点本身也是一个 <BehaviorTreeDef>
    • 数组的定义方式为 <childTree><li>元素1</li><li>元素2</li></childTree><li> 元素本身会被忽略。
  • <className> (可选,字符串,默认为 "ThinkNode_Selector")

    • 指定当前行为树 根节点 所使用的类名。如果省略,则默认为 ThinkNode_Selector。这些类是预定义好的行为节点类型,例如 ThinkNode_SelectorThinkNode_SequenceJobNode_AttackTarget 等。具体的可用类名将在下方“行为树节点类型”部分详细介绍。
  • <value> (可选,字符串)

    • 某些行为树类需要初始化参数,value 元素用于提供这些参数。例如,条件判断节点可能需要一个函数调用的字符串作为判断依据。当 <value> 用于根节点时,它将作为根节点的初始化参数。

特殊初始化方式 —— 使用 <Node> 元素

为了使行为树的定义更加简洁直观,当 <BehaviorTreeDef> 作为父级时,其子节点可以直接使用 <Node> 元素进行定义。

Node 元素参数说明:

  • className (属性,字符串)
    • 直接作为 <Node> 元素的属性指定子节点的类名。
  • value (属性,字符串)
    • 直接作为 <Node> 元素的属性指定子节点的初始化参数。

引用方式和匿名定义

行为树的子节点可以通过两种方式定义:

  1. 简便的匿名定义 (不支持引用)

    • 直接在 <BehaviorTreeDef> 内部或者其他 <Node> 元素下嵌套另一个 <Node> 元素来定义子节点。
    • 这种方式下,子节点没有 defName,只能作为当前父节点的直接子级使用,无法被其他行为树定义引用。
    • 语法:
      <Node className="子节点类名" value="子节点参数">
          <!-- 更多嵌套 Node 元素 -->
      </Node>
      
  2. 传统匿名定义 (支持引用)

    • <childTree> 元素内使用 <li> 元素包裹一个完整的 <BehaviorTreeDef> 结构。
    • 这种方式下,子节点可以拥有 defName,从而允许其他行为树定义引用它。当引用一个已命名的行为树定义作为子节点时,可以直接在 <li> 中放置被引用行为树的 defName
    • 语法:
      • 匿名子节点:
        <childTree>
            <li>
                <className>子节点类名</className>
                <value>子节点参数</value>
                <childTree>
                    <!-- ... 更多子节点 ... -->
                </childTree>
            </li>
        </childTree>
        
      • 引用命名子节点:
        <childTree>
            <li>引用行为树的defName</li>
        </childTree>
        

行为树节点类型

以下是当前可用的行为树节点类型:

  • ThinkNode_Selector: 选择节点。会不断从上到下尝试执行其子节点,直到某个子节点返回成功后,该选择节点重置并再次从第一个子节点开始判断。如果所有子节点都返回失败,则此选择节点返回失败。
  • ThinkNode_Sequence: 顺序节点。按顺序从上到下执行子节点。如果任何一个子节点返回失败,则此顺序节点重置并返回失败。只有所有子节点都成功执行后,此顺序节点才返回成功。
  • ThinkNode_Conditional: 条件节点。当其 value 中定义的条件满足(返回 true)时,它会像一个 ThinkNode_Selector 一样执行其子节点;否则返回失败。
  • ThinkNode_Branch: 分支节点。需要定义2到3个子节点。当第一个子节点返回 true 时,执行第二个子节点;否则,如果第三个子节点存在,则执行第三个子节点。如果子节点不足2个,则该分支节点总是返回失败。
  • ConstantNode_Failure: 常量节点。总是返回失败状态。
  • ConstantNode_Running: 常量节点。总是返回运行中状态。
  • ConstantNode_Success: 常量节点。总是返回成功状态。
  • JobNode_XXX: 工作节点。所有实际执行具体动作的叶节点都以 JobNode_ 为前缀。例如:
    • JobNode_MoveToAttackRange: 移动到攻击范围。
    • JobNode_AttackTarget: 攻击目标。
    • JobNode_Wander: 随机游荡。
    • JobNode_Idle: 空闲。
    • 注意: 具体的 JobNode_ 类型会根据游戏功能而定,请查阅相关文档获取完整列表。

示例

示例 1:使用 <Node> 元素的简便匿名定义

这是一个典型的怪物行为树,根节点使用默认的 ThinkNode_Selector

<Define>
    <BehaviorTreeDef>
        <defName>BasicMonsterBehavior</defName>
        <label>基本怪物行为</label>
        <description>定义了怪物在不同情况下的行为逻辑。</description>
        <!-- 根节点默认是 ThinkNode_Selector -->
        <Node className="ThinkNode_Conditional" value="HasEnemyInSight()"> <!-- 子节点1:条件 -->
            <Node className="ThinkNode_Sequence"> <!-- 条件满足时执行的序列 -->
                <Node className="ThinkNode_Conditional" value="HasWeapon()"> <!-- 更深一层的条件 -->
                    <Node className="JobNode_MoveToAttackRange"/> <!-- 行为:移动到攻击范围 -->
                    <Node className="JobNode_AttackTarget"/> <!-- 行为:攻击目标 -->
                </Node>
                <Node className="JobNode_FleeJob"/> <!-- 如果有敌人但没有武器,则逃跑 -->
            </Node>
        </Node>
        <Node className="ThinkNode_Sequence"> <!-- 子节点2:游荡和空闲 -->
            <Node className="JobNode_Wander"/> <!-- 行为:随机游荡 -->
            <Node className="JobNode_Idle"/> <!-- 行为:空闲 -->
        </Node>
    </BehaviorTreeDef>
</Define>

示例 2:修改根节点的工作类,并使用传统匿名定义方式 (支持引用)

这个示例展示了如何修改根节点的工作类为 ThinkNode_Sequence,并展示了如何引用一个预定义的子行为树。

假设我们有一个预定义的行为树叫做 AttackLogic

<Define>
    <BehaviorTreeDef>
        <defName>AttackLogic</defName>
        <label>攻击逻辑</label>
        <className>ThinkNode_Sequence</className>
        <childTree>
            <li>
                <className>JobNode_MoveToAttackRange</className>
            </li>
            <li>
                <className>JobNode_AttackTarget</className>
            </li>
        </childTree>
    </BehaviorTreeDef>
</Define>

现在,我们可以在另一个行为树中引用这个 AttackLogic,并自定义根节点类型:

<Define>
    <BehaviorTreeDef>
        <defName>AdvancedMonsterBehavior</defName>
        <label>进阶怪物行为</label>
        <description>定义了更复杂的怪物行为树,根节点为序列。</description>
        <className>ThinkNode_Sequence</className> <!-- 将根节点指定为 ThinkNode_Sequence -->
        <childTree>
           <li> <!-- 顺序节点1:检查敌人并决定攻击或逃跑 -->
                <className>ThinkNode_Conditional</className>
                <value>HasEnemyInSight()</value>
                <childTree>
                    <li> <!-- 在有敌人且有武器的情况下执行攻击逻辑,否则逃跑 -->
                        <className>ThinkNode_Branch</className>
                        <li> <className>ThinkNode_Conditional</className><value>HasWeapon()</value> </li> <!-- 条件:是否有武器 -->
                        <li>AttackLogic</li> <!-- 引用已定义的 AttackLogic 行为树 -->
                        <li> <className>JobNode_FleeJob</className> </li> <!-- 否则逃跑 -->
                    </li>
                </childTree>
            </li>
            <li> <!-- 顺序节点2:如果上层成功,则随后进行游荡和空闲 -->
                <className>ThinkNode_Sequence</className>
                <childTree>
                    <li>
                        <className>JobNode_Wander</className>
                    </li>
                    <li>
                        <className>JobNode_Idle</className>
                    </li>
                </childTree>
           </li>
        </childTree>
    </BehaviorTreeDef>
</Define>

在上述示例中,<li>AttackLogic</li> 直接引用了之前使用 defName 定义的 AttackLogic 行为树。

注意事项

  • 根节点的定义: <BehaviorTreeDef> 自身的 <className><value> 用于定义整个行为树的根节点。如果未指定 <className>,则根节点默认为 ThinkNode_Selector
  • defName 唯一性: defName 应该在同类型(BehaviorTreeDef)的全局范围内保持唯一。如果定义了同名的 defName,后加载的定义会覆盖先加载的。
  • 匿名定义与引用: 如果行为树子节点不需要被重用或在其他地方引用,简便的 <Node> 嵌套方式更简洁。如果子行为树可能被多个父行为树引用,或者需要更清晰的模块化结构,应使用带有 defName 的传统定义方式。
  • 类名可用性: <className> 中指定的类名必须是游戏代码中实际存在的行为树节点类。请查阅最新的游戏API文档或相关开发指南获取可用类名的详细列表。
  • ThinkNode_Branch 子节点数量: ThinkNode_Branch 节点要求至少有两个子节点才能正常工作(条件和执行体)。如果只有2个子节点,则当条件不满足时,该节点表现为失败。如果定义了3个子节点,则第一个子节点为条件,第二个为条件满足时执行,第三个为条件不满足时执行。如果子节点不足2个,ThinkNode_Branch 总是返回失败。