1. 程式人生 > >【Unity優化】如何實現Unity編輯器中的協程

【Unity優化】如何實現Unity編輯器中的協程

本文為博主原創文章,歡迎轉載,請保留出處:http://blog.csdn.net/andrewfan

Unity編輯器中何時需要協程

當我們定製Unity編輯器的時候,往往需要啟動額外的協程或者執行緒進行處理。比如當執行一些介面更新的時候,需要大量計算,如果使用者在不斷修正一個引數,比如從1變化到2,這種變化過程要經歷無數中間步驟,呼叫N多次Update,如果直接在Update中不斷重新整理,介面很容易直接卡死。所以在一個協程中進行一些優化,只保留使用者最後一次引數修正,省去中間步驟,就會好很多。這屬於Unity編輯器的內容,也屬於優化的內容,還是放在優化中吧。

解決問題思路

Unity官網的questions裡面也有很多人在搜尋這個問題,不過後來是看到有個人提到了這個方法。問題的關鍵點就是“EditorApplication.update ”,有個這樣的方法,你把要執行的協程傳遞給它就可以在編輯器下自動執行迴圈呼叫。

老外的寫法

當然,後來我也找到一個老外的寫法,程式碼貼出來如下:

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方法中稍作修改,再增加一個協程函式,如下:

        void Start()

        {

            rope = gameObject.GetComponent<QuickRope>();

            #if UNITY_EDITOR

            //呼叫方法

            EditorCoroutineRunner.StartEditorCoroutine(OnThreadLoop());

            #endif

        }

        public IEnumerator OnThreadLoop()

        {

            while(true)

            {

                Debug.Log("Looper");

                yield return null;

            }

        }

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

我的寫法

然而,用久了你就會發現幾個問題,一旦Loop開始了,你是無法停止的,哪怕你把GameObject從場景中刪掉都無濟於事,當然隱藏也沒有效果。為了解決這個問題,也把指令碼弄得簡單點兒,我重寫了這個指令碼,希望需要的同學可以愉快地使用。

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;

                }

                //隱藏時別執行Loop

                if(!mb.gameObject.activeInHierarchy)

                {

                    continue;

                }

                //執行Loop,執行完畢也丟棄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;

        }

    }

}

//呼叫方法原來這個樣

            EditorCoroutineRunner.StartEditorCoroutine(OnThreadLoop());

//現在改成這個樣

            EditorCoroutineLooper.StartLoop(this,OnThreadLoop());

使用這個指令碼的時候,需要傳兩個引數,一個就是你自己的指令碼,另外一個就是協程函式。原理就是程式碼裡面會檢測你的指令碼狀態,當指令碼關閉或者解除安裝的時候,都會停掉Loop呼叫。老外有時候寫程式碼,也不那麼講究,有沒有?

詳細資料,請加群獲取:586656942