• Unity——游戏AI实例


    上文Unity——模拟AI视觉已经实现了敌人视野探测功能,本文来完善敌人AI。

    注意:若要阅读此文,务必在阅读完Unity——模拟AI视觉的基础上阅读


    效果预展示:

    AI敌人追击


     

    接下来用最简单的方式实现敌人的AI状态机。首先,定义敌人的3个状态——待机、进攻和返回。

    1. enum AIState
    2. {
    3. Idle, //待机状态
    4. Attack, //进攻状态
    5. Back, //返回状态
    6. }

    然后将Update函数改为状态机的模式,直接用switch-case语句实现

    1. enum AIState
    2. {
    3. Idle, //待机状态
    4. Attack, //进攻状态
    5. Back, //返回状态
    6. }
    7. AIState state;
    8. void Update()
    9. {
    10. switch (state)
    11. {
    12. case AIState.Idle:
    13. {
    14. //待机状态。进行实现检测,若发现玩家则进攻
    15. FieldOfView();
    16. }
    17. break;
    18. case AIState.Attack:
    19. {
    20. //进攻状态,若离玩家或起点太远,则返回
    21. }
    22. break ;
    23. case AIState.Back:
    24. {
    25. //返回状态
    26. }
    27. break;
    28. }
    29. }

    状态机的原理比较复杂,但只需要用一个switch-case语句就能实现,或者用if语句编写也可以。之后只要把设计思路按部就班地编写成程序代码即可。

    1. 在待机状态下,要不断进行射线检测。如果射线检测发现了玩家,就可以将玩家的引用保存起来,以便后面进攻时使用。需要注意的是,应当将玩家及其子物体的Tag都改为Player,方便判断。
    2. 在进攻状态下,不断向目标位置移动(利用导航系统)。同时检测当前位置与起点或玩家之间的距离,如果距离过远就返回。
    3. 在返回状态下,先朝起点的位置移动,当移动到位后,再转向正面,回到一开始的朝向。有必要一开始就把初始的位置和朝向记录下来,分别是homePos和homeRot。
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. public class AIEnemy : MonoBehaviour
    5. {
    6. Transform target; //目标角色
    7. Vector3 homePos; //起始位置
    8. Quaternion homeRot; //起点的朝向
    9. UnityEngine.AI.NavMeshAgent agent;
    10. public int viewRadius = 4; //视野距离
    11. public int viewLines = 30; //射线数量
    12. public MeshFilter viewMeshFilter;
    13. List viewVerts; //定点列表
    14. List<int> viewIndices; //定点序号列表
    15. enum AIState
    16. {
    17. Idle, //待机状态
    18. Attack, //进攻状态
    19. Back, //返回状态
    20. }
    21. AIState state; //AI状态机的状态
    22. void Start()
    23. {
    24. Transform view = transform.Find("view");
    25. viewMeshFilter = view.GetComponent();
    26. agent=GetComponent();
    27. viewVerts = new List();
    28. viewIndices = new List<int>();
    29. state = AIState.Idle;
    30. homePos = transform.position;
    31. homeRot = transform.rotation;
    32. }
    33. void Update()
    34. {
    35. switch (state)
    36. {
    37. case AIState.Idle:
    38. {
    39. //待机状态。进行射线检测,若发现玩家则进攻
    40. FieldOfView();
    41. if (target != null)
    42. {
    43. state= AIState.Attack; //切换状态
    44. }
    45. }
    46. break;
    47. case AIState.Attack:
    48. {
    49. //进攻状态,若离玩家或起点太远,则返回
    50. agent.SetDestination(target.position);
    51. if(Vector3.Distance(transform.position, target.position) > 10)
    52. {
    53. target = null;
    54. state= AIState.Back;
    55. }
    56. if (Vector3.Distance(transform.position, homePos)>15)
    57. {
    58. target = null;
    59. state = AIState.Back;
    60. }
    61. }
    62. break ;
    63. case AIState.Back:
    64. {
    65. //返回状态
    66. agent.SetDestination(homePos);
    67. if (!agent.hasPath)
    68. {
    69. //回到起点,匀速转到正面
    70. if(Quaternion.Angle(homeRot,transform.rotation)>0.5f)
    71. {
    72. //逐步向目标角度转动,每次最多转2°
    73. Quaternion q = Quaternion.RotateTowards(transform.rotation, homeRot, 2f);
    74. transform.rotation = q;
    75. }
    76. else
    77. {
    78. state = AIState.Idle;
    79. }
    80. }
    81. }
    82. break;
    83. }
    84. }
    85. void FieldOfView()
    86. {
    87. viewVerts.Clear();
    88. viewVerts.Add(Vector3.zero); //加入起点坐标,局部坐标系
    89. //获得最左边那条射线的向量,相对正前方,角度是-45°
    90. Vector3 forward_left = Quaternion.Euler(0, -45, 0) * transform.forward * viewRadius;
    91. //依次处理每条射线
    92. for (int i = 0; i <= viewLines; i++)
    93. {
    94. Vector3 v = Quaternion.Euler(0, (90.0f / viewLines) * i, 0) * forward_left;
    95. //角色位置+v,就是射线终点pos
    96. Vector3 pos = transform.position + v;
    97. //实际发射射线。注意RayCast的参数,重载很多容易搞错
    98. RaycastHit hitInfo;
    99. if (Physics.Raycast(transform.position, v, out hitInfo, viewRadius))
    100. {
    101. //碰到物体,终点改为碰到的点
    102. pos = hitInfo.point;
    103. if (hitInfo.transform.CompareTag("Player"))
    104. {
    105. target=hitInfo.transform;
    106. }
    107. }
    108. //将每个点的位置加入列表,注意转为局部坐标系
    109. Vector3 p = transform.InverseTransformPoint(pos);
    110. viewVerts.Add(p);
    111. }
    112. //根据顶点绘制模型
    113. RefreshView();
    114. }
    115. void RefreshView()
    116. {
    117. viewIndices.Clear();
    118. //逐个加入三角面,每个三角面都以起点开始
    119. for (int i = 1; i < viewVerts.Count - 1; i++)
    120. {
    121. viewIndices.Add(0);
    122. viewIndices.Add(i);
    123. viewIndices.Add(i + 1);
    124. }
    125. //填写Mesh信息
    126. Mesh mesh = new Mesh();
    127. mesh.vertices = viewVerts.ToArray();
    128. mesh.triangles = viewIndices.ToArray();
    129. viewMeshFilter.mesh = mesh;
    130. }
    131. }

  • 相关阅读:
    我在winform项目里使用“Windows I/O完成端口”的经验分享
    java毕业设计的滑雪场学具租赁管理系统(附源码、数据库)
    Dubbo源码(八) - 负载均衡
    jenkies构建springboot
    业务数据分析——同环比(待补全)
    5种移动网站测试的好方法....
    模块首页UX交互升级,接口测试支持禁用本地执行,MeterSphere开源持续测试平台v2.4.0发布
    QT 菜单栏、工具栏和状态栏
    【快应用】input组件的输入框弹出后,如何点击其他地方后失去焦点,并收起键盘
    鸿蒙:从0到“Hello Harmony”
  • 原文地址:https://blog.csdn.net/m0_63024355/article/details/132835731