上文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;
}
}