文章目录
- 1 项目介绍
- 2 AI 代码介绍
- 2.1 BTBaseNode / BTControlNode
- 2.2 动作/条件节点
- 2.3 选择 / 顺序节点
- 3 怪物实现
- 4 其他功能
- 5 UML 类图
项目借鉴 B 站唐老狮 2023年直播内容。 点击前往唐老狮 B 站主页。
1 项目介绍
本项目使用 Unity 2022.3.32f1c1,实现基本的 AI 框架。其中,用 Cube(红色)代替怪物模型,Cube(蓝色)代替玩家,即 AI 目标。
项目地址:https://github.com/zheliku/BehaviourTree_AI。
2 AI 代码介绍
2.1 BTBaseNode / BTControlNode
所有结点都需要执行各自的任务,因此提取到基类节点中:
/// <summary>
/// 行为树结点基类
/// </summary>
public abstract class BTBaseNode
{
/// <summary>
/// 执行节点逻辑的抽象方法
/// </summary>
public abstract ENodeState Execute();
}
而控制节点需要知道其控制哪些节点,因此相关内容需要提取到控制节点的基类中:
using System.Collections.Generic;
public abstract class BTControlNode : BTBaseNode
{
// 存储子节点的 List
protected List<BTBaseNode> _childList = new List<BTBaseNode>();
protected int _currentIndex = 0; // 当前执行到的子节点索引
/// <summary>
/// 添加子节点
/// </summary>
public virtual void AddChild(params BTBaseNode[] node) {
_childList.AddRange(node);
}
}
2.2 动作/条件节点
动作节点执行具体的行为,没有子节点。
执行完行为后,可以返回是否执行成功,因此需要外部添加执行的方法。
using System;
/// <summary>
/// 动作节点,执行具体行为,没有子节点
/// </summary>
public class BTActionNode : BTBaseNode
{
private Func<bool> _action; // 返回值表示执行是否成功
public BTActionNode(Func<bool> action) { _action = action; }
public override ENodeState Execute() {
if (_action == null) return ENodeState.Failure;
// 执行行为
return _action.Invoke() ? ENodeState.Success : ENodeState.Failure;
}
}
条件节点用于评估条件,根据条件结果返回成功 / 失败。
和动作节点类似,也需要外部提供判断条件的方法。
using System;
/// <summary>
/// 条件节点,评估一个条件,并返回成功 / 失败
/// </summary>
public class BTConditionNode : BTBaseNode
{
private Func<bool> _action; // 返回值表示执行是否成功
public BTConditionNode(Func<bool> action) { _action = action; }
public override ENodeState Execute() {
if (_action == null) return ENodeState.Failure;
// 执行行为
return _action.Invoke() ? ENodeState.Success : ENodeState.Failure;
}
}
2.3 选择 / 顺序节点
选择 / 顺序节点都仅依据子节点的状态,返回自己的状态。因此只需要实现 Execute() 方法即可,区别在于实现的逻辑不同。
using System;
/// <summary>
/// 选择节点<br/>
/// 特点:<br/>
/// 1. 按顺序执行子节点<br/>
/// 2. 如果某子节点返回成功,则返回成功,不执行后续结点<br/>
/// 3. 如果某子节点返回失败,则继续执行下一个子节点
/// </summary>
public class BTSelectNode : BTControlNode
{
public override ENodeState Execute() {
var childNode = _childList[_currentIndex];
var result = childNode.Execute();
switch (result) {
case ENodeState.Success: { // 成功,则重置索引,直接返回
_currentIndex = 0;
return ENodeState.Success;
}
case ENodeState.Failure: { // 失败,则继续下一个节点
++_currentIndex;
if (_currentIndex == _childList.Count) { // 执行到最后,重置索引
_currentIndex = 0;
return ENodeState.Failure;
}
break;
}
case ENodeState.Running: {
return ENodeState.Running;
}
default: throw new ArgumentOutOfRangeException();
}
// 没有执行完,或者节点失败,才执行该逻辑
// 此时仍希望下一帧继续往后执行,因此返回成功
return ENodeState.Success;
}
}
/// <summary>
/// 序列节点<br/>
/// 特点:<br/>
/// 1. 按顺序执行子节点<br/>
/// 2. 只要有一个子节点返回失败,则整个节点返回失败<br/>
/// 3. 所有子节点都返回成功,则整个节点返回成功
/// </summary>
public class BTSequenceNode : BTControlNode
{
public override ENodeState Execute() {
var childNode = _childList[_currentIndex];
var result = childNode.Execute();
switch (result) {
case ENodeState.Success: { // 成功,则继续下一个节点
++_currentIndex;
if (_currentIndex == _childList.Count) { // 执行到最后,重置索引
_currentIndex = 0;
return ENodeState.Success;
}
break;
}
case ENodeState.Failure: { // 失败,则重置索引,直接返回
_currentIndex = 0;
return ENodeState.Failure;
}
case ENodeState.Running: {
return ENodeState.Running;
}
default: throw new ArgumentOutOfRangeException();
}
return ENodeState.Success;
}
}
3 怪物实现
类似 2024-07-12 Unity AI状态机1 —— 框架介绍_有限状态机编程框架-CSDN博客 中的怪物实现,但将怪物数据写在各个行为的控制类中,因此具有以下 4 个控制类:
- PatrolControl(巡逻)
- ChaseControl(追逐)
- AttackControl(攻击)
- BackControl(返回)
其余实现基本一致。
4 其他功能
为了辅助绘图,在 Monster 类中的 Update 方法里判断当前执行的行为 / 状态。用二进制位表示每个状态,异或运算来计算当前的状态:
private void Update() {
_btAIRoot.Execute(); // 执行行为树
switch (CurrentState) { // 依据当前行为绘制辅助线
case 1:
AttackCtrl.DrawGizmos();
break;
case 2:
BackCtrl.DrawGizmos();
break;
case 4:
ChaseCtrl.DrawGizmos();
break;
case 8:
PatrolCtrl.DrawGizmos();
break;
}
}