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

div#cpcontent2 {height:215px;width:215px;float:left;}
div#cpmenu {height:200px;float:left;}
div#cpcontent {height:200px;width:150px;float:left;}

文章作者:松阳

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



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

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

概述

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

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

包装

这个类我觉得有个特别适合的名字——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或重新编译时输出的。一切按照预期实现,收工。

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

时间: 2024-12-17 18:22:20

Unity3d开发(十八) 监听编辑器状态改变,制定自定义回调的相关文章

iOS开发:使用代理模式监听开关状态改变事件

记一次解决跨控制器监听开关状态改变的尝试. 为了统一设置UITableViewCell里的内容,自定义了UITableViewCell类的一个基类,命名为SettingCell.SettingCell里显示的内容由数据模型SettingItem提供:在SettingCell里定义一个属性即可. @property (nonatomic, strong) SettingItem *item; 再定义几个SettingItem的子类表示显示不同内容的Cell(如图1).由于所有开关状态的归档和解档都

android监听SIM状态

/* 监听sim状态改变的广播,返回sim卡的状态, 有效或者无效. 双卡中只要有一张卡的状态有效即返回状态为有效,两张卡都无效则返回无效. */ import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.telephony.TelephonyManager;

Android实时监听网络状态

在开发android应用时,涉及到要进行网络访问,时常需要进行网络状态的检查,以提供给用户必要的提醒.一般可以通过ConnectivityManager来完成该工作. ConnectivityManager有四个主要任务: 1.监听手机网络状态(包括GPRS,WIFI, UMTS等) 2.手机状态发生改变时,发送广播 3.当一个网络连接失败时进行故障切换 4.为应用程序提供可以获取可用网络的高精度和粗糙的状态 当我们要在程序中监听网络状态时,只要一下几个步骤即可:  1.处理Recevier接口

【COCOS CREATOR 系列教程之二】脚本开发篇&事件监听、常用函数等示例整合

[Cocos Creator ](千人群):  432818031 上一篇,介绍了Himi在使用过cc所有组件后的一篇总结,没有具体介绍每个组件的原因在于官方文档很齐全,而且也有视频的介绍. 所以希望童鞋们可以把我这两篇博文当成对组件.脚本两部分开发的整理与总结. 后续的文章,Himi应该主要更新一些官方还未补充或者还没有的教程.避免无用功. 下面直接放出代码,因为不是很难理解.所以不再一一赘述,都是常用的函数.事件监听.动作回调.定时器等开发过程中必接触的. 大致内容如下: cc 属性介绍 获

android 监听网络状态的变化及实战

android 监听网络状态的变化及实际应用 转载请注明博客地址:http://blog.csdn.net/gdutxiaoxu/article/details/53008266 平时我们在请求错误的情况下,通常会进行处理一下,一般来说,主要分为两方面的错误 - 没有网络的错误 - 在有网络的情况下,我们客户端的错误或者服务器端的错误 今天这篇博客主要阐述以下问题 怎样监听网络状态的变化,包括是否打开WiFi,否打开数据网络,当前连接的网络是否可用 网络没有打开情况下的处理,如弹出对话框,跳转到

设置休眠时间,获得休眠时间,监听屏幕状态,服务启动界面

public class MyService extends Service { @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { Log.e("zz","zz"); MyService.ScreenListener mScreenListener= new MyService.ScreenListe

监听电话状态的模板代码

package com.example.calling; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.IBinder; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; public class Ph

【C/C++】多进程:父进程监听子进程状态 wait()的使用

文章结构: wait能力介绍 wait()函数讲解 示例代码及操作演示 wait能力介绍 在上一篇[C/C++]多进程:子进程的创建fork()中演示了子进程的创建. 创建子进程后,父进程具有监听子进程的运行状态的能力,用到的函数为: #include <sys/wait.h> pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int options); 以上函数用于等待子进程子进程的状态变化回调并且获取状态变化信息.

监听元素属性改变事件的方法

一.onchange事件只在键盘或者鼠标操作改变对象属性,且失去焦点时触发,脚本触发无效.(就是说你在输入框中输入完内容,输入完了,然后鼠标点别的地方触发该事件)二.oninput事件oninput 事件在用户输入时触发.不支持JS等赋值改变的元素属性.该事件在 <input> 或 <textarea> 元素的值发生改变时触发.(也就是说,不用输入完,边输入边触发该事件)①但是,这个方法是HTML5中的标准事件,IE9以下的浏览器是不支持oninput事件的.②使用时,还需要oni