上文Unity——模拟AI视觉已经实现了敌人视野探测功能,本文来完善敌人AI。
注意:若要阅读此文,务必在阅读完Unity——模拟AI视觉的基础上阅读
效果预展示:
AI敌人追击
接下来用最简单的方式实现敌人的AI状态机。首先,定义敌人的3个状态——待机、进攻和返回。
- enum AIState
- {
- Idle, //待机状态
- Attack, //进攻状态
- Back, //返回状态
- }
然后将Update函数改为状态机的模式,直接用switch-case语句实现
- enum AIState
- {
- Idle, //待机状态
- Attack, //进攻状态
- Back, //返回状态
- }
- AIState state;
- void Update()
- {
-
- switch (state)
- {
- case AIState.Idle:
- {
- //待机状态。进行实现检测,若发现玩家则进攻
- FieldOfView();
- }
- break;
- case AIState.Attack:
- {
- //进攻状态,若离玩家或起点太远,则返回
- }
- break ;
- case AIState.Back:
- {
- //返回状态
- }
- break;
- }
- }
状态机的原理比较复杂,但只需要用一个switch-case语句就能实现,或者用if语句编写也可以。之后只要把设计思路按部就班地编写成程序代码即可。
- 在待机状态下,要不断进行射线检测。如果射线检测发现了玩家,就可以将玩家的引用保存起来,以便后面进攻时使用。需要注意的是,应当将玩家及其子物体的Tag都改为Player,方便判断。
- 在进攻状态下,不断向目标位置移动(利用导航系统)。同时检测当前位置与起点或玩家之间的距离,如果距离过远就返回。
- 在返回状态下,先朝起点的位置移动,当移动到位后,再转向正面,回到一开始的朝向。有必要一开始就把初始的位置和朝向记录下来,分别是homePos和homeRot。
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- public class AIEnemy : MonoBehaviour
- {
- Transform target; //目标角色
- Vector3 homePos; //起始位置
- Quaternion homeRot; //起点的朝向
- UnityEngine.AI.NavMeshAgent agent;
- public int viewRadius = 4; //视野距离
- public int viewLines = 30; //射线数量
- public MeshFilter viewMeshFilter;
- List
viewVerts; //定点列表 - List<int> viewIndices; //定点序号列表
- enum AIState
- {
- Idle, //待机状态
- Attack, //进攻状态
- Back, //返回状态
- }
- AIState state; //AI状态机的状态
- void Start()
- {
- Transform view = transform.Find("view");
- viewMeshFilter = view.GetComponent
(); - agent=GetComponent
(); -
- viewVerts = new List
(); - viewIndices = new List<int>();
- state = AIState.Idle;
-
- homePos = transform.position;
- homeRot = transform.rotation;
- }
-
-
- void Update()
- {
-
- switch (state)
- {
- case AIState.Idle:
- {
- //待机状态。进行射线检测,若发现玩家则进攻
- FieldOfView();
- if (target != null)
- {
- state= AIState.Attack; //切换状态
- }
- }
- break;
- case AIState.Attack:
- {
- //进攻状态,若离玩家或起点太远,则返回
- agent.SetDestination(target.position);
- if(Vector3.Distance(transform.position, target.position) > 10)
- {
- target = null;
- state= AIState.Back;
- }
- if (Vector3.Distance(transform.position, homePos)>15)
- {
- target = null;
- state = AIState.Back;
- }
- }
- break ;
- case AIState.Back:
- {
- //返回状态
- agent.SetDestination(homePos);
- if (!agent.hasPath)
- {
- //回到起点,匀速转到正面
- if(Quaternion.Angle(homeRot,transform.rotation)>0.5f)
- {
- //逐步向目标角度转动,每次最多转2°
- Quaternion q = Quaternion.RotateTowards(transform.rotation, homeRot, 2f);
- transform.rotation = q;
- }
- else
- {
- state = AIState.Idle;
- }
- }
- }
- break;
- }
- }
- void FieldOfView()
- {
- viewVerts.Clear();
- viewVerts.Add(Vector3.zero); //加入起点坐标,局部坐标系
-
-
- //获得最左边那条射线的向量,相对正前方,角度是-45°
- Vector3 forward_left = Quaternion.Euler(0, -45, 0) * transform.forward * viewRadius;
- //依次处理每条射线
- for (int i = 0; i <= viewLines; i++)
- {
- Vector3 v = Quaternion.Euler(0, (90.0f / viewLines) * i, 0) * forward_left;
- //角色位置+v,就是射线终点pos
- Vector3 pos = transform.position + v;
-
- //实际发射射线。注意RayCast的参数,重载很多容易搞错
- RaycastHit hitInfo;
- if (Physics.Raycast(transform.position, v, out hitInfo, viewRadius))
- {
- //碰到物体,终点改为碰到的点
- pos = hitInfo.point;
- if (hitInfo.transform.CompareTag("Player"))
- {
- target=hitInfo.transform;
- }
- }
- //将每个点的位置加入列表,注意转为局部坐标系
- Vector3 p = transform.InverseTransformPoint(pos);
- viewVerts.Add(p);
-
- }
- //根据顶点绘制模型
- RefreshView();
- }
- void RefreshView()
- {
- viewIndices.Clear();
- //逐个加入三角面,每个三角面都以起点开始
- for (int i = 1; i < viewVerts.Count - 1; i++)
- {
- viewIndices.Add(0);
- viewIndices.Add(i);
- viewIndices.Add(i + 1);
- }
- //填写Mesh信息
- Mesh mesh = new Mesh();
- mesh.vertices = viewVerts.ToArray();
- mesh.triangles = viewIndices.ToArray();
- viewMeshFilter.mesh = mesh;
- }
- }
-
-