使用条件
天下没有免费的午餐,在我使用unity的那一刻,我就感觉到不自在,因为开源所以不知道底层实现,如果只是简单的做点简单游戏,那就无所谓的了,但真正用到实际地方的时候,就会发现一个挨着一个坑,然后你就跟着unity做各种妥协。如果开发中需要使用网络等等涉及到多线程的地方,就会用到c#的多线程,注意不是unity的协程,你要做的妥协参考下面(网友整理,我没去搜索)的:
1. 变量(都能指向相同的内存地址)都是共享的
2. 不是UnityEngine的API能在分线程运行
3. UnityEngine定义的基本结构(int,float,Struct定义的数据类型)可以在分线程计算,如 Vector3(Struct)可以 , 但Texture2d(class,根父类为Object)不可以。
4 UnityEngine定义的基本类型的函数可以在分线程运行,类的函数不能在分线程运行
unity设计之初应该就是一个单线程,不允许在另外的线程中进行渲染等等的工作可以理解,不然又要增加很多机制去处理这个问题,会给新来的人徒增很多烦恼,比如说我:
//class public class NIDataManager: MonoBehaviour {
public static NIDataManager GetInstance() { } }
/*thread*/ private void RequestEvevtThread() { while (!m_IsExitThread) { /*wait set event*/ m_EventWait.WaitOne(-1); /*reference SendRequestToService,error ,using subclass of MonoBehaviour in thread*/ if (NIDataManager.GetInstance().isServiceOpen())
{ if (0 == ServiceInterface.sendRequest((int)(m_EventParam.ActionType), m_EventParam.DurationTime, ref m_EventParam.Response)) { if (m_EventParam.DelegateCallback != null) { m_EventParam.DelegateCallback(m_EventParam.Response); } } else { // Debug.Log("sendRequest false"); continue; } } m_isProcessing = false; } }
如果你也这样干,恭喜你,会得到错误:CompareBaseObjectsInternal can only be called from the main thread.
关于这个错误的一些分析可以参考:http://forum.unity3d.com/threads/comparebaseobjectsinternal-error.184069/
委托是个坑
第一版思路
游戏通过向体感引擎申请动作识别结果,但是这个结果在处理结束之前,需要堵塞线程,交互设计本身是有问题的,如果在主线程做肯定不可能。OK ,一般情况下,游戏中角色申请完事件之后需要根据结果对人物的状态,如位置等等的进行修改,这其中肯定会涉及到unity的API ,看到这个地方之后我的第一个思路代码如下:
using System; using System.Collections.Generic; using System.Threading; public class NIEventThread { /*事件处理后的委托*/ public delegate void EventCallbackMethod(int bSuccess); /*Event申请使用的参数*/ public struct stEventParam { public int ActionType; //动作类型 public int DurationTime; //限定时间 public EventCallbackMethod DelegateCallback; //处理后的结果返回 public stEventParam(int type, int time, EventCallbackMethod func) { ActionType = type; DurationTime = time; DelegateCallback = func; } } /*线程中申请需要的参数*/ private stEventParam m_EventParam = new stEventParam(); /*线程控制量,等待申请事件*/ private AutoResetEvent m_WaitEvent = new AutoResetEvent(false); /*线程结束控制*/ private bool m_IsExitThread = false; /*线程,需要设置为后台线程*/ private Thread m_EventThread = null; public Thread GetThread { get { return m_EventThread; } } /*初始化的时候就打开线程*/ public NIEventThread() { StartThread(); } ~NIEventThread() { EndThread(); } /*创建和开启线程*/ private void StartThread() { m_EventThread = new Thread(this.RequestEvevtThread); m_EventThread.IsBackground = true; m_EventThread.Start(); } /*结束线程*/ private void EndThread() { m_IsExitThread = false; } /* 请求事件*/ public void RequestEvent(stEventParam param) { m_EventParam = param; m_WaitEvent.Set(); } /*线程处理方法*/ public void RequestEvevtThread() { while (!m_IsExitThread) { /*等待事件激活*/ m_WaitEvent.WaitOne(-1); Console.WriteLine("等你好久了" + "duration:" + m_EventParam.DurationTime); if (m_EventParam.DelegateCallback != null) { m_EventParam.DelegateCallback(1); } } } }
unity中的调用:
// Update is called once per frame void Update () { if (Input.GetKey(KeyCode.Space)) { //申请事件 t.RequestEvent(new stEventParam(3, 256,test)); } } void test(int t) { //掉用unity API transform.Rotate(new Vector3(30,0,0)); }
处理结果:
产生错误“InternalGetTransform can only be called from the main thread.”
也就是说:这个委托还在分支线程中执行,而不是在代码所在的主线程中执行,具体原因另外一篇文章讲解,其实在c#中控件也不允许在另外一个线程中进行控制,但是微软提供了方法,unity没提供或者说本身就不建议这么干。
怎么破?我这里给出我自己的思路,增加队列,在时间请求线程中先队列中增加要处理的事件,在unity主线程中取队列中的参数进行处理,OK ,看代码:
队列:
public sealed class RequestMessageQueue { private readonly static RequestMessageQueue mInstance = new RequestMessageQueue(); public static RequestMessageQueue GetInstance() { return mInstance; } public void EnQueue(stEventResult st) { m_Event.Enqueue(st); } public stEventResult DeQueue() { return m_Event.Dequeue(); } public int Count { get { return m_Event.Count; } } private RequestMessageQueue() { m_Event = new Queue<stEventResult>() ; } Queue<stEventResult> m_Event; }
unity中事件处理
public class RequestMessageHandle : MonoBehaviour { // Use this for initialization void Start () { } // Update is called once per frame void Update () { while (RequestMessageQueue.GetInstance().Count != 0) { stEventResult st = RequestMessageQueue.GetInstance().DeQueue(); st.DelegateCallback(st.result); } } }
再来看看线程中调用:
/*线程处理方法*/ public void RequestEvevtThread() { while (!m_IsExitThread) { /*等待事件激活*/ m_WaitEvent.WaitOne(-1); Console.WriteLine("等你好久了" + "duration:" + m_EventParam.DurationTime); if (m_EventParam.DelegateCallback != null) { //m_EventParam.DelegateCallback(1); RequestMessageQueue.GetInstance().EnQueue(new stEventResult(m_EventParam.ActionType,1,m_EventParam.DelegateCallback)); } } }
修改完之后,unity中原先的逻辑不需要更该,就可以看到测试中按键点击后,物体的旋转。