Unity 检测对象是否在相机的视锥体范围内
-
using System.Collections;
-
using System.Collections.Generic;
-
using UnityEngine;
-
using Stopwatch = System.Diagnostics.Stopwatch;
-
[RequireComponent(typeof(MeshRenderer))]
-
public class TestInCamera : MonoBehaviour {
-
private Camera m_cam;
-
private MeshRenderer m_meshRenderer;
-
private Stopwatch m_sw;
-
private void Start() {
-
m_cam = Camera.main;
-
m_meshRenderer = GetComponent<MeshRenderer>();
-
// 采用给定摄像机的视椎体,然后返回形成此视椎体的六个平面。
-
// 排序:[0] = 左、[1] = 右、[2] = 下、[3] = 上、[4] = 近、[5] = 远
-
Plane[] planes = GeometryUtility.CalculateFrustumPlanes(m_cam);
-
for (int i = 0; i < 6; ++i) {
-
GameObject p = GameObject.CreatePrimitive(PrimitiveType.Plane);
-
p.name = "Plane " + i.ToString();
-
p.transform.position = -planes[i].normal * planes[i].distance;
-
p.transform.rotation = Quaternion.FromToRotation(Vector3.up, planes[i].normal);
-
}
-
}
-
void Update() {
-
if (m_sw == null) {
-
m_sw = new Stopwatch();
-
}
-
m_sw.Restart();
-
Plane[] planes = GeometryUtility.CalculateFrustumPlanes(m_cam);
-
bool inside = GeometryUtility.TestPlanesAABB(planes, m_meshRenderer.bounds);
-
m_sw.Stop();
-
//Debug.Log($"time:{m_sw.ElapsedMilliseconds} ms, inside:{inside}");
-
}
-
}
Unity3D检测目标身体是否在椎体视野范围内
public GameObject srcPos;
public GameObject endPos;
public GameObject buttonPos;
public float Dic;
public float angle;
public Transform[] pHead;
public Transform[] pFoot;
private void Start()
{
angle = Vector3.Angle(srcPos.transform.forward, endPos.transform.position - srcPos.transform.position);
Dic = Vector3.Distance(srcPos.transform.position, endPos.transform.position);
}
void Update ()
{
for(int i=0;i<pHead.Length;i++)
{
if (IsTargetBodyInSectorRange(srcPos.transform.position, srcPos.transform.forward,
angle, Dic, pHead[i].position, pFoot[i].position))
{
Debug.DrawLine(pHead[i].position, pFoot[i].position,Color.red);
}
else
Debug.DrawLine(pHead[i].position, pFoot[i].position, Color.green);
}
}
public static bool IsTargetInSectorRange(Vector3 srcPos, Vector3 srcDir, float srcAngle, float srcDist,
Vector3 tarPos)
{
Vector3 toTarVec = tarPos - srcPos;
float deltaAngle = Vector3.Angle(srcDir, toTarVec);
if (deltaAngle < srcAngle)
{
float dist = Vector3.Distance(srcPos, tarPos);
if (dist < srcDist)
{
return true;
}
}
return false;
}
public static bool IsTargetBodyInSectorRange(Vector3 srcPos, Vector3 srcDir, float srcAngle, float srcDist,
Vector3 tarHeadPos, Vector3 tarFootPos)
{
if (IsTargetInSectorRange(srcPos, srcDir, srcAngle, srcDist, tarHeadPos))
return true;
if (IsTargetInSectorRange(srcPos, srcDir, srcAngle, srcDist, tarFootPos))
return true;
if (tarFootPos.y <= srcPos.y && srcPos.y <= tarHeadPos.y &&
IsTargetInSectorRange(srcPos, srcDir, srcAngle, srcDist, new Vector3(tarFootPos.x, srcPos.y, tarFootPos.z)))
return true;
return false;
}
如何实现 AI 的视觉检测
1.RayCast
只使用射线检测的好处是简单易用,但仅适用于 2D 或者对高度没有要求的俯视角游戏,很多场景并不适用
2.角度判断 + RayCast
判断看向目标的向量与自身正前方向量的角度,若小于设定的角度,再向目标打出射线,若命中,则说明目标与自身之间没有阻碍,且角度,距离都正确
可以适用于 3D 场景,但问题在于必须指定目标,并且单一射线目标不适于所有场景
3.Sphere Collider + RayCast + 角度判断
当有碰撞体进入时进行角度判断,再进行射线检测,相较于2方法,无需指定目标
4.视锥检测
方法一 AABB包围盒
使用 TestPlanesAABB(Plane[] planes, Bounds bounds) 方法
使用 GeometryUtility.CalculateFrustumPlanes 方法从 Camera 组件上获取到视锥体的六个 Plane
这个方法有两个弊端,一是必须挂载 Camera 组件来使用,二是必须指定两个 AABB 盒才能进行判断,不适合使用
方法二 视锥形碰撞体
根据 Fov、near、far 等信息计算视锥顶点与三角面信息,手动绘制 Mesh,传入 MeshFilter,再由 MeshCollider 转化为碰撞体
顶点计算逻辑:Mathf.Tan(fov / 2 * Mathf.Deg2Rad);
使用起来与普通的碰撞体一样
5.扇形遮挡剔除
向指定角度打出若干射线,根据射线检测是否碰撞,若碰撞则返回碰撞点位置,没有碰撞则返回正常位置,根据位置信息绘制平面,实现剔除效果
缺点:不必要的 RayCast 太多,造成性能浪费
优化版:2d Visibility
只向更近的物体顶点打出射线
其他游戏视觉检测设计
《THE LAST OF US》 中的视线检测
早期沿用了神海的简单的锥体和射线来检测阻挡,但这样会导致贴AI的玩家不会被AI发现,而站在远处却会被敌人发现。
修改了视锥,如图,使近距离的可视范围较大,并且让可视角度大小随距离延长逐渐变小。
视觉检测方式
AI会对视觉范围内的玩家做射线检测(阻挡检测),起初检测乔尔身体的每个关节,每个关节都有各自的权重,当检测到的权重之和达到阈值,就会认为乔尔被看到了。
但是由于计算太复杂,玩家无法预测哪些掩体是安全的。
在经过一些尝试之后,顽皮狗最终决定只用一个点来做检测,这个点会根据敌人的状态而变化。
当玩家没被发现,也就是处在潜行状态的时候,AI只检测乔尔胸部中心点的位置,而当玩家和AI处于战斗状态的时候,AI就检测玩家的头顶。(如下图所示)
全境封锁的视觉检测
其中有三个颜色的区域用作视觉刺激
黄:该区域的玩家将会被缓慢发现
橙:该区域的玩家将会被迅速发现
为了防止玩家跑到 AI 身后,设置了一圈红色区域
红:该区域的玩家会被立刻发现
//
///