在 Unity Coroutine 中等待事件?
Waiting for event inside Unity Coroutine?
所以我有一个 Unity 协程方法,其中有一些对象。这些对象表示从某处服务器收集的值,并在准备就绪时发出 Updated
事件。
我想知道在 Unity 的协同程序中等待所有值更新的最佳方法是什么。
public IEnumerator DoStuff()
{
foreach(var val in _updateableValues)
{
if (val.Value != null) continue;
else **wait for val.Updated to be fired then continue**
}
//... here I do stuff with all the values
// I use the WWW class here, so that's why it's a coroutine
}
做这样的事情最好的方法是什么?
谢谢!
自旋锁是一种解决方案,但不是非常 CPU 温和的解决方案。在自旋锁中,您只需等待变量具有特定值,否则会休眠几毫秒。
public IEnumerator DoStuff()
{
/* wait until we have the value we want */
while( value != desiredValue)
yield return new WaitForSeconds(0.001f);
//after this loop, start the real processing
}
也许您可能想要考虑重构您的代码,这样就不需要自旋锁,但可以实施更基于 interupt/event-based 的方法。这意味着,如果您更新了一个值并且在它发生后必须发生某些事情,请在更改该值后直接将其启动。在 C# 中,甚至还有一个用于该设计模式的接口 INotifyPropertyChanged
(请参阅 MSDN),但您也可以轻松地自己设计它,例如通过在特定值更改时触发事件。如果你想要一个比自旋锁更好的解决方案,我们需要更多关于你到底想在这里做出什么反应的信息,但这应该会给你一些想法。
没有内置的直接方法来等待事件本身,但是您可以使用同步嵌套协程来等待事件设置的标志:
//Flag
bool eventHappened;
//Event subscriber that sets the flag
void OnEvent(){
eventHappened=true;
}
//Coroutine that waits until the flag is set
IEnumerator WaitForEvent() {
yield return new WaitUntil(eventHappened);
eventHappened=false;
}
//Main coroutine, that stops and waits somewhere within it's execution
IEnumerator MainCoroutine(){
//Other stuff...
yield return StartCoroutine(WaitForEvent());
//Oher stuff...
}
考虑到这一点,创建一个等待 UnityEvent
的通用协程很容易:
private IEnumerator WaitUntilEvent(UnityEvent unityEvent) {
var trigger = false;
Action action = () => trigger = true;
unityEvent.AddListener(action.Invoke);
yield return new WaitUntil(()=>trigger);
unityEvent.RemoveListener(action.Invoke);
}
我直接在需要的地方使用WaitUntil。不过我发现,WaitUntil 不需要布尔值,而是需要一个布尔谓词函数 - 所以我在下面的示例中提供了这个。
示例是来自游戏的真实 运行 代码。它在第一个场景中运行,在我开始播放音乐之前 - 稍微冗长 - 加载其他内容。
//
public class AudioSceneInitializer : MonoBehaviour
{
[SerializeField] private GlobalEventsSO globalEvents;
//globalEvents is a scriptable object that - in my case - holds all events in the game
[SerializeField] private AudioManager audioManager;
//The audio manager which in my case will raise the audio ready event
[SerializeField] public GameObject audioGO;// contains AudioSources used by AudioManager
private bool audioReady = false;
// Start is called before the first frame update
IEnumerator Start()
{
if (audioManager != null)
//better check, if we don't produce the event, we wait forever
{
//subscribe to event
globalEvents.audioManagerReadyEvent += OnAudioReady;
//do something to raise the event - else we wait forever
audioManager.onInitialization(this);//"this" needed to hand over the audioGO to the AudioManager
// waits until the flag is set;
yield return new WaitUntil(IsAudioReady);
}
//now that we have music playing, we load the actual game
SceneManager.LoadSceneAsync(1, LoadSceneMode.Additive);
}
//Event subscriber that sets the flag
void OnAudioReady()
{
//unsubscribing, if - as in my case - it happens only once in the game
globalEvents.audioManagerReadyEvent -= OnAudioReady;
audioReady = true;
}
//predicate function that WaitUntil expects
private bool IsAudioReady()
{
return audioReady;
}
public void OnDisable()
{
audioManager.onCleanup();
}
}
所以我有一个 Unity 协程方法,其中有一些对象。这些对象表示从某处服务器收集的值,并在准备就绪时发出 Updated
事件。
我想知道在 Unity 的协同程序中等待所有值更新的最佳方法是什么。
public IEnumerator DoStuff()
{
foreach(var val in _updateableValues)
{
if (val.Value != null) continue;
else **wait for val.Updated to be fired then continue**
}
//... here I do stuff with all the values
// I use the WWW class here, so that's why it's a coroutine
}
做这样的事情最好的方法是什么?
谢谢!
自旋锁是一种解决方案,但不是非常 CPU 温和的解决方案。在自旋锁中,您只需等待变量具有特定值,否则会休眠几毫秒。
public IEnumerator DoStuff()
{
/* wait until we have the value we want */
while( value != desiredValue)
yield return new WaitForSeconds(0.001f);
//after this loop, start the real processing
}
也许您可能想要考虑重构您的代码,这样就不需要自旋锁,但可以实施更基于 interupt/event-based 的方法。这意味着,如果您更新了一个值并且在它发生后必须发生某些事情,请在更改该值后直接将其启动。在 C# 中,甚至还有一个用于该设计模式的接口 INotifyPropertyChanged
(请参阅 MSDN),但您也可以轻松地自己设计它,例如通过在特定值更改时触发事件。如果你想要一个比自旋锁更好的解决方案,我们需要更多关于你到底想在这里做出什么反应的信息,但这应该会给你一些想法。
没有内置的直接方法来等待事件本身,但是您可以使用同步嵌套协程来等待事件设置的标志:
//Flag
bool eventHappened;
//Event subscriber that sets the flag
void OnEvent(){
eventHappened=true;
}
//Coroutine that waits until the flag is set
IEnumerator WaitForEvent() {
yield return new WaitUntil(eventHappened);
eventHappened=false;
}
//Main coroutine, that stops and waits somewhere within it's execution
IEnumerator MainCoroutine(){
//Other stuff...
yield return StartCoroutine(WaitForEvent());
//Oher stuff...
}
考虑到这一点,创建一个等待 UnityEvent
的通用协程很容易:
private IEnumerator WaitUntilEvent(UnityEvent unityEvent) {
var trigger = false;
Action action = () => trigger = true;
unityEvent.AddListener(action.Invoke);
yield return new WaitUntil(()=>trigger);
unityEvent.RemoveListener(action.Invoke);
}
我直接在需要的地方使用WaitUntil。不过我发现,WaitUntil 不需要布尔值,而是需要一个布尔谓词函数 - 所以我在下面的示例中提供了这个。
示例是来自游戏的真实 运行 代码。它在第一个场景中运行,在我开始播放音乐之前 - 稍微冗长 - 加载其他内容。
//
public class AudioSceneInitializer : MonoBehaviour
{
[SerializeField] private GlobalEventsSO globalEvents;
//globalEvents is a scriptable object that - in my case - holds all events in the game
[SerializeField] private AudioManager audioManager;
//The audio manager which in my case will raise the audio ready event
[SerializeField] public GameObject audioGO;// contains AudioSources used by AudioManager
private bool audioReady = false;
// Start is called before the first frame update
IEnumerator Start()
{
if (audioManager != null)
//better check, if we don't produce the event, we wait forever
{
//subscribe to event
globalEvents.audioManagerReadyEvent += OnAudioReady;
//do something to raise the event - else we wait forever
audioManager.onInitialization(this);//"this" needed to hand over the audioGO to the AudioManager
// waits until the flag is set;
yield return new WaitUntil(IsAudioReady);
}
//now that we have music playing, we load the actual game
SceneManager.LoadSceneAsync(1, LoadSceneMode.Additive);
}
//Event subscriber that sets the flag
void OnAudioReady()
{
//unsubscribing, if - as in my case - it happens only once in the game
globalEvents.audioManagerReadyEvent -= OnAudioReady;
audioReady = true;
}
//predicate function that WaitUntil expects
private bool IsAudioReady()
{
return audioReady;
}
public void OnDisable()
{
audioManager.onCleanup();
}
}