综合应用案例-FSM和BehaviourTree结合-创造更清晰的AI!

一.二者的区别和联系

状态机(State Machine)和行为树(Behavior Tree)都是用于建模和管理系统行为的工具,但它们在结构、功能和适用场景上有显著的区别和联系。以下是两者的比较:

1. 基本概念

状态机:状态机是一种数学模型,由一组状态、输入、转移规则和输出组成。它通过状态之间的转移来描述系统的行为。状态机通常用于处理有限的、离散的状态和事件。

行为树:行为树是一种层次化的结构,用于建模复杂行为。它由不同类型的节点(如动作节点、条件节点和控制节点)组成,通过自上而下的执行流程来决定行为。行为树适用于需要组合多种行为的场景,尤其在游戏开发和人工智能中得到广泛应用。

2. 结构

状态机:状态机由状态和转移组成,状态之间通过事件进行转移。每个状态可以有进入和退出的动作。通常是平坦的,状态之间的关系是直接的。

行为树:行为树由节点组成,节点可以是叶子节点(执行具体行为)和控制节点(管理执行流程)。行为树是层次化的,允许通过组合节点来构建复杂行为,具有更好的复用性和可扩展性。

3. 执行流程

状态机:状态机在每个时刻只有一个活动状态,基于当前状态和输入事件决定下一个状态。

状态机的转移通常是瞬时的,状态之间的切换是明确的。

行为树:行为树可以同时执行多个行为(并行节点),并且通过控制节点的逻辑决定执行流程。

行为树的执行是自上而下的,节点的状态(成功、失败、运行中)会影响父节点的状态。

4. 适用场景

状态机:适合用于简单的、有限的状态管理,如角色的基本状态(待机、行走、跳跃等)。

扫描二维码关注公众号,回复: 17507566 查看本文章

常用于处理明确的状态转移和事件驱动的场景。

行为树:适合用于复杂的行为建模,尤其是在需要组合多个行为和决策的情况下。常用于游戏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有限状态系统

BehaviourTree行为树系统

这里我直接把代码放在下面方便自取:

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行为。

注:这里假设的简单情景只是为了直观好理解,具体开发时可根据具体需求改造应用,但大体处理思路是近似一致的。

本篇完~

猜你喜欢

转载自blog.csdn.net/xxxxx666666/article/details/142715076