【Unity优化】如何实现Unity编辑器中的协程

来源:互联网 发布:centos 5.5 下载地址 编辑:程序博客网 时间:2024/05/18 20:12

本文为博主原创文章,欢迎转载,请保留出处: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

0 0
原创粉丝点击