一.行为树节点代码示例
衔接Part1的节点实现代码如下(简易框架)
状态类型枚举
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum E_NodeState
{
Success,//成功
Failed,//失败
Running//进行中
}
节点基类
namespace BT
{
/// <summary>
/// 行为节点基类
/// </summary>
public abstract class BTBaseNode
{
protected List<BTBaseNode> childList = new List<BTBaseNode>();
public BTBaseNode parent;
//当前执行逻辑的子节点序号
protected int NowIndex = 0;
/// <summary>
/// 添加子节点方法
/// </summary>
/// <param name="nodes"></param>
public virtual void AddChild(params BTBaseNode[] nodes)
{
for (int i = 0; i < nodes.Length; i++)
{
nodes[i].parent = this;
childList.Add(nodes[i]);
}
}
public virtual void RemoveChild(int index)
{
}
/// <summary>
/// 执行节点逻辑 (Evaluate)
/// </summary>
/// <returns></returns>
public abstract E_NodeState Excute();
}
}
行为节点(ActionNode)
namespace BT
{
/// <summary>
/// 动作节点 (执行具体逻辑)此动作节点 是将逻辑推给 外部执行
/// </summary>
public class BTActionNode : BTBaseNode
{
public Func<E_NodeState> action;
//使用委托承接外部函数,其中外部的逻辑需要一个E_NodeState返回值的函数了
public E_NodeState nodeState;
public BTActionNode(Func<E_NodeState> action)
{
this.action = action;
}
public override E_NodeState Excute()
{
if (action == null)
{
nodeState = E_NodeState.Failed;
return nodeState;
}
switch (action.Invoke())
{
case E_NodeState.Failed:
nodeState = E_NodeState.Failed;
return E_NodeState.Failed;
case E_NodeState.Running:
nodeState = E_NodeState.Running;
return E_NodeState.Running;
}
nodeState = E_NodeState.Success;
return nodeState;
}
}
}
条件节点(ConditionNode)
namespace BT
{
/// <summary>
/// 条件节点 最终去进行一个条件判断
/// </summary>
public class BTConditionNode : BTBaseNode
{
public Func<bool> action;
public BTConditionNode(Func<bool> action)
{
this.action = action;
}
public override E_NodeState Excute()
{
if (action == null)
{
return E_NodeState.Failed;
}
return action.Invoke() ? E_NodeState.Success : E_NodeState.Failed;
}
}
}
选择节点(SelectorNode)
namespace BT
{
/// <summary>
/// 选择节点 选择其中一个子节点执行,通常是第一个成功的子节点如果没有子节点成功
/// </summary>
public class BTSelectNode : BTBaseNode
{
public BTSelectNode() : base() { }
public override E_NodeState Excute()
{
BTBaseNode childNode;
if (childList.Count != 0)
{
childNode = childList[NowIndex];
switch (childNode.Excute())//返回执行子节点的结果
{
case E_NodeState.Success:
NowIndex = 0;
return E_NodeState.Success;
case E_NodeState.Failed:
++NowIndex;
if (NowIndex == childList.Count)
{
NowIndex = 0;
return E_NodeState.Failed;
}
break;
case E_NodeState.Running:
return E_NodeState.Running;
}
}
return E_NodeState.Failed;
}
}
}
顺序节点(SequenceNode)
namespace BT
{
/// <summary>
/// 顺序节点 按顺序执行他的子节点,直到一个子节点返回失败
/// </summary>
public class BTSequenceNode : BTBaseNode
{
public BTSequenceNode() : base() { }
public override E_NodeState Excute()
{
BTBaseNode childNode;
if (childList.Count != 0)
{
childNode = childList[NowIndex];
switch (childNode.Excute())
{
case E_NodeState.Success:
++NowIndex;
if (NowIndex == childList.Count)
{
NowIndex = 0;
return E_NodeState.Success;
}
break;
case E_NodeState.Failed:
NowIndex = 0;
return E_NodeState.Failed;
case E_NodeState.Running:
return E_NodeState.Running;
default:
break;
}
}
return E_NodeState.Failed;
}
}
}
反向修饰节点(DecoratorNotNode)
namespace BT
{
/// <summary>
/// 反向修饰节点
/// </summary>
public class BTDecoratorNot : BTBaseNode
{
public Func<bool> action;
public BTDecoratorNot(Func<bool> action)
{
this.action = action;
}
public override E_NodeState Excute()
{
if (action == null)
{
return E_NodeState.Failed;
}
return action.Invoke() ? E_NodeState.Failed : E_NodeState.Success;
}
}
}
黑板节点
Part1没有提到黑板节点,黑板节点用于读取和写入公共数据,以便行为树节点之间共享信息,比如存储和传递状态信息,或者存储敌人的位置,其他的环境变量等。
namespace BT
{
public class DataBase
{
public Dictionary<string, object> _dataContext = new Dictionary<string, object>();
public void Setdata(string key, object value)
{
if (_dataContext.ContainsKey(key))
{
_dataContext[key] = value;
}
else
{
_dataContext.Add(key, value);
}
}
public object GetData(string key)
{
if (_dataContext.ContainsKey(key))
{
return _dataContext[key];
}
Debug.LogWarning("没有此数据,无法获取");
return null;
}
public bool ClearData(string key)
{
if (_dataContext.ContainsKey(key))
{
_dataContext.Remove(key);
return true;
}
Debug.LogWarning("没有此数据,无法清除");
return false;
}
}
}
其他的节点以此类推,而且每个人的书写风格和逻辑不同,本人就不一一贴代码了~
一.构建树型结构以及例子逻辑
树基类
namespace BT
{
public abstract class BTTree : MonoBehaviour
{
protected BTBaseNode _root;
protected DataBase _database = new DataBase();//每个树声明一个黑板节点
public virtual void Start()
{
_root = SetUpTree();
}
private void Update()
{
if(_root != null)
{
_root.Excute();
}
}
public abstract BTBaseNode SetUpTree();
}
}
敌人代码
public class Moster : BTTree
{
public float moveSpeed;
public float potrolRange;
public float radius;
private Vector3 waypoint;
private Vector3 gardPos;
private GameObject attackTarget;
public bool isFound;
public Vector3 currentPatrolPos;
public override void Start()
{
base.Start();
gardPos = transform.position;//设置巡逻点
_database.Setdata("巡逻点", GetwayPoint());//一开始向设置好巡逻点
currentPatrolPos = (Vector3)_database.GetData("巡逻点");
}
/// <summary>
/// 构建行为树,将逻辑填入条件节点以及行为节点
/// </summary>
/// <returns></returns>
public override BTBaseNode SetUpTree()
{
BTSelectNode bt_Select = new BTSelectNode();
BTSequenceNode SeqPatrol = SeqBTInitNot(FoundPlayer, MoveToPatrolPoint);//需要装饰节点
BTSequenceNode Seqpursuit = SeqBTInit(FoundPlayer, Pursuit);
BTSequenceNode SeqBack = SeqBTInitNot(FoundPlayer, MoveToPatrolPoint);//需要装饰节点
BTSequenceNode SeqAtt = SeqBTInit(AttackRange, Attack);
bt_Select.AddChild(SeqPatrol, Seqpursuit, SeqBack, SeqPatrol);
return bt_Select;
}
//实例化顺序节点
public BTSequenceNode SeqBTInit(Func<bool> actionCondi = null, Func<E_NodeState> action = null)
{
BTSequenceNode seq = new BTSequenceNode();
BTConditionNode bTCondition = new BTConditionNode(actionCondi);
BTActionNode btActionNode = new BTActionNode(action);
//将条件节点和行为节点添加至顺序节点作为子节点
seq.AddChild(bTCondition, btActionNode);
return seq;
}
public BTSequenceNode SeqBTInitNot(Func<bool> actionCondi = null, Func<E_NodeState> action = null)
{
BTSequenceNode seq = new BTSequenceNode();
BTDecoratorNot bTCondition = new BTDecoratorNot(actionCondi);
BTActionNode btActionNode = new BTActionNode(action);
seq.AddChild(bTCondition, btActionNode);
return seq;
}
//追击
public E_NodeState Pursuit()
{
if (FoundPlayer())//在逻辑函数中,中断行为节点的Running状态
{
GameObject attTarget = _database.GetData("攻击对象") as GameObject;
if (_database.GetData("攻击对象") != null)
{
MoveToTarget(attTarget.transform.position);
return E_NodeState.Running;
}
}
return E_NodeState.Failed;
}
bool FoundPlayer()//找到敌人
{
Collider[] colliders = Physics.OverlapSphere(transform.position, radius);
foreach (var target in colliders)
{
if (target.CompareTag("Player"))
{
attackTarget = target.gameObject;
_database.Setdata("攻击对象", attackTarget);
isFound = true;
return isFound;
}
}
if (_database.GetData("攻击对象") != null)
{
_database.ClearData("攻击对象");
}
isFound = false;
return isFound;
}
Vector3 GetwayPoint()//随机巡逻点
{
float RandomX = Random.Range(-potrolRange, potrolRange);
float RandomZ = Random.Range(-potrolRange, potrolRange);
Vector3 RandomPoint = new Vector3(gardPos.x + RandomX, transform.position.y, gardPos.z + RandomZ);
waypoint = RandomPoint;
_database.Setdata("巡逻点", waypoint);//
return waypoint;
}
public E_NodeState MoveToPatrolPoint()//移动至巡逻点
{
if (!FoundPlayer())
{
if (Vector3.Distance(currentPatrolPos, transform.position) < 2f)
{
_database.Setdata("巡逻点", GetwayPoint());
currentPatrolPos = (Vector3)_database.GetData("巡逻点");
MoveToTarget(currentPatrolPos);
Debug.Log("切换巡逻点");
return E_NodeState.Running;
}
else
{
MoveToTarget(currentPatrolPos);
return E_NodeState.Running;
}
}
return E_NodeState.Failed;
}
public void MoveToTarget(Vector3 targetPos)
{
transform.position = Vector3.MoveTowards(transform.position, targetPos, moveSpeed * Time.deltaTime);
Debug.Log("移动方法");
}
private void OnDrawGizmos()
{
Gizmos.color = Color.green;
Gizmos.DrawWireSphere(transform.position, radius);
Gizmos.DrawWireSphere(transform.position, potrolRange);
}
//这里的攻击制作了一个打印hhhh
public E_NodeState Attack()
{
Debug.Log("攻击玩家");
return E_NodeState.Success;
}
//判断攻击范围
bool AttackRange()
{
if ((_database.GetData("攻击对象") as GameObject) != null)
{
if (Vector3.Distance((_database.GetData("攻击对象") as GameObject).transform.position, transform.position) < 2f)
{
return true;
}
}
return false;
}
}
玩家移动的代码在这里就不贴了,实现方式简单且多种。