Unity AI探测器
游戏中常常用到敌人检测玩家是否在扇形攻击区域内,同时还需要检测扇形的攻击区域是否被遮挡物遮挡并绘制出遮挡区域。小编写的该功能的思路是首先用mesh编程绘制出其扇形区域的mesh网格,最后用OnTriggerStay()函数检测该mesh collider下是否进来其他对象。效果如下:
原理
首先,我们需要了解什么是mesh编程。可以参考该文章:Unity网格编程篇(二) 非常详细的Mesh编程入门文章。
了解之后其实该功能原理很简单了,就是从起点发射一条射线,通过碰撞检测找到其碰撞点,两个相近的碰撞点与起点则构成一个三角面,如下图: 其中相邻的碰撞点越近,则可检测的目标的就可越小。找到顶点并通过如下代码进行设置即可: 再设置其绘制顺序(顺时针顺序),顺时针表示正面 这里便不说明如何代码体现绘制顺序,具体参考上面推荐的文章。再设置UV纹理: 最后需要调用其函数: 该函数的作用这里不作解释,参考该链接即可:Mesh.RecalculateNormals()
脚本使用介绍
1.创建一个3D游戏物体cube并挂载其脚本 2.再在该cube下创建一个Plan游戏物体,然后设置材质球(目的为了区分并直观看到扇形区域)
3.参数介绍:
Point:可检测的碰撞点的点数(密集度) FOV:扇形区域的角度 SightRange:扇形区域半径大小 OnCollistonDetection():碰撞到其他对象后触发(需要被碰撞的对象也有box collider组件) DetectionTags:可碰撞的Tag层 ShowLOS:是否显示该扇形区域
代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class Detetor : MonoBehaviour {
public MeshFilter FOVMesh;
[Range(15f, 1000f)]
public float Points = 10;
[Range(10f, 359f)]
public float FOV;
public float SightRange;
public UnityEvent OnCollistionDetection;
public string[] DetectionTags;
public float LOSRerenderInterval = 1;
public int TargetSightedID()
{
return TargetSighted;
}
public bool ShowLOS = true;
int TargetSighted = -1;
bool ISTargetSighted = false;
bool IsTargetInFOV = true;
public bool DebugLogs = false;
void Start()
{
this.GetComponent<SphereCollider>().radius = SightRange;
}
void GenLOSMesh()
{
List<Vector3> newVertices = new List<Vector3>();
List<Vector2> newUV = new List<Vector2>();
List<int> newTriangles = new List<int>();
Vector3 noAngle = transform.forward;
FOVMesh.mesh.Clear();
newVertices.Add(Vector3.zero);
bool TargetExists = false;
for (int j = 0; j < Points; j++)
{
float Angle = -FOV + (((FOV * 2) / Points) * j);
if (Angle < 0) Angle = 360 + Angle;
Vector3 Rotation = Quaternion.AngleAxis(Angle, transform.up) * transform.forward;
RaycastHit hit;
Ray ray = new Ray(transform.position, Rotation);
if (Physics.Raycast(ray, out hit, SightRange))
{
if (IsTargetInFOV) {
for (int i = 0; i < DetectionTags.Length; i++)
{
if (hit.collider.gameObject.tag == DetectionTags[i])
{
TargetExists = true;
if (DebugLogs)
{
Debug.Log("Target Exists");
}
}
}
}
newVertices.Add(hit.point - transform.position);
}
else
{
newVertices.Add(ray.GetPoint(SightRange) - transform.position);
}
}
if (TargetExists)
{
ISTargetSighted = true;
if (DebugLogs)
{
Debug.Log("Target Sighted");
}
}
else
{
ISTargetSighted = false;
}
if (ShowLOS)
{
if (DebugLogs)
{
Debug.Log("Render LOS");
}
for (int i = 1; i < newVertices.Count - 1; i += 1)
{
newTriangles.Add(0);
newTriangles.Add(i);
newTriangles.Add(i + 1);
}
for (int i = 0; i < newVertices.Count; i++)
{
newUV.Add(new Vector2(newVertices[i].x, newVertices[i].z));
}
FOVMesh.mesh.vertices = newVertices.ToArray();
FOVMesh.mesh.triangles = newTriangles.ToArray();
FOVMesh.mesh.uv = newUV.ToArray();
FOVMesh.transform.rotation = Quaternion.identity;
FOVMesh.mesh.RecalculateNormals();
}
}
void OnTriggerStay(Collider col)
{
for (int i = 0; i < DetectionTags.Length; i++)
{
if (col.gameObject.tag == DetectionTags[i])
{
if (TargetInFeildOfVeiw(col.gameObject.transform.position))
{
IsTargetInFOV = true;
if (DebugLogs)
{
Debug.Log("Target Trigger Detected");
}
}
else {
IsTargetInFOV = false;
}
break;
}
}
}
bool TargetInFeildOfVeiw(Vector3 Target)
{
Target.z = SightRange;
Vector3 targetDir = Target - transform.position;
float angle = Vector3.Angle(targetDir, transform.forward);
if (angle <= FOV)
{
return true;
}
return false;
}
void Update()
{
if (DebugLogs) this.GetComponent<SphereCollider>().radius = SightRange;
if (Time.frameCount % LOSRerenderInterval == 0)
{
GenLOSMesh();
}
if (ISTargetSighted)
{
OnCollistionDetection.Invoke();
}
}
}
|