您现在的位置是:首页 >技术杂谈 >更有效的协同程序【插件:More Effective Coroutines】网站首页技术杂谈
更有效的协同程序【插件:More Effective Coroutines】
插件地址:传送门
1、命名空间
using System.Collections.Generic;
using MEC;
2、与传统的协程相比
传统:StartCoroutine(_CheckForWin());
被RunCoroutine取代。必须选择执行循环进程,默认为“Segment.Update”。
using System.Collections.Generic;
using UnityEngine;
using MEC;
public class Test : MonoBehaviour
{
void Start()
{
StartCoroutine(_CheckForWin());
// To run in the Update segment:
Timing.RunCoroutine(_CheckForWin());
// To run in the FixedUpdate segment:
Timing.RunCoroutine(_CheckForWin(), Segment.FixedUpdate);
// To run in the LateUpdate segment:
Timing.RunCoroutine(_CheckForWin(), Segment.LateUpdate);
// To run in the SlowUpdate segment:
Timing.RunCoroutine(_CheckForWin(), Segment.SlowUpdate);
}
IEnumerator<float> _CheckForWin()
{
yield return Timing.WaitForSeconds(1);
Debug.Log("****");
}
}
3、协程的退出/停止 CancelWith
相当于StopCoroutine,CancelWith确实会增加所有协程生成的不可避免的GC分配的大小大约20个字节。20字节并不大,但是如果您想避免所有可能的GC分配,那么你就可以相对容易地做CancelWith所做的事情,而不用使用这个函数。只是确保在协程中的每个yield return语句之后都做以下检查:if(gameObject != null && gameObject.activeInHierarchy)
public class Cube01 : MonoBehaviour
{
private void Awake()
{
Timing.RunCoroutine(_moveMyButton().CancelWith(gameObject));
}
IEnumerator<float> _moveMyButton()
{
while (true)
{
yield return Timing.WaitForSeconds(1f);
Debug.Log("********");
}
}
}
4、停止和回复协程
MEC协程可以暂停,稍后再恢复。Unity的协程不会这样做,但是概念很简单。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MEC;
public class Cube01 : MonoBehaviour
{
private CoroutineHandle handleToACoroutine;
private void Awake()
{
handleToACoroutine= Timing.RunCoroutine(_moveMyButton().CancelWith(gameObject));
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.P))
{
Timing.PauseCoroutines(handleToACoroutine);
}
if (Input.GetKeyDown(KeyCode.R))
{
Timing.ResumeCoroutines(handleToACoroutine);
}
}
IEnumerator<float> _moveMyButton()
{
while (true)
{
yield return Timing.WaitForSeconds(1f);
Debug.Log("********");
}
}
}
5、等待直到完成
Unity的默认协同程序有几种情况,你可以产生返回一些变量。例如,你可以“yield return asyncOperation;”。MEC也有这样的功能函数称为WaitUntilDone。
yield return Timing.WaitUntilDone(wwwObject);
yield return Timing.WaitUntilDone(asyncOperation);
yield return Timing.WaitUntilDone(customYieldInstruction);
// With MEC Pro you can do a little more with WaitUntilDone:
yield return Timing.WaitUntilDone(newCoroutine);
// 上面的代码自动启动一个新的协程,并保存当前的协程。
yield return
Timing.WaitUntilTrue(functionDelegateThatReturnsBool); //免费版没有
yield return
Timing.WaitUntilFalse(functionDelegateThatReturnsBool); //免费版没有
6、慢更新(slow update)
Unity的协程没有慢更新循环的概念,但MEC的协程有。
慢更新循环(默认情况下)每秒运行7次。它使用绝对时间刻度,所以当你减慢Unity的时间刻度时,它不会减慢SlowUpdate。使用SlowUpdate和总是使用“yield returnTiming.WaitForSeconds(1f/7f);”有两个主要区别。第一个是绝对时间尺度,第二个是所有的SlowUpdate节拍同时发生。
private void Awake()
{
Timing.RunCoroutine(_UpdateTime(), Segment.SlowUpdate);
}
private float clock;
private IEnumerator<float> _UpdateTime()
{
while (true)
{
//当前段已运行的时间(以秒为单位)。
clock = Timing.LocalTime;
yield return 0f;
}
}
SlowUpdate还可以很好地检查临时调试变量。例如,如果重建项目需要很长时间,您可以在脚本中设置一个公共bool值,以重置该脚本上的值。您需要定期检查该bool值是否已设置为true,执行该检查的最佳时间是在SlowUpdate上。当用户选中复选框时,它会感觉它立即响应,但它将在你的应用程序中使用更少的处理每1/7秒检查一次,而不是每秒30 - 100次(取决于你的帧率)。
注意:Unity的Time. deltatime变量在SlowUpdate中不会返回正确的值,因为Unity的Time类对这个片段一无所知。可以使用Timing.DeltaTime代替。
您还可以更改SlowUpdate运行的速率。例如:
Timing.Instance.TimeBetweenSlowUpdateCalls = 3f;
上面这行代码将使SlowUpdate每3秒只运行一次。
7、标签【Tag】
在启动协程时,可以选择是否提供标记。标记是标识该协程的字符串。当您标记一个协程或一组协程时,您可以稍后使用KillCoroutine(标签)或KillAllCoroutines(标签)杀死该协程或该组。【API可能会变,可以查看相关API调用】
using System.Collections.Generic;
using UnityEngine;
using MEC;
public class Test : MonoBehaviour
{
void Start()
{
Timing.RunCoroutine(_shout(1, "Hello"), "shout");
Timing.RunCoroutine(_shout(2, "World!"), "shout");
Timing.RunCoroutine(_shout(3, "I"), "shout2");
Timing.RunCoroutine(_shout(4, "Like"), "shout2");
Timing.RunCoroutine(_shout(5, "Cake!"), "shout2");
Timing.RunCoroutine(_shout(6, "Bake"), "shout3");
Timing.RunCoroutine(_shout(7, "Me"), "shout3");
Timing.RunCoroutine(_shout(8, "Cake!"), "shout3");
Debug.Log("Killed " + Timing.KillCoroutines("shout2"));
}
IEnumerator<float> _shout(float time, string text)
{
yield return Timing.WaitForSeconds(time);
Debug.Log(text);
}
}
8、LocalTime and DeltaTime
Unity的Time. deltatime变量在SlowUpdate中不会返回正确的值,因为Unity的Time类对这个片段一无所知。可以使用Timing.DeltaTime代替。Unity中的默认Time类在大多数情况下都可以正常工作,但在SlowUpdate中无法正常工作。
9、其它的功能【Additional Functionality】
Timing对象中还包含三个辅助函数:CallDelayed、callcontinuous和callperiodic。
(1)CallDelayed: 在一定秒数后调用指定的动作。
(2)CallContinously:每帧连续调用动作,持续数秒。
(3)CallPeriodically:周期性地每隔“x”秒调用动作,持续数秒。
这三种功能都可以很容易地使用协程创建,但是这个基本功能最终被忽略了由于使用频繁,我们将其包含在基本模块中。
using System.Collections.Generic;
using UnityEngine;
using MEC;
public class Test : MonoBehaviour
{
private void Start()
{
// 2秒后启动_RunFor5Seconds。
Timing.CallDelayed(2f, delegate
{
Timing.RunCoroutine(_RunFor5Seconds(Timing.RunCoroutine(Test01())));
});
// 使物体以每秒一个世界单位的速度向前推进,持续4秒。
Timing.CallContinuously(4f, delegate {PushOnGameObject(Vector3.forward);},
Segment.FixedUpdate);
// 尽量不要对callcontinuous进行闭包是非常重要的,因为这将导致每帧都进行GC分配。
Timing.CallContinuously<Vector3>(Vector3.forward, 4f,
vector => PushOnGameObject(vector), Segment.FixedUpdate);
}
private void PushOnGameObject(Vector3 amount)
{
transform.position += amount * Time.deltaTime;
}
private IEnumerator<float> _RunFor5Seconds(CoroutineHandle waitHandle)
{
Debug.Log("Yielding 5s..");
yield return Timing.WaitUntilDone(waitHandle);
Debug.Log("Starting 5 second run.");
yield return Timing.WaitForSeconds(5f);
Debug.Log("Finished 5 second run.");
}
private IEnumerator<float> Test01()
{
yield return Timing.WaitForOneFrame;
Debug.Log("********");
}
}
10、链式结构 【Fluid Architecture】
// 常规
Timing.RunCoroutine(_Foo().CancelWith(gameObject));
// MEC的链式
_Foo().CancelWith(gameObject).RunCoroutine();
请记住:与运行协程的其他方式一样,如果您忘记使用RunCoroutine,编译器不会报错,但它不会执行协程。当对Run的调用位于行尾时,这个事实可能有点难以记住。还要记住,这种流畅的语法可能会让其他习惯使用Unity默认协同程序的开发人员感到困惑,因为Unity的默认协同程序API不支持这种语法。
QA:
问:MEC有WaitForEndOfFrame函数吗?
它没有在MEC Free中实现,但MEC Pro有一个部分。
注意:关于WaitForEndOfFrame的实际作用有一些混淆。当你只是想屈服到下一帧时,WaitForEndOfFrame不是一个理想的命令,最好使用“yield return null;”。许多人在使用Unity的协程时使用WaitForEndOfFrame,因为这是他们在Unity的默认协程中最接近WaitForOneFrame的东西,他们没有意识到这可能会导致微妙的问题。MEC定义了常数Timing。WaitForOneFrame,所以在MEC中,如果你愿意,你可以使用显式变量名,而不会产生使用EndOfFrame可能导致的视觉故障和性能下降的可能性。
问:MEC有StopCoroutine的功能吗?
是的。它叫做Timing.KillCoroutines()。它可以接受前一个Timing返回的协程的句柄。RunCoroutine命令,或者它可以接受一个标记。
注意:KillCoroutine用于从不同的函数中停止协程函数。如果你想从协程的函数内部结束协程,那么最好的命令是“yieldbreak;”,这相当于在任何其他函数中调用“return;”。yield break更好的原因是因为KillCoroutine命令不能结束当前正在运行的协程函数,因此该函数将继续执行,直到下一个yield命令,但yield break没有这个问题。
问:MEC有StopAllCoroutines函数吗?
是的。Timing.KillCorutines()。如果你想暂时停止一切,你也可以使用Timing.PauseCorutines()和Timing.ResumeAllCorutines()。
问:MEC是否有一个函数使一个协程在另一个协程完成之前生成?
是的。在你想要持有的协程内部,你调用"yield return Timing.WaitUntilDone(coroutineHandle);"无论何时调用Timing.RunCoroutine,都会返回句柄。
问:MEC完全删除GC分配吗?
答:不是。MEC删除所有逐帧GC分配。(除非你在协程内部的堆上分配内存,但MEC无法控制这一点。)当协程第一次创建时,函数指针和传递给它的任何变量都被放在堆上,最终必须由垃圾收集器清理。这种不可避免的分配发生在Unity的协程和MEC协程中。MEC协程平均分配的垃圾确实比Unity协程少。
问:MEC协程总是比Unity协程内存效率更高吗,还是只有在特定情况下才如此?
答:MEC协程在所有情况下都比Unity协程产生更少的GC分配,除非你分配大字符串并将其作为协程的标记
案例:
void Start ()
{
CoroutineHandle handle =
Timing.RunCoroutine(_RunFor10Seconds());
handle = Timing.RunCoroutine(_RunFor1Second(handle));
Timing.RunCoroutine(_RunFor5Seconds(handle));
}
private IEnumerator<float> _RunFor10Seconds()
{
Debug.Log("Starting 10 second run.");
yield return Timing.WaitForSeconds(10f);
Debug.Log("Finished 10 second run.");
}
private IEnumerator<float> _RunFor1Second(CoroutineHandle
waitHandle)
{
Debug.Log("Yielding 1s..");
yield return Timing.WaitUntilDone(waitHandle);
Debug.Log("Starting 1 second run.");
yield return Timing.WaitForSeconds(1f);
Debug.Log("Finished 1 second run.");
}
private IEnumerator<float> _RunFor5Seconds(CoroutineHandle
waitHandle)
{
Debug.Log("Yielding 5s..");
yield return Timing.WaitUntilDone(waitHandle);
Debug.Log("Starting 5 second run.");
yield return Timing.WaitForSeconds(5f);
Debug.Log("Finished 5 second run.");
}