Unity协程深入及实现

article/2025/11/1 5:26:55

Unity协程深入

文章目录

  • Unity协程深入
    • Unity协程简介
    • Unity协程的用法
    • 协程的原理
    • 协程的缺陷
    • 自己实现协程
    • 参考

Unity协程简介

Unity中的协程能够允许我们将一个任务分散到多个帧中。注意:协程(协同执行)和多线程有本质区别。 协程不是线程,协程执行过程中仍然是在主线程中执行。协程实际上是Unity利用 C# 中的yield关键字来帮忙提供的一套模拟多线程的机制。

Unity协程的用法

首先来看下一个简单的协程示例, 下面代码在案下“f”后开启一个协程。

class Example : MonoBehaviour
{void Update(){if (Input.GetKeyDown("f")){StartCoroutine(Test());}}IEnumerator Test(){yield retunr null;Debug.Log("111111111111111111");yield return new WaitForSeconds(.1f);Debug.Log("2222222222222222");}
}

我们可以看到 Test 方法的返回值是 IEnumerator,这是C#提供的迭代器,而中间的 yield return 则是语法糖,帮我们简化处理迭代器的内容。具体的迭代器相关文章可以看这里.
注意:

  1. 协程启动提供了字符串的方法,只有通过字符串方法启动的才能通过字符串方式停止。另外通过这种方法内部肯定是用了反射的方式,不太推荐。
  2. 协程开启需要额外的内存开销来跟踪对象,如果非常频繁(例如每一帧)那么使用Update或LateUpdate能获得更高的性能。

协程的原理

我们知道游戏是通过一帧一帧来运行的, 那么协程是运行在哪里呢?。在Untiy官方帮助文档中给出了脚本生命周期流程图。大致的位置就是在Update和LateUpdate之间。

协程大致有如下几种:

yield return null; // 暂停协程等待下一帧继续执行
yield return 1; // 或其他数字 暂停协程等待下一帧继续执行
yield return new WairForSeconds(1); // 等待规定时间后继续执行
yield return DoCoroutine(); // 嵌套开启一个协程

协程的缺陷

  1. 依赖于MonoBehaviour
  2. 不能有返回值
  3. 回调复杂

自己实现协程

知道了协程的实现方法后我们可以知道实际上是可以脱离unity自行模拟协程相关的内容。

先实现一个死循环,提供模拟的update:

static void Main(string[] args) {List<MonoBehaviour> monoBehaviours = new List<MonoBehaviour>();monoBehaviours.Add(new MyGameObject()); // 模拟一个MonoBehaviourwhile (true) {// 模拟游戏循环foreach (var item in monoBehaviours){item.Update();	// 简易模拟updateitem.UpdateOver(); // 在这里处理协程相关内容}Thread.Sleep(16); // 假装过了一帧 16ms}
}

再提供一个假的MonoBehavioru

class MonoBehaviour {public MonoBehaviour() { Awake(); }// 存储所有的协程,用于遍历以及停止private HashSet<Coroutine> coroutines = new HashSet<Coroutine>();public Coroutine StartCoroutine(IEnumerator routine) {Coroutine coroutine = new Coroutine();coroutine.Add(routine);coroutines.Add(coroutine);return coroutine;}// 停止一个协程,本质上是将其移除, 后续就不会再触发了public void StopCoroutine(Coroutine routine) {coroutines.Remove(routine);}virtual public void Awake() {}virtual public void Update() {}virtual public void UpdateOver() {List<Coroutine> cors = new List<Coroutine>(coroutines);foreach (var item in cors) {if (item.MoveNext()) // 将协程(迭代器)迭代一次Console.WriteLine($"\t {item.Current}"); // 打印下当前对象elsecoroutines.Remove(item); // 协程结束}}
}

上面的Coroutine主要维护了保留当前迭代器器的信息,个人认为本质上也是一个迭代器。 实现如下:

class Coroutine : IEnumerator {// 使用栈来存储当前运行的迭代器private Stack<IEnumerator> stack = new Stack<IEnumerator>();public void Add(IEnumerator enumerator) {stack.Push(enumerator);}public object Current // 获取当前对象,{get{if (stack.Count == 0) return null;return stack.Peek();}}// 迭代器迭代一次public bool MoveNext() {while (stack.Count > 0) {IEnumerator enumerator = stack.Peek();if (enumerator.MoveNext()) {if (enumerator.Current is IEnumerator) {// 如果是嵌套的迭代器,那么就入栈,依次进行迭代Add((IEnumerator)enumerator.Current);// 开启嵌套的协程}// 其他类型的都等下一帧再次进行迭代return true;}stack.Pop();}return false; // 当迭代完所有的迭代器后则认为结束,不能再移动到下一步}public void Reset() { }
}

框架以及搭好了,那么实现一个简单实现一个 WairForSeconds:

class WairForSeconds : IEnumerator {private float seconds;private bool start = false;private long timeStamp;public WairForSeconds(float seconds) {this.seconds = seconds;}public object Current => this;public bool MoveNext() {if (start == false) {start = true;timeStamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();}if (DateTimeOffset.UtcNow.ToUnixTimeSeconds() - timeStamp >= seconds) {return false;}return true;}public void Reset() { }
}

测试代码如下:

class MyGameObject : MonoBehaviour
{private int updateCount = 0;Coroutine coroutine;public override void Awake(){Console.WriteLine("Update start");coroutine = StartCoroutine(DoCoroutine());Console.WriteLine("Update end");}public override void Update(){updateCount++;if (updateCount == 10){StopCoroutine(coroutine);Console.WriteLine("协程被停止了");}}IEnumerator DoCoroutine(){Console.WriteLine($"{DateTime.Now} DoCoroutine start");yield return null; // 暂停协程等待下一帧继续执行Console.WriteLine($"{DateTime.Now} DoCoroutine 111111111111");yield return 1; // 或其他数字 暂停协程等待下一帧继续执行Console.WriteLine($"{DateTime.Now} DoCoroutine 222222222222");yield return new WairForSeconds(1); // 等待规定时间后继续执行Console.WriteLine($"{DateTime.Now} DoCoroutine 333333333333");yield return DoCoroutine1();Console.WriteLine($"{DateTime.Now} DoCoroutine 444444444444");}IEnumerator DoCoroutine1(){yield return 1;yield return 2;yield return 3;}
}

执行结果如下:
协程测试结果
完整代码放这里

参考

https://zhuanlan.zhihu.com/p/355377979
Unity 协程原理探究与实现. 感觉这篇实现复杂了。


http://chatgpt.dhexx.cn/article/JdjKhCnH.shtml

相关文章

unity 协程

首先声明&#xff1a;协程不是线程&#xff0c;协程在主线程中运行&#xff0c;而线程是单独开辟线程 以下是u3d关于协程的调用机制的解释&#xff1a; “在Unity3D中&#xff0c;使用MonoBehaviour.StartCoroutine方法即可开启一个协同程序&#xff0c;也就是说该方法必须在…

【Unity】Unity协程(Coroutine)的原理与应用

文章目录 前言一、什么是协程二、应用场景1.异步加载资源2.将一个复杂程序分帧执行3.定时器 三、协程的使用注意事项 四、Unity协程的底层原理1. 协程本体&#xff1a;C#的迭代器函数2. 协程调度&#xff1a;MonoBehaviour生命周期中实现 五、参考资料 前言 本文是作者在学习U…

Unity-协程详解

1. 简介 unity的**协程&#xff08;Coroutine&#xff09;**只是在c#的基础上做了一层封装&#xff0c;其实yield是C#的关键字。 unity协程是一个能够暂停协程执行&#xff0c;暂停后立即返回主函数&#xff0c;执行主函数剩余的部分&#xff0c;直到中断指令完成后&#xff…

Unity 协程(Coroutine)原理与用法详解

前言&#xff1a; 协程在Unity中是一个很重要的概念&#xff0c;我们知道&#xff0c;在使用Unity进行游戏开发时&#xff0c;一般&#xff08;注意是一般&#xff09;不考虑多线程&#xff0c;那么如何处理一些在主任务之外的需求呢&#xff0c;Unity给我们提供了协程这种方式…

linux的用户管理

1.linux的用户管理 linux的用户管理和组管理&#xff0c; 每个用户都必须要有一个且仅有一个初始组&#xff0c;可以有多个附加组&#xff0c;使用useradd命令创建用户时&#xff0c;如果没有指定初始组&#xff0c;系统默认会创建一个于其同名的组。 用户和组信息保存在4个文…

Linux用户管理练习

Linux下用户分为3类&#xff1a;超级用户&#xff08;root&#xff09;、系统用户、普通用户。 超级用户的用户名为root&#xff0c;它具有一切操作权力&#xff0c;因此为安全起见&#xff0c;建议不要轻易的在root账户下面对文件进行操作。在Linux操作系统的字符界面&#xf…

Linux用户管理工具

Linux用户管理工具 1. 用户 1.1创建用户 useradd -m username该命令为用户创建相应的帐号和用户目录/home/username&#xff1b; passwd username该命令为用户设置密码 1.2 删除用户 userdel -r username不带选项使用 userdel&#xff0c;只会删除用户。用户的家目录将…

Linux上的用户管理

Linux是一个多用户多任务的系统&#xff0c;任何人想要访问系统资源&#xff0c;必须通过登录账号来访问系统资源 添加用户 useradd&#xff1a;添加用户的命令&#xff08;root才能添加用户&#xff09; 用法&#xff1a;useradd 用户名 例&#xff1a;useradd water 就创建…

实现Linux用户管理

1.添加用户组&#xff1a;groupadd 用户组名称 2.创建用户 &#xff08;一&#xff09;仅创建用户&#xff1a;useradd 用户名 &#xff08;二&#xff09;指定用户所属的用户组&#xff1a; useradd -g 用户组 3.设置用户密码 &#xff08;一&#xff09;passwd 普通用户名 …

linux用户管理及操作指令

1、首先了解下linux是一个多用户多任务的操作系统。任何一个需要使用资源的用户都需要从linux系统中分配一个用户角色&#xff0c;比如&#xff1a;root、user、、然后使用对应账号进入系统。一个root用户下面能创建多个用户&#xff0c;每个用户下面对应一个目录 2、添加用户…

浅谈Linux用户管理

Linux用户管理 准备工作&#xff1a; 在管理用户时&#xff0c;执行命令后是无法看到效果的&#xff0c;最开始我们可以通过系统监视命令来对用户信息进行监控&#xff0c;使操作步骤可视。 watch -n 1 tail -n3 /etc/passwd;ls -l /home/ 注释&#xff1a; 部分含义watch …

Linux用户管理(Centos7)

用户管理 用户命令 添加登录用户&#xff1a; 例&#xff1a;添加一个名为harry的用户&#xff0c;并使用bash作为登录的shell [rootaws ~]# useradd harry [rootaws ~]# tail -1 /etc/passwd harry:x:1002:1002::/home/harry:/bin/bashharry:x:1001:1001::/home/harry:/bin/…

Linux用户管理机制

Linux系统中的用户管理涉及用户账号文件 /etc/passwd、用户密码文件 /etc/shadow、用户组文件 /etc/group。 一、用户账号文件 /etc/passwd 该文件为纯文本文件&#xff0c;可以使用cat、head等命令查看。该文件记录了每个用户的必要信息&#xff0c;文件中的每一行对应一个用…

【Linux用户管理】

目录 前言 用户管理的基本命令 前言 Linux是一个多用户、多任务的操作系统&#xff0c;具有很好的稳定性与安全性&#xff0c;在linux系统中&#xff0c;root用户具有最高的权限&#xff0c;但该身份不当使用会带来一些不必要的麻烦和潜在的风险&#xff0c;故需要添加一些普通…

Linux之用户管理

一、用户的增删改查 1.增加用户 语法&#xff1a;useradd用户名 在终端输入 useradd 用户名 在linux系统home文件夹下面会出现新建用户的文件夹 2.修改密码 语法&#xff1a;passwd用户名 输入passwd 用户名&#xff0c;输入新的密码之后就可以使用新建的用户登录 3.删除用…

Linux用户管理-相关命令及配置文件-超详细-概念详解-初学全

前言 Linux系统作为多用户多任务的操作系统&#xff0c;可以在同一时间内允许多个用户登录、操作及配置计算机&#xff0c;随着需求的增加&#xff0c;用户的增加&#xff0c;我们也就需要对用户进行管理&#xff0c;以至于更有效地开展项目&#xff0c;改善工作&#xff0c;更…

Ubuntu/Linux用户管理与权限管理(超详细解析)

由于实验室几个老师的学生要共同使用一台服务器&#xff0c;所以需要规范一下服务器的使用&#xff0c;并且给各位学生配置相关的用户和权限&#xff0c;之前一直都是自己用&#xff0c;所以借此机会学习和总结一下Linux服务器的用户管理与权限管理。 Ubuntu/Linux用户管理与权…

轻松搞懂Linux中的用户管理

文章目录 概念用户账户用户组用户权限用户管理工具 概念 用户管理是Linux系统管理员必须掌握的重要技能之一。Linux系统是一个多用户操作系统&#xff0c;可以支持多个用户同时使用&#xff0c;每个用户拥有自己的账户和权限&#xff0c;因此管理员需要了解如何创建、管理和删…

Linux用户管理相关命令(全)

1、Linux用户(账号)管理 查询用户(账号)信息&#xff08;判断用户(账号)是否存在&#xff09; id account新增用户(账号) useradd account设置用户(账号)密码 方式1&#xff1a; passwd account 方式2&#xff1a; echo 123|passwd --stdin account; #密码为123删除用户(账…