如何基于FSM有限状态机实现Enemies AI

这篇具有很好参考价值的文章主要介绍了如何基于FSM有限状态机实现Enemies AI。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


🍟 Preface

本文简单介绍如何基于FSM有限状态机实现Enemies AI,首先定义敌人的AI逻辑:默认状态下Enemy为巡逻状态,有若干巡逻点位,Enemy在这些点位之间来回巡逻走动,同时检测Player的位置,当Player进入一定范围内时,Enemy进入寻路状态,寻路到Player位置前,进入Attacking攻击状态,当Player离开一定距离时,Enemy重回巡逻状态进行巡逻。

  • Patrol State:巡逻状态
  • Path Finding State:寻路状态
  • Attacking State:攻击状态

🍕 巡逻状态

如何基于FSM有限状态机实现Enemies AI

如图所示,我们预设了三个巡逻点,Enemy会在这三个巡逻点之间来回移动巡逻,并且在到达一个巡逻点时,会随机休息几秒,首先在OnDrawGizmos函数中绘制出三个点的Position Handle,方便我们调试:

//巡逻点集合
[SerializeField] private Transform[] patrolPoints;

private void OnDrawGizmos()
{
    for (int i = 0; i < patrolPoints.Length; i++)
    {
        Handles.PositionHandle(patrolPoints[i].position, Quaternion.identity);
        Handles.Label(patrolPoints[i].position, string.Format("Patrol Point {0}", i + 1));
    }
}

动画相关变量与参数如下:

//动画组件
[SerializeField] private Animator animator;

private class AnimatorParams
{
    public static readonly int Idle = Animator.StringToHash("Idle");
    public static readonly int Walk = Animator.StringToHash("Walk");
    public static readonly int Run = Animator.StringToHash("Run");
    public static readonly int Action = Animator.StringToHash("Action");
}

寻路功能使用Unity内部功能NavMeshAgent

//寻路代理
[SerializeField] private NavMeshAgent agent;

定义Patrol State

private class PatrolState : State
{
    //当前巡逻点的索引值
    public int index;
    //休息计时
    public float timer;
}

创建状态机并构建状态:

private void Start()
{
    var machine = StateMachine.Create("Enemy AI")
        .Build<PatrolState>("巡逻状态")
            .OnEnter(s =>
            {
                agent.isStopped = false;
                //StopDistance设为0
                agent.stoppingDistance = 0f;
                //设置速度
                agent.speed = 1f;
                //进入巡逻状态时 设置第一个巡逻点
                s.index = 0;
                agent.SetDestination(patrolPoints[s.index].position);
                //设置动画参数 进入Walk
                animator.SetBool(AnimatorParams.Idle, false);
                animator.SetBool(AnimatorParams.Walk, true);
            })
            .OnStay(s =>
            {
                //判断是否到达目标巡逻点
                if (Vector3.Distance(transform.position, patrolPoints[s.index].position) <= .1f)
                {
                    //设置动画参数 进入Idle
                    animator.SetBool(AnimatorParams.Walk, false);
                    animator.SetBool(AnimatorParams.Idle, true);
                    //到达后随机休息若干秒
                    s.timer += Time.deltaTime;
                    if (s.timer >= Random.Range(3f, 5f))
                    {
                        //重置计时器
                        s.timer = 0f;
                        //设置下一个巡逻点
                        s.index++;
                        s.index = s.index == patrolPoints.Length ? 0 : s.index;
                        agent.SetDestination(patrolPoints[s.index].position);
                        //设置动画参数 进入Walk
                        animator.SetBool(AnimatorParams.Idle, false);
                        animator.SetBool(AnimatorParams.Walk, true);
                    }
                }
            })
            .OnExit(s =>
            {
                agent.isStopped = true;
                animator.SetBool(AnimatorParams.Idle, false);
                animator.SetBool(AnimatorParams.Walk, false);
            })
        .Complete();

    //进入第一个状态
    machine.Switch2Next();
}

巡逻状态下,当Player进入到5米检测范围内时,进入寻路状态:

//当Player进入5米范围内时 Enemy进入寻路状态
SwitchWhen(() => Vector3.Distance(player.position, transform.position) <= 5f, "寻路状态")

通过Handles类中的DrawWireArc方法将该范围绘制出来,方便调试:

Handles.color = Color.red;
Handles.DrawWireArc(transform.position, transform.up, transform.right, 360f, 5f);

如图所示,红色圈范围即为检测范围:

如何基于FSM有限状态机实现Enemies AI

🍿 寻路状态

寻路状态表示已经检测到Player,追击Player,不断寻路到Player前,设置AgentStop Distance属性为1.5,该寻路过程中的移动速度比巡逻状态时要快,因此调整Speed属性为2,当距离Player大于10时,重新回到巡逻状态,不再追击。

.Build<State>("寻路状态")
	.OnEnter(s =>
	{
		agent.isStopped = false;
		//StopDistance设为1
		agent.stoppingDistance = 1.5f;
		//加速移动
		agent.speed = 2f;
		//设置动画参数 进入Run
		animator.SetBool(AnimatorParams.Run, true);
	})
    .OnStay(s =>
    {
		//未到达Player前指定距离时 不断寻路
        if (Vector3.Distance(transform.position, player.position) > 1.5f)
        {
        	agent.SetDestination(player.position);
        }
        else
        {
        	//到达Player前指定距离 进入攻击状态
            s.machine.Switch("攻击状态");
        }
	})
    .OnExit(s =>
    {
    	animator.SetBool(AnimatorParams.Run, false);
    })
    //距离Player大于指定值时 重回巡逻状态
    .SwitchWhen(() => Vector3.Distance(transform.position, player.position) > 10f, "巡逻状态")
.Complete()

同样使用Handles类中的DrawWireArc方法绘制出追击范围:

private void OnDrawGizmos()
{
    for (int i = 0; i < patrolPoints.Length; i++)
    {
        Handles.PositionHandle(patrolPoints[i].position, Quaternion.identity);
        Handles.Label(patrolPoints[i].position, string.Format("Patrol Point {0}", i + 1));
    }

    Handles.color = Color.red;
    Handles.DrawWireArc(transform.position, transform.up, transform.right, 360f, 5f);
    Handles.color = Color.cyan;
    Handles.DrawWireArc(transform.position, transform.up, transform.right, 360f, 10f);
}

如图所示,青色圈范围即为追击范围:

如何基于FSM有限状态机实现Enemies AI

🌭 攻击状态

定义攻击状态:

private class AttackState : State
{
    //攻击CD
    public float attackCD = 2f;
}

构建攻击状态:

.Build<AttackState>("攻击状态")
	.OnEnter(s => agent.isStopped = true)
	.OnStay(s =>
    {
    	//朝向Player
        transform.rotation = Quaternion.LookRotation(player.position - transform.position);
        //Attack Action
        if (s.attackCD == 2f) animator.SetInteger(AnimatorParams.Action, 1);
        //攻击CD
        else
        {
        	s.attackCD -= Time.deltaTime;
            if (s.attackCD <= 0f) s.attackCD = 2f;
        }
        })
	.OnExit(s => animator.SetInteger(AnimatorParams.Action, 0))
    .SwitchWhen(() => Vector3.Distance(transform.position, player.position) >= 2f, "寻路状态")
.Complete();

这里使用一个Wolf的模型当做Player:

如何基于FSM有限状态机实现Enemies AI
Player进入巡逻检测范围:

如何基于FSM有限状态机实现Enemies AI

Player离开追击范围:

如何基于FSM有限状态机实现Enemies AI文章来源地址https://www.toymoban.com/news/detail-405598.html

🍗 完整代码

using UnityEngine;
using UnityEngine.AI;
using SK.Framework.FSM;

#if UNITY_EDITOR
using UnityEditor;
#endif

/// <summary>
/// 敌人单位
/// </summary>
public class EnemyUnit : MonoBehaviour
{
    //Player位置
    [SerializeField] private Transform player;
    //寻路代理
    [SerializeField] private NavMeshAgent agent;
    //动画组件
    [SerializeField] private Animator animator;
    //巡逻点集合
    [SerializeField] private Transform[] patrolPoints;

    private class PatrolState : State
    {
        //当前巡逻点的索引值
        public int index;
        //休息计时
        public float timer;
    }

    private class AttackState : State
    {
        public float attackCD = 2f;
    }

    private class AnimatorParams
    {
        public static readonly int Idle = Animator.StringToHash("Idle");
        public static readonly int Walk = Animator.StringToHash("Walk");
        public static readonly int Run = Animator.StringToHash("Run");
        public static readonly int Action = Animator.StringToHash("Action");
    }

    private void Start()
    {
        var machine = StateMachine.Create("Enemy AI")
            .Build<PatrolState>("巡逻状态")
                .OnEnter(s =>
                {
                    agent.isStopped = false;
                    //StopDistance设为0
                    agent.stoppingDistance = 0f;
                    //设置速度
                    agent.speed = 1f;
                    //进入巡逻状态时 设置第一个巡逻点
                    s.index = 0;
                    agent.SetDestination(patrolPoints[s.index].position);
                    //设置动画参数 进入Walk
                    animator.SetBool(AnimatorParams.Idle, false);
                    animator.SetBool(AnimatorParams.Walk, true);
                })
                .OnStay(s =>
                {
                    //判断是否到达目标巡逻点
                    if (Vector3.Distance(transform.position, patrolPoints[s.index].position) <= .1f)
                    {
                        //设置动画参数 进入Idle
                        animator.SetBool(AnimatorParams.Walk, false);
                        animator.SetBool(AnimatorParams.Idle, true);
                        //到达后随机休息若干秒
                        s.timer += Time.deltaTime;
                        if (s.timer >= Random.Range(3f, 5f))
                        {
                            //重置计时器
                            s.timer = 0f;
                            //设置下一个巡逻点
                            s.index++;
                            s.index = s.index == patrolPoints.Length ? 0 : s.index;
                            agent.SetDestination(patrolPoints[s.index].position);
                            //设置动画参数 进入Walk
                            animator.SetBool(AnimatorParams.Idle, false);
                            animator.SetBool(AnimatorParams.Walk, true);
                        }
                    }
                })
                .OnExit(s =>
                {
                    agent.isStopped = true;
                    animator.SetBool(AnimatorParams.Idle, false);
                    animator.SetBool(AnimatorParams.Walk, false);
                })
                //当Player进入5米范围内时 Enemy进入寻路状态
                .SwitchWhen(() => Vector3.Distance(player.position, transform.position) <= 5f, "寻路状态")
            .Complete()
            .Build<State>("寻路状态")
                .OnEnter(s =>
                {
                    agent.isStopped = false;
                    //StopDistance设为1
                    agent.stoppingDistance = 1.5f;
                    //加速移动
                    agent.speed = 2f;
                    //设置动画参数 进入Run
                    animator.SetBool(AnimatorParams.Run, true);
                })
                .OnStay(s =>
                {
                    //未到达Player前指定距离时 不断寻路
                    if (Vector3.Distance(transform.position, player.position) > 1.5f)
                    {
                        agent.SetDestination(player.position);
                    }
                    else
                    {
                        //到达Player前指定距离 进入攻击状态
                        s.machine.Switch("攻击状态");
                    }
                })
                .OnExit(s =>
                {
                    animator.SetBool(AnimatorParams.Run, false);
                })
                //距离Player大于指定值时 重回巡逻状态
                .SwitchWhen(() => Vector3.Distance(transform.position, player.position) > 10f, "巡逻状态")
            .Complete()
            .Build<AttackState>("攻击状态")
                .OnEnter(s => agent.isStopped = true)
                .OnStay(s =>
                {
                    //朝向Player
                    transform.rotation = Quaternion.LookRotation(player.position - transform.position);
                    //Attack Action
                    if (s.attackCD == 2f) animator.SetInteger(AnimatorParams.Action, 1);
                    //攻击CD
                    else
                    {
                        s.attackCD -= Time.deltaTime;
                        if (s.attackCD <= 0f) s.attackCD = 2f;
                    }
                })
                .OnExit(s => animator.SetInteger(AnimatorParams.Action, 0))
                .SwitchWhen(() => Vector3.Distance(transform.position, player.position) >= 2f, "寻路状态")
            .Complete();

        //进入第一个状态
        machine.Switch2Next();
    }

#if UNITY_EDITOR
    private void OnDrawGizmos()
    {
        for (int i = 0; i < patrolPoints.Length; i++)
        {
            Handles.PositionHandle(patrolPoints[i].position, Quaternion.identity);
            Handles.Label(patrolPoints[i].position, string.Format("Patrol Point {0}", i + 1));
        }

        Handles.color = Color.red;
        Handles.DrawWireArc(transform.position, transform.up, transform.right, 360f, 5f);
        Handles.color = Color.cyan;
        Handles.DrawWireArc(transform.position, transform.up, transform.right, 360f, 10f);
    }
#endif
}

到了这里,关于如何基于FSM有限状态机实现Enemies AI的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Go中的有限状态机FSM的详细介绍

    1.1 有限状态机的定义 有限状态机(Finite State Machine,FSM)是一种数学模型,用于描述系统在不同状态下的行为和转移条件。 状态机有三个组成部分: 状态(State)、事件(Event)、动作(Action) ,事件(转移条件)触发状态的转移和动作的执行。动作的执行不是必须的,可

    2023年04月24日
    浏览(26)
  • Go中的有限状态机FSM的详细介绍 _

    1、FSM简介 1.1 有限状态机的定义 有限状态机(Finite State Machine,FSM)是一种数学模型,用于描述系统在不同状态下的行为和转移条件。 状态机有三个组成部分: 状态(State)、事件(Event)、动作(Action) ,事件(转移条件)触发状态的转移和动作的执行。动作的执行不是必

    2024年02月12日
    浏览(62)
  • unity有限状态机和模糊状态机(怪物AI、自动寻路)

    自动寻路步骤: 1、把场景中不同的物体勾选static 2、烘培寻路网格 3、添加NavMeshAgent组件 4、给需要寻路的物体添加脚本 游戏中有限状态机的体现:小怪的巡逻和追逐功能 模糊状态机的体现:当玩家离小怪比较近时,小怪会追逐玩家,当玩家离小怪比较远时小怪会停止追逐玩

    2024年02月04日
    浏览(34)
  • 12-同步状态机的结构以及Mealy和Moore状态机的区别,Verilog实现有限状态机的4种方式,以及总结有限状态机设计的一般步骤

    由于寄存器传输级(RTL)描述的是以时序逻辑抽象所得到的有限状态机为依据,因此,把一个时序逻辑抽象成一个同步有限状态机是设计可综合风格的Verilog HDL模块的关键。 在本章节中,在了解状态机结构的基础上通过各种实例,由浅入深地介绍各种可综合风格的Verilog HDL模

    2024年01月17日
    浏览(35)
  • C# Unity FSM 状态机

    使用状态机可以降低代码耦合性,并且可以优化代码可读性,方便团队协作等。 对于游戏开发内容来讲游戏开发的流程控制玩家动画都可以使用FSM有限状态机来实现。 每个状态的基类,泛型参数表示所拥有者 有限状态机的接口 有限状态机管理器接口 有限状态机的基类 状态

    2024年02月09日
    浏览(38)
  • Spring状态机(FSM),让订单状态流转如丝般顺滑

    在复杂的应用程序设计中,尤其是那些涉及多个状态变迁和业务流程控制的场景,有限状态机( Finite State Machine, FSM )是一种强大而有效的建模工具。Spring框架为此提供了Spring状态机( Spring State Machine )这一组件,它允许开发者以一种声明式且结构清晰的方式来管理和控制对

    2024年03月12日
    浏览(27)
  • 前端状态管理与有限状态机

    当下前端流行的框架,都是用状态来描述界面(state = view),可以说前端开发实际上就是在维护各种状态(state),这已经成为目前前端开发的共识。 复制代码 View = ViewModel(Model); 理想情况下,ViewModel 是纯函数,给定相同的 Model,产出相同的 View。 state = view 很好理解,但如何在 vi

    2024年03月15日
    浏览(32)
  • 计算机组成原理3个实验-logisim实现“七段数码管”、“有限状态机控制的8*8位乘法器”、“单周期MIPS CPU设计”。

    目录 标题1.首先是七段数码管   标题二:有限状态机控制的8*8位乘法器 标题三:单周期MIPS CPU设计 1看一下实验要求:    2.接下来就是详细设计: 1. 组合逻辑设计        由于7段数码管由7个发光的数码管构成,因为我们想用二进制将0-9这几个数字表示出来。所以他需要

    2024年01月17日
    浏览(32)
  • 编译原理二:有限状态机

    有限状态机是一种 计算模型 ,它可以 接受一串输入并根据一组状态转移规则进行状态转移,最终输出一个结果 。有限状态机可以分为两种类型: 确定性有限状态机(DFA) 和 非确定性有限状态机(NFA) 。 DFA 是一种状态机,它的 每个状态都有一条出边对应每个输入符号,

    2024年02月11日
    浏览(29)
  • Unity有限状态机

    一、引言 在游戏开发中,经常会遇到游戏角色或实体具有多种状态,并且在不同状态之间需要切换的情况。例如,一个角色可能处于行走、奔跑、跳跃等不同的状态,并且根据玩家的输入或游戏逻辑,在这些状态之间进行切换。为了管理这些状态及其之间的转换,我们可以使

    2024年02月03日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包