Unity-自定义事件派发器的两次尝试
一、前言:
在游戏开发的很多时候,需要引用其他类的方法,但是一旦类多起来了,相互引用会导致引用关系混乱,极其难以阅读。
以前初次做抖音小游戏时,和一位经验老道的cocos程序员合作,看到我写的代码他不禁皱起眉头,说我的引用关系太乱了,看不懂,但是他又不知道unity的事件派发器怎么写,就去网上找了一个。简直惊艳到我了。后来在现在公司做,又见到了一种事件派发器,于是心生感慨,模仿写了一个,并写博客记录一下。
二、现在做的事件派发器
1.声明对应的委托,此委托主要为事件用的。委托的所有返回类型都为Void,简化派发器的复杂程度;明确委托的方法参数类型,有几种类型就定义几种委托。
public delegate void VoidDelegate();
public delegate void BoolDelegate(params bool[] parameters);
public delegate void NumberDelegate(params float[] parameters);
public delegate void GameObjectDelegate(params GameObject[] parameters);
2.存储容器,用于存储事件
private static List<VoidDelegate> GameStart_List;
3.监听者,暴露给外部调用者的接口,对监听者的+=或-=对应容器里的Add和Remove
public static event VoidDelegate GameStart_Listener
{
add
{
if(value != null)
{
if(GameStart_List == null)
{
GameStart_List = new List<VoidDelegate>(1);
}
GameStart_List.Add(value);
}
}
remove
{
if(value != null)
{
for(int i = 0;i<GameStart_List.Count;i++)
{
if(GameStart_List[i] != null && GameStart_List[i].Equals(value))
{
GameStart_List.RemoveAt(i);
break;
}
}
}
}
}
4.派发者,因为event只能在声明类内部Invoke,所以需要暴露给外部调用者接口
public static void GameStart_Dispatch()
{
if(GameStart_List == null || GameStart_List.Count <= 0)
{
return;
}
for(int i = 0;i < GameStart_List.Count;i++)
{
GameStart_List[i]?.Invoke();
}
}
5.上文可看见我的事件叫GameStart,那么我想新增一个GameEnd的事件,岂不是又要写一遍?而且假如我的方法参数是float呢?是bool呢?是GameObject呢?岂不是改动很大?所以我在Unity做了一个自动生成事件的工具
5.1 定义ScriptObject作为配置文件,可随时修改以添加或者删除事件
[CreateAssetMenu]
public class EventHandlerSetting:ScriptableObject
{
public List<EventType> types;
public List<EventItem> items;
} [System.Serializable]
public class EventItem
{
public string eventName;
public string typeName;
} [System.Serializable]
public class EventType
{
public string typeName;
public string typeDelegate;
}
5.2 自动生成脚本工具
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Text;
using System.Text.RegularExpressions; public class CodeGen
{
private static string SettingPath = @"EventHandleSetting";
private static EventHandlerSetting SettingObj;
private static string CodePath = @"Assets\EventHandler\";
private static string CodeName = "EventHandler.cs";
private static string LineFeed = "\r\n";
private static string LineTable = "\t"; [MenuItem("Tools/EventCodeGen")]
public static void CreateEventCode()
{
if (File.Exists(CodePath+CodeName))
{
File.Delete(CodePath + CodeName);
}
File.Create(CodePath + CodeName).Dispose();
LoadSetting(); File.AppendAllText(CodePath + CodeName, CreateNamespace());
File.AppendAllText(CodePath + CodeName, CreateDelegate());
File.AppendAllText(CodePath + CodeName, CreateClass()); AssetDatabase.Refresh();
} /// <summary>
/// 加载配置文件
/// </summary>
static void LoadSetting()
{
SettingObj = Resources.Load<EventHandlerSetting>(SettingPath);
} static string CreateNamespace()
{
StringBuilder result = new StringBuilder();
result.Append("using System;" + LineFeed);
result.Append("using System.Collections;" + LineFeed);
result.Append("using System.Collections.Generic;" + LineFeed);
result.Append("using UnityEngine;" + LineFeed+LineFeed);
return result.ToString();
} /// <summary>
/// 创建委托类型
/// </summary>
/// <returns></returns>
static string CreateDelegate()
{
StringBuilder result = new StringBuilder();
string commnPrefix = "public delegate void ";
using (var e = SettingObj.types.GetEnumerator())
{
while (e.MoveNext())
{
result.Append(commnPrefix + e.Current.typeDelegate + ";" + LineFeed);
}
}
result.Append(LineFeed);
return result.ToString();
} /// <summary>
/// 创建EventHandler类
/// </summary>
/// <returns></returns>
static string CreateClass()
{
StringBuilder result = new StringBuilder();
result.Append("public class EventHandler"+LineFeed);
result.Append("{"+LineFeed);
List<EventItem> ls = SettingObj.items;
for (int i = 0; i < ls.Count; i++)
{
string eventName = ls[i].eventName;
string eventType = ls[i].typeName;
result.Append(CreateEvent(eventName, eventType));
}
result.Append("}");
return result.ToString();
} /// <summary>
/// 创建事件派发器
/// </summary>
/// <param name="eventName"></param>
/// <param name="eventType"></param>
/// <returns></returns>
static string CreateEvent(string eventName,string eventType)
{
StringBuilder result = new StringBuilder();
result.Append(LineFeed + "#region " + eventName + LineFeed);
result.Append(MutiLineTable(1) + string.Format("private static List<{0}> {1}_List;", eventType + "Delegate", eventName) + LineFeed);//创建List
result.Append(CreateListener(eventName, eventType));
result.Append(CreateDispatch(eventName, eventType));
result.Append("#endregion"+LineFeed);
return result.ToString();
} /// <summary>
/// 创建Listener
/// </summary>
/// <param name="eventName"></param>
/// <param name="eventType"></param>
/// <returns></returns>
static string CreateListener(string eventName,string eventType)
{
StringBuilder result = new StringBuilder();
result.Append(MutiLineTable(1) + string.Format("public static event {0} {1}_Listener", eventType + "Delegate", eventName) + LineFeed);
result.Append(MutiLineTable(1) + "{" + LineFeed);
#region add
result.Append(MutiLineTable(2) + "add" + LineFeed);
result.Append(MutiLineTable(2) + "{" + LineFeed);
result.Append(MutiLineTable(3) + "if(value != null)" + LineFeed);
result.Append(MutiLineTable(3) + "{" + LineFeed);
result.Append(MutiLineTable(4) + string.Format("if({0}_List == null)", eventName) + LineFeed);
result.Append(MutiLineTable(4) + "{" + LineFeed);
result.Append(MutiLineTable(5) + string.Format("{0}_List = new List<{1}>(1);", eventName, eventType + "Delegate")+LineFeed);
result.Append(MutiLineTable(4) + "}" + LineFeed);
result.Append(MutiLineTable(4) + string.Format("{0}_List.Add(value);", eventName) + LineFeed);
result.Append(MutiLineTable(3) + "}" + LineFeed);
result.Append(MutiLineTable(2) + "}" + LineFeed);
#endregion
#region remove
result.Append(MutiLineTable(2) + "remove" + LineFeed);
result.Append(MutiLineTable(2) + "{" + LineFeed);
result.Append(MutiLineTable(3) + "if(value != null)" + LineFeed);
result.Append(MutiLineTable(3) + "{" + LineFeed);
result.Append(MutiLineTable(4) + string.Format("for(int i = 0;i<{0}_List.Count;i++)", eventName) + LineFeed);
result.Append(MutiLineTable(4) + "{" + LineFeed);
result.Append(MutiLineTable(5) + string.Format("if({0}_List[i] != null && {1}_List[i].Equals(value))",eventName,eventName) + LineFeed);
result.Append(MutiLineTable(5) + "{" + LineFeed);
result.Append(MutiLineTable(6) + string.Format("{0}_List.RemoveAt(i);" ,eventName) + LineFeed);
result.Append(MutiLineTable(6) + "break;" + LineFeed);
result.Append(MutiLineTable(5) + "}" + LineFeed);
result.Append(MutiLineTable(4) + "}" + LineFeed);
result.Append(MutiLineTable(3) + "}" + LineFeed);
result.Append(MutiLineTable(2) + "}" + LineFeed);
#endregion
result.Append(MutiLineTable(1) + "}" + LineFeed);
return result.ToString();
} /// <summary>
/// 创建Dispatch
/// </summary>
/// <param name="eventName"></param>
/// <param name="eventType"></param>
/// <returns></returns>
static string CreateDispatch(string eventName,string eventType)
{
StringBuilder result = new StringBuilder();
result.Append(MutiLineTable(1) + string.Format("public static void {0}_Dispatch({1})", eventName, GetTypeParameter(eventType)) + LineFeed);
result.Append(MutiLineTable(1) + "{" + LineFeed);
result.Append(MutiLineTable(2) + string.Format("if({0}_List == null || {1}_List.Count <= 0)", eventName, eventName) + LineFeed);
result.Append(MutiLineTable(2) + "{" + LineFeed);
result.Append(MutiLineTable(3) + "return;" + LineFeed);
result.Append(MutiLineTable(2) + "}" + LineFeed);
result.Append(MutiLineTable(2) + string.Format("for(int i = 0;i < {0}_List.Count;i++)", eventName) + LineFeed);
result.Append(MutiLineTable(2) + "{" + LineFeed);
if (!string.IsNullOrEmpty(GetTypeParameter(eventType)))
{
result.Append(MutiLineTable(3) + string.Format("{0}_List[i]?.Invoke(parameters[i]);", eventName) + LineFeed);
}
else
{
result.Append(MutiLineTable(3) + string.Format("{0}_List[i]?.Invoke();", eventName) + LineFeed);
}
result.Append(MutiLineTable(2) + "}" + LineFeed);
result.Append(MutiLineTable(1) + "}" + LineFeed);
return result.ToString();
} /// <summary>
/// 获取字符串中括号中的内容
/// </summary>
/// <param name="typeName"></param>
/// <returns></returns>
static string GetTypeParameter(string typeName)
{
if (string.IsNullOrEmpty(typeName))
{
return string.Empty;
}
using (var e=SettingObj.types.GetEnumerator())
{
while (e.MoveNext())
{
if (e.Current.typeName==typeName)
{
string @delegate = e.Current.typeDelegate;
string result = @delegate.Substring(@delegate.IndexOf("(") + 1, @delegate.IndexOf(")") - (@delegate.IndexOf("(") + 1));
Debug.Log(result);
return result;
}
}
}
return string.Empty;
} /// <summary>
/// 多个table
/// </summary>
/// <param name="count"></param>
/// <returns></returns>
static string MutiLineTable(int count)
{
StringBuilder result = new StringBuilder();
for (int i = 0; i < count; i++)
{
result.Append(LineTable);
}
return result.ToString() ;
}
}
6.总结
当然,我的这一套事件系统肯定还是有问题的。如果方法参数不止一个float呢?第二个参数是bool?组合起来呢?还有一个问题是,配置文件不够人性化,全部都是字符串,假如多大一个空格或者标点就废了。还有一个非常严重的问题,如果生成的脚本,在语法上有错,不能通过编译器,Unity就会报错,再次点击生成就不会生效。我认识的一个主程让我在Unity外部生成,不要依赖Unity,且使用类似Lua、Python这种脚本语言,目前还不会哈哈哈哈。
三、以前做的事件派发器
1.需要一个通用的参数类型,叫EventArgs,基础类型为Systen.Object
public class EventArgs
{
private List<System.Object> parameters;
public int Count
{
get
{
if (parameters!=null)
{
return parameters.Count;
}
else
{
Debug.Log("parameters is not init");
return 0;
}
}
}
public EventArgs(params System.Object[] parameters)
{
if (this.parameters==null)
{
this.parameters = new List<object>();
}
for (int i = 0; i < parameters.Length; i++)
{
this.parameters.Add(parameters[i]);
}
}
public System.Object this[int index]
{
get
{
if (index>=0||index<parameters.Count)
{
return parameters[index];
}
else
{
Debug.LogError("index must be in range of parameters");
return null;
}
}
}
}
2.还是那句老话,事件派发器需要容器、监听、派发三部分。
public class EventDispatcher
{
public delegate void Listener(EventArgs args);
private static Dictionary<string, Listener> cacheEvents = new Dictionary<string, Listener>(); public static void Attach(string tag,Listener listen)
{
if (cacheEvents==null)
{
cacheEvents = new Dictionary<string, Listener>();
}
if (cacheEvents.ContainsKey(tag))
{
Debug.LogWarning("this tag already exsit in cache,please check agin,tag name:" + tag);
return;
}
if (listen==null)
{
Debug.LogWarning("listen is null,cache failed");
return;
}
cacheEvents.Add(tag, listen);
} public static void Detach(string tag)
{
if (!cacheEvents.ContainsKey(tag))
{
Debug.LogWarning("tag is not exsit in cache,tag name:"+tag);
return;
}
cacheEvents.Remove(tag);
} public static void Dispatch(string tag,EventArgs args)
{
if (!cacheEvents.ContainsKey(tag))
{
Debug.LogWarning("this tag does not exsit in cache,please check agin,tag name:" + tag);
return;
}
if (cacheEvents[tag]==null)
{
Debug.LogWarning("this listen is null,invoke failed");
return;
}
cacheEvents[tag].Invoke(args);
}
}
3.总结:
此事件派发器也存在缺陷,任何方法的参数类型都会被转换成System.Object类型,有多余的封装箱操作。且不能重复添加一个方法,至少他的tag不能一样。代码可读性差,报错了都不知道在哪儿。
四、关于事件派发器自己的看法
我相信没有完美的派发器这一说,好的派发器与坏的派发器区别在于,调用是否方便?会不会存在隐藏的危险bug?性能上如何?不同项目有不同的事件派发器,适合自己的才是最好的。如果强行将事件派发器做成那种万金油工具,且不论他是否真的是万金油,代码开发成本之大,耗费时间之长,也不是一般小游戏公司能够耗得起的。上文所述两个派发器,实际上都可以用,而且经历过实战的,并没有什么大问题。
Unity-自定义事件派发器的两次尝试的更多相关文章
- 48、[源码]-Spring容器创建-初始化事件派发器、监听器等
48.[源码]-Spring容器创建-初始化事件派发器.监听器等 8.initApplicationEventMulticaster();初始化事件派发器: 获取BeanFactory 从BeanFa ...
- 使用lua实现一个简单的事件派发器
设计一个简单的事件派发器,个人觉得最重要的一点就是如何保证事件派发过程中,添加或删除同类事件,不影响事件迭代顺序和结果,只要解决这一点,其它都好办. 为了使用pairs遍历函数,重写了pairs(lu ...
- cocos2d JS 自定义事件分发器(接收与传递数据) eventManager
简而言之,它不是由系统自动触发,而是人为的干涉 较多情况用于传递数据 var _listener1 = cc.EventListener.create({ event: cc.EventListene ...
- JS 事件派发器EventDispatcher
在Java和AS中经常用到EventDispatcher,写了一个JS版本的. addListener :添加事件监听器 removeListener:移除事件监听器 dispatchEvent:派发 ...
- JS事件派发器EventEmitter
原文地址:http://zhangyiheng.com/blog/articles/js_event_mitter.html 需求 随着Browser客户端JS越来越复杂,MVC(Client端)设计 ...
- jQuery 自定义事件的学习笔记
jquery中提供了两种方法可以绑定自定义事件: bind()和one()而绑定的自定义事件的触发,必须得用jquery中的trigger()方法才能触发. 我们先来看on事件 代码如下 复制代码 ...
- 编写高质量代码改善C#程序的157个建议——建议151:使用事件访问器替换公开的事件成员变量
建议151:使用事件访问器替换公开的事件成员变量 事件访问器包含两部分内容:添加访问器和删除访问器.如果涉及公开的事件字段,应该始终使用事件访问器.代码如下所示: class SampleClass ...
- Flex事件机制学习-自定义事件实现类间通信 .
今天,学习Flex自定义事件,可以使两个类通信,定义一个Main类. public class Main extends Sprite { public function ...
- [问题贴]mui.openWindow+自定义事件监听操作让alert()执行两次
仔细看,Alert函数执行了两次 共两个页面:index.html和detail.html, detail.html为按钮设置了自定义事件监听(newsId),触发alert. 在index.html ...
随机推荐
- 使用 Blueprint 要注意 render_template 函数
此文章主要是为了记录在使用 Flask 的过程中遇到的问题.本章主要讨论 render_template 函数的问题. 使用 Flask 的同学都应该知道,项目中的 url 和视图函数是在字典里一一对 ...
- ES6-11学习笔记--数组遍历
ES5中数组遍历方式: for循环 forEach():没有返回值,只是针对每个元素调用func map():返回新的Array,每个元素为调用func的结果 filter():返回符合func条件的 ...
- Hive进行数据统计时报错:org.apache.hadoop.mapreduce.v2.app.MRAppMaster: Error starting MRAppMaster
报错详情: 2020-04-09 22:56:58,827 ERROR [Listener at 0.0.0.0/45871] org.apache.hadoop.mapreduce.v2.app.M ...
- uni-app中 未收藏和已收藏功能展示
效果图如下: 未收藏: 已收藏: 代码实现: 1 <view class="jichu"> 2 <view class="name">x ...
- Linux_文件传输工具_FileZilla
什么是FileZilla? FileZilla是一个免费开源的FTP软件,分为客户端版本和服务器版本,具备所有的FTP软件功能.可控性.有条理的界面和管理多站点的简化方式使得Filezilla客户端版 ...
- php代码审计之——phpstorm动态调试
xdebug调试 调试环境部署 xdebug的版本需要与PHP版本相对于,所以不要轻易改变PHP环境版本. 0 配置php解析器 1 下载对应版本的xdebug xdebug官网下载地址:https: ...
- MFC---文档与视图结构
文档与视图结构 文档.视图的关系,是一对多的映射,一个文档可以对应多个视图,而一个视图只能对应一个文档.例如,一个.html文件,可以用记事本打开,也可以用浏览器打开,这里的.html文件就是文档,记 ...
- Hadoop安装部署
Hadoop伪分布式搭建 1.准备Linux环境 ①开启网络,ifconfig指令查看ip ②修改主机名为自己名字(hadoop) vim /etc/sysconfig/network NETWORK ...
- Codeforces Round #752 (Div. 2) A B C
Problem - A - Codeforces Problem - B - Codeforces Problem - C - Codeforces A. Era 每个a[i] - i 表示的是当前a ...
- sqlmap Tamper脚本编写
sqlmap Tamper脚本编写 前言 sqlmap是一个自动化的SQL注入工具,其主要功能是扫描,发现并利用给定的URL的SQL注入漏洞,目前支持的数据库是MySQL, Oracle, Postg ...