引言

一般的软件开发过程中,为了方便对项目进行管理、维护和扩展,通常会采用一种MVC框架,以将显示逻辑、业务逻辑和数据进行分离。

这在传统企业软件的开发中很常见,但我在使用Unity做游戏开发的时候却几乎找不到相关框架。

其原因猜测大概有两点,一是游戏开发模式多变,不同类型的游戏代码结构差异很大,很难有一个适用性很强的框架出现;二是Unity太年轻,其大范围使用也不过是最近三四年的事情。

没有框架也不是意味着没有办法,MVC只是一种规范,只要在开发过程中对代码的组织结构及用途做一定的约束,就能达到各层分离的效果。

在代码分层组织的结构中,出于解耦合的需求,通常需要一个对事件/消息进行管理的类,以便在各层之间传送消息,其功能包括事件/消息的订阅、发布以及取消订阅。

本文不会写怎么实现一个MVC结构(驾驭不了),只说说这个事件管理类的实现方法。

事件管理类的实现

1、编写EventManager类

一个事件管理类通过包括三个功能,订阅、发布消息、取消订阅,对应到代码中,也是就三个方法:AddEvent、DispatchEvent、RemoveEvent,还有一个字典List,对订阅事件做管理,实现如下:

 /**
* UnityVersion: 2018.3.1f1
* FileName: EventManager.cs
* Author: TYQ
* CreateTime: 2019/04/04 15:49:53
* Description: 自定义的事件派发类
*/
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class EventManager
{
/// <summary>
/// 带返回参数的回调列表,参数类型为T,支持一对多
/// </summary>
public static Dictionary<string, List<Delegate>> events = new Dictionary<string, List<Delegate>>(); /// <summary>
/// 注册事件,1个返回参数
/// </summary>
/// <param name="eventName"></param>
/// <param name="callback"></param>
public static void AddEvent<T> (string eventName, Action<T> callback)
{
List<Delegate> actions = null; //eventName已存在
if (events.TryGetValue(eventName, out actions))
{
actions.Add(callback);
}
//eventName不存在
else
{
actions = new List<Delegate>(); actions.Add(callback);
events.Add(eventName ,actions);
}
} /// <summary>
/// 注册事件,不带返回参数
/// </summary>
/// <param name="eventName"></param>
/// <param name="callback"></param>
public static void AddEvent(string eventName, Action callback)
{
List<Delegate> actions = null; //eventName已存在
if (events.TryGetValue(eventName, out actions))
{
actions.Add(callback);
}
//eventName不存在
else
{
actions = new List<Delegate>(); actions.Add(callback);
events.Add(eventName, actions);
}
} /// <summary>
/// 移除事件
/// </summary>
/// <param name="eventName"></param>
/// <param name="callback"></param>
public static void RemoveEvent<T>(string eventName, Action<T> callback)
{
List<Delegate> actions = null; if (events.TryGetValue(eventName, out actions))
{
actions.Remove(callback);
if (actions.Count == )
{
events.Remove(eventName);
}
}
}
/// <summary>
/// 移除全部事件
/// </summary>
public static void RemoveAllEvents ()
{
events.Clear();
} /// <summary>
/// 派发事件
/// </summary>
/// <param name="eventName"></param>
/// <param name="arg"></param>
public static void DispatchEvent<T>(string eventName, T arg)
{
List<Delegate> actions = null; if (events.ContainsKey(eventName))
{
events.TryGetValue(eventName, out actions); foreach (var act in actions)
{
act.DynamicInvoke(arg);
}
}
}
/// <summary>
/// 派发事件,不带参数
/// </summary>
/// <param name="eventName"></param>
/// <param name="arg"></param>
public static void DispatchEvent(string eventName)
{
List<Delegate> actions = null; if (events.ContainsKey(eventName))
{
events.TryGetValue(eventName, out actions); foreach (var act in actions)
{
act.DynamicInvoke();
}
}
}
}

EventManager.cs

2、测试事件类

2.1、先制作测试界面,包括两个接收(订阅)消息的Text组件,以及一个发布消息的Slider组件,层次结构见下图:

预期效果:拖动Slider,Slider的值会同步显示到两个用于接收的Text组件上。

2.2、编写测试类

先写一个发布消息的类,在Slider的onValueChanged事件中执行发布操作,如下

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; public class Sender : MonoBehaviour
{
public Slider slider = null; private void Awake()
{
slider.onValueChanged.AddListener(delegate (float value) {
Debug.LogFormat("slider:{0}", value);
//有参分发
EventManager.DispatchEvent<float>("NumberEvent", value); //无参分发
EventManager.DispatchEvent("NumberEventNoParam");
});
}
}

Sender.cs

再写一下接收消息的类,,如下

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; public class Receiver : MonoBehaviour
{
public Text receiveText1 = null;
public Text receiveText2 = null; private void Awake()
{
//带参数回调 //注册方法1
EventManager.AddEvent<float>("NumberEvent", OnNumberChangeEventHandler); //注册方法2
EventManager.AddEvent("NumberEvent", delegate (float arg) {
receiveText2.text = (Convert.ToInt32(arg)).ToString();
}); //无参回调
EventManager.AddEvent("NumberEventNoParam", delegate () {
Debug.Log("无参回调");
});
} /// <summary>
/// 事件处理方法
/// </summary>
/// <param name="arg"></param>
private void OnNumberChangeEventHandler (float arg)
{
receiveText1.text = (Convert.ToInt32(arg)).ToString();
}
}

Receiver.cs

2.3、运行

运行结果如下图:

可以看到,Slider值的改变会立马同步到接收端Text中,实现了预期的功能。

3、后记

1、我在EventManager.cs中使用Action类型来接受事件的回调,而不是使用c#的delegate,是因为,Action是Unity已经定义好的一种公共delegate,使用起来更方便。

2、目录的EventManager.cs只支持无参回调和一个参数的回调,如需更多参数回调,可以依照AddEvent<T>的写法,添加重载。

3、在EventManager.cs中好像还缺一个无参的RemoveEvent方法,请自行补充。

4、补充(2019-04-22)

实际使用中发现,派发消息时,传递两个参数和三个参数的情况还是挺多的,因此对EventManager进行了补充,有些不优雅的地方也进行了修改:

 /**
* UnityVersion: 2018.3.1f1
* FileName: EventManager.cs
* Author: TYQ
* CreateTime: 2019/04/04 15:49:53
* Description: 自定义的事件派发类
*/
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class EventManager
{
/// <summary>
/// 带返回参数的回调列表,参数类型为T,支持一对多
/// </summary>
public static Dictionary<string, List<Delegate>> events = new Dictionary<string, List<Delegate>>(); /// <summary>
/// 通用注册事件方法
/// </summary>
/// <param name="eventName"></param>
/// <param name="callback"></param>
private static void CommonAdd (string eventName, Delegate callback)
{
List<Delegate> actions = null; //eventName已存在
if (events.TryGetValue(eventName, out actions))
{
actions.Add(callback);
}
//eventName不存在
else
{
actions = new List<Delegate>(); actions.Add(callback);
events.Add(eventName, actions);
}
} /// <summary>
/// 注册事件,0个返回参数
/// </summary>
/// <param name="eventName"></param>
/// <param name="callback"></param>
public static void AddEvent(string eventName, Action callback)
{
CommonAdd(eventName, callback);
} /// <summary>
/// 注册事件,1个返回参数
/// </summary>
/// <param name="eventName"></param>
/// <param name="callback"></param>
public static void AddEvent<T> (string eventName, Action<T> callback)
{
CommonAdd(eventName, callback);
}
/// <summary>
/// 注册事件,2个返回参数
/// </summary>
/// <param name="eventName"></param>
/// <param name="callback"></param>
public static void AddEvent<T, T1>(string eventName, Action<T, T1> callback)
{
CommonAdd(eventName, callback);
}
/// <summary>
/// 注册事件,3个返回参数
/// </summary>
/// <param name="eventName"></param>
/// <param name="callback"></param>
public static void AddEvent<T, T1, T2>(string eventName, Action<T, T1, T2> callback)
{
CommonAdd(eventName, callback);
} /// <summary>
/// 通用移除事件的方法
/// </summary>
/// <param name="eventName"></param>
/// <param name="callback"></param>
private static void CommonRemove (string eventName, Delegate callback)
{
List<Delegate> actions = null; if (events.TryGetValue(eventName, out actions))
{
actions.Remove(callback);
if (actions.Count == )
{
events.Remove(eventName);
}
}
} /// <summary>
/// 移除事件 0参数
/// </summary>
/// <param name="eventName"></param>
/// <param name="callback"></param>
public static void RemoveEvent(string eventName, Action callback)
{
CommonRemove(eventName, callback);
} /// <summary>
/// 移除事件 1个参数
/// </summary>
/// <param name="eventName"></param>
/// <param name="callback"></param>
public static void RemoveEvent<T>(string eventName, Action<T> callback)
{
CommonRemove(eventName, callback);
} /// <summary>
/// 移除事件 2个参数
/// </summary>
/// <param name="eventName"></param>
/// <param name="callback"></param>
public static void RemoveEvent<T, T1>(string eventName, Action<T, T1> callback)
{
CommonRemove(eventName, callback);
}
/// <summary>
/// 移除事件 3个参数
/// </summary>
/// <param name="eventName"></param>
/// <param name="callback"></param>
public static void RemoveEvent<T, T1, T2>(string eventName, Action<T, T1, T2> callback)
{
CommonRemove(eventName, callback);
} /// <summary>
/// 移除全部事件
/// </summary>
public static void RemoveAllEvents ()
{
events.Clear();
} /// <summary>
/// 派发事件,0参数
/// </summary>
/// <param name="eventName"></param>
/// <param name="arg"></param>
public static void DispatchEvent(string eventName)
{
List<Delegate> actions = null; if (events.ContainsKey(eventName))
{
events.TryGetValue(eventName, out actions); foreach (var act in actions)
{
act.DynamicInvoke();
}
}
} /// <summary>
/// 派发事件 1个参数
/// </summary>
/// <param name="eventName"></param>
/// <param name="arg"></param>
public static void DispatchEvent<T>(string eventName, T arg)
{
List<Delegate> actions = null; if (events.ContainsKey(eventName))
{
events.TryGetValue(eventName, out actions); foreach (var act in actions)
{
act.DynamicInvoke(arg);
}
}
} /// <summary>
/// 派发事件 2个参数
/// </summary>
/// <param name="eventName">事件名</param>
/// <param name="arg">参数1</param>
/// <param name="arg2">参数2</param>
public static void DispatchEvent<T, T1>(string eventName, T arg, T1 arg2)
{
List<Delegate> actions = null; if (events.ContainsKey(eventName))
{
events.TryGetValue(eventName, out actions); foreach (var act in actions)
{
act.DynamicInvoke(arg, arg2);
}
}
} /// <summary>
/// 派发事件 3个参数
/// </summary>
/// <param name="eventName">事件名</param>
/// <param name="arg">参数1</param>
/// <param name="arg2">参数2</param>
/// <param name="arg3">参数3</param>
public static void DispatchEvent<T1, T2, T3>(string eventName, T1 arg, T2 arg2, T3 arg3)
{
List<Delegate> actions = null; if (events.ContainsKey(eventName))
{
events.TryGetValue(eventName, out actions); foreach (var act in actions)
{
act.DynamicInvoke(arg, arg2, arg3);
}
}
}
}

EventManager

Unity3D中利用Action实现自己的消息管理(订阅/发布)类的更多相关文章

  1. 基于Redis消息的订阅发布应用场景

    目录 基于Redis消息的订阅发布应用场景 1.应用背景 2.困境 2.1 锁表风险 2.2 实时性差 2.3 增加编程复杂性 2.4 实时效果 3.解决方案 3.1 前端传值给服务端 3.2 服务端 ...

  2. 在Unity3D中利用 RenderTexture 实现游戏内截图

    using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; publ ...

  3. SpringBoot使用JMS(activeMQ)的两种方式 队列消息、订阅/发布

    刚好最近同事问我activemq的问题刚接触所以分不清,前段时间刚好项目中有用到,所以稍微整理了一下,仅用于使用 1.下载ActiveMQ 地址:http://activemq.apache.org/ ...

  4. (五)RabbitMQ消息队列-安装amqp扩展并订阅/发布Demo(PHP版)

    原文:(五)RabbitMQ消息队列-安装amqp扩展并订阅/发布Demo(PHP版) 本文将介绍在PHP中如何使用RabbitMQ来实现消息的订阅和发布.我使用的系统依然是Centos7,为了方便, ...

  5. Android中利用Handler实现消息的分发机制(三)

    在第二篇文章<Android中利用Handler实现消息的分发机制(一)>中,我们讲到主线程的Looper是Android系统在启动App的时候,已经帮我们创建好了,而假设在子线程中须要去 ...

  6. Unity3D中事件函数的运行顺序

    Unity3D中脚本的生命周期是依照预先定义好的事件函数的运行流程来演化的,详细流程例如以下: Editor模式下Reset: 当脚本第一次被挂到GameObject上或用户点击Resetbutton ...

  7. [原创]MYSQL中利用外键实现级联删除和更新

    MySQL中利用外键实现级联删除.更新 MySQL支持外键的存储引擎只有InnoDB,在创建外键的时候,要求父表必须有对应的索引,子表在创建外键的时候也会自动创建对应的索引.在创建索引的时候,可以指定 ...

  8. 通常Struts框架会自动地从action mapping中创建action对象

    开发者不必在Spring中去注册action,尽管可以这么去做,通常Struts框架会自动地从action mapping中创建action对象 struts2-spring-plugin-x-x-x ...

  9. Unity3D中灵活绘制进度条

    有时我们需要在Unity3D中绘制进度条,如:           或        如果使用4.6版本以下的unity绘制环形的进度条可能需要费点劲.我搜到的大多数方法都是用NGUI插件,但有时只是 ...

随机推荐

  1. 详细分析LoadRunner参数化

    在进行网页的性能测试时,对网页的登录界面进行压力测试情况下就会使用到多用户进行登录,就需要对登录名和密码进行参数化,那么loadrunner怎么参数化设置呢?下面我们来详细分析一下. 一.我们这里通过 ...

  2. vue中 关于$emit的用法

    1.父组件可以使用 props 把数据传给子组件.2.子组件可以使用 $emit 触发父组件的自定义事件. vm.$emit( event, arg ) //触发当前实例上的事件 vm.$on( ev ...

  3. https多网站1个IP多个SSL证书的Apache设置办法

    这些天接触了解SSL证书后,写了一篇<申请免费的SSL证书,开通https网站>博文,其中简单记录了Apache的设置,后来又涉及到多个域名.泛域名解析.通配符SSL证书.单服务器/多服务 ...

  4. Dom事件流、冒泡、捕获

    Dom事件流 dom的结构是一个倒立的树状结构.当一个html元素触发事件时,事件会在dom的根节点和触发事件的元素节点之间传播,中间的节点都会收到该事件. 捕获:div元素触发事件时,事件先从根节点 ...

  5. BJOI2018 简要题解

    二进制 序列上线段树维护DDP好题. 题解可以看这篇 代码: #include<bits/stdc++.h> #define ri register int using namespace ...

  6. CSS3背景相关新增属性

    background-clip border-box:充满边框和内边距,内容. padding-box:充满内边距,内容 content-box:只充满内容 background-origin bor ...

  7. 从git远程仓库Checkout项目到本地

    一.登录coding  并且项目已创建好  已经是项目的组员 二.打开idea 1.弹出如下页面  复制远程项目上的SSH(URL)到下框URL 并且Test测试 成功就Clone即可 2.Clone ...

  8. 关于c++的一篇随笔

    众所周知c++是一门极其深奥的学科,正因为其深奥之处,才会让人们觉得学习起来特别难.当然,我想说我自己也不例外,想起当初就像一场噩梦一样,直到今日还历历在目.尽管如此,c++还是一门相当有魅力的课程, ...

  9. 背水一战 Windows 10 (94) - 选取器: 自定义文件打开选取器

    [源码下载] 背水一战 Windows 10 (94) - 选取器: 自定义文件打开选取器 作者:webabcd 介绍背水一战 Windows 10 之 选取器 自定义文件打开选取器 示例1.演示如何 ...

  10. 酷炫,用Html5/CSS实现文字阴影

    前两天有一个学html5前端小美女问我一个有关文字阴影的效果怎么去实现.她和我说文字阴影嘛,她也知道text-shadow,.但是却做不出想要的样子,其实css3的新功能是很强大的,不要把你的思想太过 ...