一.二者的区别和联系
状态机(State Machine)和行为树(Behavior Tree)都是用于建模和管理系统行为的工具,但它们在结构、功能和适用场景上有显著的区别和联系。以下是两者的比较:
1. 基本概念
状态机:状态机是一种数学模型,由一组状态、输入、转移规则和输出组成。它通过状态之间的转移来描述系统的行为。状态机通常用于处理有限的、离散的状态和事件。
行为树:行为树是一种层次化的结构,用于建模复杂行为。它由不同类型的节点(如动作节点、条件节点和控制节点)组成,通过自上而下的执行流程来决定行为。行为树适用于需要组合多种行为的场景,尤其在游戏开发和人工智能中得到广泛应用。
2. 结构
状态机:状态机由状态和转移组成,状态之间通过事件进行转移。每个状态可以有进入和退出的动作。通常是平坦的,状态之间的关系是直接的。
行为树:行为树由节点组成,节点可以是叶子节点(执行具体行为)和控制节点(管理执行流程)。行为树是层次化的,允许通过组合节点来构建复杂行为,具有更好的复用性和可扩展性。
3. 执行流程
状态机:状态机在每个时刻只有一个活动状态,基于当前状态和输入事件决定下一个状态。
状态机的转移通常是瞬时的,状态之间的切换是明确的。
行为树:行为树可以同时执行多个行为(并行节点),并且通过控制节点的逻辑决定执行流程。
行为树的执行是自上而下的,节点的状态(成功、失败、运行中)会影响父节点的状态。
4. 适用场景
状态机:适合用于简单的、有限的状态管理,如角色的基本状态(待机、行走、跳跃等)。

常用于处理明确的状态转移和事件驱动的场景。
行为树:适合用于复杂的行为建模,尤其是在需要组合多个行为和决策的情况下。常用于游戏AI、机器人控制等领域,能够处理复杂的决策逻辑和状态管理。
5. 联系
决策模型:两者都是用于决策和行为建模的工具,可以在系统中共同使用。例如,状态机可以管理大状态(如“巡逻”、“追逐”),而行为树可以在每个状态下管理具体的行为(如“移动到目标”、“攻击”)。
状态管理:行为树中的某些节点可以视为状态机的状态,尤其是在处理条件和控制流时。行为树的控制节点可以被视为在不同状态之间进行切换的逻辑。
6.总结
状态机和行为树各有优缺点,适用于不同的场景。状态机适合简单的状态管理,而行为树则更适合复杂的行为组合和决策逻辑。在实际应用中,可以根据需求选择合适的模型,甚至将两者结合使用,以实现更灵活和强大的行为管理。我们今天就尝试把二者组合运用,优化我们的AI代码结构!
二.实验原理:
FSM的作用:管理AI的高层次状态和状态切换。
行为树的作用:在特定状态下执行具体的行为逻辑。
FSM提供了状态管理的框架,而行为树则提供了在每个状态下的具体行为实现。这种结合能够有效地处理复杂的AI行为,使得设计和维护变得更加高效。
实现思路:FSM状态机来控制大的状态切换,内部通过行为树来创建分支决定本状态下AI的可选行为,这些分支按照优先级进行排列,当AI的一种特定行为失败后会回溯到另一个策略,使FSM结构清晰的前提下达到复杂行为进一步拓展
三.实验目标
实现一种Enemy AI , 当玩家按下空格时 Enemy的HP-10
1.当未检测到玩家Player时,处于Patro巡逻l状态,在Patrol状态进行两种决策,IdlePatrol待机巡逻和SearchPatrol搜寻巡逻,
2.当检测到Player,会进入Chase追逐状态,向玩家移动,
3.当玩家进入可攻击范围,Enemy会根据自身的HP来选择当前的行为,这里我就简单假设
当HP > 80 时,Enemy选择近战攻击;
当HP <=80时,Enemy选择远程攻击;
当HP <=20时,Enemy选择逃跑;
4.当HP<= 0,Enemy进入Dead状态。
注意:这里只是假设的一种情景,实际开发过程中如果遇到如此简单的情景就没有必要为了使用FSM和BehaviourTree 而硬要使用 ,一切都是看需求来选择合适的,当需求一旦复杂,为了保证程序的健壮性,我们就应该提前选择结构更清晰,便于拓展的框架。
四.实验思路
一个控制器有一架状态机,这架状态机用来控制大的状态切换,在每种状态内部,我们进行各自状态的切换判定,在每种状态内部,都会有一棵行为树来进行本状态下决策的执行逻辑
五.前置脚本
在进行实验前,先准备以下脚本:
FSM系列:脚本实现了泛型有限状态机脚本
BehaviourTree系列:实现了行为树的各个结构节点
一.FSM系列:
具体可以去对应的文章查看详细内容:
这里我直接把代码放在下面方便自取:
FSM_StateMachine (泛型有限状态机类)
public class FSM_StateMachine<T>
{
public FSM_State<T> currentState;
public virtual void InitializeState(FSM_State<T> initState)
{
currentState = initState;
currentState.OnEnter();
}
public virtual void ChangeState(FSM_State<T> newState)
{
currentState.OnExit();
currentState = newState;
currentState.OnEnter();
}
}
FSM_State(泛型状态基类)
public class FSM_State<T>
{
protected FSM_StateMachine<T> fsm;
public virtual void OnEnter() { }
public virtual void OnUpdate() { }
public virtual void OnExit() { }
}
二.BehaviourTree系列:
Node (节点基类)
using System.Collections.Generic;
public class Node
{
/// <summary>
/// 当前状态
/// </summary>
public Status status;
//readonly修饰的常量则可以延迟到构造函数初始化;
/// <summary>
/// 子节点
/// </summary>
public List<Node> children = new List<Node>();
/// <summary>
/// 优先级
/// </summary>
public int priority;
public int currentChild;
public string name;
public Node(string name, int priority = 0)
{
this.name = name;
this.priority = priority;
}
public virtual void AddChild(Node child) => children.Add(child);
public virtual Status Process() => children[currentChild].Process();
public virtual void Reset()
{
currentChild = 0;
children.ForEach(child => child.Reset());
}
}
public enum Status
{
Running,
Success,
Failure,
}
BehaviourTree (行为树类)
public class BehaviourTree : Node
{
public BehaviourTree(string name) : base(name) { }
public override Status Process()
{
while (currentChild < children.Count)
{
var status = children[currentChild].Process();
if (status != Status.Success)
{
return status;
}
//当前节点成功,再进入下一节点
currentChild++;
}
return Status.Success;
}
}
IStrategy (策略接口)
public interface IStrategy
{
Status Process();
void Reset() { }
}
Condition(条件节点类)
public class Condition : IStrategy
{
readonly Func<bool> predicate;
public Condition(Func<bool> predicate)
{
this.predicate = predicate;
}
public Status Process() => predicate() ? Status.Success : Status.Failure;
}
SelectorNode (选择节点类)
public class SelectorNode : Node
{
public SelectorNode(string name) : base(name) { }
public override Status Process()
{
if (currentChild < children.Count)
{
switch (children[currentChild].Process())
{
case Status.Running:
return Status.Running;
case Status.Success:
Reset();
return Status.Success;
default:
currentChild++;
return Status.Running;
}
}
Reset();
return Status.Failure;
}
}
PrioritySelectorNode (优先级选择节点类)
public class PrioritySelectorNode:Node
{
List<Node> sortedChildren;
public PrioritySelectorNode(string name, int priority = 0) : base(name, priority) { }
List<Node> SortedChildren => sortedChildren ??= SortCildren();// ??=:检查左侧的变量是否为null,如果是则将右侧的值赋给左侧的变量
protected virtual List<Node> SortCildren()=>children.OrderByDescending(child=>child.priority).ToList();
public override void Reset()
{
base.Reset();
sortedChildren = null;
}
public override Status Process()
{
foreach (var child in SortedChildren)
{
switch (child.Process())
{
case Status.Running:
return Status.Running;
case Status.Success:
return Status.Success;
default:
continue;
}
}
Reset() ;
return Status.Failure;
}
}
SequenceNode(序列节点类)
public class SequenceNode : Node
{
public SequenceNode(string name, int priority = 0) : base(name, priority) { }
public override Status Process()
{
if (currentChild < children.Count)
{
switch (children[currentChild].Process())
{
case Status.Running:
return Status.Running;
case Status.Failure:
Reset();
return Status.Failure;
default:
currentChild++;
return currentChild == children.Count ? Status.Success : Status.Running;
}
}
Reset();
return Status.Success;
}
}
LeafNode(叶节点类)
public class LeafNode : Node,IStrategy
{
readonly IStrategy strategy;
public LeafNode(string name, IStrategy strategy, int priority = 0) : base(name, priority)
{
this.strategy = strategy;
}
public override Status Process()=>strategy.Process();
public override void Reset()=>strategy.Reset();
}
六.测试脚本
EnemyState(Enemy状态基类)
using UnityEngine;
public class Enemy : MonoBehaviour
{
public EnemyStateMachine stateMachine;
public EnemyPatrolState patrolState;
public EnemyChaseState chaseState;
public EnemyAttackState attackState;
public EnemyDeadState deadState;
Animator anim;
/// <summary>
/// 检测到玩家
/// </summary>
public bool isPlayerDetected;
/// <summary>
/// 进入攻击范围,可以攻击
/// </summary>
public bool canAttack;
public int currentHP = 100;
private void Awake()
{
anim = GetComponent<Animator>();
stateMachine = new EnemyStateMachine();
patrolState = new EnemyPatrolState(this, stateMachine, anim, "patrol");
chaseState = new EnemyChaseState(this, stateMachine, anim, "chase");
deadState = new EnemyDeadState(this, stateMachine, anim, "dead");
attackState = new EnemyAttackState(this, stateMachine, anim, "attack");
stateMachine.InitializeState(patrolState);
}
void Update()
{
stateMachine.currentState.OnUpdate();
if (Input.GetKeyDown(KeyCode.Space))
{
currentHP -= 10;
Debug.Log("enemy当前血量:" + currentHP);
}
}
}
EnemyStateMachine(Enemy状态机类)
public class EnemyStateMachine : FSM_StateMachine<Enemy>
{
public override void ChangeState(FSM_State<Enemy> newState)
{
base.ChangeState(newState);
}
public override void InitializeState(FSM_State<Enemy> initState)
{
base.InitializeState(initState);
}
}
EnemyPatrolState(巡逻状态)
using UnityEngine;
public class EnemyPatrolState : EnemyState
{
public bool IsMove => isMove;
bool isMove = false;
bool canChange = true;
float timer = 0;
//4秒钟切换Idle或Search
float changeDuration = 4;
public EnemyPatrolState(Enemy enemy, EnemyStateMachine stateMachine, Animator anim, string animBoolName) : base(enemy, stateMachine, anim, animBoolName)
{
LoadTree();
}
public override void LoadTree()
{
base.LoadTree();
PrioritySelectorNode selector = new PrioritySelectorNode("EnemyPatrolSelector");
//不移动就Idle一会
SequenceNode IdlePatrolSeq = new SequenceNode("EnemyPatrolSequence_Idle");
IdlePatrolSeq.AddChild(new LeafNode("EnemyPatrol_Idle", new Condition(() => !isMove)));
IdlePatrolSeq.AddChild(new LeafNode("EnemyPatrol_Idle", new EnemyPatrolStrategy_Idle(this)));
//移动就Search一会
SequenceNode SearchPatrolSeq = new SequenceNode("EnemyPatrolSequence_Search", 1);
SearchPatrolSeq.AddChild(new LeafNode("EnemyPatrol_Search", new Condition(() => isMove)));
SearchPatrolSeq.AddChild(new LeafNode("EnemyPatrol_Search", new EnemyPatrolStrategy_Search(this)));
selector.AddChild(IdlePatrolSeq);
selector.AddChild(SearchPatrolSeq);
stateTree.AddChild(selector);
}
public override void OnEnter()
{
base.OnEnter();
Debug.Log("进入PatrolState");
}
public override void OnExit()
{
base.OnExit();
Debug.Log("退出PatrolState");
}
public override void OnUpdate()
{
base.OnUpdate();
Debug.Log("正在PatrolState");
if (enemy.isPlayerDetected)
stateMachine.ChangeState(enemy.chaseState);
if (!isMove && canChange)
{
canChange = false;
isMove = true;
timer = changeDuration;
}
if (isMove && canChange)
{
canChange = false;
isMove = false;
timer = changeDuration;
}
Debug.Log(isMove);
if (timer >= 0)
{
timer -= Time.deltaTime;
}
else
{
canChange = true;
}
}
}
EnemyChaseState(追逐状态)
using UnityEngine;
public class EnemyChaseState : EnemyState
{
public EnemyChaseState(Enemy enemy, EnemyStateMachine stateMachine, Animator anim, string animBoolName) : base(enemy, stateMachine, anim, animBoolName)
{
LoadTree();
}
public override void LoadTree()
{
base.LoadTree();
}
public override void OnEnter()
{
base.OnEnter();
Debug.Log("进入ChaseState");
}
public override void OnExit()
{
base.OnExit();
Debug.Log("退出ChaseState");
}
public override void OnUpdate()
{
base.OnUpdate();
Debug.Log("正在ChaseState");
if (enemy.canAttack)//当进入可攻击范围,进入攻击状态
stateMachine.ChangeState(enemy.attackState);
}
}
EnemyAttackState(攻击状态)
using UnityEngine;
public class EnemyAttackState : EnemyState
{
public EnemyAttackState(Enemy enemy, EnemyStateMachine stateMachine, Animator anim, string animBoolName) : base(enemy, stateMachine, anim, animBoolName)
{
LoadTree();
}
public int CurrentHP => currentHP;
int currentHP;
public override void LoadTree()
{
base.LoadTree();
PrioritySelectorNode selector = new PrioritySelectorNode("EnemyAttackSelector");
//Enemy生命值>80,选择近战攻击
SequenceNode closeAttackSeq = new SequenceNode("EnemyAttackSequence_Close");
closeAttackSeq.AddChild(new LeafNode("EnemyAttack_Close", new Condition(() => enemy.currentHP > 80)));
closeAttackSeq.AddChild(new LeafNode("EnemyAttack_Close", new EnemyAttackStrategy_Close(this)));
//Enemy生命值<=80,选择远程攻击
SequenceNode remoteAttackSeq = new SequenceNode("EnemyAttackSequence_Remote", 2);
remoteAttackSeq.AddChild(new LeafNode("EnemyAttack_Remote", new Condition(() => enemy.currentHP <= 80)));
remoteAttackSeq.AddChild(new LeafNode("EnemyAttack_Remote", new EnemyAttackStrategy_Remote(this)));
//Enemy生命值<=20,选择逃跑
SequenceNode runAwayAttackSeq = new SequenceNode("EnemyAttackSequence_RunAway", 3);
runAwayAttackSeq.AddChild(new LeafNode("EnemyAttack_RunAway", new Condition(() => enemy.currentHP <= 20)));
runAwayAttackSeq.AddChild(new LeafNode("EnemyAttack_RunAway", new EnemyAttackStrategy_RunAway(this)));
selector.AddChild(closeAttackSeq);
selector.AddChild(remoteAttackSeq);
selector.AddChild(runAwayAttackSeq);
stateTree.AddChild(selector);
}
public override void OnEnter()
{
base.OnEnter();
Debug.Log("进入AttackState");
}
public override void OnExit()
{
base.OnExit();
Debug.Log("退出AttackState");
}
public override void OnUpdate()
{
base.OnUpdate();
currentHP = enemy.currentHP;
Debug.Log("正在AttackState");
if (currentHP <= 0)
stateMachine.ChangeState(enemy.deadState);
}
}
EnemyDeadState(死亡状态)
using UnityEngine;
public class EnemyDeadState : EnemyState
{
public EnemyDeadState(Enemy enemy, EnemyStateMachine stateMachine, Animator anim, string animBoolName) : base(enemy, stateMachine, anim, animBoolName)
{
}
public override void LoadTree()
{
base.LoadTree();
}
public override void OnEnter()
{
base.OnEnter();
Debug.Log("进入DeadState");
}
public override void OnExit()
{
base.OnExit();
Debug.Log("退出DeadState");
}
public override void OnUpdate()
{
base.OnUpdate();
Debug.Log("正在DeadState");
}
}
EnemyPatrolStrategy_Idle(巡逻策略_待机巡视)
using UnityEngine;
public class EnemyPatrolStrategy_Idle : IStrategy
{
bool isMove;
EnemyPatrolState patrolState;
public EnemyPatrolStrategy_Idle(EnemyPatrolState patrolState)
{
this.patrolState = patrolState;
}
public Status Process()
{
isMove = patrolState.IsMove;
Debug.Log("我正在Idle巡视");
if (isMove)
{
return Status.Failure;
}
return Status.Running;
}
}
EnemyPatrolStrategy_Search(巡逻策略_搜寻巡视)
using UnityEngine;
/// <summary>
/// 搜寻策略:搜查巡视
/// </summary>
public class EnemyPatrolStrategy_Search : IStrategy
{
bool isMove;
EnemyPatrolState patrolState;
public EnemyPatrolStrategy_Search(EnemyPatrolState patrolState)
{
this.patrolState = patrolState;
}
public Status Process()
{
isMove = patrolState.IsMove;
Debug.Log("我正在Search巡视");
if (!isMove)
{
return Status.Failure;
}
return Status.Running;
}
}
EnemyAttackStrategy_Close(攻击策略_近战)
public class EnemyAttackStrategy_Close : IStrategy
{
EnemyAttackState attackState;
public EnemyAttackStrategy_Close(EnemyAttackState attackState)
{
this.attackState = attackState;
}
public Status Process()
{
Debug.Log("我在近战攻击");
if (attackState.CurrentHP <= 80)
{
return Status.Failure;
}
return Status.Running;
}
}
EnemyAttackStrategy_Remote(攻击策略_远程)
using UnityEngine;
public class EnemyAttackStrategy_Remote : IStrategy
{
EnemyAttackState attackState;
public EnemyAttackStrategy_Remote(EnemyAttackState attackState)
{
this.attackState = attackState;
}
public Status Process()
{
Debug.Log("我在远程攻击");
if (attackState.CurrentHP <= 20)
{
return Status.Failure;
}
return Status.Running;
}
}
EnemyAttackStrategy_RunAway:(攻击策略_逃跑)
using UnityEngine;
public class EnemyAttackStrategy_RunAway : IStrategy
{
EnemyAttackState attackState;
public EnemyAttackStrategy_RunAway(EnemyAttackState attackState)
{
this.attackState = attackState;
}
public Status Process()
{
Debug.Log("我选择逃跑");
if (attackState.CurrentHP <= 0)
{
return Status.Failure;
}
return Status.Running;
}
}
七.测试结果
在编辑器内为空物体挂载脚本并运行
基本实现了实验目标,运用FSM和行为树实现了简单AI行为。
注:这里假设的简单情景只是为了直观好理解,具体开发时可根据具体需求改造应用,但大体处理思路是近似一致的。
本篇完~