从线程更改主线程的游戏对象
Change GameObject at main thread from an Thread
我想以某种方式从异步线程更改游戏对象。我需要保留 listener/interface 架构。
下面我创建了一个我当前实现的简单示例。
主要脚本:
using UnityEngine;
public class MyScript : MonoBehaviour, IMyComponentListener
{
public GameObject cube;
private MyComponent _myComponent;
private void Start()
{
_myComponent = new MyComponent(this);
}
public void OnThreadCompleted()
{
cube.transform.Rotate(Vector3.up, 10f);
}
}
线程脚本:
using System.Threading;
public class MyComponent
{
private readonly IMyComponentListener _listener;
public MyComponent(IMyComponentListener listener)
{
_listener = listener;
Thread myThread = new Thread(Run);
myThread.Start();
}
private void Run()
{
Thread.Sleep(1000);
_listener.OnThreadCompleted();
}
}
侦听器接口:
public interface IMyComponentListener
{
void OnThreadCompleted();
}
如果我尝试此代码,我将遇到以下错误:
get_transform can only be called from the main thread.
Constructors and field initializers will be executed from the loading thread when loading a scene.
Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
UnityEngine.GameObject:get_transform()
MyScript:OnThreadCompleted() (at Assets/Scripts/MyScript.cs:18)
MyComponent:Run() (at Assets/Scripts/MyComponent.cs:20)
很明显我不能从异步线程更改主线程元素,但我该如何解决它或正确实现支持它的架构?
我想出了一个使用外部组件的解决方法 UnityMainThreadDispatcher。
在遵循 installation instructions 之后,我已将我的调度程序更新为以下示例并发挥了魅力!
线程脚本:
using System.Threading;
public class MyComponent
{
private readonly IMyComponentListener _listener;
public MyComponent(IMyComponentListener listener)
{
_listener = listener;
Thread myThread = new Thread(Run);
myThread.Start();
}
private void Run()
{
Thread.Sleep(1000);
UnityMainThreadDispatcher.Instance().Enqueue(() => _listener.OnThreadCompleted());
}
}
这只是一个例子,因为你要求它。
一般来说,您的 link 中的 UnityMainThreadDispatcher
没问题。
无论如何,它只是在 Update
方法中处理队列。我不想使用这个 Singleton-Pattern 解决方案,而是简单地在你已有的 MonoBehaviour
组件中做同样的事情(Singleton 在大多数情况下只是一个快速但肮脏的解决方案)
public class MyScript : MonoBehaviour, IMyComponentListener
{
public GameObject cube;
private MyComponent _myComponent;
private ConcurrentQueue<Action> callbacks = new ConcurrentQueue<Action>();
private void Start()
{
_myComponent = new MyComponent(this);
}
// Work the callbacks queue
private void Update()
{
if(callbacks.Count == 0) return;
while(callbacks.Count != 0)
{
Action a;
if(!callbacks.TryDequeue(out a)) continue;
a.Invoke();
}
}
public void ThreadCompleted()
{
callbacks.Enqueue(OnThreadCompleted);
}
private void OnThreadCompleted()
{
cube.transform.Rotate(Vector3.up, 10f);
}
}
而且你会在那个上面调用 enqueue
,例如喜欢
((MyScript)_listener).ThreadCompleted();
问题可能出在此处的类型转换.. 或者您可以将方法添加到接口中。
如果您有多个侦听器(这似乎是由于界面的原因),我也会使用您的方式,也许仍然没有单例,而是使用适当的引用,例如通过检查器。
在我的智能手机上打字,所以没有保修,但我希望我能表达清楚
我想以某种方式从异步线程更改游戏对象。我需要保留 listener/interface 架构。
下面我创建了一个我当前实现的简单示例。
主要脚本:
using UnityEngine;
public class MyScript : MonoBehaviour, IMyComponentListener
{
public GameObject cube;
private MyComponent _myComponent;
private void Start()
{
_myComponent = new MyComponent(this);
}
public void OnThreadCompleted()
{
cube.transform.Rotate(Vector3.up, 10f);
}
}
线程脚本:
using System.Threading;
public class MyComponent
{
private readonly IMyComponentListener _listener;
public MyComponent(IMyComponentListener listener)
{
_listener = listener;
Thread myThread = new Thread(Run);
myThread.Start();
}
private void Run()
{
Thread.Sleep(1000);
_listener.OnThreadCompleted();
}
}
侦听器接口:
public interface IMyComponentListener
{
void OnThreadCompleted();
}
如果我尝试此代码,我将遇到以下错误:
get_transform can only be called from the main thread.
Constructors and field initializers will be executed from the loading thread when loading a scene.
Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
UnityEngine.GameObject:get_transform()
MyScript:OnThreadCompleted() (at Assets/Scripts/MyScript.cs:18)
MyComponent:Run() (at Assets/Scripts/MyComponent.cs:20)
很明显我不能从异步线程更改主线程元素,但我该如何解决它或正确实现支持它的架构?
我想出了一个使用外部组件的解决方法 UnityMainThreadDispatcher。
在遵循 installation instructions 之后,我已将我的调度程序更新为以下示例并发挥了魅力!
线程脚本:
using System.Threading;
public class MyComponent
{
private readonly IMyComponentListener _listener;
public MyComponent(IMyComponentListener listener)
{
_listener = listener;
Thread myThread = new Thread(Run);
myThread.Start();
}
private void Run()
{
Thread.Sleep(1000);
UnityMainThreadDispatcher.Instance().Enqueue(() => _listener.OnThreadCompleted());
}
}
这只是一个例子,因为你要求它。
一般来说,您的 link 中的 UnityMainThreadDispatcher
没问题。
无论如何,它只是在 Update
方法中处理队列。我不想使用这个 Singleton-Pattern 解决方案,而是简单地在你已有的 MonoBehaviour
组件中做同样的事情(Singleton 在大多数情况下只是一个快速但肮脏的解决方案)
public class MyScript : MonoBehaviour, IMyComponentListener
{
public GameObject cube;
private MyComponent _myComponent;
private ConcurrentQueue<Action> callbacks = new ConcurrentQueue<Action>();
private void Start()
{
_myComponent = new MyComponent(this);
}
// Work the callbacks queue
private void Update()
{
if(callbacks.Count == 0) return;
while(callbacks.Count != 0)
{
Action a;
if(!callbacks.TryDequeue(out a)) continue;
a.Invoke();
}
}
public void ThreadCompleted()
{
callbacks.Enqueue(OnThreadCompleted);
}
private void OnThreadCompleted()
{
cube.transform.Rotate(Vector3.up, 10f);
}
}
而且你会在那个上面调用 enqueue
,例如喜欢
((MyScript)_listener).ThreadCompleted();
问题可能出在此处的类型转换.. 或者您可以将方法添加到接口中。
如果您有多个侦听器(这似乎是由于界面的原因),我也会使用您的方式,也许仍然没有单例,而是使用适当的引用,例如通过检查器。
在我的智能手机上打字,所以没有保修,但我希望我能表达清楚