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_Selector、ThinkNode_Sequence、JobNode_AttackTarget等。具体的可用类名将在下方“行为树节点类型”部分详细介绍。
- 指定当前行为树 根节点 所使用的类名。如果省略,则默认为
-
<value>(可选,字符串)- 某些行为树类需要初始化参数,
value元素用于提供这些参数。例如,条件判断节点可能需要一个函数调用的字符串作为判断依据。当<value>用于根节点时,它将作为根节点的初始化参数。
- 某些行为树类需要初始化参数,
特殊初始化方式 —— 使用 <Node> 元素
为了使行为树的定义更加简洁直观,当 <BehaviorTreeDef> 作为父级时,其子节点可以直接使用 <Node> 元素进行定义。
Node 元素参数说明:
className(属性,字符串)- 直接作为
<Node>元素的属性指定子节点的类名。
- 直接作为
value(属性,字符串)- 直接作为
<Node>元素的属性指定子节点的初始化参数。
- 直接作为
引用方式和匿名定义
行为树的子节点可以通过两种方式定义:
-
简便的匿名定义 (不支持引用)
- 直接在
<BehaviorTreeDef>内部或者其他<Node>元素下嵌套另一个<Node>元素来定义子节点。 - 这种方式下,子节点没有
defName,只能作为当前父节点的直接子级使用,无法被其他行为树定义引用。 - 语法:
<Node className="子节点类名" value="子节点参数"> <!-- 更多嵌套 Node 元素 --> </Node>
- 直接在
-
传统匿名定义 (支持引用)
- 在
<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总是返回失败。