最新文章
泰课在线 | 微信拼团成功后如何获取课程?
08-09 17:57
Unity教程 | 使用ARKit为iOS开发AR应用
07-31 17:23
Unity Pro专业版7折订阅四选一工具包之VR开发与艺术设计
07-28 11:47
网友使用虚幻UE4实现CAVE 多通道立体渲染的沉浸式环境
07-27 11:57
VR晕动症调查:未来5年内大部分VR晕动症将得到解决
07-27 11:26
AMD CEO:未来3-5年最重要 希望5年达1亿VR用户
07-27 10:44
Unity3d开发监听编辑器状态改变 制定自定义回调
在进行Unity3D编辑器插件开发时,我们常常需要监听编辑器的状态变化。例如,在打开编辑器时启动自定义服务,这就需要捕获编辑器打开事件;又或者在游戏退出运行模式前,缓存一些编辑内容并进行自动化处理,这就需要监听退出运行模式的事件。
另一方面,我们希望采用观察者模式实现自动化注册。以资源导入时的AssetImporter回调为例,用户只需实现一个接口,就能收到相应回调,大大简化了扩展流程。由于编辑器代码对效率的要求相对较低,借助C#的反射机制,可以轻松实现这一功能。
整体框架概述
整套框架的启动核心是InitializeOnLoad属性。当Unity3D运行或启动时,会重新加载脚本。使用该属性标注的类,编辑器会自动将其实例化到内存中。因此,我们可以利用这一特性,在类的构造函数中启动整个服务。
不过,这里有个小问题。在启动Unity编辑器时,如果在构造函数中创建对象,可能会被其他清除函数清除。推测这可能是由于脚本初始化顺序或场景切换导致的,具体原因有待向Unity官方进一步确认。为了解决这个问题,我们借助update函数,延迟一帧执行相应逻辑。
自动注册功能借助C#的反射机制实现。通过GetAssemblies和GetTypes方法获取所有类,然后创建对应的实例。
框架包装
我们将这个类命名为NightsWatch。如果您不熟悉《冰与火之歌》,理解这个框架可能会有一定难度。在原著中,“守夜人”是一个守护长城的组织,这个框架的命名灵感就来源于此。
代码实现
接口类
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刚刚加入“守夜人”,抵达黑城堡(Castle Black)。 - 点击Play按钮开始运行游戏时,会先调用
PrepareForBattle函数,就像在城堡中为出征做准备。 - 游戏开始运行时,会调用
FaceWeirwood函数,对应着在城外的鱼梁木前祈祷。 - 游戏运行结束时,会调用
OpenTheGate函数,相当于出征归来,在长城下呼喊开门。 Vow接口用于点名,城堡里的“乌鸦”(实现了ICrow接口的类实例)都要列队应答。
使用示例
创建实例类
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");
}
}
测试
代码编写并编译完成后,在输出窗口中可以看到JonSnow和Samwell加入“守夜人”的日志信息。点击运行程序和停止运行程序时,会分别输出相应的日志。其中,点击Play操作对应红线标记,停止Unity运行操作对应绿线标记,红线以上的日志是在打开Unity或重新编译时输出的。测试结果表明,整个框架按照预期正常运行。