zoukankan      html  css  js  c++  java
  • 【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调用。老外有时候写代码,也不那么讲究,有没有?

  • 相关阅读:
    linux_java_同时启动三个项目脚本
    Python 项目-飞机大战_02.飞机大战-2
    Python 项目-飞机大战_01.飞机大战-1
    Mysql为什么要使用视图?
    Python语法基础_10.加强练习
    Python语法基础_09.面向对象3、异常、模块
    crontab--设置周期性被执行的指令
    面试者应向公司问什么问题?
    简单的触发黑名单阻断演示 control+c
    SetConsoleCtrlHandler演示
  • 原文地址:https://www.cnblogs.com/driftingclouds/p/6603287.html
Copyright © 2011-2022 走看看