前言
Unity的大部分API(例如,与游戏对象交互,修改组件属性等)都需要在主线程中调用。然而,偶然你可能在另一个线程(例如,网络请求,长时间运行的盘算等)中完成一些工作,并且在完成后需要更新Unity的某些东西。在这种情况下,你不能直接从那个线程调用Unity API,因为这可能会导致未定义的举动或错误。
虽然你可以在其他线程中举行盘算麋集型的使命(例如,AI盘算,物理模拟等),但是你不能在其他线程中直接调用Unity的API。因此你需要类似DwkUnityMainThreadDispatcher如许的类,它可以让你在其他线程中安全地调用Unity的API。
例如,你可以如许使用它:
- DwkUnityMainThreadDispatcher.Instance().Enqueue(() =>
- {
- // 在这里调用Unity API
- });
复制代码
UnityMainThreadDispatcher类代码
DwkUnityMainThreadDispatcher类原貌:
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- public class DwkUnityMainThreadDispatcher : MonoBehaviour
- {
- private static DwkUnityMainThreadDispatcher instance;
- private readonly Queue<System.Action> actions = new Queue<System.Action>();
- private void Awake()
- {
- if (instance == null)
- {
- instance = this;
- DontDestroyOnLoad(gameObject);
- }
- else
- {
- Destroy(gameObject);
- }
- }
- public static DwkUnityMainThreadDispatcher Instance()
- {
- if (!instance)
- {
- throw new System.Exception("UnityMainThreadDispatcher could not find the UnityMainThreadDispatcher object. Please ensure you have added the MainThreadExecutor Prefab to your scene.");
- }
- return instance;
- }
- public void Enqueue(System.Action action)
- {
- lock (actions)
- {
- actions.Enqueue(action);
- }
- }
- public void Update()
- {
- while (actions.Count > 0)
- {
- actions.Dequeue().Invoke();
- }
- }
- }
复制代码
举例:在非主线程中播放Unity声音
- if (sum > 0)
- {
- DwkUnityMainThreadDispatcher.Instance().Enqueue(() =>
- {
- audioSource1.PlayOneShot(audioSource1.clip);
- });
- //Debug.Log("加分特效");
- }
复制代码
附录:
实现单例模式的标准流程
- private static DwkUnityMainThreadDispatcher instance;
- private void Awake()
- {
- if (instance == null)
- {
- instance = this;
- DontDestroyOnLoad(gameObject);
- }
- else
- {
- Destroy(gameObject);
- }
- }
- public static DwkUnityMainThreadDispatcher Instance()
- {
- if (!instance)
- {
- throw new System.Exception("UnityMainThreadDispatcher could not find the UnityMainThreadDispatcher object. Please ensure you have added the MainThreadExecutor Prefab to your scene.");
- }
- return instance;
- }
复制代码
这个单例模式的实现步骤如下:
- 声明一个私有静态变量instance来存储单例实例。
- 在Awake()方法中,检查instance是否为null。如果为null,则将instance设置为当前实例,并使用DontDestroyOnLoad(gameObject)确保在加载新场景时不会销毁这个对象。如果instance不为null,则销毁当前游戏对象,如许可以确保只有一个DwkUnityMainThreadDispatcher实例存在。
- 提供一个公共静态方法Instance()来获取单例实例。如果instance为null,则抛出一个异常,提示用户需要在场景中添加MainThreadExecutor Prefab。
这种方式的单例模式在Unity中很常见,因为它可以确保在整个游戏运行期间,只有一个DwkUnityMainThreadDispatcher实例存在,而且可以在任何地方通过DwkUnityMainThreadDispatcher.Instance()访问这个实例。
允许其他线程将使命安全地调度到主线程中执行
- private readonly Queue<System.Action> actions = new Queue<System.Action>();
- public void Enqueue(System.Action action)
- {
- lock (actions)
- {
- actions.Enqueue(action);
- }
- }
- public void Update()
- {
- while (actions.Count > 0)
- {
- actions.Dequeue().Invoke();
- }
- }
复制代码
- 在其他线程中,当你需要执行一些必须在主线程中举行的操作(例如,调用Unity的API)时,你可以创建一个System.Action,这个System.Action包含了你想要执行的操作。
- 你将这个System.Action通报给Enqueue方法。Enqueue方法将这个System.Action添加到actions队列中。这个过程是线程安全的,因为Enqueue方法使用了lock关键字来确保在多线程环境下,添加使命到队列的操作是线程安全的。
- 在主线程中,Unity每一帧都会调用Update方法。在Update方法中,它会检查actions队列中是否有使命。如果有,它会使用Dequeue方法取出队列中的第一个使命,并使用Invoke方法执行这个使命。这个过程会一直举行,直到actions队列中没有使命为止。
如许,你就可以在其他线程中安全地调度使命到主线程中执行了。这对于在其他线程中举行一些耗时的操作,然后需要更新Unity的某些东西(例如,更新UI,创建游戏对象等)非常有效。
- private readonly Queue<System.Action> actions = new Queue<System.Action>(); 这行代码创建了一个队列,用于存储要在主线程中执行的使命。每个使命都是一个System.Action,也就是一个无参数且无返回值的委托。
- public void Enqueue(System.Action action) 这个方法用于将一个使命添加到队列中。这个使命将在主线程中执行。lock (actions)这行代码确保了在多线程环境下,添加使命到队列的操作是线程安全的。
- public void Update() 这个方法在每一帧都会被Unity调用,它在主线程中执行。在这个方法中,它会执行队列中的全部使命。这是通过在队列中有使命时调用actions.Dequeue().Invoke()来实现的。Dequeue()方法移除并返回队列中的第一个使命,Invoke()方法则执行这个使命。
这部分代码提供了一种在主线程中安全执行使命的方式。你可以在任何线程中使用Enqueue方法来添加一个使命,这个使命将在主线程中执行。如许,你就可以安全地从任何线程调用Unity API了。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |