提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
射线检测是指通过发出一条射线,对射线碰撞到的碰撞体进行检测,发生碰撞满足条件时返回值为真。
其中包括:
1、Physics.Linecast 线性投射:开始位置到结束位置进行光线投射
2、Physics.Raycast(较常用):显示一条与碰撞器发生碰撞的射线,射线的方向和长度可以设置
3、Physics.RaycastAll:发出一条射线返回所有的碰撞,返回的是一个RaycastHit[]的结构体
4、Physics.OverlapSphere :球形射线检测,返回的是球形半径内的所有碰撞器collider[],可以用来判断捡拾物品以及手雷爆炸的触发等。
3D游戏场景中发射子弹,为了避免子弹穿透的问题,考虑通过射线检测,从枪口发出一条射线,在枪的射程范围内判断碰撞,发生碰撞证明击中物体,在发生碰撞的位置生成一个弹孔特效。
这里我以第一人称射击游戏(FPS)中实现的射击效果为例
代码如下(示例):
public class Player_OpenFire : MonoBehaviour
{
//屏幕中心点位置坐标
private Vector3 point;
//FPS人物相机
public Camera fpscam;
//枪的射程
private float GunRange = 20f;
void Start()
{
//第一人称相机在不停的转动,但是准星的位置始终是在屏幕的中心点,这里获取到屏幕的中心点位置坐标
point = new Vector3(Screen.width / 2, Screen.height / 2, 0);
}
void Update()
{
OpenFire();
}
void OpenFire()
{
RaycastHit hit;
//初始化一条从屏幕中心点发出的射线
Ray ray = fpscam.ScreenPointToRay(point);
//这里的5表示的是所在的层,因为我的项目中用了多个相机,所以需要分层处理。(读者可以根据自己的需要自行修改)
if (Physics.Raycast(ray, out hit, GunRange, 5))
{
//这里进行的判断是防止枪口朝向脚下时,射线碰撞到玩家,玩家身上产生弹孔,造成自己打自己的情况。
if (hit.collider.gameObject.tag=="Player")
{
return;
}
//这里我的弹孔特效是做成了预设体,放在Resources目录下进行动态加载,参数hit.point表示的是发生碰撞的坐标点是一个Vector3值
GameObject bullet = Instantiate(Resources.Load("Bullet/BulletMark1"), hit.point, Quaternion.identity);
//在Unity三维坐标中的一个点包括三个值(三角朝向、法线值、UV值),这里用到了碰撞点的法线值
bullet.transform.LookAt(hit.point - hit.normal);
//把子弹特效设为碰撞到的物体的子物体
bullet.transform.parent = hit.collider.transform;
//我的预设体方向做的时候有问题,所以这里加载后再次修改了方向(读者根据需要自行修改这行代码)
bullet.transform.Rotate(90, 0, 0);
//我们让子弹移动一点点位置,调整子弹的特效能够显示在碰撞的物体上,避免遮盖的现象
bullet.transform.Translate(Vector3.back * 0.01f);
//为了方便演示这里设置弹孔特效一秒后自动销毁(读者可以用子弹对象池进行修改)
Destroy(bullet, 1f);
}
}
脚本挂在给枪口的空物体使用,空物体要包含LineRenderer以及Outline组件Li,否则运行时会报错。
具体代码如下:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;public class StartGame : MonoBehaviour
{
//获取LineRenderer组件
private LineRenderer line;
//获取开始游戏按钮组件
public Button startGame;
//获取初始射线的终点坐标
Vector3 endPoint;
// Start is called before the first frame update
void Start()
{
line = this.GetComponent();
endPoint = line.GetPosition(1);
//给按钮添加点击事件
startGame.onClick.AddListener(delegate()
{
GameObject.Find("StartGame").SetActive(false);
});
}
void Update()
{
//初始化一条射线,从枪口位置向正前方发射
Ray ray = new Ray(this.transform.position, this.transform.forward);
RaycastHit hit;
//设置射线的碰撞距离为50米
if (Physics.Raycast(ray, out hit, 50))
{
//判断是不是碰到了开始游戏按钮
if (hit.collider.tag == "StartGame")
{
//碰到开始游戏按钮时使按钮出现明亮边框
startGame.GetComponent().enabled = true;
//绘制射线,颜色设置为红色
Debug.DrawLine(transform.position, hit.point, Color.red);
//将世界坐标转换为Local
line.SetPosition(1, line.transform.InverseTransformPoint(hit.point));
//点击了鼠标左键会执行回调函数
if (Input.GetMouseButtonDown(0))
{
/*
* 这里用伪代码简单说明可以实现的具体功能
* 1、可以实现上文提到的出现弹孔特效
* 2、可以跳转进入游戏界面
* 3、也可以通过异步加载的方式,进入加载界面,加载游戏(推荐使用这种方式设计,直接加载进入,如果游戏场景过大会造成卡顿,异步加载能够给用户更好的游戏体验)
*/
ExecuteEvents.Execute(gameObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerClickHandler);
}
}
}
else
{
//没有碰到碰撞体时让按钮不能有明亮边框
startGame.GetComponent().enabled = false;
//同时恢复射线原有的样子
line.SetPosition(1, endPoint);
}
}
}
LineRenderer组件能够显示射线
这里我没有使用World Space坐标是防止射线出现跑偏的现象造成的不真实效果。
LineRenderer中坐标点是可以编辑、添加的,同时要选中Loop使射线连续更加真实。
射线线条的宽度通过修改Width的值可以实现。默认值是1,我这里用0.01能够达到线条的效果而不是条状带。
线条的颜色是可以进行更改的,但是要注意的是必须要有材质才可以更改颜色,否则更改颜色是不起作用的。多个材质也只会有一个起作用。
在这里使用时,默认不要勾选,当射线检测到时才显示明亮的边框。
EffectColer设置颜色,根据场景颜色基调以及视觉效果合理搭配。
EffectDistance设置宽高的大小,不同的值显示的边框的厚度不一样,值越大厚度越大。
Use Graphic Alpha:应用Alpha值的图像,视觉效果更好,同样的占的空间字节数也大。
上述代码中之所以设置endPoint这个点来存放最初的射线位置,是为了节省射线,避免穿透。
line.SetPosition(1, line.transform.InverseTransformPoint(hit.point));
这句代码是将射线的终点设置为碰撞点,这样碰撞点之后就不会再出现射线,而离开碰撞物体后射线又能够通过endPoint点恢复原来的样子,不会产生固定长度的现象。
这里提到的一些问题都是出现的情况,读者可以自行尝试。
这里特别说明一点:通过3D人物场景触发Canvas上的Button按钮,属于与2D相结合进行射线检测,这里的处理方法是给按钮添加一个BoxCollider的碰撞器,才能够进行碰撞,检测射线。