• 热门专题

Unity3d开发(十八)监听编辑器状态改变 制定自定义回调

作者:  发布日期:2016-11-24 21:08:05
Tag标签:编辑器  状态  
  • 文章作者:松阳

    本文出自 阿修罗道,禁止用于商业用途,转载请注明出处。

    原文链接:http://blog.csdn.net/fansongy/article/details/53318791

     

     

     

     

     

     

     

     

     



     


     

    做编辑器插件时,我总是想要拿到监听编辑器的状态变化。比如在打开编辑器开始运行自己的服务。这时就需要用户打开编辑器的事件。再比如我希望在游戏退出运行模式之前,把一些编辑的东西缓存出来,然后对这些数据做自动化处理,那么我就需要退出运行模式的事件。诸如此类吧。

    另一方面,我希望用观察者模式,并且能自动化注册。因为我注意到,导入资源时的AssetImporter回调就是这样做的。用户只需要实现一个接口,就可以收到回调。极大的简化了扩展流程。编辑器代码又不必考虑效率问题,借助C#的反射,可以很容易的实现这种功能。

    概述

    整套框架的启动核心是属性InitializeOnLoad。当Unity3d运行或启动时,会重新加载有脚本。当使用这个宏时,编辑器会自动将被标注的类实例化到内存中。因此我们可以利用这个特性,在它的构造函数中拉起我们整个服务。 这里有个小技巧。在启动Unity编辑器的情况下,如果在构造函数中创建对象,会被其他清除函数干掉。我认为是脚本初始化顺序,或是场景切换引起的,具体原因得问Unity了。为了解决这个问题,我借助了update函数,跳了一帧执行应有的逻辑。

    自动注册是借助C#的反射,通过GetAssemblies和GetTypes获取到所有的类,然后创建出对应的实例。

    包装

    这个类我觉得有个特别适合的名字——NightWatch。如果你没看过冰与火之歌,可能理解这个框架还算有点难度。总的说来,这个框架讲述了一个少年加入守夜人队伍,并去长城之外战斗的故事...

    实现

    接口类如下:

    public interface ICrow
    {
        /// <summary>
        /// Join the Nights Watch 
        /// </summary>
        void Enroll();
    
        /// <summary>
        /// Before to Enter Wild
        /// </summary>
        void PrepareForBattle();
    
        /// <summary>
        /// To the Weirwood outside the wall
        /// </summary>
        void FaceWeirwood();
    
        /// <summary>
        /// Back To the Castle Black
        /// </summary>
        void OpenTheGate();
    
        /// <summary>
        /// Tell Vow to the Old God
        /// </summary>
        void Vow();
    }

    实例类如下:

    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEditor;
    
    [InitializeOnLoad]
    public class NightsWatch
    {
        #region Public Attributes
    
        #endregion
    
        #region Private Attributes
        private static List<ICrow> m_crows = new List<ICrow>();
        #endregion
    
        #region Public Methods
    
        static NightsWatch()
        {
            if (!EditorApplication.isPlayingOrWillChangePlaymode)
            {
                EditorApplication.update += WelcomeToCastleBlack;
            }
            else 
            {
                EditorApplication.update += BeyondTheWall;
            }
        }
    
        static void WelcomeToCastleBlack()
        {
            EditorApplication.update -= WelcomeToCastleBlack;
    
            //Debug.Log("Welcome To castle black");
            m_crows.Clear();
            var crows = GetAllImplementTypes<ICrow>(System.AppDomain.CurrentDomain);
            foreach (var eachCrow in crows)
            {
                eachCrow.Enroll();
                m_crows.Add(eachCrow);
            }
    
            EditorApplication.update += WaitForWild;
        }
    
        static void WaitForWild()
        {
            if (EditorApplication.isPlayingOrWillChangePlaymode)
            {
                foreach (var eachCrow in m_crows)
                {
                    eachCrow.PrepareForBattle();
                }
                EditorApplication.update -= WaitForWild;
            }
        }
    
        static void BeyondTheWall()
        {
            EditorApplication.update -= BeyondTheWall;
    
            //Debug.Log("Welcome To The Wild");
            m_crows.Clear();
            var crows = GetAllImplementTypes<ICrow>(System.AppDomain.CurrentDomain);
            foreach (var eachCrow in crows)
            {
                eachCrow.FaceWeirwood();
                m_crows.Add(eachCrow);
            }
    
            EditorApplication.update += WaitForCrowReturn;
        }
        
        static void WaitForCrowReturn()
        {
            if (!EditorApplication.isPlayingOrWillChangePlaymode )
            {
                //Debug.Log("Open the Door");
                EditorApplication.update -= WaitForCrowReturn;
                foreach (var eachCrow in m_crows)
                {
                    eachCrow.OpenTheGate();
                }
                EditorApplication.update += WelcomeToCastleBlack;
            }
        }
    
        public static void CrowsVow()
        {
            foreach (var eachCrow in m_crows)
            {
                eachCrow.Vow();
            }
        }
    
        [MenuItem("Land/CastleBlack")]
        public static void MakeVow()
        {
            NightsWatch.CrowsVow();
        }
        #endregion
    
        #region Override Methods
    
        #endregion
    
        #region Private Methods
        public static T[] GetAllImplementTypes<T>(System.AppDomain aAppDomain) where T : class
        {
            var result = new List<T>();
            var assemblies = aAppDomain.GetAssemblies();
            foreach (var assembly in assemblies)
            {
                var types = assembly.GetTypes();
                foreach (var type in types)
                {
                    if (typeof(T).IsAssignableFrom(type))
                    {
                        if (!type.IsAbstract)
                        {
                            var tar = assembly.CreateInstance(type.FullName) as T;
                            result.Add(tar);
                        }
                    }
                }
            }
            return result.ToArray();
        }
        #endregion
    }
    

    简单解释一下,所有的接口都是按照冰与火之歌中的剧情定义。当在编辑状态下时,会创建对应的实例类,并调用Enroll函数,这相当于Jon刚刚进入CastleBlack。当点击Play运行时,会先调用PrepareForBattle,相当于在城堡中准备出征。当游戏开始运行时,会调用FaceToWeirWood,这里对应的是城外那颗鱼梁木,一般出征之前都是要去祈祷一下。然后当游戏运行结束时,会调用OpenTheGate,对应出征回来,在长城下面喊门。然后有个Vow接口,这个是用来点名的,城堡里的乌鸦都要列队答“道”。

    使用

    新建两个实例: 一个是JonSnow:

    public class JonSnow :  ICrow
    {
        public void Enroll()
        {
            Debug.Log(this + " join the NightWatch!");
        }
    
        public void PrepareForBattle()
        {
            Debug.Log(this + " follow your lead!");
        }
    
        public void FaceWeirwood()
        {
            Debug.Log("I'm the wolf in the north");
        }
    
        public void OpenTheGate()
        {
            Debug.Log(this + " request enter Castle Black");
        }
    
        public void Vow()
        {
            Debug.Log(this + " For The Watch");
        }
    }

    一个是Samwell:

    public class Samwell :  ICrow
    {
        public void Enroll()
        {
            Debug.Log(this + " I came form Lord Randyll Tarly,and I even his oldest son ...");
        }
    
        public void PrepareForBattle()
        {
            Debug.Log(this + " is not ready yet...");
        }
    
        public void FaceWeirwood()
        {
            Debug.Log("I'm a useless warrior,but may be ... helpful");
        }
    
        public void OpenTheGate()
        {
            Debug.Log(this + " also want enter");
        }
    
        public void Vow()
        {
            Debug.Log(this + " For The ... alive");
        }
    }

    测试

    当写好代码编译完成时,就能在输出中看到他俩到长城去报道了。点击运行程序,关闭运行程序,会分别有日志输出,效果如下:


    其中红线是点击Play操作,绿线是停止Unity运行的操作,红线以上的日志是打开unity或重新编译时输出的。一切按照预期实现,收工。

    如果你觉得这篇文章对你有帮助,可以顺手点个顶,不但不会喜当爹,还能让更多人能看到它...
     

About IT165 - 广告服务 - 隐私声明 - 版权申明 - 免责条款 - 网站地图 - 网友投稿 - 联系方式
本站内容来自于互联网,仅供用于网络技术学习,学习中请遵循相关法律法规