Godot - 通过C#实现类似Unity协程
参考博客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协程的更多相关文章
- 用Lua的协程实现类似Unity协程的语句块
local co_time_tbl = {} setmetatable(co_time_tbl, { __len = function(o) for k, v in pairs(o) do count ...
- unity协程coroutine浅析
转载请标明出处:http://www.cnblogs.com/zblade/ 一.序言 在unity的游戏开发中,对于异步操作,有一个避免不了的操作: 协程,以前一直理解的懵懵懂懂,最近认真充电了一下 ...
- Unity协程(Coroutine)管理类——TaskManager工具分享
博客分类: Unity3D插件学习,工具分享 源码分析 Unity协程(Coroutine)管理类——TaskManager工具分享 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处 ...
- Unity协程(Coroutine)原理深入剖析
Unity协程(Coroutine)原理深入剖析 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com 其实协程并没有那么复杂,网上很多地方都说是多 ...
- Unity协程(Coroutine)原理深入剖析(转载)
记得去年6月份刚开始实习的时候,当时要我写网络层的结构,用到了协程,当时有点懵,完全不知道Unity协程的执行机制是怎么样的,只是知道函数的返回值是IEnumerator类型,函数中使用yield r ...
- Unity协程Coroutine使用总结和一些坑
原文摘自 Unity协程Coroutine使用总结和一些坑 MonoBehavior关于协程提供了下面几个接口: 可以使用函数或者函数名字符串来启动一个协程,同时可以用函数,函数名字符串,和Corou ...
- 深入浅出!从语义角度分析隐藏在Unity协程背后的原理
Unity的协程使用起来比较方便,但是由于其封装和隐藏了太多细节,使其看起来比较神秘.比如协程是否是真正的异步执行?协程与线程到底是什么关系?本文将从语义角度来分析隐藏在协程背后的原理,并使用C++来 ...
- Unity 协程使用指南
0x00 前言 在使用Unity的过程中,对协程仅仅知道怎样使用,但并不知道协程的内部机理,对于自己不清楚的部分就像一块大石压力心里.让自己感觉到担忧和不适. 这篇文章一探到底,彻底揭开协程的面纱,让 ...
- Unity协程(Coroutine)原理深入剖析再续
Unity协程(Coroutine)原理深入剖析再续 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com 前面已经介绍过对协程(Coroutine ...
- Unity协程使用经验
[Unity协程使用经验] 1.协程的好处是,异步操作发起的地方和结束的地方可以统一在一个方法,这样就不用引入额外的成员变量来进行状态同步. 2.在一个协程中,StartCoroutine()和 yi ...
随机推荐
- Seal AppManager v0.2 发布:进一步简化应用部署体验
经过近3个月的研发,Seal AppManager v0.2 已正式发布. Seal AppManager 是一款基于平台工程理念的应用统一部署管理平台,于今年4月首次推出.在上一版本中,我们已经释出 ...
- 【Docker】部署Redis
1.下载镜像 #下载最新版Redis镜像 (其实此命令就等同于 : docker pull redis:latest ) docker pull redis # 下载指定版本的Redis镜像 (xxx ...
- Word转换HTML(Java实用版)
前言: 在业务中,如果需要在浏览器中预览Word文档,或者需要将Word文档转成HTML文件保存,那么本章内容,可以帮助到你. 实现这一功能,有多种实现方式,如:docx4j.poi.Free Spi ...
- React:TypeError: Cannot read properties of undefined (reading 'map')
解决方案 js中?问号代表可选项 可选链式操作符 参考链接 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Oper ...
- .Net Core 3.0 对 MongoDB 的多条件查询(两种)操作
前言 在日常开发中,偶尔会用到 MongoDB 的数据操作,也花费了一些时间调试,因此在此处记录一下,共同进步. 废话少说,出招吧! 正文 2.1 准备工作 首先需要引入 .Net 平台链接 Mo ...
- Jenkins服务开机自启动
最近因为护网行动,每天都要对服务器进行开.关机操作.为了省事儿,对Jenkins服务进行开机自动启动服务改造.实现如下: 1. 通过chkconfig --list命令列出系统中已安装的服务及其启动状 ...
- Programming abstractions in C阅读笔记p111-p113: boilerplate
<Programming Abstractions In C>学习第47天,p111-p113,总结如下: 一.技术总结 1.boilerplate /* * File: random.h ...
- Java爬虫实战系列——常用的Java网络爬虫库
常用的Java网络爬虫库 Java 开发语言是业界使用最广泛的开发语言之一,在互联网从业者中具有广泛的使用者,Java 网络爬虫可以帮助 Java 开发人员以快速.简单但广泛的方式为各种目的抓取数据. ...
- mysql到底需不需要容器化?
前言:在容器化的时代,当然一切皆可容器化.在docker官网首页赫然有下面这几个大字.足以知道docker的优势.那么且问,mysql适合跑在docker中吗? 当然,这个问题有人说可以,也有人说不可 ...
- Codeforces 1257E - The Contest
题意 三个人,每个人有一些数字,组合起来是\(1\)-\(n\),每个人可以给另一个人一个拥有的数字,问最小操作数,使得第一个人拥有\(1\)-\(i\)的数,第二个人拥有\(i+1\)-\(j\)的 ...