范围检测
只要是让某指定范围内触发某种效果,比如让敌人受到伤害等,都可以使用范围检测。
比如:
- 玩家在前方5m出释放了一个地刺魔法,在此处范围内的对象将会受到地刺伤害。
- 玩家释放魔法,在前方1m圆形范围内所有对象都受到伤害
Layer和LayerMask层级相关
Layer:Unity中使用int32来表示32个Layer层。int32表示二进制一共有32位。
在Unity中每个GameObject都有Layer属性。
LayerMask:实际上是一个位码操作。
LayerMask实际上是用Int32的32个位与Inspector窗口中的32个Layer一一对应,当这个位为1时表示使用这个层,为0时表示不用这个层。
每一个编号 代表的 都是二进制的一位
0—— 1 << 0——0000 0000 0000 0000 0000 0000 0000 0001 = 1
1—— 1 << 1——0000 0000 0000 0000 0000 0000 0000 0010 = 2
2—— 1 << 2——0000 0000 0000 0000 0000 0000 0000 0100 = 4
3—— 1 << 3——0000 0000 0000 0000 0000 0000 0000 1000 = 8
4—— 1 << 4——0000 0000 0000 0000 0000 0000 0001 0000 = 16
5—— 1 << 5——0000 0000 0000 0000 0000 0000 0010 0000 = 32
开启第几层
LayerMask mask = 1 << 3; // 开启第三层
LayerMask mask1 = (1<<16) | (1<<8); // 开启第16层和8层
关闭第几层
LayerMask mask = 0 << 8; // 表示关闭Layer8层
除了第几层之外的所有层
LayerMask mask = ~(1 << 16) // 打开除了Layer16的层之外的层。
LayerMask mask = ~(1 << 0) // 打开所有的层。
相关API
public static int GetMask (params string[] layerNames);
public static string LayerToName (int layer);
// 根据层名字转换为层编号
public static int NameToLayer (string layerName);
如何进行范围检测
必备条件:被检测到的对象,必须具备碰撞体。
1. 盒状范围检测
void Start()
{
// 参数一:立方体中心点
// 参数二:立方体三边大小
// 参数三:立方体角度
// 参数四:检测指定层级(不填检测所有层)
// 参数五:是否忽略触发器 UserGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器,不填使用UserBlobal
// 返回值:在该范围内的触发器(得到对象触发器就可以得到对象的所有信息)
Collider[] colliders = Physics.OverlapBox(Vector3.zero, Vector3.one, Quaternion.AngleAxis(45, Vector3.up),
1 << LayerMask.NameToLayer("UI") | 1 << LayerMask.NameToLayer("Default"),
QueryTriggerInteraction.UseGlobal);
for (int i = 0; i < colliders.Length; i++)
{
print(colliders[i].gameObject.name);
}
//参数:传入一个数组进行存储
//返回值:碰撞到的碰撞器数量
if(Physics.OverlapBoxNonAlloc(Vector3.zero, Vector3.one, colliders) != 0)
{
}
}
2. 球形范围检测
刀的攻击、拳击,都可以使用范围检测。
例子:在人物的正前方一米进行球形范围检测,球的半径1m,必要要检测到怪物层级
注意:一般都是在动画帧中触发事件
public void KnifeEvent()
{
// 1. 检测刀的正前方有没有敌人,进行范围检测
// 参数一:立方体中心点
// 参数二:球半径
// 参数三:检测指定层级(不填检测所有层)
// 参数四:是否忽略触发器 UserGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器,不填使用UserBlobal
// 返回值:在该范围内的触发器(得到对象触发器就可以得到对象的所有信息)
Collider[] collider = Physics.OverlapSphere(transform.position + transform.forward + transform.up, 1,
1 << LayerMask.NameToLayer("Monster"));
for(int i = 0; i < collider.Length; i++)
{
print("检测到的敌人");
// TODO:敌人受到伤害等操作
}
}
3. 胶囊范围检测
private void Start()
{
Collider[] colliders;
//参数一:半圆一中心点
//参数二:半圆二中心点
//参数三:半圆半径
//参数四:检测指定层级(不填检测所有层)
//参数五:是否忽略触发器 UseGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器 不填使用UseGlobal
//返回值:在该范围内的触发器(得到了对象触发器就可以得到对象的所有信息)
colliders = Physics.OverlapCapsule(Vector3.zero, Vector3.up, 1,
1 << LayerMask.NameToLayer("UI"), QueryTriggerInteraction.UseGlobal);
//另一个API
//返回值:碰撞到的碰撞器数量
//参数:传入一个数组进行存储
//Physics.OverlapCapsuleNonAlloc
if (Physics.OverlapCapsuleNonAlloc(Vector3.zero, Vector3.up, 1, colliders) != 0)
{
}
}
射线检测
物理射线
从某个初始点开始,沿着特定的方向发射一条不可见且无限长的射线,通过此射线检测是否有任何模型添加了Collider碰撞器组件。一旦检测到碰撞,停止射线发射。
1. 射线对象
射线就是:一个点和一个方向
// 参数一:起点
// 参数二:方向(一定记住 不是两点决定射线方向,第二个参数 直接就代表方向向量)
// 目前只是申明了一个射线对象 对于我们来说 没有任何的用处
Ray r = new Ray(Vector3.right, Vector3.forward);
摄像机发射出来的射线
// 得到一条从屏幕位置作为起点
// 摄像机视口方向为 方向的射线
Ray r2 = Camera.main.ScreenPointToRay(Input.mousePosition);
2. 碰撞检测函数
2.1 最原始的射线检测
// 准备一条射线
Ray r3 = new Ray(Vector3.zero, Vector3.forward);
// 进行射线检测 如果碰撞到对象 返回true
//参数一:射线
//参数二: 检测的最大距离 超出这个距离不检测
//参数三:检测指定层级(不填检测所有层)
//参数四:是否忽略触发器 UseGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器 不填使用UseGlobal
//返回值:bool 当碰撞到对象时 返回 true 没有 返回false
if (Physics.Raycast(r3, 1000, 1 << LayerMask.NameToLayer("Monster"), QueryTriggerInteraction.UseGlobal))
{
print("碰撞到了对象");
}
//还有一种重载 不用传入 射线 直接传入起点 和 方向 也可以用于判断
//就是把 第一个参数射线 变成了 射线的 两个点 一个起点 一个方向
if (Physics.Raycast(Vector3.zero, Vector3.forward, 1000, 1 << LayerMask.NameToLayer("Monster"), QueryTriggerInteraction.UseGlobal))
{
print("碰撞到了对象2");
}
2.2 获取相交的单个物体信息
//物体信息类 RaycastHit
RaycastHit hitInfo;
//参数一:射线
//参数二:RaycastHit是结构体 是值类型 Unity会通过out 关键在 在函数内部处理后 得到碰撞数据后返回到该参数中
//参数三:距离
//参数四:检测指定层级(不填检测所有层)
//参数五:是否忽略触发器 UseGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器 不填使用UseGlobal
if (Physics.Raycast(r3, out hitInfo, 1000, 1 << LayerMask.NameToLayer("Monster"), QueryTriggerInteraction.UseGlobal))
{
print("碰撞到了物体 得到了信息");
//碰撞器信息
print("碰撞到物体的名字" + hitInfo.collider.gameObject.name);
//碰撞到的点
print(hitInfo.point);
//法线信息
print(hitInfo.normal);
//得到碰撞到对象的位置
print(hitInfo.transform.position);
//得到碰撞到对象 离自己的距离
print(hitInfo.distance);
//RaycastHit 该类 对于我们的意义
//它不仅可以得到我们碰撞到的对象信息
//还可以得到一些 碰撞的点 距离 法线等等的信息
}
//还有一种重载 不用传入 射线 直接传入起点 和 方向 也可以用于判断
if (Physics.Raycast(Vector3.zero, Vector3.forward, out hitInfo, 1000, 1 << LayerMask.NameToLayer("Monster"), QueryTriggerInteraction.UseGlobal))
{
}
2.3 获取相交的多个物体
//可以得到碰撞到的多个对象
//如果没有 就是容量为0的数组
//参数一:射线
//参数二:距离
//参数三:检测指定层级(不填检测所有层)
//参数四:是否忽略触发器 UseGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器 不填使用UseGlobal
RaycastHit[] hits = Physics.RaycastAll(r3, 1000, 1 << LayerMask.NameToLayer("Monster"), QueryTriggerInteraction.UseGlobal);
for (int i = 0; i < hits.Length; i++)
{
print("碰到的所有物体 名字分别是" + hits[i].collider.gameObject.name);
}
//还有一种重载 不用传入 射线 直接传入起点 和 方向 也可以用于判断
//之前的参数一射线 通过两个点传入
hits = Physics.RaycastAll(Vector3.zero, Vector3.forward, 1000, 1 << LayerMask.NameToLayer("Monster"), QueryTriggerInteraction.UseGlobal);
//还有一种函数 返回的碰撞的数量 通过out得到数据
if (Physics.RaycastNonAlloc(r3, hits, 1000, 1 << LayerMask.NameToLayer("Monster"), QueryTriggerInteraction.UseGlobal) > 0)
{
}
图形射线
- 图形射线用于UI控件响应
- 可以响应图形射线的UI组件,RayCast Target有Text、Image、RawImage,并且所有可以交互的UI组件都包含RayCast Target
- UI控件事件响应基于EventSystem
案例与练习
案例:射击墙壁
using UnityEngine;
/// <summary>
/// 射线检测Demo - 小功能
///
/// 作者: DY
/// 创建日期: 2025-01-01
/// 版本: 1.0
///
/// 详细描述: 1. 子弹打在墙上的效果
/// </summary>
public class 射线检测Demo : MonoBehaviour
{
RaycastHit hit;
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit, 1000, 1 << LayerMask.NameToLayer("Wall"), QueryTriggerInteraction.UseGlobal))
{
GameObject go = Resources.Load("Effect/FireImpactSmall") as GameObject;
go = GameObjectPool.instance.CreateObject("bulleteffect", go, hit.point + hit.normal * 0.2f, Quaternion.identity);
go.transform.rotation = Quaternion.LookRotation(hit.normal);
GameObjectPool.instance.CollectObject(go, 1f);
GameObject go1 = Resources.Load("Effect/FireProjectileSmall") as GameObject;
go1 = GameObjectPool.instance.CreateObject("walleffect", go1, hit.point + hit.normal * 0.5f, Quaternion.identity);
go1.transform.rotation = Quaternion.LookRotation(hit.normal);
GameObjectPool.instance.CollectObject(go1, 5f);
}
}
}
}
案例:找到敌人
需求:当射线射到敌人,敌人的颜色就会变成红色,射到好人,好人会变绿
思路:为敌人和好人添加Tag标签,用于去找到敌人和好人
using UnityEngine;
// 射线检测
public class TestDemo1 : MonoBehaviour
{
// 碰撞信息对象
RaycastHit hit;
public LayerMask layer;
private void Update()
{
bool flag = Physics.Raycast(transform.position, transform.forward, out hit, 100, layer);
if (flag && hit.collider.tag == "Enemy")
{
hit.collider.GetComponent<Renderer>().material.color = Color.red;
Debug.DrawLine(transform.position, hit.collider.transform.position, Color.red);
} else if(flag && hit.collider.tag == "Friends")
{
hit.collider.GetComponent<Renderer>().material.color = Color.green;
Debug.DrawLine(transform.position, hit.collider.transform.position, Color.green);
}
}
}
案例2:拖拽3d物品
【需求】:场景上有一个平面,有一个立方体,当鼠标点击选中立方体时,长按鼠标左键,可以拖拽立方体,在屏幕上移动,点击鼠标右键取消选中。
using UnityEngine;
/// <summary>
/// 拖拽物品 - 类描述
///
/// 作者: DY
/// 创建日期: 2025-01-01
/// 版本: 1.0
///
/// 详细描述: 功能描述
/// </summary>
public class 拖拽物品 : MonoBehaviour
{
private GameObject currentSelectedObj;
RaycastHit hit;
private void Update()
{
if(Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit, 100, 1 << LayerMask.NameToLayer("Player")))
{
currentSelectedObj = hit.collider.gameObject;
currentSelectedObj.GetComponent<MeshRenderer>().material.color = Color.red;
}
}
if(Input.GetMouseButtonDown(1))
{
if(currentSelectedObj != null)
{
currentSelectedObj.GetComponent<MeshRenderer>().material.color = Color.white;
}
currentSelectedObj = null;
}
if(Input.GetMouseButton(0) && currentSelectedObj)
{
Vector3 pos = Input.mousePosition;
pos.z = 10;
currentSelectedObj.transform.position = Camera.main.ScreenToWorldPoint(pos);
}
}
}
评论区