开发文章

Unity3d监听编辑器状态改变,制定自定义回调

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

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

概述

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

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

包装

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

战斗的故事.jpg

实现

接口类如下:

复制内容到剪贴板
  1. public interface ICrow    
  2. {    
  3.     /// <summary>    
  4.     /// Join the Nights Watch     
  5.     /// </summary>    
  6.     void Enroll();    
  7.     
  8.     /// <summary>    
  9.     /// Before to Enter Wild    
  10.     /// </summary>    
  11.     void PrepareForBattle();    
  12.     
  13.     /// <summary>    
  14.     /// To the Weirwood outside the wall    
  15.     /// </summary>    
  16.     void FaceWeirwood();    
  17.     
  18.     /// <summary>    
  19.     /// Back To the Castle Black    
  20.     /// </summary>    
  21.     void OpenTheGate();    
  22.     
  23.     /// <summary>    
  24.     /// Tell Vow to the Old God    
  25.     /// </summary>    
  26.     void Vow();    
  27. }    

实例类如下:

复制内容到剪贴板
  1. using UnityEngine;    
  2. using System.Collections;    
  3. using System.Collections.Generic;    
  4. using UnityEditor;    
  5.     
  6. [InitializeOnLoad]    
  7. public class NightsWatch    
  8. {    
  9.     #region Public Attributes    
  10.    
  11.     #endregion    
  12.    
  13.     #region Private Attributes    
  14.     private static List<ICrow> m_crows = new List<ICrow>();    
  15.     #endregion    
  16.    
  17.     #region Public Methods    
  18.     
  19.     static NightsWatch()    
  20.     {    
  21.         if (!EditorApplication.isPlayingOrWillChangePlaymode)    
  22.         {    
  23.             EditorApplication.update += WelcomeToCastleBlack;    
  24.         }    
  25.         else     
  26.         {    
  27.             EditorApplication.update += BeyondTheWall;    
  28.         }    
  29.     }    
  30.     
  31.     static void WelcomeToCastleBlack()    
  32.     {    
  33.         EditorApplication.update -= WelcomeToCastleBlack;    
  34.     
  35.         //Debug.Log("Welcome To castle black");    
  36.         m_crows.Clear();    
  37.         var crows = GetAllImplementTypes<ICrow>(System.AppDomain.CurrentDomain);    
  38.         foreach (var eachCrow in crows)    
  39.         {    
  40.             eachCrow.Enroll();    
  41.             m_crows.Add(eachCrow);    
  42.         }    
  43.     
  44.         EditorApplication.update += WaitForWild;    
  45.     }    
  46.     
  47.     static void WaitForWild()    
  48.     {    
  49.         if (EditorApplication.isPlayingOrWillChangePlaymode)    
  50.         {    
  51.             foreach (var eachCrow in m_crows)    
  52.             {    
  53.                 eachCrow.PrepareForBattle();    
  54.             }    
  55.             EditorApplication.update -= WaitForWild;    
  56.         }    
  57.     }    
  58.     
  59.     static void BeyondTheWall()    
  60.     {    
  61.         EditorApplication.update -= BeyondTheWall;    
  62.     
  63.         //Debug.Log("Welcome To The Wild");    
  64.         m_crows.Clear();    
  65.         var crows = GetAllImplementTypes<ICrow>(System.AppDomain.CurrentDomain);    
  66.         foreach (var eachCrow in crows)    
  67.         {    
  68.             eachCrow.FaceWeirwood();    
  69.             m_crows.Add(eachCrow);    
  70.         }    
  71.     
  72.         EditorApplication.update += WaitForCrowReturn;    
  73.     }    
  74.         
  75.     static void WaitForCrowReturn()    
  76.     {    
  77.         if (!EditorApplication.isPlayingOrWillChangePlaymode )    
  78.         {    
  79.             //Debug.Log("Open the Door");    
  80.             EditorApplication.update -= WaitForCrowReturn;    
  81.             foreach (var eachCrow in m_crows)    
  82.             {    
  83.                 eachCrow.OpenTheGate();    
  84.             }    
  85.             EditorApplication.update += WelcomeToCastleBlack;    
  86.         }    
  87.     }    
  88.     
  89.     public static void CrowsVow()    
  90.     {    
  91.         foreach (var eachCrow in m_crows)    
  92.         {    
  93.             eachCrow.Vow();    
  94.         }    
  95.     }    
  96.     
  97.     [MenuItem("Land/CastleBlack")]    
  98.     public static void MakeVow()    
  99.     {    
  100.         NightsWatch.CrowsVow();    
  101.     }    
  102.     #endregion    
  103.    
  104.     #region Override Methods    
  105.    
  106.     #endregion    
  107.    
  108.     #region Private Methods    
  109.     public static T[] GetAllImplementTypes<T>(System.AppDomain aAppDomain) where T : class    
  110.     {    
  111.         var result = new List<T>();    
  112.         var assemblies = aAppDomain.GetAssemblies();    
  113.         foreach (var assembly in assemblies)    
  114.         {    
  115.             var types = assembly.GetTypes();    
  116.             foreach (var type in types)    
  117.             {    
  118.                 if (typeof(T).IsAssignableFrom(type))    
  119.                 {    
  120.                     if (!type.IsAbstract)    
  121.                     {    
  122.                         var tar = assembly.CreateInstance(type.FullName) as T;    
  123.                         result.Add(tar);    
  124.                     }    
  125.                 }    
  126.             }    
  127.         }    
  128.         return result.ToArray();    
  129.     }    
  130.     #endregion    
  131. }    

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

使用

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

JonSnow.jpg

复制内容到剪贴板
  1. public class JonSnow :  ICrow    
  2. {    
  3.     public void Enroll()    
  4.     {    
  5.         Debug.Log(this + " join the NightWatch!");    
  6.     }    
  7.     
  8.     public void PrepareForBattle()    
  9.     {    
  10.         Debug.Log(this + " follow your lead!");    
  11.     }    
  12.     
  13.     public void FaceWeirwood()    
  14.     {    
  15.         Debug.Log("I'm the wolf in the north");    
  16.     }    
  17.     
  18.     public void OpenTheGate()    
  19.     {    
  20.         Debug.Log(this + " request enter Castle Black");    
  21.     }    
  22.     
  23.     public void Vow()    
  24.     {    
  25.         Debug.Log(this + " For The Watch");    
  26.     }    
  27. }    

一个是Samwell:

Samwell.jpg

复制内容到剪贴板
  1. public class Samwell :  ICrow    
  2. {    
  3.     public void Enroll()    
  4.     {    
  5.         Debug.Log(this + " I came form Lord Randyll Tarly,and I even his oldest son ...");    
  6.     }    
  7.     
  8.     public void PrepareForBattle()    
  9.     {    
  10.         Debug.Log(this + " is not ready yet...");    
  11.     }    
  12.     
  13.     public void FaceWeirwood()    
  14.     {    
  15.         Debug.Log("I'm a useless warrior,but may be ... helpful");    
  16.     }    
  17.     
  18.     public void OpenTheGate()    
  19.     {    
  20.         Debug.Log(this + " also want enter");    
  21.     }    
  22.     
  23.     public void Vow()    
  24.     {    
  25.         Debug.Log(this + " For The ... alive");    
  26.     }    
  27. }    

测试

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

测试.png

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

感谢 松阳 支持 磐实编程网 原文地址:
blog.csdn.net/fansongy/article/details/53318791

文章信息

发布时间:2016-11-25

作者:松阳

发布者:aquwcw

浏览次数: