【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