使用條件
天下沒有免費的午餐,在我使用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中原先的邏輯不需要更該,就可以看到測試中按鍵點擊后,物體的旋轉。