【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調用。老外有時候寫代碼,也不那么講究,有沒有?


免責聲明!

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



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