本文為博主原創(chuàng)文章,歡迎轉(zhuǎn)載,請(qǐng)保留出處:http://blog.csdn.net/andrewfan
Unity編輯器中何時(shí)需要協(xié)程
當(dāng)我們定制Unity編輯器的時(shí)候,往往需要啟動(dòng)額外的協(xié)程或者線程進(jìn)行處理。比如當(dāng)執(zhí)行一些界面更新的時(shí)候,需要大量計(jì)算,如果用戶在不斷修正一個(gè)參數(shù),比如從1變化到2,這種變化過(guò)程要經(jīng)歷無(wú)數(shù)中間步驟,調(diào)用N多次Update,如果直接在Update中不斷刷新,界面很容易直接卡死。所以在一個(gè)協(xié)程中進(jìn)行一些優(yōu)化,只保留用戶最后一次參數(shù)修正,省去中間步驟,就會(huì)好很多。這屬于Unity編輯器的內(nèi)容,也屬于優(yōu)化的內(nèi)容,還是放在優(yōu)化中吧。
解決問(wèn)題思路
Unity官網(wǎng)的questions里面也有很多人在搜索這個(gè)問(wèn)題,不過(guò)后來(lái)是看到有個(gè)人提到了這個(gè)方法。問(wèn)題的關(guān)鍵點(diǎn)就是“EditorApplication.update ”,有個(gè)這樣的方法,你把要執(zhí)行的協(xié)程傳遞給它就可以在編輯器下自動(dòng)執(zhí)行循環(huán)調(diào)用。
老外的寫(xiě)法
當(dāng)然,后來(lái)我也找到一個(gè)老外的寫(xiě)法,代碼貼出來(lái)如下:
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;
}
}
}
用法就是大概在你自己的類(lèi)的Start方法中稍作修改,再增加一個(gè)協(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ù)處理了。這個(gè)類(lèi)基本是滿足要求了。如果你把你自己的腳本做了這樣的修改之后,它是可以在編輯狀態(tài)不斷執(zhí)行到Loop的,要注意它需要先執(zhí)行到Start,也就是說(shuō),你可能需要把GameObject做成Prefab,然后把它從場(chǎng)景中刪除,再把Prefab拖回場(chǎng)景,才會(huì)在編輯狀態(tài)下觸發(fā)腳本上的Star方法,從而激發(fā)Loop。
我的寫(xiě)法
然而,用久了你就會(huì)發(fā)現(xiàn)幾個(gè)問(wèn)題,一旦Loop開(kāi)始了,你是無(wú)法停止的,哪怕你把GameObject從場(chǎng)景中刪掉都無(wú)濟(jì)于事,當(dāng)然隱藏也沒(méi)有效果。為了解決這個(gè)問(wèn)題,也把腳本弄得簡(jiǎn)單點(diǎn)兒,我重寫(xiě)了這個(gè)腳本,希望需要的同學(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>
/// 開(kāi)啟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;
//卸載時(shí)丟棄Looper
if(mb == null)
{
M_DropItems.Add(item.Key);
continue;
}
//隱藏時(shí)別執(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)用方法原來(lái)這個(gè)樣
EditorCoroutineRunner.StartEditorCoroutine(OnThreadLoop());
//現(xiàn)在改成這個(gè)樣
EditorCoroutineLooper.StartLoop(this,OnThreadLoop());
使用這個(gè)腳本的時(shí)候,需要傳兩個(gè)參數(shù),一個(gè)就是你自己的腳本,另外一個(gè)就是協(xié)程函數(shù)。原理就是代碼里面會(huì)檢測(cè)你的腳本狀態(tài),當(dāng)腳本關(guān)閉或者卸載的時(shí)候,都會(huì)停掉Loop調(diào)用。老外有時(shí)候?qū)懘a,也不那么講究,有沒(méi)有?