延迟函数/协同程序/多线程/特殊文件夹/资源加载/异步加载资源或场景/LineRender/范围检测/射线检测

📅 发布时间:2026/7/7 14:28:19 👁️ 浏览次数:
延迟函数/协同程序/多线程/特殊文件夹/资源加载/异步加载资源或场景/LineRender/范围检测/射线检测
延迟函数定义延迟函数是MonoBehaviour基类提供的可以延时执行的函数特点可以自定义要执行的函数和延迟时间继承MonoBehaviour后即可使用通过反射机制根据函数名查找并执行对应方法延迟函数的使用//Invoke() //参数一函数名 字符串 //参数二延迟时间 秒为单位 Invoke(DelayFunc, 1); private void DelayFunc() { print(延时执行函数); } //注意 //延时函数第一个参数传入的是函数名字符串 //延时函数无法传入参数需包裹一层无参函数 //函数名必须是该脚本上申明的函数如何包装有参函数Invoke(DelayDoSomething, 1); private void DelayDoSomething() { print(延时执行的函数); TestFun(2); } private void TestFun(int i) { print(传入参数 i); }延迟重复函数其注意事项与延迟函数一致延时函数第一个参数传入的是函数名字符串延时函数无法传入参数需包裹一层无参函数函数名必须是该脚本上申明的函数//InvokeRepeating() //参数一函数名 字符串 //参数二第一次执行的延迟时间 秒为单位 //参数三之后每次执行的间隔时间 秒为单位 //延迟5s执行第一次后续每隔2s重复执行一次 InvokeRepeating(DelayFunc, 5, 2); private void DelayFunc() { print(延时重复执行函数); }延迟函数练习题延迟函数一般放在Start函数中如果放在Update函数中会一直启用该函数就会导致混乱。计时器void Start() { //第一次在0s瞬间执行该函数后面每一次过一秒执行DelayFun InvokeRepeating(DelayFun, 0, 1); DelayFun2(); } private void DelayFun() { print(time 秒); time; } private void DelayFun2() { print(time 秒); time; Invoke(DelayFun2, 1); }输出结果如下延迟摧毁void Start() { //通过Destroy来进行延迟销毁 Destroy(this.gameObject, 5); Invoke(DelayDes, 5); } 0 个引用 private void DelayDes() { Destroy(this.gameObject); }取消延迟函数取消该脚本上的所有延时函数 CancelInvoke(); 取消指定函数名的延迟函数 只要取消了指定延迟无论函数开启了多少次延迟执行都会统一取消 CancelInvoke(DelayFunc);判断是否存在延迟函数if( IsInvoking() ) { print(存在延迟函数); } if( IsInvoking(DelayFunc) ) { print(存在函数名为 Func 的延迟函数); }延迟函数的失活对象或脚本失活无法停止延时函数执行销毁组件或者对象才会停止或者代码停止多线程Unity中多线程的使用不算常见只是面试需要了解部分内容支持情况Unity确实支持多线程可以创建并运行新线程关键限制新线程无法访问Unity相关对象的内容如Transform、GameObject等尝试访问会抛出get_transform can only be called from the main thread异常注意事项必须手动关闭创建的多线程在OnDestroy中使用t.Abort()Unity大部分API都不能在非主线程中调用using System; using System.Collections.Generic; using System.Threading; using UnityEngine; public class MultiThreadTest : MonoBehaviour { // 声明线程对象 private Thread t; // 申明一个公共内存容器队列用于线程间通信 private QueueVector3 queue new QueueVector3(); // 可选另一个队列演示多个共享容器 private QueueVector3 queue2 new QueueVector3(); void Start() { t new Thread(Test); t.Start(); } // 后台线程执行的方法 private void Test() { while (true) { Thread.Sleep(1000); // 模拟耗时操作每秒执行一次 // 相当于模拟复杂算法算出了一个结果然后放入公共容器中 queue.Enqueue(new Vector3(1, 2, 3)); } } // 当对象销毁时终止线程并清理资源 private void OnDestroy() { if (t ! null t.IsAlive) { t.Abort(); // 强制终止线程不推荐但简单 // 更安全的方式是使用标志位控制循环避免 Abort } t null; } // 主线程中定期处理队列中的数据例如在 Update 中 void Update() { if (queue.Count 0) { Vector3 result queue.Dequeue(); Debug.Log($从队列中取出结果: {result}); } } }协程协同程序简称协程。它是一种“假”的多线程技术本质上并不是真正的多线程。它的主要作用是将代码分时执行从而避免卡住主线程。简单理解就是把那些可能会让主线程卡顿的耗时逻辑分时分步地执行。主要使用场景包括异步加载文件异步下载文件场景异步加载批量创建时防止卡顿协程与线程的关系协程的案例使用协程的使用如下所示返回值类型IEnumerator所以协程函数中必须要返回某种数据即yield return……关键点一协程程序协程函数返回值必须是 IEnumerator 或者继承它的类型 IEnumerator MyCoroutine(int i, string str) { print(i); // 关键点二协程函数当中必须使用 yield return 进行返回 yield return new WaitForSeconds(5f); print(str); }银鸟工作室——协程https://blog.csdn.net/2303_80204192/article/details/156720077开启协程// 第二步开启协程函数 // 协程函数是不能够直接这样去执行 // 这样执行没有任何效果 // MyCoroutine(1, 123); // 常用开启方式 第一种 IEnumerator ie MyCoroutine(1, 123); StartCoroutine(ie); 第二种 StartCoroutine(MyCoroutine(1, 123));这里可以开启多个相同的协程协程的关闭Coroutine c1 StartCoroutine(MyCoroutine(1, 123)); Coroutine c2 StartCoroutine(MyCoroutine(1, 123)); Coroutine c3 StartCoroutine(MyCoroutine(1, 123)); // 第三步关闭协程 // 关闭所有协程 // StopAllCoroutines(); // 关闭指定协程 StopCoroutine(c1);yield return相关下一帧执行 yield return 数字; yield return null; 等待指定秒后执行 yield return new WaitForSeconds(秒); 等待下一个固定物理帧更新时执行 yield return new WaitForFixedUpdate(); 等待摄像机和GUI渲染完成后执行 yield return new WaitForEndOfFrame(); 一些特殊类型的对象 比如异步加载相关函数返回的对象 之后讲解 异步加载资源 异步加载场景 网络加载时再讲解 一般在Update和LateUpdate之间执行 跳出协程 yield break;yield return new WaitForSeconds(5f);当延迟五秒的这个条件满足了然后在之后这个满足条件过后update中所有函数执行完过后然后再在lateupdate将要执行之前去执行的跳出协程yield break后面的协程不会被执行协程的失活协程的使用原理组成结构协同程序可以分成两部分1.协程函数本体2.协程调度器函数本体特性协程本体就是一个能够中间暂停返回的函数调度器功能协程调度器是Unity内部实现的会在对应的时机帮助我们继续执行协程函数实现范围Unity只实现了协程调度部分协程的本体本质上就是一个C#的迭代器方法练习题——协程的使用public class Corotine : MonoBehaviour { private int time 0; void Start() { StartCoroutine(settime()); } public IEnumerator settime() { while (true) { print($第{time}秒); time; yield return new WaitForSeconds(1); } } }1秒等待结束后协程会从yield return语句之后继续执行也就是循环体的结束然后由于是while(true)循环会再次进入循环体。开始协程放在Start函数中——不可能每帧都执行开始协程private void Update() { if (Input.GetKeyDown(KeyCode.Space)) { StartCoroutine(CreateCube(10000)); } } IEnumerator CreateCube(int num) { for (int i 0; i num; i) { GameObject obj GameObject.CreatePrimitive(PrimitiveType.Cube); obj.transform.position new Vector3(Random.Range(-100, 100), Random.Range(-100, 100), Random.Range(-100, 100)); if (num % 1000 0) yield return null; } }创建100000个物体如果一次性在一帧中创建会发生卡顿。但如果分批次处理——每生成一千个等待一帧再生成一千个就缓解卡顿的情况特殊文件夹此处代码输出结果是当前游戏项目的Asset文件夹Resource文件夹因为打包后会加密所以一般不直接获取描述存放在运行时需通过Resources.Load动态加载的资源但使用 Resources文件夹中的资源会增加构建体积因为这些资源会被包含在构建文件中即使在运行时并不一定用到这些资源且使用Resources文件夹的资源会被打包到游戏的所有平台版本中因此要谨慎使用。#region 知识点二 Resources 资源文件夹 //路径获取 //一般不获取 //只能使用Resources相关API进行加载 //如果硬要获取 可以用工程路径拼接 print(Application.dataPath /Resources); //注意 //需要我们自己将创建 //作用 //资源文件夹 //1-1.需要通过Resources相关API动态加载的资源需要放在其中 //1-2.该文件夹下所有文件都会被打包出去 //1-3.打包时Unity会对其压缩加密 //1-4.该文件夹打包后只读 只能通过Resources相关API加载输出结果如下正好是Resources文件夹的路径StreamingAssets 流动资源文件夹不能使用Application.dataPath /StreamingAssets拼接路径平台差异在不同平台下打包后路径会变化拼接方式会导致路径错误描述存储配置文件、外部数据或其他需要在运行时直接访问的资源且在运行时不需要修改。这些文件会被原样复制到构建目录中可以通过绝对路径访问。#region 知识点三 StreamingAssets 流动资源文件夹 //路径获取 print(Application.streamingAssetsPath); //注意 //需要我们自己将创建 //作用 //流文件夹 //2-1.打包出去不会被压缩加密可以任由我们摆布 //2-2.移动平台只读PC平台可读可写 //2-3.可以放入一些需要自定义动态加载的初始资源persistentDataPath 持久数据文件夹自动创建不需要手动创建该文件夹Unity会自动生成平台差异性在不同平台下路径不同无法在Unity编辑器中直接查看存储位置存储在设备本地如PC的C盘用户目录或手机存储中与用户名相关#region 知识点四 persistentDataPath 持久数据文件夹 //路径获取 print(Application.persistentDataPath); //注意 //不需要我们自己将创建 //作用 //固定数据文件夹 //3-1.所有平台都可读可写 //3-2.一般用于放置动态下载或者动态创建的文件游戏中创建或者获取的文件都放在其中Plugins 插件文件夹描述存放插件或本地化的插件库。这里的插件通常包括DLL文件、原生插件等用于扩展Unity的功能或与外部系统集成。插件通常包括平台特定的代码如Android的.jar文件或iOS的.a文件这些文件在构建时会被正确地处理。使用场景集成了一个第三方的原生插件库如一个用于图像处理的DLL文件。在 Assets/Plugins/文件夹中放置这个DLL文件这样Unity可以在构建过程中正确处理和链接这个插件。#region 知识点五 Plugins 插件文件夹 //路径获取 //一般不获取 //注意 //需要我们自己将创建 //作用 //插件文件夹 //不同平台的插件相关文件放在其中 //比如IOS和Android平台Editor 编辑器文件夹描述存放自定义编辑器脚本、工具或编辑器扩展这些内容只在Unity编辑器中有效不会被包含在最终构建中。如可以创建自定义Inspector、编辑器窗口等功能来增强编辑器的功能。#region 知识点六 Editor 编辑器文件夹 //路径获取 //一般不获取 //如果硬要获取 可以用工程路径拼接 print(Application.dataPath /Editor); //注意 //需要我们自己将创建 //作用 //编辑器文件夹 //5-1.开发Unity编辑器时编辑器相关脚本放在该文件夹中 //5-2.该文件夹中内容不会被打包出去默认资源文件夹 Standard Assets创建方式需要手动创建命名为Standard Assets典型内容Unity官方提供的默认资源从Asset Store下载的标准资源包#region 知识点七 默认资源文件夹 Standard Assets //路径获取 //一般不获取 //注意 //需要我们自己将创建 //作用 //默认资源文件夹 //一般Unity自带资源都放在这个文件夹下 //代码和资源优先被编译 #endregion资源加载为什么需要进行资源加载是避免直接将资源模型内容直接进行拖拽导致管理混乱。通过代码进行统一管理自动化配置加载预设体// 加载预设体资源未实例化 GameObject prefab Resources.LoadGameObject(Cube); // 实例化到场景中 Instantiate(prefab)1加载的预设体只是配置数据需通过Instantiate生成实例2假设Cube.prefab位于Resources/Prefabs/Cube则路径为Prefabs/Cube Resource文件夹在Resource类中不直接写入路径中概念定义关键操作加载资源 (Load)从存储介质磁盘、内存、网络读取资源数据到内存Resources.Load、AssetBundle.LoadAsset、Addressables.LoadAsset生成/实例化 (Instantiate)在内存中根据资源数据创建可运行的游戏对象GameObject.Instantiate、Object.Instantiate音频使用//加载资源 AudioClip clip Resources.LoadAudioClip(Music/BkMusic); //播放音频 audioSource.clip clip; audioSource.Play();加载文本TextAsset textAsset Resources.LoadTextAsset(Txt/Text); Debug.Log(textAsset.text); // 获取文本内容 //文本内容 print(ta.text); //字节数据组 print(ta.bytes);1支持txt/.xml/.json等文本格式2通过text属性获取字符串bytes获取二进制数据加载图片Texture texture Resources.LoadTexture(Picture/4); // GUI显示示例 void OnGUI() { GUI.DrawTexture(new Rect(0,0,100,100), texture); }文件同名怎么办● 问题Resources.Load加载同名资源时无法准确识别● 解决方案○ 使用指定类型加载API○ 加载指定名字的所有资源● 推荐做法避免资源同名保持资源命名唯一性// 指定类型加载——同名资源类型不同一个Text一个Texture Texture tex Resources.Load(Picture/4, typeof(Texture)) as Texture // 加载所有同名资源——用if函数判断是否是指定类型 Object[] all Resources.LoadAll(Picture/4); foreach(Object obj in all){ if(obj is Texture2D) { // 使用Texture2D资源 }使用泛型加载如上资源类型// 泛型方法推荐 T Resources.LoadT(string path) where T : Object; 优势 无需类型转换省去as操作 直接返回指定泛型类型的结果 TextAsset ta2 Resources.LoadTextureAsset(Tex/TestJPG); print(ta2.text); tex Resources.LoadTexture(Tex/TestJPG);异步加载如上都是同步加载中如果我们加载过大的资源可能会造成程序卡顿。卡顿的原因是从硬盘将数据读取到内存的过程需要进行计算而资源越大耗时越长从而导致掉帧或卡顿。Resources 异步加载的原理Unity 在内部开启一个独立线程来执行资源加载操作不会阻塞主线程因此不会造成游戏画面卡顿能有效保障运行流畅性。加载特性异步加载不能立即得到资源至少需要等待一帧ResourceRequest变量类型描述异步加载的状态https://docs.unity.cn/cn/2021.3/ScriptReference/ResourceRequest.html完成事件异步加载不能马上得到资源所以要先判断是否为空这里比较疑惑加载资源时明明使用了泛型却依旧使用asLoadOver()方法接收的是一个AsyncOperation类型参数而AsyncOperation是一个基类它不知道具体是哪种异步操作比如资源加载、场景加载、协程等。所以在LoadOver()里rq实际上是一个AsyncOperation丢失了具体的子类信息。(rq as ResourceRequest)将AsyncOperation转换为ResourceRequest以便访问.asset属性。因为.asset的类型是Object所以tex想得到图片还需要as Texture代码剖析流程原理题目练习——创建一个异步加载的资源管理器using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; public class ResourceMgr { private static ResourceMgr instancenew ResourceMgr(); public static ResourceMgr Instance instance; private ResourceMgr() { } public void LoadResoureT(string name, UnityActionTcallback) where T : Object { ResourceRequest rq Resources.LoadAsyncT(name); rq.completed (AsyncOperation op) { // 安全转换op 一定是 ResourceRequestAsyncOperation是ResourceRequest父类 ResourceRequest request op as ResourceRequest; T asset request.asset as T; // 安全调用回调避免 callback 为 null 导致异常 callback?.Invoke(asset); }; } }public class Lesson18Test : MonoBehaviour { private Texture tex; // 存储加载的纹理 void Start() { ResourceMgr.Instance.LoadResoureTexture(Tex/TestJPG, (obj) { tex obj; // 将加载的纹理赋值给成员变量 Debug.Log(加载纹理成功); }); } private void OnGUI() { if (tex ! null) GUI.DrawTexture(new Rect(0, 0, 100, 100), tex); // 绘制纹理 } }协程为什么协程也能异步获取——因为返回值数据类型的基类是一致的如上红字标识有误——不是等一帧而是至少一帧直到异步加载完成才会继续执行后面的代码通过协程加载资源判断资源是否加载结束两种异步加载的使用优劣资源卸载缓存机制Resources加载一次资源后该资源会一直存放在内存中作为缓存重复加载特性第二次加载时发现缓存中存在该资源会直接取出使用多次重复加载不会浪费内存性能消耗每次加载都会去查找取出始终伴随一些性能消耗void Update() { if (Input.GetKeyDown(KeyCode.Alpha1)) { print(加载资源); tex Resources.LoadTexture(Tex/TestJPG); } if (Input.GetKeyDown(KeyCode.Alpha2)) { print(卸载资源); Resources.UnloadAsset(tex); tex null; } }检测是否其效果可以Ctrl7打开Profiler界面——然后按键查看Textrue的变化卸载未使用的资源//2. 卸载未使用的资源 //注意 //一般在过场景时和GC一起使用 Resources.UnloadUnusedAssets(); GC.Collect();异步切换场景通常我们进行场景加载的时候我们会使用如下代码SceneManager.LoadScene();但是如果此时场景内资源特别多这个时候我们就会卡住那么在游戏中我们此时就不会立即跳转而是使用进度条来告诉玩家已经加载了多少内容本质上就是在不影响当前画面的前提下加载场景通过事件异步加载场景#region 知识点一 场景异步切换 //1.通过事件回调函数 异步加载 AsyncOperation ao SceneManager.LoadSceneAsync(Lesson20Test); //当场景异步加载结束后 就会自动调用该事件函数 我们如果希望在加载结束后 做一些事情 那么久可以在该函数中 //lamda表达式 ao.completed (a) { print(加载结束); }; //使用方法函数 ao.completed LoadOver;练习题——写一个场景管理器为什么不推荐无回调切换时间线 → ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 【带回调的版本】 调用 LoadSceneAsync ──加载中──┬──回调执行──→ 确定完成 ↑ 你在这里知道完成了 【你的版本无回调】 调用 LoadSceneAsync ──加载中──┬──??──→ 场景变了 ↑ 你不知道具体是哪一帧完成的无回调版本就会出现如下情况void Start() { SceneMgr.Instance.LoadScene(Game); // 下一行执行时场景可能还没切换 Debug.Log(这行立即打印场景还是旧的); // ❌ 无法在这里安全地访问新场景的对象 // GameObject.Find(NewSceneObject); // 可能找不到 }using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; public class SceneMgr { private static SceneMgr instance new SceneMgr(); // 单例实例 public static SceneMgr Instance instance; // 公共访问点 private SceneMgr() { } // 私有构造函数防止外部实例化 public void LoadScene(string name, UnityAction action) { AsyncOperation ao SceneManager.LoadSceneAsync(name); ao.completed (a) action(); // 加载完成后执行回调 } }void Start() { // 场景切换前显示加载界面 SceneMgr.Instance.LoadScene(Game, () { Debug.Log(场景切换完成); // 可以在这里关闭加载界面、初始化玩家、播放音乐等 }); }协程异步加载场景StartCoroutine(LoadScene(MyScene)); IEnumerator LoadScene(string name) { AsyncOperation ao SceneManager.LoadSceneAsync(name); print(异步加载过程中 打印的信息); // ✅ 立即执行 yield return ao; // ✅ 协程在此挂起等待加载完成 print(异步加载结束后 打印的信息); // ✅ 加载完成后才执行 }在异步加载场景过程中更新进度条第一种第一种不太推荐//比如 我们可以在异步加载过程中 去更新进度条 //第一种 就是利用 场景异步加载 的进度 去更新 但是 不是特别准确 一般也不会直接用 while(!ao.isDone) { print(ao.progress); yield return null; }第二种不要盲目相信 Unity 的自动进度值而是根据游戏自身的加载流程手动设计进度条的增长逻辑资源可能早已加载完成比如通过Resources.LoadAsync()或Addressables提前加载了怪物、模型等。它们已经在内存中只是还没有被实例化Instantiate或放置到场景中。进度条反映的是“游戏就绪度”而非“文件读取进度”你关心的不是“硬盘读了多少字节”而是场景结构是否建好怪物是否生成完毕UI 是否初始化完成音效系统是否准备就绪这些才是玩家感知的“加载完成”。//第二种 就是根据你游戏的规则 自己定义 进度条变化的条件 yield return ao; //场景加载结束 更新20%进度 //接着去加载场景中的其它信息 //比如 //动态加载怪物 //这时 进度条 再更新20% //动态加载 场景模型 //这时 就认为 加载结束了 进度条顶满两种异步加载方式的优劣前者是加载结束啊才能够去毁掉。后者是在这个异步加载过程中我就可以去做一些处理根据需求选择即可LineRender组件功能: Unity提供的专门用于画线的组件可在场景中绘制线段主要用途:绘制攻击范围武器红外线辅助功能其他画线需求如圆形、方形或不规则线段本质理解: 通过设置多个点坐标将这些点连接起来形成线段主要功能略讲顶点圆角材质设置几种编辑模式都不太常用一般使用代码布置点位代码控制LineRender#region 知识点三 LineRenderer代码相关 //动态添加一个线段 GameObject line new GameObject(); line.name Line; LineRenderer lineRenderer line.AddComponentLineRenderer(); //首尾相连 lineRenderer.loop true; //开始结束宽 lineRenderer.startWidth 0.02f; lineRenderer.endWidth 0.02f; //开始结束颜色 lineRenderer.startColor Color.white; lineRenderer.endColor Color.red; //接着就设置 对应每个点的位置 //第一种是直接数组一次性声明 lineRenderer.SetPositions(new Vector3[] { new Vector3(0,0,0), new Vector3(0,0,5), new Vector3(5,0,5) }); //第二种是索引声明 lineRenderer.SetPosition(3, new Vector3(5, 0, 0)); //是否使用世界坐标系 //决定了 是否随对象移动而移动 lineRenderer.useWorldSpace false; //让线段受光影影响 会接受光数据 进行着色器计算 lineRenderer.generateLightingData true;练习题范围检测碰撞相关的概念重温碰撞产生的必要条件至少有一个物体必须挂载刚体Rigidbody组件。刚体是 Unity 物理系统的核心只有带有刚体的物体才能参与物理模拟如受重力影响、产生碰撞响应等。两个物体都必须拥有碰撞器Collider组件。碰撞器定义了物体的物理形状如 Box Collider、Sphere Collider 等用于检测与其他物体的接触。碰撞与触发的区别碰撞Collision会产生真实的物理效果例如反弹、停止运动、施加力等。适用于需要物理交互的场景如角色撞击墙壁、车辆相撞等。触发Trigger不会产生物理效果但可以通过脚本监听进入、停留或离开事件。只需将任意一方的碰撞器勾选Is Trigger属性即可启用。常用于逻辑判断如拾取道具、区域检测、自动开门等。而范围检测的基本概念定义: 检测特定区域内是否存在符合条件物体的方法特点: 不需要实际物理碰撞即可判断物体位置关系必备条件想要被范围检测到的对象必须具备碰撞器Collider注意点范围检测相关API是瞬时的只有当执行该代码时才进行一次范围检测。范围检测相关API并不会真正产生一个碰撞器它仅仅是进行碰撞判断计算Unity Physics 类提供了一系列范围检测的API。它们通常分为两类直接返回Collider[]数组的方法有GC开销例如 Physics.OverlapBox每次调用会创建一个新的数组。非分配式方法NonAlloc无GC开销例如 Physics.OverlapBoxNonAlloc需要传入一个预先分配好的Collider[]数组来存储结果推荐在频繁调用时使用。检测的层级相关事项LayerMask.NameToLayer(Layer1)这个函数返回的是图层Layer的索引编号整数。如果该图层索引是5相当于把1左移5位来表示第5层盒状范围检测检测该范围内的对应层级的碰撞物体//范围检测API //1.盒状范围检测 //参数一立方体中心点 //参数二立方体三边大小 //参数三立方体角度 //参数四检测指定层级不填检测所有层 //参数五是否忽略触发器 UseGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器 不填使用UseGlobal //返回值在该范围内的触发器得到了对象触发器就可以得到对象的所有信息 print(LayerMask.NameToLayer(UI)); 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); }球形范围检测//2.球形范围检测 //参数一中心点 //参数二球半径 //参数三检测指定层级不填检测所有层 //参数四是否忽略触发器 UseGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器 不填使用UseGlobal //返回值在该范围内的触发器得到了对象触发器就可以得到对象的所有信息 Physics.OverlapSphere(Vector3.zero, 5, 1 LayerMask.NameToLayer(Default));胶囊体范围检测//3.胶囊范围检测 //参数一半圆一中心点 //参数二半圆二中心点 //参数三半圆半径 //参数四检测指定层级不填检测所有层 //参数五是否忽略触发器 UseGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器 不填使用UseGlobal //返回值在该范围内的触发器得到了对象触发器就可以得到对象的所有信息 Physics.OverlapCapsule(Vector3.zero, Vector3.up, 1, 1 LayerMask.NameToLayer(UI), QueryTriggerInteraction.UseGlobal);射线检测射线检测Raycasting是游戏开发中一种常用的碰撞检测技术核心原理是发射一条虚拟的射线有起点和方向的无限长直线检测这条射线是否与场景中的碰撞体Collider相交并获取相交的详细信息。射线生成摄像机发射射线#region 知识点二 射线对象 //1.3D世界中的射线 //假设有一条起点为坐标(1,0,0)方向为世界坐标z轴正方向的射线 //注意 //理解参数含义 //参数一起点 //参数二方向一定记住 不是两点决定射线方向第二个参数 直接就代表方向向量 //目前只是申明了一个射线对象 对于我们来说 没有任何的用处 Ray r new Ray(Vector3.right, Vector3.forward); //Ray中的参数 print(r.origin); //起点 print(r.direction); //方向 2.摄像机发射出的射线 //得到一条从屏幕位置作为起点 //摄像机视口方向为 方向的射线 Ray r2 Camera.main.ScreenPointToRay(Input.mousePosition); //从摄像机出发向屏幕中间发射射线 Ray OneShotRay Camera.main.ScreenPointToRay(ScreenMidPos); // 以屏幕中央点为原点发射射线【屏幕发出的射线】根据玩家在屏幕上点击的位置发射一条射线向屏幕中间发射射线单独的射线对于我们来说没有意义它一定是和别的模块配合使用的射线检测1——是否碰撞到对象只能检测到是否有碰撞对象//进行射线检测 如果碰撞到对象 返回true //参数一射线 //参数二检测的最大距离 超出这个距离不检测 //参数三检测指定层级不填检测所有层 //参数四是否忽略触发器 UseGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器 不填使用UseGlobal //返回值bool 当碰撞到对象时 返回 true 没有 返回false if (Physics.Raycast(r3, 1000, 1 LayerMask.NameToLayer(Monster), QueryTriggerInteraction.UseGlobal)) { print(碰撞到了对象); }射线检测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)) { // ... }射线检测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)) { // ... }