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 ...
随机推荐
- 前端Vue组件之仿京东拼多多领取优惠券弹出框popup 可用于电商商品详情领券场景使用
随着技术的发展,开发的复杂度也越来越高,传统开发方式将一个系统做成了整块应用,经常出现的情况就是一个小小的改动或者一个小功能的增加可能会引起整体逻辑的修改,造成牵一发而动全身.通过组件化开发,可以有效 ...
- 论文日记四:Transformer(论文解读+NLP、CV项目实战)
导读 重磅模型transformer,在2017年发布,但就今天来说产生的影响在各个领域包括NLP.CV这些都是巨大的! Paper<Attention Is All You Need>, ...
- Point Free
这是一种函数编码模式: 把数据处理的过程定义成和数据无关的合成运算,不需要用到数据参数,只是简单合成运算步骤,但需要定义一些辅助的基本运算函数. for example: 采用了lodash的fp ...
- mysql根据.frm和.ibd文件恢复数据表
忠人之事受人之托 起因是因为一位朋友的数据库服务器被重装了,只剩下一个zbp_post.frm和zbp_post.ibd文件.咨询我能不能恢复,确实我只用过mysqldump这种工具导出数据 然后进行 ...
- #Powerbi 1分钟学会利用AI,为powerbi报表进行高端颜色设计
在BI报表的设计中,配色方案往往成为一大难题,一组切合主题.搭配合理的颜色设计往往能为我们的报表,加分不少. 今天,就介绍一个AI配色的网站,利用AI为pbi报表进行配色设计. 一:网站网址 http ...
- 【游戏开发笔记】编程篇_C#面向对象{上}
@ 目录 1.变量和表达式 1.1注释 1.2C#控制台程序基本结构 1.3变量(从存储长度来看) 1.4变量的命名 1.5字面值 1.6运算符 2流程控制 2.1分支 2.2循环 3变量知识拓展 3 ...
- word中查找替换不能使用 解决方案
打开查找,然后点更多,最下面点不限定格式
- 代码随想录算法训练营第四天|力扣24.两两交换链表节点、力扣19.删除链表的倒数第N个结点、力扣面试02.07链表相交、力扣142.环形链表
两两交换链表中的节点(力扣24.) dummyhead .next = head; cur = dummyhead; while(cur.next!=null&&cur.next.ne ...
- 【升职加薪秘籍】我在服务监控方面的实践(6)-业务维度的mysql监控
大家好,我是蓝胖子,关于性能分析的视频和文章我也大大小小出了有一二十篇了,算是已经有了一个系列,之前的代码已经上传到github.com/HobbyBear/performance-analyze,接 ...
- 无界AI绘画基础教程,和Midjourney以及Stable Diffusion哪个更好用?
本教程收集于:AIGC从入门到精通教程汇总 简单的总结 Midjourney,Stable Diffusion,无界AI的区别? Midjourney,收费,上手容易,做出来高精度的图需要自己掌握好咒 ...