参考博客Unity 协程原理探究与实现

Godot 3.1.2版本尚不支持C#版本的协程,仿照Unity的形式进行一个协程的尝试

但因为Godot的轮询函数为逐帧的_Process(float delta)和固定时间的_PhysicsProcess(float delta), 不像untiy可以在同一函数中同时取得逻辑时间和物理时间,一些时间误差还是可能有的。

基本协程执行

协程原理见上面的参考博客,即通过在游戏轮询函数中进行迭代,通过迭代器的yield语句将逻辑进行分段执行。

首先把游戏引擎的轮询函数接入

// GDMain.cs
// 把这个脚本挂到一个节点上启动即可 using Godot; public class GDMain : Node
{
public override void _Process(float delta)
{
CoroutineCore.Update(delta);
} public override void _PhysicsProcess(float delta)
{
CoroutineCore.FixedUpdate(delta);
}
}
// CoroutineCore.cs

using Godot;
using System.Collections; public static class CoroutineCore
{
private static s_It; public static void StartCoroutine(IEnumerator e)
{
//这里就产生了一个问题,第一次在下一帧时执行,可以做相关逻辑调整
s_It = e;
} public static void Update(float delta)
{
InnderDo(delta, false);
} public static void FixedUpdate(float delta)
{
InnderDo(delta, true);
} private static void InnderDo(float delta, bool isFixedTime)
{
if (s_It == null) return;
IEnumerator it = s_It;
object current = it.Current;
bool isNotOver = true; if (current is WaitForFixedUpdate)
{
if (isFixedTime)
{
isNotOver = it.MoveNext();
}
}
else if (current is WaitForSeconds wait)
{
if (!isFixedTime && wait.IsOver(delta))
{
isNotOver = it.MoveNext();
}
}
else if (!isFixedTime)
{
isNotOver = it.MoveNext();
} if (!isNotOver)
{
GD.Print("one cor over!");
s_It = null;
}
}
} // WaitForFixedUpdate.cs public struct WaitForFixedUpdate
{
} // WaitForSeconds.cs
public class WaitForSeconds
{
private float m_Limit;
private float m_PassedTime; public WaitForSeconds(float limit)
{
m_Limit = limit;
m_PassedTime = 0;
} public bool IsOver(float delta)
{
m_PassedTime += delta;
return m_PassedTime >= m_Limit;
}
}

这样就可以在一个IEnumerator中通过yield return null;等待下一帧,yield return null WaitForFixedUpdate();等待下一个物理更新,yield return new WaitForSeconds(1);等待一秒。WaitWhile()WaitUtil()实现同理

协程嵌套

协程的实用情景主要是资源加载之类耗时较久的地方,Unity中通过协程将异步操作以同步形式表现,如果这里的“协程”不能实现嵌套,那么也就没有多少价值了。

在尝试实现的过程中遇到的一个主要问题是子协程结束后如何呼叫父协程的下一个迭代...之后用层级计数的方法暂时处理。

仅实现了一种可行的方案,如果要投入实用,还需要做相关优化、bug修复、异常处理。

// CoroutineCore.cs
// 考虑协程嵌套的情况,单一IEnumerator变量就不能满足需求了,从直觉上,首先想到使用Stack结构 public static class CoroutineCore
{
private static Stack<IEnumerator> s_Its = new Stack<IEnumerator>();
private static int s_SubCount = 0; public static void StartCoroutine(IEnumerator e);
{
s_Its.Push(e);
} public static void Update(float delta)
{
InnderDo(delta, false);
} public static void FixedUpdate(float delta)
{
InnderDo(delta, true);
} private static void InnderDo(float delta, bool isFixedTime)
{
if (s_Its.Count == 0) return;
IEnumerator it = s_It.Peek();
object current = it.Current;
bool isNotOver = true; if (current is WaitForFixedUpdate)
{
if (isFixedTime)
{
isNotOver = it.MoveNext();
}
}
else if (current is WaitForSeconds wait)
{
if (!isFixedTime && wait.IsOver(delta))
{
isNotOver = it.MoveNext();
}
}
else if (current is IEnumerator nextIt)
{
s_Its.Push(nextIt);
s_SubCount++;
}
else if (!isFixedTime)
{
isNotOver = it.MoveNext();
} if (!isNotOver)
{
GD.Print("one cor over!");
s_Its.Pop(); if (s_SubCount > 0)
{
it = s_Its.Peek();
it.MoveNext();
s_SubCount--;
}
}
}
}

测试代码如下

private void TestBtn_pressed()
{
CoroutineCore.StartCoroutine(TestA);
} IEnumerator TestA()
{
DateTimeOffset now; now = DateTimeOffset.Now;
GD.Print(string.Format("{0}, {1}", now.Second, now.Millisecond));
yield return null; now = DateTimeOffset.Now;
GD.Print(string.Format("{0}, {1}", now.Second, now.Millisecond));
yield return new WaitForSeconds(2); now = DateTimeOffset.Now;
GD.Print(string.Format("{0}, {1}", now.Second, now.Millisecond));
yield return new WaitForFixedUpdate(); now = DateTimeOffset.Now;
GD.Print(string.Format("{0}, {1}", now.Second, now.Millisecond));
yield return TestB(); now = DateTimeOffset.Now;
GD.Print(string.Format("{0}, {1}", now.Second, now.Millisecond));
yield return null; now = DateTimeOffset.Now;
GD.Print(string.Format("{0}, {1}", now.Second, now.Millisecond));
yield return null;
} IEnumerator TestB()
{
DateTimeOffset now; now = DateTimeOffset.Now;
GD.Print(string.Format("this is B!, {0}, {1}", now.Second, now.Millisecond));
yield return null; now = DateTimeOffset.Now;
GD.Print(string.Format("this is B!, {0}, {1}", now.Second, now.Millisecond));
yield return new WaitForSeconds(1); yield return TestC(); now = DateTimeOffset.Now;
GD.Print(string.Format("this is B!, {0}, {1}", now.Second, now.Millisecond));
yield return new WaitForSeconds(1);
} IEnumerator TestC()
{
DateTimeOffset now; now = DateTimeOffset.Now;
GD.Print(string.Format("this is C!, {0}, {1}", now.Second, now.Millisecond));
yield return null; now = DateTimeOffset.Now;
GD.Print(string.Format("this is C!, {0}, {1}", now.Second, now.Millisecond));
}

执行结果

18, 130
18, 158
20, 158
20, 175
this is B!, 20, 192
this is B!, 20, 208
this is C!, 21, 242 *这里只执行了WaitForSeconds(1), 和预期值差了大概两帧的时间
this is C!, 21, 258
one cor over!
this is B!, 21, 262
one cor over!
22, 260
22, 275
one cor over!

运行帧率是60FPS,即每次更新delta == 0.0167,运行顺序逻辑是满足预期的,但执行细节需要调整一下

Godot - 通过C#实现类似Unity协程的更多相关文章

  1. 用Lua的协程实现类似Unity协程的语句块

    local co_time_tbl = {} setmetatable(co_time_tbl, { __len = function(o) for k, v in pairs(o) do count ...

  2. unity协程coroutine浅析

    转载请标明出处:http://www.cnblogs.com/zblade/ 一.序言 在unity的游戏开发中,对于异步操作,有一个避免不了的操作: 协程,以前一直理解的懵懵懂懂,最近认真充电了一下 ...

  3. Unity协程(Coroutine)管理类——TaskManager工具分享

    博客分类: Unity3D插件学习,工具分享 源码分析   Unity协程(Coroutine)管理类——TaskManager工具分享 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处 ...

  4. Unity协程(Coroutine)原理深入剖析

    Unity协程(Coroutine)原理深入剖析 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com 其实协程并没有那么复杂,网上很多地方都说是多 ...

  5. Unity协程(Coroutine)原理深入剖析(转载)

    记得去年6月份刚开始实习的时候,当时要我写网络层的结构,用到了协程,当时有点懵,完全不知道Unity协程的执行机制是怎么样的,只是知道函数的返回值是IEnumerator类型,函数中使用yield r ...

  6. Unity协程Coroutine使用总结和一些坑

    原文摘自 Unity协程Coroutine使用总结和一些坑 MonoBehavior关于协程提供了下面几个接口: 可以使用函数或者函数名字符串来启动一个协程,同时可以用函数,函数名字符串,和Corou ...

  7. 深入浅出!从语义角度分析隐藏在Unity协程背后的原理

    Unity的协程使用起来比较方便,但是由于其封装和隐藏了太多细节,使其看起来比较神秘.比如协程是否是真正的异步执行?协程与线程到底是什么关系?本文将从语义角度来分析隐藏在协程背后的原理,并使用C++来 ...

  8. Unity 协程使用指南

    0x00 前言 在使用Unity的过程中,对协程仅仅知道怎样使用,但并不知道协程的内部机理,对于自己不清楚的部分就像一块大石压力心里.让自己感觉到担忧和不适. 这篇文章一探到底,彻底揭开协程的面纱,让 ...

  9. Unity协程(Coroutine)原理深入剖析再续

    Unity协程(Coroutine)原理深入剖析再续 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com 前面已经介绍过对协程(Coroutine ...

  10. Unity协程使用经验

    [Unity协程使用经验] 1.协程的好处是,异步操作发起的地方和结束的地方可以统一在一个方法,这样就不用引入额外的成员变量来进行状态同步. 2.在一个协程中,StartCoroutine()和 yi ...

随机推荐

  1. iota简介

    当声明枚举类型或定义一组相关常量时,Go语言中的iota关键字可以帮助我们简化代码并自动生成递增的值.本文档将详细介绍iota的用法和行为. iota关键字 iota是Go语言中的一个预定义标识符,它 ...

  2. 10/29/2017_C语言_三道题

    1. 用标准C编程:找出整形数字1-100之间的素数,并打印出来.(素数:除了1和自己本身可以被整除.) 2. 用标准C编程:有两个整形变量m.n,求出这两个数的最小公倍数. 3. 用标准C编程:输出 ...

  3. quarkus实战之七:使用配置

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是<quarkus实战>系列 ...

  4. return true 与 return false的妙用——jQuery

    var arr = [1, 3, 5,7,9]; jQuery.each(arr, function(key, value){ if(key === 2){ return true; } consol ...

  5. Django资源网站

    Django的官网:https://www.djangoproject.com/ Django Book2.0版本的中文文档:http://djangobook.py3k.cn/2.0/chapter ...

  6. Chrome 报错: Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.

    经检查,是由浏览器中的插件导致的报错. 解决方案: 将该插件移除或关闭

  7. 关于微信小程序原生组件与uniApp混合开发过程遇到的问题与解决方式

    前言: 在实际开发过程中,尤其是小程序的开发,我们常常会遇到一些在文档中解决不了的问题,在这里,我就浅谈一下我遇到的一些问题 1.小程序的构建框架是uni-app,却突然被要求用原生的微信小程序代码来 ...

  8. 绕过PHP执行命令的函数执行系统cmd命令

    <?php $evil=`set`; echo '<pre>'.$evil.'</pre>'; ?> 成功执行set命令,你懂的! 顺手写了个php执行cmd命令的 ...

  9. python-多继承构造函数声明问题

    背景 有场景分别定义两组逻辑,随后有统一入口做基类属性的整合 其中两组逻辑的积累构造函数定义入参不同 设计类继承图如: 实际的使用方式抽象为[使用] 小节 实际开发过程中遇到问题 先说结论 pytho ...

  10. SpringBoot3安全管理

    目录 一.简介 二.工程搭建 1.工程结构 2.依赖管理 三.配置管理 1.核心配置类 2.认证数据源 3.认证流程 4.身份过滤器 四.核心功能 1.登录退出 2.权限校验 五.参考源码 标签:Se ...