2024-07-13 Unity AI状态机2 —— 项目介绍

文章目录

  • 1 项目介绍
  • 2 模块介绍
    • 2.1 BaseState
    • 2.2 ...State
      • 2.2.1 PatrolState
      • 2.2.2 ChaseState / AttackState / BackState
    • 2.3 StateMachine
    • 2.4 Monster
  • 3 其他功能
  • 4 类图

项目借鉴 B 站唐老狮 2023年直播内容。 点击前往唐老狮 B 站主页。

1 项目介绍

​ 本项目使用 Unity 2022.3.32f1c1,实现基本的 AI 框架。其中,用 Cube(绿色)代替怪物模型,Cube(红色)代替玩家,即 AI 目标。

image-20240713164018099

​ 项目地址:https://github.com/zheliku/StateMachine_AI。

2 模块介绍

2.1 BaseState

​ 在框架介绍的基础上,添加了两个方法:

  1. DistanceOfXZ(Vector3, Vector3)

    用于计算 xz 平面上的距离(不考虑 y 轴方向)。

  2. DrawGizmos()

    用于辅助绘制范围,在 Scene 窗口中显示。

using UnityEngine;

/// <summary>
/// 状态基类
/// </summary>
public abstract class BaseState
{
    public virtual EAIState AIState { get; } // 状态类型

    protected StateMachine _stateMachine; // 附属的状态机

    public BaseState(StateMachine stateMachine) {
        _stateMachine = stateMachine;
    }

    public abstract void OnStateEnter();

    public abstract void OnStateUpdate();

    public abstract void OnStateExit();

    /// <summary>
    /// 辅助绘制范围,不强制重写
    /// </summary>
    public virtual void DrawGizmos() { }

    /// <summary>
    /// XZ 平面上的距离
    /// </summary>
    protected float DistanceOfXZ(Vector3 pos1, Vector3 pos2) {
        pos1.y = pos2.y = 0;
        return Vector3.Distance(pos1, pos2);
    }
}

2.2 …State

​ 项目实现了 4 种 AI 状态,包括

  • PatrolState(巡逻状态)
  • ChaseState(追逐状态)
  • AttackState(攻击状态)
  • BackState(返回状态)

2.2.1 PatrolState

​ PatrolState 实现较为详细,其将巡逻方式分为 3 种:

public enum EPatrolType
{
    Stay,       // 原地播放某个动作(睡觉、放哨等)
    CircleMove, // 圆形范围内随机移动
    PathMove    // 按照路径移动
}

​ 巡逻数据可以直接在 PatrolState 类中声明。本项目选择封装在 PatrolStateData 中。所有 Data 均继承 ScripteObject 类,可以在 Project 窗口中右键直接创建并配置对应数据。

  • PatrolStateData:所有巡逻种类的共有数据。

  • PatrolStateStayData:原地巡逻的数据。

  • PatrolStateMoveData:移动巡逻的数据。

  • PatrolStateCircleMoveData:圆形范围移动巡逻的数据。

  • PatrolStatePathMoveData:路径移动巡逻的数据。

StateMachine_AI PatrolStateData
  1. Stay:

    原地播放某个动作,因此需要 AI 动作枚举。目前只添加 Sleep 动作。

    public enum EAction
    {
        Sleep,
    }
    
  2. Move:

    范围内随机移动,分为两种:Circle、Path。区别是获取下一次目标位置的方式不同,因此提取出如下逻辑:

    public class PatrolState : BaseState
    {
        private PatrolStateData _data; // 巡逻数据
        
        ...
            
        /// <summary>
        /// 移动
        /// </summary>
        private void OnMoveUpdate(IAIObject aiObject, EPatrolType moveType) {
            var data = (PatrolStateMoveData)_data; // 转化数据
    
            ...
    
            _data.targetPos = moveType switch {
                EPatrolType.CircleMove => CalCircleTargetPos((PatrolStateCircleMoveData)data),
                EPatrolType.PathMove   => CalPathTargetPos((PatrolStatePathMoveData)data),
                _                      => throw new ArgumentOutOfRangeException(nameof(moveType), moveType, null)
            };
    
            ...
        }
    
        /// <summary>
        /// 更新圆形范围目标位置
        /// </summary>
        private Vector3 CalCircleTargetPos(PatrolStateCircleMoveData data) { ... }
    
        /// <summary>
        /// 更新路径范围目标位置
        /// </summary>
        private Vector3 CalPathTargetPos(PatrolStatePathMoveData data) { ... }
        
        ...
    }
    

2.2.2 ChaseState / AttackState / BackState

​ 该 3 个状态都遵循以下大致框架:

using UnityEngine;

public class ...State : BaseState
{
    private ...StateData _data;

    public override EAIState AIState { get => ...; }

    public AttackState(StateMachine stateMachine) : base(stateMachine) {
        // 加载数据
        var data = Resources.Load<...StateData>("StateData/.../...StateData");
        _data = Object.Instantiate(data);
    }

    public override void OnStateEnter() { ... }

    public override void OnStateUpdate() { ... }

    public override void OnStateExit() { ... }
    
    ...
}

2.3 StateMachine

​ AddState()、ChangeState() 和 UpdateState() 逻辑如下:

/// <summary>
/// 添加 AI 状态
/// </summary>
public void AddState(EAIState state) {
    switch (state) {
        case EAIState.Patrol:
            _stateDic.Add(state, new PatrolState(this));
            break;
        case EAIState.Back:
            _stateDic.Add(state, new BackState(this));
            break;
        case EAIState.Chase:
            _stateDic.Add(state, new ChaseState(this));
            break;
        case EAIState.Attack:
            _stateDic.Add(state, new AttackState(this));
            break;
        default: throw new ArgumentOutOfRangeException(nameof(state), state, null);
    }
}

/// <summary>
/// 切换状态
/// </summary>
/// <param name="state"></param>
public void ChangeState(EAIState state) {
    _nowState?.OnStateExit(); // 退出状态

    if (_stateDic.TryGetValue(state, out BaseState nowState)) { // 进入状态
        _nowState = nowState;
        _nowState.OnStateEnter();
    }
}

/// <summary>
/// 更新当前状态
/// </summary>
public void UpdateState() {
    _nowState?.OnStateUpdate();

    _nowState?.DrawGizmos(); // 辅助绘图
}

2.4 Monster

​ 怪物类实现了 IAIObject 接口(详见 2024-07-12 Unity AI状态机1 —— 框架介绍),通过 Unity 导航系统中的 NavMeshAgent 实现基本移动。除了 IAIObject 接口,还包含一些自己的数据。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class Monster : MonoBehaviour, IAIObject
{
    public Transform  targetTransform; // 目标位置
    public GameObject bullet;          // 子弹
    public float      attackRange = 3; // 攻击范围

    private Quaternion _startRotation;  // 记录开始攻击时的角度
    private Quaternion _targetRotation; // 记录目标角度
    private float      _rotateTime;     // 旋转计时

    private NavMeshAgent _navMeshAgent; // 导航代理

    private StateMachine _aiMachine; // AI 状态机
    
    private void Start() {
        _navMeshAgent = GetComponent<NavMeshAgent>();
        BornPos       = transform.position;

        _aiMachine = new StateMachine();
        _aiMachine.Init(this);

        // 为 AI 添加巡逻状态
        _aiMachine.AddState(EAIState.Patrol);
        _aiMachine.AddState(EAIState.Chase);
        _aiMachine.AddState(EAIState.Attack);
        _aiMachine.AddState(EAIState.Back);
        
        // 更改初始状态
        _aiMachine.ChangeState(EAIState.Patrol);
    }

    private void Update() {
        _aiMachine.UpdateState();
    }

    #region IAIObject 接口实现
    
    ...
    
    #endregion
}

3 其他功能

​ 为了辅助绘图,为 BaseState 添加了 DrawGizmos() 方法(virtual),子类可以实现该方法,在 Scene 窗口中绘制辅助线。同时,在 BaseState 中添加 DistanceOfXZ() 方法,以便所有状态都可使用。以下是 BaseState 的全部逻辑:

using UnityEngine;

/// <summary>
/// 状态基类
/// </summary>
public abstract class BaseState
{
    public virtual EAIState AIState { get; } // 状态类型

    protected StateMachine _stateMachine; // 附属的状态机

    public BaseState(StateMachine stateMachine) {
        _stateMachine = stateMachine;
    }

    public abstract void OnStateEnter();

    public abstract void OnStateUpdate();

    public abstract void OnStateExit();

    /// <summary>
    /// 辅助绘制范围,不强制重写
    /// </summary>
    public virtual void DrawGizmos() { }

    /// <summary>
    /// XZ 平面上的距离
    /// </summary>
    protected float DistanceOfXZ(Vector3 pos1, Vector3 pos2) { ... }
}

​ 辅助绘图时,使用 Unity Asset Store 中的插件 DrawXXL 实现,例如 ChaseState 中的 DrawGizmos 如下:

public override void DrawGizmos() {
    var aiObject = _stateMachine.AIObject;

    // 绘制攻击范围
    DrawShapes.Circle(aiObject.Transform.position, aiObject.AttackRange, Color.red,
                      Vector3.up, lineWidth: 0.05f);

    // 绘制脱离范围
    DrawShapes.Circle(aiObject.Transform.position, _data.chaseDistance, new Color(1, 0.5f, 0),
                      Vector3.up, lineWidth: 0.05f, outlineStyle: DrawBasics.LineStyle.dotted);
}

4 类图

StateMachine_AI 部分类图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/798490.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SQL 字段类型-上

总 数据类型关键字描述整数迷你整型tinyint使用1个字节存储整数短整型smallint使用2个字节存储整数中整型mediumint使用3个字节存储整数标准整型int使用4个字节存储整数小数大整型bigint使用8个字节存储单进度float (.. , ..)使用4个字节 ...表示宽度 后面的... 表示小数位双精…

链接追踪系列-08.mac m1安装logstash-番外

下载地址&#xff1a;https://elasticsearch.cn/download/ 配置es相关&#xff1a; #安装plugin&#xff1a; jelexbogon bin % ./logstash-plugin install logstash-codec-json_lines启动&#xff1a;指定配置文件运行 jelexbogon bin % nohup ./logstash -f ../config…

docker安装mysql, 虚拟机连接mysql

docker已安装&#xff1a;安装教程docker和docker的安装-CSDN博客docker是容器技术&#xff08;软件&#xff09;&#xff0c;提供标准的应用镜像&#xff08;包含应用&#xff0c;和应用的依赖&#xff09;可以轻松在docker里安装应用&#xff0c;每个应用独立容器。https://b…

Linux系列--命令详解

目录 一、Linux资源管理方式 二、查询类型命令详解 三、文件管理类型命令详解 四、文件压缩与解压 五、文件编辑 六、系统命令 七、文件内容查看命令 一、Linux资源管理方式 linux操作系统采用一个文档树来组织所有的资源。这棵树的根目录的名字叫做&#xff1a;//…

Spring AOP 实现 Excel 导出统一处理

你好&#xff0c;我是柳岸花开。在实际开发中&#xff0c;经常会遇到需要导出 Excel 数据的需求。为了避免代码重复&#xff0c;我们可以使用 Spring AOP&#xff08;面向切面编程&#xff09;来实现 Excel 导出的统一处理。本文将介绍如何使用 Spring AOP 在项目中统一处理 Ex…

三参数陷波器

传统陷波器特性 传统陷波器的传递函数为&#xff1a; 传统陷波器的 Bode 图如图所示&#xff0c;根据图中曲线表明&#xff0c;当ξ 0.1、ξ 1、 ξ 10 时&#xff0c;随着ξ 值的增加&#xff0c;陷波宽度增大&#xff0c;陷波幅值也增大&#xff0c;此时&#xff0c;陷波…

线程安全(五)volatile 修饰共享变量(JIT即时编译器、指令重排序)

目录 一、volatile 简介1.1 定义1.2 volatile 的两个特性二、特性1:保证线程间的可见性示例1:普通场景1)代码示例:2)执行结果:3)总结:示例2:被 JIT 即时编译器优化1)代码示例:2)执行结果:3)原因分析:4)什么是 JIT 即时编译器?4)解决方案一:5)解决方案二:三…

三相PWM整流器PI双闭环控制Simulink

1.模型简介 本仿真模型基于MATLAB/Simulink&#xff08;版本MATLAB 2017Rb&#xff09;软件。建议采用matlab2017 Rb及以上版本打开。&#xff08;若需要其他版本可联系代为转换&#xff09; 2.拓扑结构&#xff1a; 3.模型算法架构&#xff1a; 4.仿真算法&#xff1a; &am…

Camunda如何通过外部任务与其他系统自动交互

文章目录 简介流程图外部系统pom.xmllogback.xml监听类 启动流程实例常见问题Public Key Retrieval is not allowed的解决方法java.lang.reflect.InaccessibleObjectException 流程图xml 简介 前面我们已经介绍了Camunda的基本操作、任务、表&#xff1a; Camunda组件与服务与…

OpenStack Yoga版安装笔记(六)glance练习

1、glance架构 Glance api处理来自用户端&#xff08;OpenStackClient等&#xff09;的请求&#xff0c;如果是读写镜像元数据&#xff0c;则对glance db进行读写操作&#xff0c;因为镜像元数据都保存在glance db里面&#xff1b;如果是存取镜像本身&#xff0c;则对后端存储…

Ubuntu系统上安装Apache和WordPress

** 第一步跟新系统包 ** 首先跟新系统包 sudo apt update sudo apt upgrade第二步下载安装apache sudo apt install apache2 ##查看apache的状态是否启动成功 sudo systemctl status apache2 ##查看服务器的ip地址 sudo ip a通过ip地址进行访问apache页面 第三步下载安装…

vue3+vite从零架构:写组件、构建、打包并上传个人前端组件库至私有npm仓库最终在自己项目中下载并使用(超详细的流程,小编已实现)

目录 第一章 前言 第二章 准备工作 第三章 封装组件 3.1 文件结构 3.2 编写组件代码 第四章 项目打包 第五章 发布到npm 5.1 npm准备工作 5.2 发布npm包 ​编辑 第六章 从npm安装使用 第一章 前言 在我们很多开发过程中&#xff0c;不管是使用vue还是react&#x…

github相关命令

如果我们要从 GitHub 上拉取一个项目到本地&#xff0c;进行修改并上传回去&#xff0c;通常需要以下步骤&#xff1a; 1. 克隆远程仓库到本地 使用 git clone 命令将 GitHub 上的项目克隆到本地&#xff1a; (网址示例如下所示&#xff09; git clone https://github.com/你的…

DP- 使用最小花费爬楼梯 DAY19

使用最小花费爬楼梯 给你一个整数数组 cost &#xff0c;其中 cost[i] 是从楼梯第i个台阶向上爬需要支付的费用。一旦你支付此费用&#xff0c;即可选择向上爬一个或者两个台阶。 你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。请你计算并返回达到楼梯顶部的最低花费。 …

STM32对flash中程序的加密保护

2024.7.14 今天学习了很多关于STM32对于程序的保护措施&#xff0c;原先一直不理解为什么DF CAR需要做加密&#xff0c;他的加密流程我也不是很知道&#xff0c;后面发现他是在控制任务初始化的时候&#xff0c;加了一个判断flash中某个区域的数值的程序&#xff0c;如果判断失…

易懂的吉文斯(Givens)变换(一)

文章目录 二阶Givens旋转矩阵作用于向量作用于矩阵更一般的情况 二阶Givens旋转矩阵 在QR分解中&#xff0c;Givens旋转是一种用于将矩阵变成上三角形的技术。 别的教程里面往往会直接给出一个n*n阶的通用Givens矩阵形式&#xff0c;但是这样太过抽象难懂了&#xff0c;而且难…

ceph 部署

端口号 NFS 2049 rpcbind 111 NFS 目录越深&#xff0c;写入性能越差 操作简单&#xff0c; 一.前言&#xff1a;存储知识 1、存储基础 单机存储设备 【1】DAS&#xff08;直接附加存储&#xff0c;是直接接到计算机的主板总线上去的存储&#xff09; IDE、SATA、SCSI、SAS…

记录些Redis题集(2)

Redis 的多路IO复用 多路I/O复用是一种同时监听多个文件描述符&#xff08;如Socket&#xff09;的状态变化&#xff0c;并能在某个文件描述符就绪时执行相应操作的技术。在Redis中&#xff0c;多路I/O复用技术主要用于处理客户端的连接请求和读写操作&#xff0c;以实现高并发…

《后端程序员 · Nacos 配置优先级动态刷新》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

J025_斗地主游戏案例开发(简版)

一、需求描述 完成斗地主游戏的案例开发。 业务&#xff1a;总共有54张牌&#xff1b; 点数&#xff1a;3、4、5、6、7、8、9、10、J、Q、K、A、2 花色&#xff1a;黑桃、红桃、方片、梅花 大小王&#xff1a;大王、小王 点数分别要组合4种花色&#xff0c;大小王各一张。…