小言_互联网的博客

Unity引擎基础补充

348人阅读  评论(0)

Unity引擎基础补充

本博客将对之前的Unity脚本,3D数学基础博客内容做一定的补充。所以部分知识点可能并不全。

使用Unity的API,我们要清楚各个参数其代表的含义。

文章目录

3D数学基础

基础的一些一次计算的方法不再赘述。

Math和Mathf的区别

Math是C#官方提供的数学计算的工具类,而Mathf是Unity提供的数学计算结构体。

Mathf中包含了几乎所有Math中的数学计算方法,同时还额外添加了一些适用于游戏开发的数学计算方法。

Lerp

result = Mathf.Lerp(start, end, t);

数学公式:result = start + (end - start)*t;其中t为插值系数,取值范围0~1;

插值运算用法一:传统用法

使用start值直接接收lerp结果,变化速度先快后慢,位置无限接近,但永远不能得到end的位置。

start = Mathf.Lerp(start, 10, Time.deltaTime);

插值运算用法二:改良用法

使用lerp时多添加一个time参数,且不再使用start直接接收lerp的返回结果。变化速度匀速,位置每帧接近,当t >= 1时,到达目的地。

time += Time.deltaTime;

result = Mathf.Lerp(start, 10, time);

不再使用start直接接收lerp则,每次start和(end - start)的部分都是固定不变的。使用每帧变化的time代替之前不变的t参数,则,每次(end - start)*t的值是稳定增长的。这导致result最终可以等于end,(如果不对Time.deltaTime做处理的话,这个时间是1秒)。

3D数学向量

Unity中,Vector3结构体既可以表示一个点,也可以表示一个向量,具体表示什么取决于我们的代码逻辑。

两点之间的向量,假设现在有两个点A,B,则向量AB(向量AB表示这条向量由A指向B)为B-A,也就是有两个点确定的向量,其值的计算为终点减去起点。

向量模长:

当我们谈论向量时,我们要记住,向量总是以原点为起点。所以向量的模长说白了也是原点到向量终点的距离,数学公式使用勾股定理计算。

当我们想要使用向量来表示方向时,我们要尽可能的将这个向量先归一化后在使用。

向量计算:

Vector3的加法计算有以下几种情况:

位置加位置:

在Unity中,位置加位置是没有意义的。

位置加向量:

在Unity中,位置加向量会得到一个新的位置,此位置是旧位置沿向量方向平移向量模长的距离得到。

向量加向量:

在Unity中,向量加向量会得到一个新的向量。

Vector3的减法计算有以下几种情况:

位置减位置:

在Unity中,位置减位置得到的是一个向量,两点确定一条向量。

位置减向量:

在Unity中,位置减向量得到的是一个新的位置,此位置是旧位置沿向量相反方向平移向量模长的距离得到。

向量减向量:

在Unity中,向量减一个向量得到的是一个新的向量。

向量减位置:

在Unity中向量减位置没有意义。

Vector3的乘除法计算有以下几种情况:

向量乘除标量:

在数学中,向量乘除标量表示向量的放缩。

向量之间点乘:

首先需要注意的是两个向量A,B点乘的结果是是个标量,此标量表示B向量在A向量上的投影的长度。

向量点乘的结果大于零,说明两个向量的夹角为锐角。

向量点乘的结果等于零,说明两个向量的夹角为直角。

向量点乘的结果小于零,说明两个向量的夹角为钝角。

通过向量点乘的结果可以大概判断两个游戏物体的相对位置。但这还不够准确。我们希望得到具体的夹角。我们可以通过下面这个推导式来计算。A·B = |A|*|B|*cos<A,B>。

向量点乘的结果为两个向量的模长乘积乘上两个向量夹角的cos值。如果两个向量都为单位向量则会有A·B = cos<A,B>。这也是Vector3方法Angel的原理。

向量之间叉乘:

上面,我们可以通过向量模长和向量点乘可以判断出一个游戏物体和另一个游戏物体的相对夹角,这里已经可以做敌人巡视范围,但是我们仍不知道这个物体是在我的左边还是右边,所以为了判断这条信息,我们就需要使用叉乘。

叉乘计算公式如图所示:

这里的公式规律是:

  1. 计算的方向不参与计算,比如我们计算新向量的X是YaZb - ZaYb,这里没有X分量参与计算。
  2. 参与计算的分量顺序延续计算方向的顺序,这里的意思比较抽象,意思如图所示,当我们要计算X时,我们参与计算的分量会从X的下一个Y开始计算,当我们要计算Y时,我们参与计算的分量会从Y的下一个分量Z开始,当我们要计算Z时,我们参与计算的分量会从X开始。

叉乘的几何意义:A向量叉乘B向量得到的向量是一条垂直于AB平面的向量。当B在A的右面时向量的Y值大于零,当B在A的左边时向量的Y值小于零。利用此结果可以判断左右。

叉乘判断作用常用于游戏中的受击方位提示,如全境封锁中的敌人方位提示。

向量插值运算:

线性插值:

向量的线性插值和Mathf中的线性插值原理相同。

球形插值:

使用较少,相当于是把线性插值的轨迹变为弧形。

3D数学四元数

为什么使用四元数?

欧拉角虽然具有以下优点:

  1. 只使用三个数来存储物体方位,占用空间小。
  2. 沿坐标轴旋转为单位的角度,符合人的思考。
  3. 任意三个数都是合法的,不存在不合法的欧拉角。

但是欧拉角有两个比较严重的问题:

  1. (导致角的表示不唯一)为保证每个角度都是唯一的,unity引擎限制了欧拉角的取值:X轴限制在-90到90度,Y,Z轴限制在0到360度。
  2. 万向节死锁。当两个轴重合时就会发生。

为什么产生了万向节死锁?

这和欧拉角的旋转原理有关系,欧拉角的各个轴向并不是同一层级的,而是分为三层,x层,y层,z层,层层嵌套。在unity中这个层级关系为y,x,z。这个层级关系会导致的旋转现象:当我们旋转y轴时,我们会同时旋转z轴和x轴。同理,当我们要旋转x轴的时候,我们会同时旋转z轴。这种现象直接导致了万向节死锁的产生。因为不管怎样,当我们旋转中间层的轴时总会有一个特定角度使得另外两个轴处于同一平面,从而失去一个维度。

四元数两种初始化方法:

公式初始化:直接套用四元数公式。

假设我们想要一个游戏物体A绕自身Y轴旋转60°,则可以套用四元数公式:

quaternion = [Cos(60°/2), Sin(60°/2)*Y轴]。

轴角对初始化:使用Unity官方提供的API来初始化,本质还是套用公式。

四元数知识补充:

单位四元数:

Quaternion.identity 表示一个标量为1,向量为(0,0,0)的四元数。

常用于创建游戏物体后设置默认旋转角度,单位四元数表示不旋转。

四元数的优点:

  1. 避免万向节死锁。
  2. 角度表示唯一。

四元数的缺点:

  1. 难于使用。
  2. 存在不合法的四元数。

延时函数

延时执行一次:

Invoke

延时持续执行:

InvokeRepeating

注意事项:

  1. 延时函数的第一个参数传入的是函数名字字符串
  2. 延时函数必须是无参函数,如果想要传入有参函数我们只能包裹一层。
  3. 函数名必须是该脚本上申明的函数。
  4. 延时函数的本质是反射,所以只要脚本对象还在就可以执行,不受对象失活的影响。

取消延时函数:

CanelInvoke

判断是否存在延时函数:

IsInvoking

协同程序

Unity是否支持多线程?

Unity是支持多线程的。但是untiy在多线程的使用上存在限制。在新开的线程中不能操作Unity相关的对象,但是可以使用unity的脚本生命周期。

通常我们会使副线程来处理一些可能会卡住主线程的操作,比如网络请求和A*寻路等。

关于unity中副线程的使用,由于unity的限制,主线程和副线程之间不能直接进行数据通信,所以我们通常会创建一个公共数据区来为主副线程提供数据交互能力。

在unity下开启线程一定要记得关闭线程。如果不关闭线程,则线程可能会和unity程序共生。关闭线程我们可以在unity的脚本生命周期中关闭。

Unity协同程序

协同程序本质上并没有开辟一个新的线程,而是在原线程上将一个复杂操作分解处理了。

unity会每帧判断协程是否满足条件,如果满足就会执行否则不执行。

注意事项:

  1. yield break的作用:yield break的作用等价于StopCoroutine。且使用yield break后我们不需要再使用StopCoroutine。
  2. 只有组件失活对协程是没有影响的,其他情况下协程都会失效。

协程原理:

unity协程可以分为两个部分,协程函数本体和协程调度器。

协程本体本质上是一个迭代器函数,协程调度器说白了就是判断什么时候调用moveNext的函数,由unity内部实现。

在学习过C#的迭代器后我们知道了迭代器中所存在的结构主要有三部分:Current:yield return返回的内容,MoveNext,Reset。而协程调度器就是通过每帧判断Current的内容来决定什么时候运行哪个迭代器的MoveNext。具体的yield return的内容也是unity自定义的一些规则,这意味着我们可以自己实现属于自己的协程。

自定义协程管理器:

其中这里的MonoSingleton是一个mono单例基类

using System.Collections;
using System.Collections.Generic;
using CsharpBaseModule.Singleton;
using UnityEngine;
/// <summary>
/// WaitOneSecond自定义协程规则,等待一秒
/// </summary>
public class WaitOneSecond
{
   
    public static int WaitTime = 1;
}
/// <summary>
/// WaitOneSecond条件类
/// </summary>
public class WaitOneSecondCondition
{
   
    public IEnumerator MyEnumerator;
    public float Time;
}
/// <summary>
/// 自定义unity迭代器调节器
/// </summary>
public class CoroutineManager : MonoSingleton<CoroutineManager>
{
   
    /// <summary>
    /// 用于循环检测条件的时间列表
    /// </summary>
    private List<WaitOneSecondCondition> _timeList = new List<WaitOneSecondCondition>();
    /// <summary>
    /// 我们自定义的协程管理,
    /// 每次调用都会将当前迭代器的当前信息(迭代器将一个函数以yield return为边界分为若干部分)保存到调用列表中。
    /// </summary>
    /// <param name="myEnumerator">迭代器函数</param>
    public void MyStartCoroutine(IEnumerator myEnumerator)
    {
   
        //如果存在下一项,就继续
        if (myEnumerator.MoveNext())
        {
   
            //判断yield return返回的类型是什么,如果是WaitOneSecond就继续
            if (myEnumerator.Current is WaitOneSecond)
            {
   
                //创建一个WaitOneSecond条件类
                WaitOneSecondCondition waitOneSecondCondition = new WaitOneSecondCondition();
                waitOneSecondCondition.MyEnumerator = myEnumerator;
                waitOneSecondCondition.Time = Time.time + WaitOneSecond.WaitTime;
                //将此条件加入判断列表
                _timeList.Add(waitOneSecondCondition);
            }
        }
        else
        {
   
            print("结束了");
        }
    }
    /// <summary>
    /// 在Update中循环检测自定义协程条件
    /// </summary>
    private void Update()
    {
   
        //每帧判断条件列表中的条件是否满足
        for (int i = _timeList.Count - 1; i >= 0; i--)
        {
   
            //如果满足就去查找满足条件的迭代器的下一项
            if (_timeList[i].Time <= Time.time)
            {
   
                MyStartCoroutine(_timeList[i].MyEnumerator);
                //调用后说明满足条件,之后要删除此项,防止第二次更新循环时再次调用此条件。
                _timeList.RemoveAt(i);
            }
        }
    }
}

特殊文件夹

工程路径获取

这里工程路径的获取主要指的是获取Assets文件夹的绝对路径。

调用属性:Application . dataPath

注意,该方法获取的路径一般情况下只在编辑模式下使用,我们不会再实际游戏发布后还是用此路径,因为游戏发布后此路径就不存在了。

Resources资源文件夹

我们一般不会去获取此文件夹的路径,都是通过ResourcesAPI进行加载。

作用:

存放资源的文件夹,想要通过ResourcesAPI动态加载的资源需要放在其中。

注意:

此文件夹需要我们自己手动创建。

该文件夹下所有的文件都会被打包出去,所以不要将所有的资源文件都存放在这个文件夹下。

打包后该文件夹不存在,并且只读,只能通过Resources相关API加载。

StreamingAssets流动资源文件夹

路径获取:Application . streamingAssetsPath

作用:

可以存放一些需要自定义动态记载的初始资源。

注意:

此文件夹需要我们手动自己创建。

此文件夹打包出去后不会被压缩加密,依旧存在。

移动平台只读,PC平台可读可写。

persistentDataPath持久数据文件夹

路径获取:Application . persistentDataPath

作用:

一般用于放置下载或者动态创建的文件,游戏中创建或者获取的文件都放在其中。

注意:

此文件夹不需要我们手动创建。

此文件夹在所有平台都是可读可写的。

此文件夹在路径获取一定要使用API获取,不同平台的此文件夹的路径和名称都不一样。

Plugins插件文件夹

路径获取:一般不获取。

作用:

插件存放使用的文件夹。我们将不同平台的插件存放在这里。

注意:

需要我们手动创建文件夹。

Editor编辑器文件夹

路径获取:一般不需要获取。如果硬要获取,可以使用工程路径拼接得到。

作用:

存放编辑器代码

注意:

此文件夹中的内容不会被打包出去。

开发unity编辑器功能时需要把编辑器相关脚本放在该文件夹下。

Standard Assets默认资源文件夹

路径获取:一般不获取

作用:

一般unity自带的资源文件会放在这个文件夹下,该文件夹下的代码和资源会被优先编译。

注意:
此文件夹需要我们自己手动创建。

此文件夹不常用。一般当我们导入unity标准资源时会带有此文件夹。

Resources

首先我们需要知道:Unity允许多个Resources同名文件夹存在在若干个不同文件夹中,当我们使用ResourcesAPI动态加载资源时,unity会查找所有的Resources文件夹。最后项目打包的时候Unity也会帮助我们将所有的Resources文件夹打包到一起。

Resources资源加载的作用:

  1. 通过代码动态加载Resources文件夹下的指定路径资源。
  2. 避免繁琐的拖曳操作。

常用资源类型:

  1. 预设体资源——GameObject
  2. 音效切片资源——AudioClip
  3. 文本文件资源——TextAsset
  4. 图片文件资源——Texture
  5. 其他类型——需要什么就用什么类型

注意:使用ResourcesAPI加载预制体资源文件时,本质是加载了一个配置文件,所以想要预制体游戏对象出现在场景中,我们还需要对加载的数据进行实例化。

资源同步加载:

API:

Resources . Load(“资源路径”);

Resources . Load(“资源路径” , Type);

Resources . Load<类型>(“资源路径”);

注意:Object类型是Unity自己实现的“万物之父”,但它也继承自C#的object。

资源异步加载:

为什么使用异步加载?

当我们使用同步加载去加载一个较大的文件时可能会造成程序卡顿,尔科钝的原因就在于从硬盘中读取数据的时候是需要计算的,越大的资源耗时就越长,从而堵塞主线程,使得一帧执行的时间变长,出现我们俗称的掉帧。

什么是异步加载?

Resources异步加载,会在内部新开一个线程进行资源加载,当主线程开辟完新线程后会继续进行下面的逻辑,不会因为资源加载而等待。当资源加载完成后会将加载好的资源放在一个公共内存区中,当主线程再次回来检测时发现资源已经加载完毕后就会取出来使用。

使用异步加载可以有效避免程序卡顿的情况。这里需要注意:异步加载不会马上得到加载的资源,最少要等一帧。

如何在异步加载结束后使用资源?

通过监听异步加载中的事件使用加载资源。

异步加载的返回类型不是资源类型而是一个叫做ResourcesRequest类型的变量,继承自AsyncOperation,此变量包含一些异步加载时的进度信息和我们要监听的事件completed。

//调用异步加载API
ResourceRequest result = Resources.LoadAsync<T>(path);
//为异步加载事件添加回调函数
result.completed += Load;

private void Load(AsyncOperation operation)
{
   
    //操作资源:(operation as ResourceRequest).asset
}

通过协程使用加载的资源。

除了上面的通过一个完成事件来调用异步资源,我们还可以通过协程来调用异步资源。

这时我们就需要用到上面提到的一些异步加载的进度信息:isDone,progress。

isDone属性表示此资源是否已经加载完毕,progress可以用来显示资源加载的进度。

在上面我们已经知道,想要使用Unity协程,我们需要两个部分:第一个是迭代器,第二个是协程调度器,其中协程调度器Unity已经帮我们实现了,我们只需要实现一个迭代器就可以。

写法一:

/// <summary>
/// 异步加载迭代器
/// </summary>
/// <param name="path">资源路径</param>
/// <param name="func">由于这里使用了迭代器,无法返回资源类型,
/// 我们需要从外部传入一个外部响应函数来进行加载完成后的资源处理
/// 此函数也可以只作为一个参数传递的作用</param>
/// <typeparam name="T">要查找的资源类型</typeparam>
private IEnumerator RealLoadAsync<T>(string path, UnityAction<T> func) where T : Object
{
   
    ResourceRequest result = Resources.LoadAsync<T>(path);
    while (!result.isDone)
    {
   
        //这里可以做一些别的操作,比如通知外界刷新进度条等。
        yield return result.progress;
    }
    func(result.asset as T);
}

写法二:

private IEnumerator RealLoadAsync2<T>(string path, UnityAction<T> func) where T : Object
{
   
    ResourceRequest result = Resources.LoadAsync<T>(path);
    yield return result;
    func(result.asset as T);
}

这里着重说一下写法二:

写法二中,我们直接yield return了一个ResourcesRequest类型的变量,这里我们去看ResourcesRequest的基类就会发现ResourcesRequest的基类AsyncOperation继承自YieldInstruction,也就是说,我们的ResourcesRequest同样也可以作为协程调度的条件出现,当我们使用ResourcesRequest类型作为yield return的条件时,Unity会认为我们正在进行异步加载资源,这时只有当异步资源加载完毕后,unity才会执行yield return后面的内容。

两种异步资源使用的对比:

完成事件监听的异步加载:

好处:写法简单。

坏处:只能在资源加载结束后进行处理。

协程异步加载:

好处:可以在协程中处理复杂逻辑,比如同时加载多个资源,比如进度条更新。

坏处:写法稍麻烦。

Resources资源卸载

Resources重复加载资源会浪费内存吗?

Resources在加载过一次资源后就会将其作为缓存一直存放在内存中,第二次加载相同资源时如果发现内存中存在此资源就会直接取出来进行使用。所以多次重复加载不会浪费内存,但是会浪费性能(每次加载都会区查找,这会伴随一些性能的消耗)。

如何手动释放掉缓存中的资源?

卸载指定资源:

Resources . UnloadAsset

注意:该方法不能释放GameObject对象和Component对象。

它只能用于一些不需要实例化的内容,比如图片,音频,文本等等。

一般情况下我们很少单独使用它。

卸载未使用的资源:

一般再过场景时和GC垃圾回收一起使用。

Resources.UnloadUnusedAssets();
GC.Collect();

Unity场景加载

unity场景加载也分为场景同步加载和场景异步加载,和资源加载不同的是场景一旦加载完就会直接切换过去,除非是异步加载时我们将allowSceneActivation设置为false。

这里需要注意SceneManager为我们提供了三个sceneLoaded事件,sceneUnLoaded事件和activeSceneChanged事件。

unity场景同步加载:

使用API直接加载。

unity场景异步加载:

unity场景异步加载也和资源异步加载一样可以有加载完成的事件:completed,或者通过协程处理。

需要注意的是,在我们加载场景时会删除上一个场景的所有没有经过特殊处理的对象,这意味着如果我们使用协程,那么协程对象在切换场景时是不允许被删除的。这里就要用到unity提供的DontDestroyOnLoad方法了。

利用协程异步加载场景:

当然这里的func也可以直接成为completed的回调函数。

/// <summary>
/// 异步加载迭代器
/// </summary>
/// <param name="sceneName">场景名称</param>
/// <param name="func">加载完成后的外部响应函数</param>
/// <param name="characterTags"></param>
/// <returns>每次返回加载进度</returns>
private IEnumerator RealLoadSceneAsync(string sceneName, UnityAction func,params string[] characterTags)
{
   
    var result = SceneManager.LoadSceneAsync(sceneName);
    result.allowSceneActivation = false;
    //如果没有加载完
    while (!result.isDone)
    {
   
        //可以使用事件中心来派发加载事件,
        yield return result.progress;
    }
    func();
}

关于场景同步异步的比较和资源那里介绍的是一样的,这里不再赘述。

LineRenderer

LineRenderer是什么?

LineRenderer是unity提供的一个用于画线的组件,使用它我们可以在场景中绘制线段。一般可用于绘制攻击范围,武器红外线,辅助功能,和其他画线功能。

LineRenderer参数:

Loop:是否连接起点和终点,勾选此项后,uniy会自动帮助我们将线段的起点和终点连在一起,视情况勾选。

Positions:线段点数组,我们通过此项添加新点,LineRenderer的连接机制是各个点顺序相连。

Widths:设置线段的宽度,可以设置渐变效果。

Color:线段颜色,可以设置渐变效果。没有材质时是无效的,默认LineRenderer不带有材质。

Corner Vertices:可以理解为线段中间点(不包括起点终点)过渡的平滑程度,不设置的化为尖角,设置的值越大,则角越圆润。

End Cap Vertices:线段端点(起点终点)的过渡平滑程度。

Alignment:对齐方式,有面向自身Z轴,和面向相机两种模式。

Textrue mode:纹理模式,设置材质贴在线段上的效果。

Shadow Bias:阴影偏移。

Generic Lighting Data:生成光照数据。

Use World Space:是否使用世界坐标系。默认勾选,表示点位置使用的世界坐标,和组件挂载对象的位置没有关系。

Material:材质

Lighting:光照相关。

Probes:光照探针相关。

Additional Settings:额外选项。

  • Motion Vectors:运动矢量。
  • Dynamic Occlusion:动态遮挡剔除。
  • Sorting Layer:排序图层。
  • Order in Layer:排序图层中的顺序。

Unity物理系统之范围检测

使用unity提供的范围检测时,被检测物体必须有碰撞器!!!!

首先我们需要补充一个知识点:Unity层级表示。

层级表示

关于unity中的层级,我们可以通过API:LayerMask . NameToLayer来获得其层级编号。

但是在Unity的参数调用中,往往我们不可以传入这个数字,这里Unity使用了和flagEnum一样的原理来判断需要检测的层,首先打开Unity的层级管理我们可以看到层级从0开始,到31结束,也就是说有32层,这个数字不是巧合,正是因为Unity使用Int32存储层级所以这里最多只能有32层。

为什么呢?现在让我们回想flagEnum的内容,在flagEnum中我们有意的将各个枚举值改变为2的次方,这是因为如果转换为2进制我们就可以看出每一个枚举值都是只有一位为1的数值,在使用这种枚举时我们通过位或运算将想要使用的枚举叠加起来,并使用位与运算判断一个数值中有那些枚举值。Unity的层级判断也使用了这种原理。所以Int32型最多只能存放32个值,这也是为什么Unity只提供了32个层级。

知道了原理,那么要传入什么信息就比较清楚了,上面我们提到一个API可以获得对应名字的层级编号,我们可以通过位运算来获得我们想要的枚举值。这里举个例子:我们知道Unity的UI层是第5层位于前八层,这意味这UI层是系统层,其层级是稳定的。我们就用它来举例子。这里获得UI层的枚举值:

int UILayerNum = 1 << layerMask.NameToLayer("UI");

层级剔除

如果我们不想检测一层时,我们可以用全1的二进制数异或我们不想检测的那层,经过计算,除了我们不想检测的那层为0其他都为1。

Unity范围检测解决方案

简单来说在API中所有的Physics . OverLapXX方法都是Unity为我们提供的范围检测解决方案。细分还可分为:OverLapXX和OverLapXXNonAlloc。两者区别主要在于返回值不同。

OverLapXX

此类方法的返回值类型为collider数组

Unity主要提供了OverLapBox,OverLapSphere和OverLapCapsule,他们都有对应的OverLapXXNonAlloc方法。这里我们对其逐个进行介绍:

1. OverLapBox

overLapBox的所有重载中最多有5个参数:

center 盒体的中心。
halfExtents 盒体各个维度大小的一半。也就是各个边的长度,Vector3类型。
orientation 盒体的旋转。
layerMask 层遮罩,用于在投射射线时有选择地忽略碰撞体。
queryTriggerInteraction 指定该查询是否应该命中触发器。

其中orientation,控制的是盒体的旋转,什么意思呢?此方法是用于检测所有与此方法生成的碰撞体重叠的碰撞体,而Box碰撞体由于是个方形的,当旋转不同时碰撞体笼罩的范围也会不同,所以存在一个旋转参数,此参数在Sphere和Capsule中没有。

这里的layerMask需要填写的内容就是上面提到的层级表示里的内容。

queryTriggerInteraction,默认使用全局设置。useGlobal,你也可以自己选择collider检测,或者ignore忽略。

2. OverLapSphere

OverLapSphere的所有重载中最多有4个参数:

position 球体的中心。
radius 球体的半径。
layerMask 层遮罩,用于在投射射线时有选择地忽略碰撞体。
queryTriggerInteraction 指定该查询是否应该命中触发器。

这里球状碰撞器由于不论怎么旋转看起来都一样,所以没有orientation这个参数。

3. OverLapCapsule

OverLapCapsule的所有重载中最多有5个参数:

point0 胶囊体在start处的球体中心。
point1 胶囊体在end处的球体中心。
radius 胶囊体的半径。
layerMask 层遮罩,用于在投射胶囊体时有选择地忽略碰撞体。
queryTriggerInteraction 指定该查询是否应该命中触发器。

OverLapCapsule的参数比较特殊,我们着重来说一下其中的point0和point1。

当我们创建一个胶囊体碰撞体时我们发现,一个胶囊体是由两个半球和一个圆柱体构成的,这里的point0和point1就是指的这两个半球的求球心位置。当这两个球心的位置确定了,那么这个胶囊体的旋转,高度也就确定了。

OverLapXXNonAlloc

此类方法返回int数值,表示撞到了多少个碰撞体。

在实际使用中我们更推荐使用NonAlloc。NonAlloc可以结合缓存池一起使用。

NonAlloc类的方法相对于传统范围检测,多了一个out数组参数,我们使用NonAlloc方法,unity将不会返回数组,而是会返回一个int值表示检测到了若干碰撞体,同时将所有碰撞体信息存入我们传入的数组参数中。其他参数和传统范围检测相同。

Unity物理系统之投射检测

简单来说,打开Unity官方文档中Physics所有XXCast的方法都属于投射检测,投射检测细分可分为XXCast,XXCastAll和XXCastNonAlloc。

XXCast

此类方法返回bool类型,表示是否击中物体。

关于XXCast,Unity官方提供了五种检测类型,分别是BoxCast,SphereCast,CapsuleCast,LineCast和RayCast,这些方法的参数构成都是大同小异的,具体区别在于碰撞体构成参数(就是构成这个形状需要的参数)的区别。这里我们以RayCast为例进行讲解。

RayCast

说到RayCast,我们就要先了解一下Unity中的射线Ray。

射线是从 origin 开始并按照某个 direction 行进的无限长的线。

direction 射线的方向。
origin 射线的原点。

这里需要注意:射线的两个参数一个是原点坐标,一个是方向向量,不要错认为Ray是由两个点确定的。

RayCast中所有的重载中最多有6个参数:

origin 射线在世界坐标系中的起点。
direction 射线的方向。
raycastHit 射线检测信息类,其内部存放本次射线检测的击中信息。
maxDistance 射线应检查碰撞的最大距离。
layerMask 层遮罩,用于在投射射线时有选择地忽略碰撞体。
queryTriggerInteraction 指定该查询是否应该命中触发器。

这里需要注意:

  • raycasthit信息类参数的传入,传入的out参数,原因在于RayCastHit是一个结构体。
  • 这里的origin和direction参数可以直接替换成Ray射线。
  • RayCastHit中包含许多的重要信息,包括击中物体的碰撞体collider,击中物体的落点point,击中物体落点平面的法向量normal,击中物体与发射点的距离distance等。
  • 此类方法在击中第一个物体后就不再穿透。

XXCastAll

上面我们知道传统的XXCast只会检测到第一个击中的物体,不论后面有多少物体都不会穿透,为了解决这个问题,Unity为我们提供了XXCastAll的方法。

参数上基本与XXCast保持一致,但是此类方法将返回RayCastHit数组,这意味着我们不再需要手动传入RayCastHit类型的out参数了。

XXCastNonAlloc

--------------------------- |
| direction | 射线的方向。 |
| raycastHit | 射线检测信息类,其内部存放本次射线检测的击中信息。 |
| maxDistance | 射线应检查碰撞的最大距离。 |
| layerMask | 层遮罩,用于在投射射线时有选择地忽略碰撞体。 |
| queryTriggerInteraction | 指定该查询是否应该命中触发器。 |

这里需要注意:

  • raycasthit信息类参数的传入,传入的out参数,原因在于RayCastHit是一个结构体。
  • 这里的origin和direction参数可以直接替换成Ray射线。
  • RayCastHit中包含许多的重要信息,包括击中物体的碰撞体collider,击中物体的落点point,击中物体落点平面的法向量normal,击中物体与发射点的距离distance等。
  • 此类方法在击中第一个物体后就不再穿透。

XXCastAll

上面我们知道传统的XXCast只会检测到第一个击中的物体,不论后面有多少物体都不会穿透,为了解决这个问题,Unity为我们提供了XXCastAll的方法。

参数上基本与XXCast保持一致,但是此类方法将返回RayCastHit数组,这意味着我们不再需要手动传入RayCastHit类型的out参数了。

XXCastNonAlloc

同上面范围检测的原理相同,这里此方法将返回int数值,表示击中了多少个物体,并且在传参数的时候,我们需要额外传入一个RayCastHit类型的数组。看作是XXCastAll的升级版,推荐使用。


转载:https://blog.csdn.net/BraveRunTo/article/details/116809953
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场