【Unity优化】如何实现Unity编辑器中的协程
发表于2018-10-10
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调用。
来自:https://blog.csdn.net/AndrewFan/article/details/65022103