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


项目借鉴 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 部分类图

相关推荐

  1. Github 2024-07-13 Rust开源项目日报 Top10

    2024-07-14 10:10:03       25 阅读
  2. Github 2024-04-07 开源项目日报 Top10

    2024-07-14 10:10:03       31 阅读
  3. Github 2024-04-07php开源项目日报 Top10

    2024-07-14 10:10:03       35 阅读
  4. Github 2024-05-07Python开源项目日报Top10

    2024-07-14 10:10:03       36 阅读
  5. Github 2024-06-07开源项目日报 Top10

    2024-07-14 10:10:03       26 阅读
  6. Github 2024-07-06 开源项目日报 Top10

    2024-07-14 10:10:03       21 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-14 10:10:03       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-14 10:10:03       72 阅读
  3. 在Django里面运行非项目文件

    2024-07-14 10:10:03       58 阅读
  4. Python语言-面向对象

    2024-07-14 10:10:03       69 阅读

热门阅读

  1. [NOIP2005 普及组] 采药

    2024-07-14 10:10:03       24 阅读
  2. 【Git使用】管理代码

    2024-07-14 10:10:03       21 阅读
  3. 分区和分桶的区别

    2024-07-14 10:10:03       23 阅读
  4. vue vite自动化路由 无需手动配置

    2024-07-14 10:10:03       18 阅读
  5. C#学习

    2024-07-14 10:10:03       27 阅读
  6. 华为生成树协议技术概述

    2024-07-14 10:10:03       26 阅读
  7. 如何使用Gunicorn配置SSL/TLS加密Web服务

    2024-07-14 10:10:03       35 阅读
  8. 八部金刚功-1.0.5-july 14th

    2024-07-14 10:10:03       24 阅读
  9. R 绘图 - 中文支持

    2024-07-14 10:10:03       22 阅读
  10. 不遵守全局主键配置【PGSQL】

    2024-07-14 10:10:03       19 阅读
  11. 手撕排序算法:冒泡排序

    2024-07-14 10:10:03       25 阅读