参考博客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. mysql高级进阶(存储过程、游标、触发器)

    废话不多说,直接进入正题... 一.存储过程 a.概述 存储过程可以看成是对一系列 SQL 操作的批处理: 使用存储过程的好处 代码封装,保证了一定的安全性: 代码复用: 由于是预先编译,因此具有很高 ...

  2. git 访问仓库错误

    通过https访问git出现错误, failed: Error in the pull function 尝试将https改为http

  3. 使用clip-path将 GIF 绘制成跳动的字母

    前言 之前看到过一个有趣的CSS效果,今天我们也来实现一遍,将动图GIF通过clip-path绘制成一个个跳动的字母. 效果如下: GIF随便找的,嗯?这不是重点,重点是下面的实现过程,别被GIF吸引 ...

  4. 超越.NET极限:我打造的高精度数值计算库

    超越.NET极限:我打造的高精度数值计算库 还记得那一天,我大学刚毕业,紧张又兴奋地走进人生第一场.NET工作面试.我还清楚地记得那个房间的气氛,空调呼呼地吹着,面试官的表情严肃而深沉.我们进行了一番 ...

  5. 剪切图片, 原文自https://blog.csdn.net/sinat_41104353/article/details/85209456

    因为在 OpenCV2 里面,所有的东西都是 numpy array 即 np.ndarray1,所以使用 opencv 剪切图像主要原理是用 ndarray 的切片.一张图片基本上都是三维数组:行, ...

  6. 【go笔记】目录操作

    基本目录操作 涉及:创建目录.重命名目录.删除目录 package main import ( "fmt" "os" "time" &quo ...

  7. golang trace view 视图详解

    大家好,我是蓝胖子,在golang中可以使用go pprof的工具对golang程序进行性能分析,其中通过go trace 命令生成的trace view视图对于我们分析系统延迟十分有帮助,鉴于当前对 ...

  8. MyBatis-Plus批量插入方法saveBatch

    1. saveBatch能否提高插入的效率? 先说结论,saveBatch()方法也是一条一条的插入,也就是说它会产生多条insert语句,而不是一条insert语句,所以它不是真正的批量插入,更不能 ...

  9. SQL 注入学习手册【笔记】

    SQL 注入基础 [若本文有问题请指正] 有回显 回显正常 基本步骤 1. 判断注入类型 数字型 or 字符型 数字型[示例]:?id=1 字符型[示例]:?id=1' 这也是在尝试闭合原来的 sql ...

  10. .NET周刊【8月第2期 2023-08-14】

    本周由于Myuki大佬感染新冠,国际板块暂停更新一周,将在下周补齐,所以本周只有国内板块. 国内文章 解决 Blazor 中因标签换行导致的行内元素空隙问题 https://www.cnblogs.c ...