Unity协程从入门到精通:告别卡顿,用Coroutine优雅处理异步与时序任务 (Day 27)
Langchain系列文章目录
01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
PyTorch系列文章目录
Python系列文章目录
C#系列文章目录
01-C#与游戏开发的初次见面:从零开始的Unity之旅
02-C#入门:从变量与数据类型开始你的游戏开发之旅
03-C#运算符与表达式:从入门到游戏伤害计算实践
04-从零开始学C#:用if-else和switch打造智能游戏逻辑
05-掌握C#循环:for、while、break与continue详解及游戏案例
06-玩转C#函数:参数、返回值与游戏中的攻击逻辑封装
07-Unity游戏开发入门:用C#控制游戏对象移动
08-C#面向对象编程基础:类的定义、属性与字段详解
09-C#封装与访问修饰符:保护数据安全的利器
10-如何用C#继承提升游戏开发效率?Enemy与Boss案例解析
11-C#多态性入门:从零到游戏开发实战
12-C#接口王者之路:从入门到Unity游戏开发实战 (IAttackable案例详解)
13-C#静态成员揭秘:共享数据与方法的利器
14-Unity 面向对象实战:掌握组件化设计与脚本通信,构建玩家敌人交互
15-C#入门 Day15:彻底搞懂数组!从基础到游戏子弹管理实战
16-C# List 从入门到实战:掌握动态数组,轻松管理游戏敌人列表 (含代码示例)
17-C# 字典 (Dictionary) 完全指南:从入门到游戏属性表实战 (Day 17)
18-C#游戏开发【第18天】 | 深入理解队列(Queue)与栈(Stack):从基础到任务队列实战
19-【C# 进阶】深入理解枚举 Flags 属性:游戏开发中多状态组合的利器
20-C#结构体(Struct)深度解析:轻量数据容器与游戏开发应用 (Day 20)
21-Unity数据持久化进阶:告别硬编码,用ScriptableObject优雅管理游戏配置!(Day 21)
22-Unity C# 健壮性编程:告别崩溃!掌握异常处理与调试的 4 大核心技巧 (Day 22)
23-C#代码解耦利器:委托与事件(Delegate & Event)从入门到实践 (Day 23)
24-Unity脚本通信终极指南:从0到1精通UnityEvent与事件解耦(Day 24)
25-精通C# Lambda与LINQ:Unity数据处理效率提升10倍的秘诀! (Day 25)
26-# Unity C#进阶:掌握泛型编程,告别重复代码,编写优雅复用的通用组件!(Day26)
27-Unity协程从入门到精通:告别卡顿,用Coroutine优雅处理异步与时序任务 (Day 27)
文章目录
- Langchain系列文章目录
- PyTorch系列文章目录
- Python系列文章目录
- C#系列文章目录
- 前言
- 一、什么是协程 (Coroutine)?
- 1.1 协程的基本概念
- 1.2 为什么需要协程?
- 二、协程的核心:IEnumerator 与 yield return
- 2.1 IEnumerator 接口
- 2.2 yield return 关键字
- 2.3 编写第一个协程函数
- 三、启动与管理协程
- 3.1 StartCoroutine()
- 3.2 StopCoroutine()
- 3.3 StopAllCoroutines()
- 3.4 注意事项与陷阱
- 四、常见的 yield 指令
- 4.1 `yield return null`
- 4.2 `yield return new WaitForSeconds(float seconds)`
- 4.3 `yield return new WaitForEndOfFrame()`
- 4.4 `yield return StartCoroutine(IEnumerator routine)`
- 4.5 其他 Yield 指令
- 4.6 表格总结常见指令
- 五、协程 vs Update:区别与应用场景
- 5.1 执行频率与控制
- 5.2 性能考量
- 5.3 适用场景对比
- 六、协程实战演练
- 6.1 案例一:延时技能释放
- 6.2 案例二:UI 渐变效果
- 6.3 案例三:按顺序执行的 NPC 对话
- 七、总结
前言
欢迎来到《C# for Unity》学习之旅的第27天!在之前的学习中,我们掌握了C#的基础语法、面向对象编程、数据结构以及Unity的一些核心机制。今天,我们将深入探讨Unity中一个非常重要且强大的特性——协程 (Coroutine)。
在游戏开发中,我们经常需要处理一些并非立即完成的任务,比如:等待几秒后执行某个动作、加载资源时显示进度条、按顺序播放一系列动画或对话、实现淡入淡出效果等。如果直接在Update
函数中处理这些逻辑,可能会导致代码复杂难以维护,甚至因为执行耗时操作而阻塞主线程,造成游戏卡顿。
协程正是Unity为解决这类异步(Asynchronous)和时序(Sequential Timing)任务而提供的优雅方案。它允许我们在不阻塞主线程的情况下,将一个任务挂起,稍后再从挂起点继续执行。本文将带你全面理解协程的工作原理,掌握其核心用法,并通过实战案例,让你能够自如地运用协程优化你的游戏逻辑。
一、什么是协程 (Coroutine)?
1.1 协程的基本概念
从字面上理解,“Co-routine” 意为“协同程序”或“协作式例程”。不同于操作系统层面的线程(抢占式多任务),协程是在应用程序层面实现的协作式多任务。
你可以将协程想象成一个可以暂停和恢复执行的特殊函数。当协程遇到一个需要等待的指令(比如等待一段时间),它会主动交出控制权给Unity主线程,让主线程可以继续处理其他任务(如渲染、物理计算、响应输入等)。当等待条件满足后,Unity会在合适的时机恢复该协程的执行,从上次暂停的地方继续往下运行。
通俗类比: 想象你在厨房做饭(主线程任务)。你需要炖汤(一个耗时操作)。如果采用“阻塞”方式,你会一直守在锅边盯着,其他事情(切菜、洗碗)都干不了,直到汤炖好。而协程的方式则像是:你把汤放上炉子,设定好时间(yield return new WaitForSeconds(30 * 60)
),然后就去做其他事情(切菜、洗碗)。炖汤这个“子任务”暂停了,但厨房(主线程)没有停转。等半小时后闹钟响了(等待条件满足),你再回来处理炖汤的后续步骤(加调料、关火)。
1.2 为什么需要协程?
在Unity中,游戏逻辑主要运行在主线程上。Update
、FixedUpdate
、LateUpdate
等生命周期函数都在主线程中被调用。如果在这些函数中执行了耗时过长的操作(例如:复杂的计算、文件读写、网络请求、或者仅仅是Thread.Sleep()
),就会导致主线程阻塞,游戏画面冻结,用户输入无响应,给玩家带来极差的体验。
协程的引入完美解决了这个问题:
- 处理耗时操作: 对于加载资源、网络通信等可能耗时的操作,可以将其放入协程中,通过
yield
等待异步操作完成,避免阻塞主线程。 - 实现时间相关的序列: 对于需要按特定时间顺序执行的逻辑,如技能延迟释放、分步动画、NPC对话序列等,协程提供了简洁直观的实现方式。
- 简化复杂的Update逻辑: 相比于在
Update
中使用状态变量和计时器来管理复杂序列,协程的代码结构更清晰,更易于理解和维护。
二、协程的核心:IEnumerator 与 yield return
要理解协程的工作机制,必须掌握两个关键概念:IEnumerator
接口和yield return
关键字。
2.1 IEnumerator 接口
在C#中,IEnumerator
是一个接口,通常用于实现迭代器模式,允许你遍历一个集合中的元素。一个实现了IEnumerator
接口的对象,可以被认为是一个“可枚举序列”的“游标”或“状态机”,它知道如何获取序列中的下一个元素,并能判断是否已到达序列末尾。
在Unity协程的上下文中,一个返回IEnumerator
类型的方法,实际上是定义了一个可以被中断和恢复的执行序列。Unity的协程调度器能够理解并管理这个序列的执行过程。
2.2 yield return 关键字
yield return
是C#中用于实现迭代器的关键字。当在一个返回IEnumerator
的方法中使用yield return
语句时,它会执行以下操作:
- 暂停执行: 当前方法的执行会在此处暂停。
- 返回控制权/值: 将
yield return
后面的表达式(称为"yield指令")返回给调用者(在Unity中是协程调度器)。这个返回值告诉调度器何时以及如何恢复协程。 - 保留状态: 方法的当前状态(包括局部变量的值、执行到的位置等)会被保存下来。
- 恢复执行: 当调用者(协程调度器)决定继续执行时,方法会从上次
yield return
语句之后的地方恢复执行,并且之前的状态得以保留。
正是yield return
机制使得协程能够实现“暂停”和“恢复”的核心功能。
2.3 编写第一个协程函数
下面是一个简单的协程示例,它会先打印一条消息,等待1秒,然后打印另一条消息:
using System.Collections;
using UnityEngine;public class SimpleCoroutine : MonoBehaviour
{void Start(){Debug.Log("开始启动协程...");StartCoroutine(MyFirstCoroutine()); // 启动协程Debug.Log("协程已经启动,但这行代码会立即执行,不会等待协程结束。");}// 协程方法:返回类型必须是 IEnumeratorIEnumerator MyFirstCoroutine(){Debug.Log("协程:第一步执行。");// yield return指令:告诉Unity等待1秒yield return new WaitForSeconds(1.0f); // 1秒后,协程从这里恢复执行Debug.Log("协程:等待1秒后,第二步执行。");yield return null; // 再等待一帧Debug.Log("协程:又等待一帧后,第三步执行。协程结束。");}
}
将此脚本挂载到场景中的任意GameObject上,运行游戏,观察控制台输出的顺序和时间间隔。
三、启动与管理协程
定义好协程函数后,你需要使用特定的方法来启动和管理它。
3.1 StartCoroutine()
StartCoroutine()
是MonoBehaviour
类提供的方法,用于启动一个协程。它有几种常用的重载形式:
-
StartCoroutine(IEnumerator routine)
: 这是最常用且推荐的方式。它接受一个IEnumerator
对象(即调用你的协程方法得到的返回值)作为参数。StartCoroutine(MyFirstCoroutine());
这种方式是类型安全的,并且返回一个
Coroutine
对象,可以用于后续停止该协程。 -
StartCoroutine(string methodName)
: 接受一个字符串参数,该字符串是协程方法的名称。StartCoroutine("MyFirstCoroutine");
这种方式不推荐,因为它:
- 不安全: 如果方法名拼写错误,编译器无法检查,只会在运行时报错。
- 性能稍差: Unity内部需要通过反射查找方法。
- 限制: 无法向协程传递参数(除非使用带参数的重载
StartCoroutine(string methodName, object value = null)
,但依然不推荐)。
3.2 StopCoroutine()
同样是MonoBehaviour
的方法,用于停止正在运行的协程。
-
StopCoroutine(Coroutine routine)
: 接受一个Coroutine
对象(由StartCoroutine(IEnumerator routine)
返回)作为参数,精确停止指定的协程实例。Coroutine myCoroutineInstance = StartCoroutine(MyFirstCoroutine()); // ... 稍后在需要的时候 ... if (myCoroutineInstance != null) {StopCoroutine(myCoroutineInstance); }
-
StopCoroutine(IEnumerator routine)
: 接受一个IEnumerator
对象(通常是之前启动协程时传入的对象)。注意: 这只能停止最近一次使用完全相同的IEnumerator
实例启动的协程。如果同一个协程方法被启动了多次,这种方式可能不会按预期工作,因为它比较的是实例引用。 -
StopCoroutine(string methodName)
: 接受协程方法名的字符串。这会停止所有通过该方法名启动的协程。同样不推荐使用。StopCoroutine("MyFirstCoroutine"); // 停止所有名为MyFirstCoroutine的协程
3.3 StopAllCoroutines()
这个方法会停止当前MonoBehaviour
实例上所有正在运行的协程。使用时需要谨慎,因为它会无差别地终止所有协程。
StopAllCoroutines();
3.4 注意事项与陷阱
- 协程依附于 MonoBehaviour: 协程必须由
MonoBehaviour
组件启动,并且其生命周期与该组件关联。如果该组件被禁用(enabled = false
),协程会暂停,启用后会恢复。如果该组件或其所在的GameObject被销毁(Destroy()
),其上所有运行的协程都会自动停止。 - 字符串启动/停止的弊端: 再次强调,尽量避免使用基于字符串的方法,优先使用基于
IEnumerator
或Coroutine
对象的方式。 StopCoroutine
无法停止已完成或未启动的协程: 对一个已经执行完毕或从未启动的协程调用StopCoroutine
不会产生任何效果,也不会报错。- 异常处理: 如果协程内部发生未捕获的异常,该协程会停止执行,并在控制台打印错误,但这通常不会影响其他协程或主线程的运行。
四、常见的 yield 指令
yield return
后面跟的表达式(yield指令)决定了协程何时恢复执行。以下是几种最常用的yield指令:
4.1 yield return null
这是最简单的yield指令。它告诉Unity暂停当前协程的执行,并在下一帧(Update
之后,LateUpdate
之前)恢复执行。这对于需要在每一帧执行一小部分逻辑,或者等待一帧再继续的场景非常有用。
4.2 yield return new WaitForSeconds(float seconds)
这是用于实现延时的最常用指令。它会暂停协程,直到游戏内时间(Time.time
)过去了指定的秒数后才恢复。
- 重要:
WaitForSeconds
受Time.timeScale
的影响。如果Time.timeScale
被设置为0(例如游戏暂停时),那么WaitForSeconds
将永远不会结束等待。如果需要不受timeScale
影响的真实时间等待,可以使用WaitForSecondsRealtime
。
IEnumerator WaitAndDo()
{Debug.Log("开始等待...");yield return new WaitForSeconds(3.0f); // 等待3秒游戏时间Debug.Log("3秒过去了!");
}
4.3 yield return new WaitForEndOfFrame()
这个指令会暂停协程,直到当前帧的所有渲染工作都完成之后(在LateUpdate
之后,下一帧开始之前)才恢复。通常用于需要在所有相机和GUI渲染完毕后执行的操作,比如截图。
4.4 yield return StartCoroutine(IEnumerator routine)
协程可以嵌套!你可以yield return
另一个StartCoroutine
调用。这表示当前协程会暂停,直到被嵌套启动的那个协程执行完毕后,当前协程才会恢复。这对于组织复杂的、按顺序执行的异步任务流非常有用。
IEnumerator OuterCoroutine()
{Debug.Log("外部协程:开始");yield return StartCoroutine(InnerCoroutine()); // 等待内部协程完成Debug.Log("外部协程:内部协程已完成,外部继续");yield return new WaitForSeconds(1f);Debug.Log("外部协程:结束");
}IEnumerator InnerCoroutine()
{Debug.Log(" 内部协程:开始");yield return new WaitForSeconds(2f);Debug.Log(" 内部协程:结束");
}
4.5 其他 Yield 指令
Unity还提供了其他一些有用的yield指令:
yield return new WaitForFixedUpdate()
: 等待下一次FixedUpdate
执行完毕后恢复。yield return new WaitUntil(Func<bool> predicate)
: 暂停协程,直到提供的委托(一个返回bool的方法或Lambda表达式)返回true
时才恢复。yield return new WaitWhile(Func<bool> predicate)
: 暂停协程,只要提供的委托返回true
就一直暂停,直到返回false
时才恢复。yield return new WWW(string url)
/yield return UnityWebRequest.SendWebRequest()
: (旧版/新版)用于等待网络请求完成。
4.6 表格总结常见指令
Yield 指令 | 恢复时机 | 主要用途 | 受 Time.timeScale 影响 |
---|---|---|---|
yield return null | 下一帧 (Update 之后) | 分帧处理、等待一帧 | 否 |
yield return new WaitForSeconds(t) | 游戏时间过去 t 秒后 | 按游戏时间延时 | 是 |
yield return new WaitForSecondsRealtime(t) | 真实时间过去 t 秒后 | 按真实时间延时(如暂停菜单) | 否 |
yield return new WaitForEndOfFrame() | 当前帧所有渲染完成后 | 截图、渲染后处理 | 否 |
yield return new WaitForFixedUpdate() | 下一次 FixedUpdate 结束后 | 同步物理相关的时序逻辑 | 是 |
yield return StartCoroutine(routine) | 嵌套的 routine 协程执行完毕后 | 组织复杂序列任务 | (取决于嵌套协程内容) |
yield return new WaitUntil(() => condition) | condition 变为 true 的那一帧之后 | 等待特定条件满足 | (取决于条件判断) |
yield return new WaitWhile(() => condition) | condition 变为 false 的那一帧之后 | 等待特定条件不再满足 | (取决于条件判断) |
五、协程 vs Update:区别与应用场景
虽然Update
是Unity中最常用的逐帧更新函数,但它和协程在处理某些任务时各有优劣。
5.1 执行频率与控制
Update()
: 每帧自动调用一次。执行频率受帧率影响,开发者对其执行的确切时间点控制较少。- 协程: 执行频率由
yield return
指令决定。可以精确控制暂停多久、等待什么条件,执行时机更灵活。
5.2 性能考量
Update()
: 如果在Update
中放入大量逻辑,特别是包含条件判断和状态管理的复杂序列,每帧都会执行这些检查,可能造成不必要的性能开销。- 协程: 协程在
yield
时基本不消耗CPU资源(除了管理协程本身的少量开销)。它只在需要执行逻辑的时候才被唤醒,对于时间驱动或事件驱动的序列任务通常更高效。然而,频繁启动和停止协程本身也有一定开销,需要权衡。
5.3 适用场景对比
-
何时使用
Update()
:- 需要每帧检查输入(如玩家移动控制)。
- 需要每帧更新与物理无关的位置、旋转等(平滑移动、相机跟随)。
- 简单的、需要持续运行的逻辑。
- 需要与游戏主循环同步的逻辑。
-
何时使用协程 (Coroutine):
- 实现延时操作(技能冷却、定时效果)。
- 执行按顺序发生的任务(动画序列、对话系统、教程引导)。
- 处理耗时操作(资源加载、网络请求)并等待其完成,同时保持主线程流畅。
- 实现需要分步执行的复杂逻辑(如一个多阶段的AI行为)。
- 创建渐变效果(颜色、透明度、音量随时间变化)。
核心思想: Update
适合处理持续性的、每帧都需要响应或检查的状态;协程适合处理阶段性的、有明确起止点或时间间隔的任务序列。
六、协程实战演练
理论讲完了,让我们通过几个具体的例子来实践协程的应用。
6.1 案例一:延时技能释放
假设玩家按下一个键,希望技能在短暂延迟后才生效。
using System.Collections;
using UnityEngine;public class DelayedSkill : MonoBehaviour
{public GameObject skillPrefab; // 技能特效预制体public float delay = 0.5f; // 技能释放延迟时间public Transform castPoint; // 技能释放点void Update(){if (Input.GetKeyDown(KeyCode.Space)) // 按下空格键{StartCoroutine(CastSkillAfterDelay(delay));}}IEnumerator CastSkillAfterDelay(float waitTime){Debug.Log("开始吟唱...");// 等待指定的延迟时间yield return new WaitForSeconds(waitTime); Debug.Log("技能释放!");// 在释放点实例化技能特效if (skillPrefab != null && castPoint != null){Instantiate(skillPrefab, castPoint.position, castPoint.rotation);}else{Debug.LogWarning("技能预制体或释放点未设置!");}}
}
6.2 案例二:UI 渐变效果
实现一个UI元素(如CanvasGroup
控制的面板)在指定时间内淡入或淡出的效果。
using System.Collections;
using UnityEngine;
using UnityEngine.UI; // 如果直接操作Image/Text的alpha,需要引入[RequireComponent(typeof(CanvasGroup))] // 确保对象有CanvasGroup组件
public class UIFader : MonoBehaviour
{private CanvasGroup canvasGroup;void Awake(){canvasGroup = GetComponent<CanvasGroup>();}// 公开方法供外部调用public void FadeIn(float duration){StartCoroutine(FadeCanvasGroup(canvasGroup, 1f, duration)); // 淡入到完全不透明}public void FadeOut(float duration){StartCoroutine(FadeCanvasGroup(canvasGroup, 0f, duration)); // 淡出到完全透明}// 协程:控制CanvasGroup的alpha值随时间变化private IEnumerator FadeCanvasGroup(CanvasGroup cg, float targetAlpha, float duration){float startAlpha = cg.alpha; // 当前alpha值float time = 0f; // 计时器while (time < duration){// 计算当前时间比例下的alpha值// 使用 Mathf.Lerp 进行线性插值cg.alpha = Mathf.Lerp(startAlpha, targetAlpha, time / duration);// 累加时间(使用Time.deltaTime确保平滑过渡,与帧率无关)time += Time.deltaTime;// 等待下一帧,然后继续循环yield return null; }// 循环结束后,确保alpha精确达到目标值cg.alpha = targetAlpha; Debug.Log("渐变完成!");}
}
使用方法: 将此脚本挂载到含有CanvasGroup
组件的UI面板上。在其他脚本中获取UIFader
组件引用,然后调用FadeIn(1.0f)
或FadeOut(0.5f)
即可实现1秒淡入或0.5秒淡出。
6.3 案例三:按顺序执行的 NPC 对话
模拟NPC按顺序说出几句话,每句话之间有短暂的停顿。
using System.Collections;
using UnityEngine;
using UnityEngine.UI; // 需要操作UI Textpublic class NPCDialogue : MonoBehaviour
{public Text dialogueTextUI; // 用于显示对话的UI Text组件public string[] dialogueLines; // NPC要说的所有话public float delayBetweenLines = 2.0f; // 每句话之间的停顿时间private Coroutine currentDialogueCoroutine = null;// 可以由触发器或其他事件调用此方法开始对话public void StartDialogue(){// 如果当前有对话正在进行,先停止旧的if (currentDialogueCoroutine != null){StopCoroutine(currentDialogueCoroutine);}// 启动新的对话协程currentDialogueCoroutine = StartCoroutine(ShowDialogue());}IEnumerator ShowDialogue(){if (dialogueTextUI == null || dialogueLines == null || dialogueLines.Length == 0){Debug.LogError("对话UI或对话内容未设置!");yield break; // 协程提前退出}dialogueTextUI.gameObject.SetActive(true); // 确保对话框是可见的// 遍历所有对话行for (int i = 0; i < dialogueLines.Length; i++){dialogueTextUI.text = dialogueLines[i]; // 显示当前行对话Debug.Log($"NPC says: {dialogueLines[i]}");// 等待指定时间(除非是最后一句)if (i < dialogueLines.Length - 1) {yield return new WaitForSeconds(delayBetweenLines);}}// 对话结束后可以做些事情,比如等待一小会后隐藏对话框yield return new WaitForSeconds(1.0f); dialogueTextUI.gameObject.SetActive(false); // 隐藏对话框Debug.Log("对话结束。");currentDialogueCoroutine = null; // 重置协程引用}
}
使用方法: 将此脚本挂载到NPC对象或对话管理器上,设置好UI Text组件和对话内容数组。当需要NPC开始说话时,调用StartDialogue()
方法。
七、总结
恭喜你完成了第27天的学习!今天我们深入探讨了Unity中的协程(Coroutine),它是处理异步和时序任务的强大武器。以下是本文的核心要点总结:
- 协程是什么: 是一种在应用程序层面实现的协作式多任务机制,允许函数在特定点暂停执行,稍后恢复,而不会阻塞主线程。
- 为何使用协程: 用于优雅地处理耗时操作(如加载、网络)、实现时间相关的序列(延时、动画、对话)以及简化复杂的
Update
逻辑。 - 核心机制: 协程函数返回
IEnumerator
接口,使用yield return
关键字暂停执行并返回一个yield指令,告知Unity何时恢复。 - 启动与管理: 使用
MonoBehaviour
的StartCoroutine()
启动协程(推荐使用IEnumerator
参数版本),使用StopCoroutine()
(推荐使用Coroutine
对象参数版本)或StopAllCoroutines()
来停止。协程生命周期与启动它的MonoBehaviour
绑定。 - 常用Yield指令:
null
(下一帧)、new WaitForSeconds(t)
(游戏时间延时)、new WaitForEndOfFrame()
(帧渲染后)、StartCoroutine()
(嵌套等待)等,各自适用于不同场景。 - 协程 vs Update:
Update
适合持续性、每帧更新的逻辑;协程适合阶段性、有时间间隔或顺序要求的任务。协程在处理特定序列任务时通常更高效、代码更清晰。 - 实战应用: 协程广泛应用于技能延迟、UI渐变、NPC对话序列、加载流程控制等多种游戏开发场景。
熟练掌握协程将极大提升你编写复杂游戏逻辑的能力和代码质量。在后续的开发中,当你遇到需要等待、按顺序执行或避免阻塞主线程的情况时,记得首先考虑使用协程!
继续努力,下一天我们将学习Unity的核心机制实践:输入、物理与碰撞!
相关文章:
Unity协程从入门到精通:告别卡顿,用Coroutine优雅处理异步与时序任务 (Day 27)
Langchain系列文章目录 01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南 02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖 03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南 04-玩转 LangChai…...
Web攻防—SSRF服务端请求伪造Gopher伪协议无回显利用
前言 重学Top10的第二篇,希望各位大佬不要见笑。 SSRF原理 SSRF又叫服务端请求伪造,是一种由服务端发起的恶意请求,SSRF发生在应用程序允许攻击者诱使服务器向任意域或资源发送未经授权的请求时。服务器充当代理,执行攻击者构造…...
青少年编程与数学 02-016 Python数据结构与算法 16课题、贪心算法
青少年编程与数学 02-016 Python数据结构与算法 16课题、贪心算法 一、贪心算法的基本概念定义组成部分 二、贪心算法的工作原理三、贪心算法的优点四、贪心算法的缺点五、贪心算法的应用实例(一)找零问题(二)活动安排问题&#x…...
Linux系统Debian最新版12.10安装详细教程,虚拟机系统安装(附系统镜像资源)
前言 Debian是一款以稳定性著称的免费开源Linux发行版,支持多种硬件架构,遵循自由软件原则,并提供庞大的软件仓库和长期维护。 一、环境下载 Debian 12.10镜像下载 💾资源下载👉Debian 12.10镜像下载:ht…...
android display 笔记(十一)surfaceflinger 如何将图层传到lcd驱动的呢?
SurfaceFlinger->>HWC: 提交所有图层(Layer) HWC->>DRM/KMS: 硬件合成(Overlay)或 GPU 合成 DRM/KMS->>LCD Driver: 配置显示控制器(CRTC/Encoder) LCD Driver->>Display: 通过 MI…...
管理大规模监控技术栈的最佳实践
集中管理可观测性数据 集中化监控数据有助于打破信息孤岛,提供系统全景视图。彭博社发现,当团队各自为战时,系统中断往往持续很久才有人意识到多个团队正在独立处理同一问题。通过数据集中管理,他们获得了更全面的基础设施视图&a…...
深入探索C++ STL:从基础到进阶
目录 引言 一、什么是STL 二、STL的版本 三、STL的六大组件 容器(Container) 算法(Algorithm) 迭代器(Iterator) 仿函数(Functor) 空间配置器(Allocator…...
GitHub Desktop 推送报错 Authentication Failed 身份验证失败
弹窗问题: Authentication Failed 验证失败 We were unable to authenticate with https://gitee.com/.Pleaseenter your username and password to try again. 用户名 密码 Depending on your repository’s hosting service, you might need touse a Personal Acc…...
Webpack中的文件指纹:给资源戴上个“名牌”
Webpack中的文件指纹:给资源戴上个“名牌” 你是否想过,当你修改代码后,浏览器为什么仍然拿着旧版资源不放?秘密就在于——文件指纹!简单来说,文件指纹就像给每个构建出来的文件贴上独一无二的“姓名牌”&…...
ESP32开发之ubuntu环境搭建
1. 在Ubuntu官网下载Ubuntu server 20.04版本https://releases.ubuntu.com/20.04.6/ 2. 在vmware下安装Ubuntu 3. 改Ubuntu静态IP $ sudo vi /etc/netplan/00-installer-config.yaml# This is the network config written by ‘subiquity’ network: renderer: networkd eth…...
重构艺术 | 如何优雅地“提炼函数“
在工作中总数遇到非常多的长代码,俗称“屎山”,这类代码读起来特别费劲。自己想重构一遍,但是总感觉缺乏经验指导,因此,多读书,读好书可能是最优解之一。读《重构改善即有代码的设计》有感,便写…...
[项目]基于RT-Thread的CAN通信仪表盘显示屏: 一.项目概述与软硬件说明
基于RT-Thread的CAN通信仪表盘显示屏: 一.项目概述与软硬件说明 一.项目概述二.硬件与软件资源 一.项目概述 功能结构图: 通过上位机发出模拟CAN数据给协议转换板,协议转换板将CAN协议数据转换为迪文屏数据,并通过迪文数据控制相关性质。 …...
如何查看自己 Android App 的私有数据?从 `adb backup` 到数据提取全过程
🛠️ 如何查看自己 Android App 的私有数据?从 adb backup 到数据提取全过程 📌 前言:作为一名 Android 开发者,我常常想知道自己写的 App 在用户设备上的数据存储结构是怎样的,比如有没有数据写入成功、有…...
Windows中xxx.dll动态链接库文件转xxx.a静态库文件
最近在学习探索C/C程序代码中调用Python代码时,出现了一个问题:下载的程序库文件,在使用MinGW编译C/C的代码时,一直提示无法链接,才发现是库类型不对应,无法导入链接。 上图所示的Python对应库,…...
用Java NIO模拟HTTPS
HTTPS流程 名词解释: R1:随机数1 R2:随机数2 R3:随机数3 publicKey:公钥 privateKey:私钥 step1 客户端 client hello R1 服务端 server hello R2 publicKey(验证证书,证书包含公钥) step2 客户端 R3 publicKey加密 服务端 privateKey解密 s…...
基于YOLOv8的火车轨道检测识别系统:技术实现与应用前景
✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ 1. 引言:火车轨道检测领域概述 铁路运输作为国民经济的大动脉,其安全运行至关重要…...
解决 Ubuntu 上 Docker 安装与网络问题:从禁用 IPv6 到配置代理
解决 Ubuntu 上 Docker 安装与网络问题的实践笔记 在 Ubuntu(Noble 版本)上安装 Docker 时,我遇到了两个常见的网络问题:apt-get update 失败和无法拉取 Docker 镜像。通过逐步排查和配置,最终成功运行 docker run he…...
DVDFab Virtual Drive电脑DVD备份和制作软件 v13.0.3.7 中文绿色便携
前言 DVDFab Virtual Drive是一个很厉害的光盘处理软件,它能帮你做几件事情:复制你的DVD或蓝光光盘作为备份,把这些光盘里的内容转换成其他格式,还能把视频文件刻录到DVD或蓝光光盘上。这个软件很灵活,能处理像DVD、蓝…...
FISCO BCOS区块链Postman接口测试:高级应用与实战技巧 [特殊字符]
引言:为什么Postman是FISCO BCOS测试的利器? 在区块链开发领域,接口测试是确保系统稳定性和安全性的关键环节。作为国产领先的联盟链平台,FISCO BCOS在金融、政务、供应链等多个领域得到广泛应用。而Postman作为一款功能强大的API测试工具,凭借其直观的图形界面和丰富的测…...
a sort.py demo
这份代码展示了如何使用 sort.py。注意,此处,我将文件名改为 my_sort.py。 你并不能直接 copy 使用,因为环境,包,还有模型。 此处使用 SSD-MobileNetv2 进行物体检测,将结果传入以 np 数组的形式传入sort…...
Linux 入门八:Linux 多进程
一、概述 1.1 什么是进程? 在 Linux 系统中,进程是程序的一次动态执行过程。程序是静态的可执行文件,而进程是程序运行时的实例,系统会为其分配内存、CPU 时间片等资源。例如,输入 ls 命令时,系统创建进程…...
学习如何设计大规模系统,为系统设计面试做准备!
前言 在当今快速发展的技术时代,系统设计能力已成为衡量一名软件工程师专业素养的重要标尺。随着云计算、大数据、人工智能等领域的兴起,构建高性能、可扩展且稳定的系统已成为企业成功的关键。然而,对于许多工程师而言,如何有效…...
前端防御性编程
关于防御性编程 你是否遇到过,接口请求失败或者返回数据错误,导致系统白屏或者前端自身写的代码存在一些缺陷,导致整个系统不够健壮,从而导致系统白屏 常见的问题与防范 最常见的问题 访问了null或者undefined的属性 null.a …...
Java工具类-assert断言
我们可能经常在项目的单元测试或者一些源码中看到别人在使用assert关键字,当然也不只是Java语言,很多编程语言也都能看到,我们大概知道断言可以用于测试中条件的校验,但却不经常使用,本文总结了Java中该工具类的使用。…...
215. 数组中的第K个最大元素
1、题目分析 顾名思义。 2.算法原理 利用排序,再找到第k个最大的数字即可。 3.代码实操 class Solution { public:int findKthLargest(vector<int>& nums, int k) {sort(nums.begin(),nums.end());return nums[nums.size()-k];//123456真的方便啊} };…...
【2025年泰迪杯数据挖掘挑战赛】B题 详细解题思路+数据预处理+代码分享
目录 2025年泰迪杯B题详细解题思路问题一问题分析数学模型Python代码Matlab代码 问题二问题分析数学模型Python代码Matlab代码 问题三问题分析数学模型Python代码Matlab代码 问题四问题分析数学模型Python代码Matlab代码 2025年泰迪杯B题详细解题思路 初步分析整理了B题的赛题分…...
对shell脚本敏感命令进行加密执行
我要加密这条命令:rm /root/scripty.sh 如何利用openssl aes-256-cbc 实现加密和解密,并执行命令 加密、解密并执行命令的完整流程 以下是使用 openssl aes-256-cbc 加密命令 rm /root/scripty.sh,解密并执行的详细步骤: 1. 加密…...
SQL ⑦-索引
索引 索引是一种特殊的数据结构,它帮助数据库系统高效地找到数据。 索引通过一定的规则排列数据表中的记录,使得对表的查询可以通过对索引的搜索来加快速度。 索引好比书籍的目录,能帮助你快速找到相应的章节。 B树 B树是一种经常用于数…...
LinkedBlockingQueue使用场景有哪些
1、LinkedBlockingQueue 的特点 LinkedBlockingQueue 是 Java 中 java.util.concurrent 包下的一种阻塞队列,它有以下几个主要特点: 1.线程安全 LinkedBlockingQueue 是线程安全的,它内部使用了锁机制来确保多线程环境下的并发访问不会导致…...
关于难例损失函数小记
什么是难例损失函数(Hard Example Loss Function) 这玩意儿是深度学习训练中非常重要又很实用的一个概念,特别适用于处理 数据不平衡、模型收敛缓慢、或者**想让模型更“挑剔”**的场景。 🌟 先从名字讲起: “难例”…...
小程序开发指南
小程序开发指南 目录 1. 小程序开发概述 1.1 什么是小程序1.2 小程序的优势1.3 小程序的发展历程 2. 开发准备工作 2.1 选择开发平台2.2 开发环境搭建2.3 开发模式选择 3. 小程序开发流程 3.1 项目规划3.2 界面设计3.3 代码开发3.4 基本开发示例3.5 数据存储3.6 网络请求3.7 …...
RCE漏洞学习
1,What is RCE? 在CTF(Capture The Flag)竞赛中,RCE漏洞指的是远程代码执行漏洞(Remote Code Execution)。这类漏洞允许攻击者通过某种方式在目标系统上执行任意代码,从而完全控制目…...
青少年编程考试 CCF GESP图形化编程 三级认证真题 2025年3月
图形化编程 三级 2025 年 03 月 一、单选题(共 15 题,每题 2 分,共 30 分) 1、2025 年春节有两件轰动全球的事件,一个是 DeepSeek 横空出世,另一个是贺岁 片《哪吒 2》票房惊人,入了全球票房榜…...
一、绪论(Introduction of Artificial Intelligence)
写在前面: 老师比较看重的点:对问题的概念本质的理解,不会考试一堆运算的东西,只需要将概念理解清楚就可以,最后一个题会出一个综合题,看潜力,前面的部分考的不是很深,不是很难&…...
多模态大语言模型arxiv论文略读(十五)
## Jailbreaking GPT-4V via Self-Adversarial Attacks with System Prompts ➡️ 论文标题:Jailbreaking GPT-4V via Self-Adversarial Attacks with System Prompts ➡️ 论文作者:Yuanwei Wu, Xiang Li, Yixin Liu, Pan Zhou, Lichao Sun ➡️ 研究机…...
漏洞报告:多短视频平台时间差举报滥用漏洞
漏洞标题:跨平台内容发布时序漏洞导致的恶意举报攻击向量 漏洞类型:逻辑缺陷/滥用机制 漏洞等级:中高风险 漏洞描述: 攻击者可利用多平台内容发布时间差,伪造原创证明对合法内容发起恶意举报。该漏洞源于平台间缺乏发…...
【LINUX】学习宝典
一.Linux系统常用单词翻译 1.new folder 新建文件夹 2.paste 粘贴 3.select all 全选 4.open in terminal 打开终端/命令行 5.keep aligned 保持对齐 6.organize deaktop by name按名称组织桌面 7.change background更改背景 8.cancel 取消 9.create创造 创建 10.wal…...
青少年编程考试 CCF GESP图形化编程 四级认证真题 2025年3月
图形化编程 四级 2025 年 03 月 一、单选题(共 10 题,每题 2 分,共 30 分) 1、2025 年春节有两件轰动全球的事件,一个是 DeepSeek 横空出世,另一个是贺岁片《哪吒 2》票房惊人,入了全球票房榜…...
学习海康VisionMaster之平行线查找
一:进一步学习了 今天学习下VisionMaster中的平行线查找,这个还是拟合直线的衍生应用,可以同时测量两条线段,输出中线 二:开始学习 1:什么是平行线查找? 按照传统的算法,必须是开两…...
小甲鱼第004讲:变量和字符串(下)| 课后测试题及答案
问答题: 0. 请问下面代码有没有毛病,为什么? 请问下面代码为什么会出错,应该如何解决? 答:这是由于在字符串中,反斜杠()会与其随后的字符共同构成转义字符。 为了避免这种不测情况的发生,我们可以在字符串的引号前面…...
2025 蓝桥杯省赛c++B组个人题解
声明 本题解为退役蒻苟所写,不保证正确性,仅供参考。 花了大概2个半小时写完,感觉比去年省赛简单,难度大概等价于 codeforces dv4.5 吧 菜鸡不熟悉树上背包,调了一个多小时 题目旁边的是 cf 预测分 所有代码均以通…...
2025蓝桥杯算法竞赛深度突破:创新题型与高阶策略全解析
一、新型算法范式实战 1.1 元启发式算法应用(预测难度:★★★★) 题目场景:星际货物装载 需在飞船载重限制下选择最优货物组合,引入遗传算法解决NP-Hard问题: 染色体编码:二进制串表示货物选择…...
网络流量管理-流(Flow)
1. 传统网络的问题:快递员送信模式 想象你每天要寄100封信给同一个朋友,传统网络的处理方式就像一个固执的快递员: 每封信都单独处理:检查地址、规划路线、盖章、装车…即使所有信的目的地、收件人都相同,也要重复100…...
Spring Boot对接马来西亚股票数据源API
随着对东南亚市场的兴趣日益增长,获取马来西亚股票市场的实时和历史数据变得尤为重要。本文将指导您如何使用Spring Boot框架对接一个假定的马来西亚股票数据源API(例如,StockTV API),以便开发者能够轻松访问和处理这些…...
MySQL 面经
1、什么是 MySQL? MySQL 是一个开源的关系型数据库,现在隶属于 Oracle 公司。是我们国内使用频率最高的一种数据库,我本地安装的是比较新的 8.0 版本。 1.1 怎么删除/创建一张表? 可以使用 DROP TABLE 来删除表,使用…...
【Flink运行时架构】作业提交流程
本文介绍在单作业模式下Flink提交作业的具体流程,如下图所示。 客户端将作业提交给YARN的RM;YARN的RM启动Flink JobManager,并将作业提交给JobMaster;JobMaster向Flink内置的RM请求slots;Flink内置的RM向YARN RM请求…...
【AutoTest】自动化测试工具大全(Java)
😊 如果您觉得这篇文章有用 ✔️ 的话,请给博主一个一键三连 🚀🚀🚀 吧 (点赞 🧡、关注 💛、收藏 💚)!!!您的支持 &#x…...
当DRAM邂逅SSD:新型“DRAM+”存储技术来了!
在当今快速发展的科技领域,数据存储的需求日益增长,对存储设备的性能和可靠性提出了更高的要求。传统DRAM以其高速度著称,但其易失性限制了应用范围;而固态硬盘SSD虽然提供非易失性存储,但在速度上远不及DRAM。 为了解…...
【算法】快速排序
算法系列六:快速排序 一、快速排序的递归探寻 1.思路 2.书写 3.搭建 3.1设计过掉不符情况(在最底层时) 3.2查验能实现基础结果(在最底层往上点时) 3.3跳转结果继续往上回搭 4.实质 二、快速排序里的基准排序 …...
Python快速入门指南:从零开始掌握Python编程
文章目录 前言一、Python环境搭建🥏1.1 安装Python1.2 验证安装1.3 选择开发工具 二、Python基础语法📖2.1 第一个Python程序2.2 变量与数据类型2.3 基本运算 三、Python流程控制🌈3.1 条件语句3.2 循环结构 四、Python数据结构🎋…...