上文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<Vector3> viewVerts; //定点列表 List<int> viewIndices; //定点序号列表 enum AIState { Idle, //待机状态 Attack, //进攻状态 Back, //返回状态 } AIState state; //AI状态机的状态 void Start() { Transform view = transform.Find("view"); viewMeshFilter = view.GetComponent<MeshFilter>(); agent=GetComponent<UnityEngine.AI.NavMeshAgent>(); viewVerts = new List<Vector3>(); 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; } }