关闭

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

标签: 优化unity编辑器
3192人阅读 评论(0) 收藏 举报
分类:

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调用。老外有时候写代码,也不那么讲究,有没有?

2
0
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

Unity如何在Editor下执行协程(coroutine)

在处理Unity5新的AssetBundle的时候,我有一个需求,需要在Editor下(比如一个menuitem的处理函数中,游戏没有运行,也没有MonoBehaviour)加载AssetBundle...
  • langresser
  • langresser
  • 2015-03-13 18:12
  • 4775

如何用Unity加载一个真实的进度条

实际上就是MOMO贴出来的方法其实是有问题的,用过的才知道。至于别的几乎不用找,根本一个抄一个,抄来抄去都是一样,没自己的东西。 用过MOMO那个方法的都应该会发现了,当进度条开始显...
  • bright_mmmmmmmmmmmm
  • bright_mmmmmmmmmmmm
  • 2015-05-20 18:59
  • 2168

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

本文为博主原创文章,欢迎转载,请保留出处:http://blog.csdn.net/andrewfan Unity编辑器中何时需要协程 当我们定制Unity编辑器的时候,往往需要启动额外的协程或者线程...
  • fanfzj
  • fanfzj
  • 2017-03-23 14:04
  • 198

【Unity】理解协程的原理1——实现一个自己的WaitForSeconds

协程的所能达到的效果就是在指定的时间点上执行需要执行的代码,Unity中开始一个协程的函数是StartCoroutine,而提供的延迟的类有以下几种分别是 new WaitF...
  • baijiajie2012
  • baijiajie2012
  • 2015-02-08 11:56
  • 1241

「Unity3D」(7)协程使用3种算法实现CameraShake震屏

本文主要讨论CameraShake震屏的实现思路,但不仅限于震屏,震动算法可以震动任意属性,比如Position,Scale,Rotation,Color等等。思路震动,就是围绕某个固定点的波动,最后...
  • tom_221x
  • tom_221x
  • 2017-12-06 13:14
  • 172

【术】Unity中c#协程原理及实现

使用Unity的都知道Unity提供了协程的方式,使用协程能够使异步回调更加简洁方便。但是Unity的协程只有继承MonoBehaviour才能开启,也就是说,如果你想用,则必须存在一个全局类,其挂在...
  • zhou8jie
  • zhou8jie
  • 2015-10-20 09:27
  • 2196

unity3D学习【功能实现】之十三:简单式的理解协程和做下巡逻

再用一个for循环来稍微深入的理解一下for(int i=0;i<5;i++){ Debug.Log("打"+(i+1)+"条狗"); yield return new WaitForSeconds(...
  • vipzjh
  • vipzjh
  • 2016-08-24 11:22
  • 1297

Unity3d协程实现倒数计时

Unity3d协程的知识,不了解的同学可以在网上查找一下相关资料或者看一下Unity3D协程介绍 以及 使用。 下面介绍Unity3d协程实现倒数计时,实现代码: public class Gam...
  • Gary_888
  • Gary_888
  • 2016-05-17 14:57
  • 2734

Unity 模拟协程实现计时器功能

众所周知,在Unity里面开启协程会有消耗,有时候就算我们只需要用到一个简单的计时器,必须开一个协程或者自己写一个计时器。这里提供了一个通用的计时器类,里面还实现了内存池管理,使用完的计时器回收备用。...
  • z625309640
  • z625309640
  • 2017-09-26 19:01
  • 117

C#实现Unity协程

原理 利用c#的yied特性,利用迭代器,在每一帧tick IEnumerator 用来保存迭代状态,用于恢复迭代器中的代码执行点 unity中协程调用位置 普通迭代器 //继承IEnumer...
  • u010544244
  • u010544244
  • 2017-07-11 22:39
  • 1051
    个人资料
    • 访问:87382次
    • 积分:1280
    • 等级:
    • 排名:千里之外
    • 原创:24篇
    • 转载:1篇
    • 译文:1篇
    • 评论:43条
    关于Andrew
    Unity游戏开发程序员,
    开源游戏引擎Cyclone2D作者,
    我最近在开发一款3D赛车游戏
    热爱图形学、物理学、架构等研究,
    有兴趣的同学加群:528266922。
    博客专栏
    最新评论