Unity時鍾定時器插件——Vision Timer源碼分析之一


因為項目中,UI的所有模塊都沒有MonBehaviour類(純粹的C#類),只有像NGUI的基本組件的類是繼承MonoBehaviour。因為沒有繼承MonoBehaviour,這也不能使用Update,InVoke,StartCoroutine等方法,這樣就會顯得很蹩腳。后來一個同事添加vp_Timer和vp_TimeUtility這兩個類。后來研究了下vp_Timer至少可以彌補沒有Update,InVoke,InVokeRepeat的不足。

 

 

      之前用的時候,就粗略的研究過vp_Timer這個類,一直就想仔細剖析下,但是由於不僅工作任務中,自己也有很多要瑣碎的事情,我都很久自己好好學習了,雖然還有一堆事情要做,但是憋了太久了,先滿足下自己,所以才有開始認真分析vp_Timer的代碼並才有這篇博客。

       

       vp_Timer 一共有3個class,都各司其職:vp_Timer,Event,Handle

                 1)vp_Timer:提供的使用接口,通過靜態方法vp_Timer.In(),加入定時器事件(函數,這里將傳入的函數稱為事件)

                 2)Event:用來封裝傳入的事件(函數),保持事件的狀態

                 3)Handle:對事件狀態提供查詢接口(事件執行了多長時間,結束時間,是否還是Active)以及提供 Excute(立即執行事件),Cancel(取消事件),Pause(暫停事件)等操作

       很容易就可以理清這三者的關系,通過vp_Timer.In方法將傳入的事件(函數)封裝為Event對象,然后返回(雖然是通過參數)Handle對象讓調用者可以查詢事件的狀態和進行相關操作。

       

vp_Timer:

       先看下vp_Timer的成員變量(c#應該稱為filed)

GameObject m_MainObject:vp_Timer是一個MonoBehaviour,就一定要掛載GameObject上,m_MainObject會在第一次調用vp_Timer.In方法時創建:

 

// setup main gameobject  
if (m_MainObject == null)  
{  
    m_MainObject = new GameObject("Timers");  
    m_MainObject.AddComponent<vp_Timer>();  
    UnityEngine.Object.DontDestroyOnLoad(m_MainObject);  
 
#if (UNITY_EDITOR && !DEBUG)  
    m_MainObject.gameObject.hideFlags = HideFlags.HideInHierarchy;  
#endif  
}  

  

List<Event> m_Active 和 List<Event> m_Pool :這個List都是Event的緩存,其中,m_Active緩存Active的Event,m_Pool緩存無效的Event,這里的Acitive是事件仍然需要執行,無效說明不會再被調用。之所有要緩存無效的Event,是為了節省創建Event對象的消耗。m_Pool就好比垃圾箱,m_Active是一個成品工廠,每次m_Active要生產(Add)新的Event,都去m_Pool取沒用的原料(Event),當m_Active的成品沒用了,用放會m_Pool中去,這樣就達到了循環利用作用。

 

 Event m_NewEvent :在Schedule方法里使用的變量,其實完全可以聲明為Schedule的局部變量,為了節省重復創建和銷毀的消耗,vp_Timer就聲明一個成員變量。

private static int m_EventCount = 0;

 

// variables for the Update method

int m_EventBatch 和int m_EventIterator:在Update使用的變量,m_EventBatch記錄在一次Update中執行事件的次數,m_EventIterator記錄是每次執行事件在m_Active的索引。

 

int MaxEventsPerFrame :一次循環(Update)執行事件最大次數

 

       假設MaxEventPerFarme = 10 , m_Active.Count = 5,那么每次Update都會遍歷2次m_Active的Event,看是否可以執行(調用Excute函數)。這樣就可以理解這三個參數的具體含義了。

 

Event:

private class Event  
    {  
  
        public int Id;   //標記Event,如果Id = 0 ,表示該Event已經無效,就被Add進m_Pool中,Handle對象和Evnt就是通過Id來關聯的  
  
        public Callback Function = null;   //函數委托 Callback和ArgCallback是vp.Timer定義的函數委托(原型)  
        public ArgCallback ArgFunction = null;  
        public object Arguments = null;  
  
        public int Iterations = 1;   //事件的迭代(執行次數)  
        public float Interval = -1.0f;   //執行時間間隔  
        public float DueTime = 0.0f;    //下一個事件執行的時間 DueTime = Time.time + Time.deltaTime  
        public float StartTime = 0.0f;   //事件開始執行事件 StartTime = Time.Time + delayTime  
        public float LifeTime = 0.0f;   //事件累積的總時間 LifeTime += Time.deltaTime  
        public bool Paused = false;  
 
#if (DEBUG && UNITY_EDITOR)  
        private string m_CallingMethod = "";  
#endif  
               //省略其他代碼  
       }  

  

當然還有幾個方法:

       Excute():執行Function和ArgFunction

       Recyle():

private void Recycle()  
{  
  
    Id = 0;  
    DueTime = 0.0f;  
    StartTime = 0.0f;  
  
    Function = null;  
    ArgFunction = null;  
    Arguments = null;  
  
    if (vp_Timer.m_Active.Remove(this))  //從m_Active進入m_Pool  
        m_Pool.Add(this);  
 
#if (UNITY_EDITOR && DEBUG)  
    EditorRefresh();  
#endif  
  
}  

MethodName:由於D.S.Qiu對delegate還沒有深入研究理解,目前還說不清如何比較兩個委托是否相等,但是得到一個經驗就是不能用 函數 來比較,所以看到很多插件(最典型的就是Unity的StopCoroutine只有字符串作為參數和NGUI的EventDelegate)都使用的字符串來標記delegate,看下面的代碼:

 

public string MethodName  
{  
    get  
    {  
        if (Function != null)  
        {  
            if (Function.Method != null)  
            {  
                if (Function.Method.Name[0] == '<')  
                    return "delegate";  
                else return Function.Method.Name;  
            }  
        }  
        else if (ArgFunction != null)  
        {  
            if (ArgFunction.Method != null)  
            {  
                if (ArgFunction.Method.Name[0] == '<')  
                    return "delegate";  
                else return ArgFunction.Method.Name;  
            }  
        }  
        return null;  
    }  
}  

這樣vp_Timer才有Cancel(string methodName)的方法:

 

public static void CancelAll(string methodName)  
{  
    for (int t = vp_Timer.m_Active.Count - 1; t > -1; t--)  
    {  
        if (vp_Timer.m_Active[t].MethodName == methodName)  
            vp_Timer.m_Active[t].Id = 0;  
    }  
}  

 

Handle:

        前面介紹過,Handle是用來查詢和操作Event的對象,Handle對象和Event桶Id關聯起來。

public int Id  
{  
    get  
    {  
        return m_Id;  
    }  
    set  
    {  
        m_Id = value;  
  
        if (m_Id == 0)  
        {  
            m_Event.DueTime = 0.0f;  
            return;  
        }  
  
        m_Event = null;  
        for (int t = vp_Timer.m_Active.Count - 1; t > -1; t--)  
        {  
            if (vp_Timer.m_Active[t].Id == m_Id)  
            {  
                m_Event = vp_Timer.m_Active[t];  
                break;  
            }  
        }  
        if (m_Event == null)  
            UnityEngine.Debug.LogError("Error: (vp_Timer.Handle) Failed to assign event with Id '" + m_Id + "'.");  
  
        // store some initial event info  
        m_StartIterations = m_Event.Iterations;  
        m_FirstDueTime = m_Event.DueTime;  
  
    }  
}  

  

 

小結:

       D.S.Qiu覺得在項目中很有必要有“管理”的思想,很多功能都是用一個類實現的,其他人只要調用就可以了,具體的邏輯只需要在一個類內部維護,可以做的統一控制,可以做到更自如,就拿vp_Timer和MonoBehaviour的InVokeRepeat方法來對比就有明顯的優勢:

               1)vp_Timer可以隨時查詢事件的狀態(事件被執行了次數等)還可以暫停事件,而InVokeRepeat做不到的

               2)vp_Timer可以設置時間delatTime受不受Time.timeScale影響,而InVokeRepeat是沒有這個參數設置的

               3)vp_Timer可以對事件進行統一的管理,如果暫停所有事件的執行,這個點當Time.timeScale = 0 時特別管用,而InVokeRepeat是分散的,沒有統一管理其他。

 這有點“一夫當萬夫莫開”的感覺。

       不管是InVokeRepeat方法,MonoBehaviour的很多方法都有類似的缺陷,因為每一個MonoBehaviour都可以調用這些方法,就不能統一起來管理了,所以如果Unity當初能寫一個專門的類我想會方便很多。

 

       雖然覺得vp_Timer用的很爽,但是D.S.Qiu還是覺得有很多可以改進的地方:

              1)vp_Timer提供Pause(string methodName)和PauseAll()的方法,從“管理”的角度上就更加完美了,當然還有對應的Play方法。

              2)當Event的參數: Iterations 和  Interval 沒有很好處理 Interval 和 Time.deltaTime 的具體情況,假設我們的 Iterations =100 , interval = 0.01f  即我需要達到1s內執行100次的目的,但按照vp_Timer的實現結果是執行了100次,但是時間一定是>= 1s,即當Time.deltaTime > interval 時,還是只執行一次,例如 Time.deltaTime = 0.02f, 理論上我們希望能執行兩次,但是卻只執行了一次。

             3)vp_Timer要是提供 string methodName 到 Event 或 Handle 的查詢接口就更加完美了。

             4)vp_Timer雖然用了很多設計,對象的重復利用避免 new 和銷毀對象的系統開銷,但是專門用Handle專門管理Event,Handle的的功能只是對Event的一個封裝,其實完全沒有必要,完全可以讓Event自己充當Handle的角色,直接返回Event對象會更直觀,只有在回調的時候用參數返回關聯的對象,要不然采用直接返回會更明白。

 

Vision Timer下載地址:

https://item.taobao.com/item.htm?spm=0.7095261.0.0.70991debBfRIWJ&id=565519569907

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM