本文為博主原創(chuàng)文章,歡迎轉(zhuǎn)載,請保留出處:http://blog.csdn.net/andrewfan

Unity編輯器中何時需要協(xié)程

當(dāng)我們定制Unity編輯器的時候,往往需要啟動額外的協(xié)程或者線程進(jìn)行處理。比如當(dāng)執(zhí)行一些界面更新的時候,需要大量計算,如果用戶在不斷修正一個參數(shù),比如從1變化到2,這種變化過程要經(jīng)歷無數(shù)中間步驟,調(diào)用N多次Update,如果直接在Update中不斷刷新,界面很容易直接卡死。所以在一個協(xié)程中進(jìn)行一些優(yōu)化,只保留用戶最后一次參數(shù)修正,省去中間步驟,就會好很多。這屬于Unity編輯器的內(nèi)容,也屬于優(yōu)化的內(nèi)容,還是放在優(yōu)化中吧。

解決問題思路

Unity官網(wǎng)的questions里面也有很多人在搜索這個問題,不過后來是看到有個人提到了這個方法。問題的關(guān)鍵點(diǎn)就是“EditorApplication.update ”,有個這樣的方法,你把要執(zhí)行的協(xié)程傳遞給它就可以在編輯器下自動執(zhí)行循環(huán)調(diào)用。

老外的寫法

當(dāng)然,后來我也找到一個老外的寫法,代碼貼出來如下:

using UnityEngine;using UnityEditor;using System.Collections;using System.Collections.Generic;using System.Runtime.CompilerServices;public static class EditorCoroutineRunner{    private class EditorCoroutine : IEnumerator
    {        private Stack<IEnumerator> executionStack;        public EditorCoroutine(IEnumerator iterator)        {            this.executionStack = new Stack<IEnumerator>();            this.executionStack.Push(iterator);
        }        public bool MoveNext()        {
            IEnumerator i = this.executionStack.Peek();            if (i.MoveNext())
            {                object result = i.Current;                if (result != null && result is IEnumerator)
                {                    this.executionStack.Push((IEnumerator)result);
                }                return true;
            }            else
            {                if (this.executionStack.Count > 1)
                {                    this.executionStack.Pop();                    return true;
                }
            }            return false;
        }        public void Reset()        {            throw new System.NotSupportedException("This Operation Is Not Supported.");
        }        public object Current
        {            get { return this.executionStack.Peek().Current; }
        }        public bool Find(IEnumerator iterator)        {            return this.executionStack.Contains(iterator);
        }
    }    private static List<EditorCoroutine> editorCoroutineList;    private static List<IEnumerator> buffer;    public static IEnumerator StartEditorCoroutine(IEnumerator iterator)    {        if (editorCoroutineList == null)
        {
            editorCoroutineList = new List<EditorCoroutine>();
        }        if (buffer == null)
        {
            buffer = new List<IEnumerator>();
        }        if (editorCoroutineList.Count == 0)
        {
            EditorApplication.update += Update;
        }        // add iterator to buffer first
        buffer.Add(iterator);        return iterator;
    }    private static bool Find(IEnumerator iterator)    {        // If this iterator is already added
        // Then ignore it this time
        foreach (EditorCoroutine editorCoroutine in editorCoroutineList)
        {            if (editorCoroutine.Find(iterator))
            {                return true;
            }
        }        return false;
    }    private static void Update()    {        // EditorCoroutine execution may append new iterators to buffer
        // Therefore we should run EditorCoroutine first
        editorCoroutineList.RemoveAll
        (
            coroutine => { return coroutine.MoveNext() == false; }
        );        // If we have iterators in buffer
        if (buffer.Count > 0)
        {            foreach (IEnumerator iterator in buffer)
            {                // If this iterators not exists
                if (!Find(iterator))
                {                    // Added this as new EditorCoroutine
                    editorCoroutineList.Add(new EditorCoroutine(iterator));
                }
            }            // Clear buffer
            buffer.Clear();
        }        // If we have no running EditorCoroutine
        // Stop calling update anymore
        if (editorCoroutineList.Count == 0)
        {
            EditorApplication.update -= Update;
        }
    }
}

用法就是大概在你自己的類的Start方法中稍作修改,再增加一個協(xié)程函數(shù),如下:

        void Start()        {
            rope = gameObject.GetComponent<QuickRope>();            #if UNITY_EDITOR
            //調(diào)用方法
            EditorCoroutineRunner.StartEditorCoroutine(OnThreadLoop());            #endif
        }        public IEnumerator OnThreadLoop()        {            while(true)
            {
                Debug.Log("Looper");                yield return null;
            }
        }

當(dāng)然最好是加上#if UNITY_EDITOR預(yù)處理了。這個類基本是滿足要求了。如果你把你自己的腳本做了這樣的修改之后,它是可以在編輯狀態(tài)不斷執(zhí)行到Loop的,要注意它需要先執(zhí)行到Start,也就是說,你可能需要把GameObject做成Prefab,然后把它從場景中刪除,再把Prefab拖回場景,才會在編輯狀態(tài)下觸發(fā)腳本上的Star方法,從而激發(fā)Loop。

我的寫法

然而,用久了你就會發(fā)現(xiàn)幾個問題,一旦Loop開始了,你是無法停止的,哪怕你把GameObject從場景中刪掉都無濟(jì)于事,當(dāng)然隱藏也沒有效果。為了解決這個問題,也把腳本弄得簡單點(diǎn)兒,我重寫了這個腳本,希望需要的同學(xué)可以愉快地使用。

using UnityEngine;using UnityEditor;using System.Collections;using System.Collections.Generic;using System.Runtime.CompilerServices;public static class EditorCoroutineLooper{    private static Dictionary<IEnumerator,MonoBehaviour> m_loopers = new Dictionary<IEnumerator,MonoBehaviour> ();    private static bool M_Started = false;    /// <summary>
    /// 開啟Loop
    /// </summary>
    /// <param name="mb">腳本</param>
    /// <param name="iterator">方法</param>
    public static void StartLoop(MonoBehaviour mb, IEnumerator iterator)    {        if(mb!=null && iterator != null)
        {            if(!m_loopers.ContainsKey(iterator))
            {
                m_loopers.Add(iterator,mb);
            }            else
            {
                m_loopers[iterator]=mb;
            }
        }        if (!M_Started)
        {
            M_Started = true;
            EditorApplication.update += Update;
        }
    }    private static List<IEnumerator> M_DropItems=new List<IEnumerator>();    private static void Update()    {        if (m_loopers.Count > 0)
        {            
            var allItems = m_loopers.GetEnumerator();            while(allItems.MoveNext())
            {                var item = allItems.Current;                var mb = item.Value;                //卸載時丟棄Looper
                if(mb == null)
                {
                    M_DropItems.Add(item.Key);                    continue;
                }                //隱藏時別執(zhí)行Loop
                if(!mb.gameObject.activeInHierarchy)
                {                    continue;
                }                //執(zhí)行Loop,執(zhí)行完畢也丟棄Looper
                IEnumerator ie = item.Key;                if(!ie.MoveNext())
                {
                    M_DropItems.Add(item.Key);
                }
            }            //集中處理丟棄的Looper
            for(int i = 0;i < M_DropItems.Count;i++)
            {                if(M_DropItems[i] != null)
                {
                    m_loopers.Remove(M_DropItems[i]);
                }
            }
            M_DropItems.Clear();
        }        if (m_loopers.Count == 0)
        {
            EditorApplication.update -= Update;
            M_Started = false;
        }
    }
}
//調(diào)用方法原來這個樣
            EditorCoroutineRunner.StartEditorCoroutine(OnThreadLoop());//現(xiàn)在改成這個樣
            EditorCoroutineLooper.StartLoop(this,OnThreadLoop());

使用這個腳本的時候,需要傳兩個參數(shù),一個就是你自己的腳本,另外一個就是協(xié)程函數(shù)。原理就是代碼里面會檢測你的腳本狀態(tài),當(dāng)腳本關(guān)閉或者卸載的時候,都會停掉Loop調(diào)用。老外有時候?qū)懘a,也不那么講究,有沒有?

http://www.cnblogs.com/driftingclouds/p/6603287.html