行为树(Behavior Trees)是一种在游戏开发中广泛使用的AI设计模式,主要用于描述AI的行为和决策过程,实现更加智能和自然的游戏AI。它由多个节点组成,每个节点代表一个行为或决策,按照特定的方式连接在一起,形成一个树状结构。
在行为树中,根节点是AI的起点,通过遍历子节点来决策AI的行为。节点有以下三种状态:成功(Success)、失败(Failure)和运行(Running)。前两个通知其父节点其操作是成功还是失败。第三种意味着尚未确定成功或失败,并且该节点仍在运行。下次树被选择时,该节点将再次被选择,此时它将再次有机会成功,失败或继续运行。
行为树的节点有以下几种主要原型:
-
组合控制节点(Composite):一种将多个子节点组合在一起的节点,用于实现复杂的行为和决策逻辑。主要包括次序节点(Sequencer)和选择节点(Selector)。次序节点并行执行多个子节点,直到所有子节点都返回成功或者任意一个子节点返回失败为止。选择节点按照顺序执行子节点,当某个子节点返回成功时,停止执行并返回成功。
-
修饰节点(Decorator):一种特殊的节点,它不执行具体的行为或决策,而是修饰其它节点的行为或决策。主要包括逆变节点(Inverter)和重复节点(Repeater)。逆变节点可以将子节点的结果倒转,比如子节点返回了失败,则这个修饰节点会向上返回成功,以此类推。重复节点重复执行其子节点指定的次数或者一直重复执行,直到其子节点返回成功或者失败。
-
叶节点(Leaf):树的最末端,就是这些AI实际上去做事情的命令。
行为树通过模拟树状结构来描述AI的行为和决策过程,使得AI的行为更加灵活、自然和智能。
代码实现
csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BehaviourTree;
namespace BehaviourTree
{
public class BehaviourTree : MonoBehaviour
{
private Node root = null;
private Blackboard blackboard;
public Node Root
{
get
{
return root;
}
set
{
root= value;
}
}
void Start()
{
Init();
}
void Update()
{
if(root!=null&& gameObject!=null)
{
root.Evaluate(gameObject.transform,blackboard);
}
}
protected virtual void Init()
{
blackboard = new Blackboard();
}
}
}
csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BehaviourTree;
namespace BehaviourTree
{
//行为树共享数据
public class Blackboard
{
private Dictionary<string,object> Data;
public Blackboard()
{
Data = new Dictionary<string, object>();
}
//获取数据
public T Get<T>(string key)
{
if(Data.ContainsKey(key))
{
return (T)Data[key];
}
return default;
}
//添加数据
public void Add<T>(string key,T value)
{
if(Data.ContainsKey(key))
{
Data[key] = value;
}
else
{
Data.Add(key,value);
}
}
//删除数据
public void Remove(string key)
{
if(Data.ContainsKey(key))
{
Data.Remove(key);
}
else
{
Debug.Log("数据不存在 "+key);
}
}
}
}
csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BehaviourTree;
namespace BehaviourTree
{
//状态类型
public enum Status
{
Running, //运行中
Failure, // 失败
Success, //成功
}
//节点基类
public abstract class Node
{
protected Node parent;
protected Status status;
Status Status
{
get
{
return status;
}
set
{
status = value;
}
}
public Node()
{
}
//声明节点状态返回方法
public abstract Status Evaluate(Transform transform,Blackboard blackboard);
}
}
csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BehaviourTree;
namespace BehaviourTree
{
//复合节点类
public abstract class Composite : Node
{
//子节点列表
protected List<Node> children = new List<Node>();
protected int index;//子节点下标计数
protected Composite(List<Node> children)
{
index = 0;
foreach(var child in children)
{
this.children.Add(child);
}
}
}
}
csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BehaviourTree;
namespace BehaviourTree
{
//修饰器节点
public abstract class Decorator : Node
{
//子节点列表
protected List<Node> children;
protected Decorator(List<Node> children)
{
children = new List<Node>(){};
foreach(var child in children)
{
this.children.Add(child);
}
}
}
}
csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BehaviourTree;
namespace BehaviourTree
{
//选择节点,所有子节点都为失败则失败
public class Selector : Composite
{
public Selector(List<Node> children) : base(children)
{
}
//所有子节点都为失败则失败
public override Status Evaluate(Transform transform, Blackboard blackboard)
{
if(index>=children.Count)
{
index = 0;
return Status.Success;
}
var status = children[index].Evaluate(transform,blackboard);
switch(status)
{
case Status.Success:
index = 0;
return Status.Success;
case Status.Failure:
index+=1;
if(index>=children.Count)
{
index = 0;
return Status.Failure;
}
return Status.Running;
case Status.Running:
return Status.Running;
default:
return Status.Failure;
}
}
}
}
csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BehaviourTree;
namespace BehaviourTree
{
//顺序节点,所有子节点成功才成功
public class Sequencer : Composite
{
public Sequencer(List<Node> children) : base(children)
{
}
//所有子节点成功才成功
public override Status Evaluate(Transform transform, Blackboard blackboard)
{
if(index>=children.Count)
{
index = 0;
return Status.Success;
}
var status = children[index].Evaluate(transform,blackboard);
switch(status)
{
case Status.Success:
index+=1;
if(index>=children.Count)
{
index = 0;
return Status.Success;
}
return Status.Running;
case Status.Failure:
return Status.Failure;
case Status.Running:
return Status.Running;
default:
return Status.Failure;
}
}
}
}
csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BehaviourTree;
namespace BehaviourTree
{
//任务节点,这里会处理对象具体行为逻辑(叶节点)
public abstract class Task : Node
{
}
}
简单使用
实现敌人在两点之间巡逻,人物靠近会变红温并停止移动,人物远离时继续巡逻
csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BehaviourTree;
using System.ComponentModel.Design.Serialization;
using System.Linq;
namespace BehaviourTree
{
public class EnemyBT : BehaviourTree
{
Vector3 aPos = new Vector3(4.07999992f,-2.21000004f,-2);
Vector3 bPos = new Vector3(4.07999992f,1.65999997f,-2)
protected override void Init()
{
//调用基类初始化
base.Init();
//创建变红节点
TurnRed turnRed = new TurnRed();
//创建巡逻节点
Patrol patrol = new Patrol(aPos,bPos);
//创建查找节点
FindObject findObject = new FindObject();
//创建侦查节点子节点序列
Node[] spyChildren = {findObject,turnRed};
//创建顺序节点用于执行侦查行为
EnemySequencer enemySequencer = new EnemySequencer(spyChildren.ToList());
//创建根节点子节点序列
Node[] selectorChildren = {enemySequencer,patrol};
//创建选择节点用于处理侦查和巡逻行为
EnemySelector enemySelector = new EnemySelector(selectorChildren.ToList());
//将选择节点设置为根节点
Root = enemySelector;
}
}
}
csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BehaviourTree;
namespace BehaviourTree
{
public class EnemySelector : Selector
{
public EnemySelector(List<Node> children) : base(children)
{
}
}
}
csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BehaviourTree;
namespace BehaviourTree
{
public class EnemySequencer : Sequencer
{
public EnemySequencer(List<Node> children) : base(children)
{
}
}
}
csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BehaviourTree;
using DG.Tweening;
namespace BehaviourTree
{
public class FindObject : Task
{
float radius = 5f;
int layer = 512;
public override Status Evaluate(Transform transform, Blackboard blackboard)
{
Collider2D obj = Physics2D.OverlapCircle(transform.position,radius,layer);
if(obj!=null)
{
return Status.Success;
}
transform.gameObject.GetComponent<SpriteRenderer>().DOColor(Color.white,1);
return Status.Failure;
}
}
}
csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BehaviourTree;
using DG.Tweening;
namespace BehaviourTree
{
//在aPoint和bPoint之间来回移动
public class Patrol : Task
{
Vector3 aPoint;
Vector3 bPoint;
float speed = 4f;
Vector3 target;
public Patrol(Vector3 aPos,Vector3 bPos)
{
aPoint = aPos;
bPoint = bPos;
target = aPoint;
}
public override Status Evaluate(Transform transform, Blackboard blackboard)
{
if(Vector2.Distance(transform.position,target)<0.1f)
{
if(target == aPoint)
{
target = bPoint;
}
else
{
target = aPoint;
}
}
else
{
transform.position = Vector2.MoveTowards(transform.position,target,Time.deltaTime*speed);
}
return Status.Success;
}
}
}
csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BehaviourTree;
using DG.Tweening;
namespace BehaviourTree
{
//变红
public class TurnRed : Task
{
Vector3 bPoint;
public override Status Evaluate(Transform transform, Blackboard blackboard)
{
transform.gameObject.GetComponent<SpriteRenderer>().DOColor(Color.red,1);
return Status.Success;
}
}
}