Unity协程学习心得

前言

个人总结的一些Unity协程学习心得,如有不对请在评论区指出一起学习!感谢。

在Unity编程中谈到异步逻辑,可以考虑使用协程来实现。协程(Coroutine)在Unity中的主要作用就是把一个任务暂停(挂起),并在下一帧(或之后的某一时间点)继续。常见案例有网络请求、临时计时器、资源加载、打字机特效等。需要注意的是,协程不是线程,协程是在主线程中执行的。使用协程可以不用考虑同步和锁的问题。

1、C#迭代器

官方文档:遍历集合 - C# | Microsoft Learn

在了解并使用协程之前,我们可以先扩展一下关于C#迭代器的知识,方便我们理解什么是yield

迭代器方法或 get 访问器可对集合执行自定义迭代。 迭代器方法使用 yield return 语句返回元素,每次返回一个。 到达 yield return 语句时,会记住当前在代码中的位置。 下次调用迭代器函数时,将从该位置重新开始执行。迭代器方法或 get 访问器的返回类型可以是 IEnumerableIEnumerable<T>IEnumerator 或 IEnumerator<T>

在C#中,yield可以粗浅的理解为“产出”。用于实现迭代器,逐步产出序列中的元素

案例:写一个返回数字的迭代器方法

void Start()
{
    var numbers = GetNumbers();
    while (numbers.MoveNext())
    {
        Debug.Log($"t_Coroutine {numbers.Current}");
    }
}

IEnumerator GetNumbers()
{
    yield return 0;
    yield return 1;
    yield return 200;
}

 输出:

但是在Unity中,我们只用到了IEnumerator来创建协程方法。因此,我着重介绍一下IEnumerator接口。

1.1 IEnumerator

public interface IEnumerator
{
    object Current
    {
        get;
    }

    bool MoveNext();

    void Reset();
}

可以看到其实迭代器内部很简单。

Current是当前迭代器指向的对象;MoveNext()是移动指针指向下一个对象,如果是最后一个对象则返回false;Reset()是重置为最初状态。

2、协程基本语法

2.1 写一个简单的协程

协程开始时,会顺序执行yield return null;之前的代码,遇到yield return 会被挂起(暂停),等待指定时间后,从被挂起的地方继续往下执行。

using System.Collections;
using UnityEngine;

public class CoroutineExample : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(MyCoroutine());
    }

    IEnumerator MyCoroutine()
    {
        // 开启协程就会执行的逻辑
        Debug.Log("t_Coroutine Start Coroutine");
        yield return null;  //在这里被暂停,暂停1帧
        //挂起并恢复之后的逻辑
        Debug.Log("t_Coroutine Wait One Frame After");
        yield return new WaitForSeconds(2); //在这里被暂停,暂停2秒
        Debug.Log("t_Coroutine Wait Two Seconds After");
        //协程结束
    }
}

输出:

 

2.2 yield return的不同用法

2.2.1 yield return null 与 yield return 1

在Unity协程中,yield return null; 和 yield return 1; 实际上是等效的,二者都会暂停协程的执行并在下一帧继续执行。即等待一帧

之所以等效,是因为yield return会返回一个值给Unity的协程调度器,这个值用于决定协程何时继续。null和任意整数(例如1)都会导致协程在下一帧恢复执行。

2.2.2 yield break

立即停止协程

案例:可以看到Break After并没有被打印出来,在此之前协程就停止了

using System.Collections;
using UnityEngine;

public class CoroutineExample : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(MyCoroutineBreak());
    }

    IEnumerator MyCoroutineBreak()
    {
        Debug.Log("t_Coroutine Start MyCoroutineBreak");
        yield return null;  //在这里被暂停,暂停1帧
        Debug.Log("t_Coroutine Wait Two Seconds After");
        yield break;//协程结束
        Debug.Log("t_Coroutine Break After");
    }
}

输出: 

2.2.3 yield return StartCoroutine(OtherCoroutine())

协程是可以嵌套的,可以使用yield return StartCoroutine(OtherCoroutine()),等待OtherCoroutine协程方法执行完毕后,再继续往下执行

案例:

void Start()
{
    Debug.Log("t_Coroutine Start1");
    StartCoroutine(TestCoroutine());
    Debug.Log("t_Coroutine Start2");
}

IEnumerator TestCoroutine()
{
    Debug.Log("t_Coroutine test1");
    yield return StartCoroutine(LoadCoroutine());
    Debug.Log("t_Coroutine test2");
}

IEnumerator LoadCoroutine()
{
    Debug.Log("t_Coroutine load 1");
    yield return null;
    Debug.Log("t_Coroutine load 2");
}

输出:

 

 2.2.4 其他用法

yield return new WaitForEndOfFrame();//等待帧结束

yield return new WaitForSeconds(1f);//等待1秒;

yield return WebRequest();//等待到网络请求结束返回结果的时候;

yield return new WaitUntil()//等待到结果返回为true的时候

yield return new WaitWhile()//等待到结果返回为false的时候

2.3 开启 / 停止协程

2.3.1 直接开启一个协程

无法控制暂停,直接调用协程方法开启

StartCoroutine(MyCoroutine());

2.3.2 用变量控制协程

private Coroutine myCoroutine;
myCoroutine = StartCoroutine(MyCoroutine());//开启协程
StopCoroutine(myCoroutine);//停止协程

2.3.3 用协程方法名控制协程

StartCoroutine("MyCoroutine");//开启协程
StopCoroutine("MyCoroutine");//停止协程

2.3.4 停止所有正在运行的协程

会停止该对象上所有正在运行的协程,慎用!

当一个游戏对象(GameObject)把Active设置为false,即SetActive(false),也会停止该对象上附加的协程。

调用Destroy(GameObject)时,Unity会调用OnDisable(),处理并停止协程。在这一帧结束时,会调用OnDestroy()。

StopAllCoroutines();

3、学习参考博客

官方文档:Unity - Manual: Coroutines

通俗易懂博客(建议先看这个,很好理解):Unity C#中协程/携程Coroutine简单易懂详解-优快云博客

更进一步理解:Unity StartCoroutine - 简书

<think>首先,用户的问题是:“我想在Unity中使用协程时如何处理返回值 请问Unity coroutine return value handling”。这涉及到Unity中的协程(coroutine)如何返回和处理值。 回顾系统级指令: - 所有行内数学表达式用$...$格式。 - 独立公式用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识。 - 回答结构清晰,帮助用户逐步解决问题。 - 参考提供的引用,但用户提供的引用是关于协程的,可能相关。 用户提供的引用: - [^1]: 关于Unity协程的理解,包括yield和IEnumerator。 - [^2]: 等待异步操作结果的技术,包括协程、事件处理、任务处理。 - [^3]: 关于C#中的null条件操作符和事件简化的例子。 这些引用与协程处理返回值相关,特别是[^2]提到了协程和IEnumerator用于异步操作。 关键点:Unity协程使用IEnumerator接口,通过yield return语句暂停执行。协程本身不能直接返回值,因为它是一个迭代器方法。但可以通过回调、事件、或使用Unity特定的方式来处理结果。 如何返回和处理值: 1. **使用回调或事件**:在协程结束时调用一个回调函数来传递值。 2. **使用Coroutine的返回值**:协程返回一个Coroutine对象,但这不是返回值;值需要通过其他方式传递。 3. **使用UnityYieldInstruction**:如WaitForSeconds,但不直接处理返回值。 4. **使用C#特性**:如Action或delegate来传递值。 5. **使用Task或async/await**:在Unity中,可以使用.NET 4.x或更高版本支持async/await,但协程Unity特有的机制。 从引用[^2]:它提到了使用协程和IEnumerator循环来处理异步操作的结果。 结构化回答: - **逐步解决问题**: 1. 解释协程的基本概念。 2. 说明协程为什么不能直接返回值。 3. 提供处理返回值的方法。 4. 给出代码示例。 5. 添加引用标识。 6. 生成相关问题。 使用中文回答。 代码示例:使用Unity协程处理返回值。例如,通过一个回调函数。 生成相关问题:在回答末尾添加相关问题。 引用标识:在引用的段落末尾添加[^1], [^2], [^3]。我需要自然地融入引用。 用户提供的引用内容: - [^1]: 关于协程的理解。 - [^2]: 处理异步操作的技术。 - [^3]: C#语法示例。 在回答中引用这些: - 当解释协程时,引用[^1]。 - 当讨论处理返回值时,引用[^2]。 - 当使用C#特性时,引用[^3]。 回答结构: 1. **引言**:解释问题。 2. **方法1:使用回调**:在协程结束时调用回调函数。 3. **方法2:使用事件**:定义事件在协程完成时触发。 4. **方法3:使用C#的Action或delegate**。 5. **方法4:使用Unity的Coroutine与Yield**:通过yield return来传递值,但协程本身不返回值。 6. **代码示例**。 7. **总结**。 8. **相关问题**。 确保行内数学表达式:可能没有数学表达式,但如果有,比如在解释算法时,但这里可能不需要。 独立公式:如果有公式,单独成段。 在Unity上下文中,可能不需要数学公式。 最终,生成相关问题。</think>### Unity协程返回值处理指南 在Unity中,协程(coroutine)是一种强大的异步编程工具,它基于C#的`IEnumerator`接口和`yield`语句实现。协程本身不能直接返回值,因为它是一个迭代器方法,返回类型必须是`IEnumerator`或`IEnumerator`。然而,我们可以通过多种间接方式处理返回值,例如使用回调函数、事件系统或C#委托。下面我将一步步解释这些方法,并提供代码示例,确保解决方案清晰可靠。参考Unity文档和最佳实践[^1][^2],以下方法适用于Unity 2018及以上版本。 #### 步骤1: 理解协程的局限性 - **为什么协程不能直接返回值?** 协程使用`yield return`暂停执行,并在下一帧恢复。其返回类型是`IEnumerator`,只能用于控制执行流程,不能像普通方法那样返回具体值(如整数或字符串)。例如,定义一个协程: ```csharp IEnumerator MyCoroutine() { yield return new WaitForSeconds(1); // 这里无法直接返回值 } ``` 要获取协程内部的计算结果,需要借助外部机制传递值[^1]。 #### 步骤2: 处理方法1——使用回调函数 回调函数是最简单的方式。在协程结束时,调用一个传入的委托(如`Action`)来传递值。 - **优点**:简单直接,适合小型项目。 - **缺点**:可能造成回调嵌套,降低代码可读性。 ```csharp using UnityEngine; using System.Collections; public class CoroutineHandler : MonoBehaviour { void Start() { StartCoroutine(RunCoroutineWithCallback(result => { Debug.Log("返回值: " + result); // 处理返回值 })); } IEnumerator RunCoroutineWithCallback(System.Action<int> callback) { yield return new WaitForSeconds(1); // 模拟耗时操作 int result = 42; // 计算返回值 callback?.Invoke(result); // 通过回调传递值 } } ``` 在这个示例中,`RunCoroutineWithCallback`协程接收一个`Action<int>`回调参数。协程完成后,调用`callback.Invoke(result)`将值传递给外部。注意使用`?.`操作符避免空引用错误,这参考了C#的null条件操作符最佳实践[^3]。 #### 步骤3: 处理方法2——使用事件系统 事件系统适用于需要多个监听器的场景。定义一个事件,在协程完成时触发它。 - **优点**:解耦协程和调用者,支持多个订阅者。 - **缺点**:事件管理稍复杂,可能引入内存泄漏(需注意取消订阅)。 ```csharp using UnityEngine; using System.Collections; public class CoroutineHandler : MonoBehaviour { public delegate void CoroutineCompletedHandler(int result); public event CoroutineCompletedHandler OnCoroutineCompleted; void Start() { OnCoroutineCompleted += HandleResult; // 订阅事件 StartCoroutine(RunCoroutineWithEvent()); } IEnumerator RunCoroutineWithEvent() { yield return new WaitForSeconds(1); int result = 42; OnCoroutineCompleted?.Invoke(result); // 触发事件传递值 } void HandleResult(int result) { Debug.Log("事件返回值: " + result); } void OnDestroy() { OnCoroutineCompleted -= HandleResult; // 清理订阅 } } ``` 这里,`OnCoroutineCompleted`事件在协程结束时触发,`HandleResult`方法处理返回值。事件处理机制参考了Unity异步操作的最佳实践[^2]。 #### 步骤4: 处理方法3——使用Unity的Coroutine返回值结合Yield 虽然协程不能返回值,但可以通过`yield return`暂停并传递中间状态,结合外部变量存储结果。 - **优点**:无需额外委托,适合简单场景。 - **缺点**:返回值存储在外部变量中,可能引发线程安全问题。 ```csharp using UnityEngine; using System.Collections; public class CoroutineHandler : MonoBehaviour { private int _result; // 外部变量存储返回值 void Start() { StartCoroutine(RunCoroutine()); // 注意:协程是异步的,不能立即获取_result,需等待完成 } IEnumerator RunCoroutine() { yield return new WaitForSeconds(1); _result = 42; // 计算并存储值 Debug.Log("返回值: " + _result); // 直接使用值 } } ``` 在此方法中,返回值保存在`_result`字段中。但需注意协程的异步性:调用者不能立即访问`_result`,需通过其他机制(如标志位)确认完成[^1]。 #### 步骤5: 高级方法——结合async/await(Unity 2019+) 如果项目使用.NET 4.x或更高版本,可以整合C#的`async/await`与协程,实现更直观的返回值处理。 - **优点**:语法简洁,类似同步代码。 - **缺点**:需要Unity版本支持,不适合旧项目。 ```csharp using UnityEngine; using System.Threading.Tasks; public class AsyncHandler : MonoBehaviour { async void Start() { int result = await RunAsyncCoroutine(); Debug.Log("async返回值: " + result); } async Task<int> RunAsyncCoroutine() { await Task.Delay(1000); // 模拟耗时操作 return 42; // 直接返回值 } } ``` 这里,`RunAsyncCoroutine`方法返回`Task<int>`,允许直接通过`await`获取值。这结合了协程的异步优势和C#的现代特性[^2]。 #### 总结 - **推荐方法**:小型项目用回调,大型项目用事件或async/await。 - **关键点**:协程通过`IEnumerator`控制流程,返回值需间接传递;优先使用null安全操作符(如`?.`)避免错误[^3]。 - **性能注意**:避免在频繁调用的协程中处理返回值,以防性能开销。测试时使用Unity Profiler监控。 通过以上方法,您可以高效处理Unity协程的返回值。如果需要更深入讨论,请参考Unity官方文档[^1][^2]。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TDSSS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值