一、简介

  最近马三换了一家大公司工作,公司制度规范了一些,因此平时的业余时间多了不少。但是人却懒了下来,最近这一个月都没怎么研究新技术,博客写得也是拖拖拉拉,周六周天就躺尸在家看帖子、看小说,要么就是吃鸡,唉!真是罪过罪过。希望能从这篇博客开始有些改善吧,尽量少玩耍,还是多学习吧~

  好了扯得有点远了,来说说我们今天博客的主题——“用C#和Lua实现Unity中的事件分发机制”,事件分发机制或者叫事件监听派发系统,在每个游戏框架中都是不可或缺的一个模块。我们可以用它来解耦,监听网络消息,或者做一些异步的操作,好处多多(其实是别人的框架都有这个,所以我们的框架也必须有这玩意~)。今天马三就和大家一起,分别使用C#和Lua实现两种可以用在Unity游戏开发中的事件分发处理机制,希望能对大家有些帮助吧~

二、C#版的事件分发机制

  首先我们来实现C#版本的事件分发机制,目前这套流程已经集成到了马三自己的 ColaFrameWork框架 中了。这套框架还在架构阶段,里面很多东西都不完善,马三也是会随时把自己的一些想法放到里面,大家感兴趣的话也可以帮忙维护一下哈!

  一般来说事件订阅、派发这种机制都是使用观察者模式来实现的,本篇博客也不例外,正是利用了这种思想。为了解耦和面向接口编程,我们制定了一个接口IEventHandler,凡是观察者都需要实现这个接口,而GameEventMgr事件中心维护了一个IEventHandler列表,保存着一系列的观察者,并在需要的时候进行一系列的动作。这样操作正是遵循了依赖倒置的设计原则:“高层模块不应该依赖于低层模块,两者都应该依赖于抽象概念;”、“抽象接口不应该依赖于实现,而实现应该依赖于抽象接口”。下面的代码定义了IEventHandler接口和一些委托还有事件传递时需要携带的参数。

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using EventType = ColaFrame.EventType;
  4.  
  5. /// <summary>
  6. /// 接收消息后触发的回调
  7. /// </summary>
  8. /// <param name="data"></param>
  9. public delegate void MsgHandler(EventData data);
  10.  
  11. /// <summary>
  12. /// 事件处理器的接口
  13. /// </summary>
  14. public interface IEventHandler
  15. {
  16. bool HandleMessage(GameEvent evt);
  17.  
  18. bool IsHasHandler(GameEvent evt);
  19. }
  20.  
  21. /// <summary>
  22. /// 事件消息传递的数据
  23. /// </summary>
  24. public class EventData
  25. {
  26. public string Cmd;
  27. public List<object> ParaList;
  28. }
  29.  
  30. /// <summary>
  31. /// 游戏中的事件
  32. /// </summary>
  33. public class GameEvent
  34. {
  35. /// <summary>
  36. /// 事件类型
  37. /// </summary>
  38. public EventType EventType { get; set; }
  39. /// <summary>
  40. /// 携带参数
  41. /// </summary>
  42. public object Para { get; set; }
  43. }
  44.  
  45. namespace ColaFrame
  46. {
  47. /// <summary>
  48. /// 事件的类型
  49. /// </summary>
  50. public enum EventType : byte
  51. {
  52. /// <summary>
  53. /// 系统的消息
  54. /// </summary>
  55. SystemMsg = ,
  56. /// <summary>
  57. /// 来自服务器推送的消息
  58. /// </summary>
  59. ServerMsg = ,
  60. /// <summary>
  61. /// UI界面消息
  62. /// </summary>
  63. UIMsg = ,
  64. }
  65. }

  然后我们再来看一下最核心的事件中心处理器GameEventMgr是如何实现的,还是先上一下全部的代码 GameEventMgr.cs:

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using EventType = ColaFrame.EventType;
  6.  
  7. /// <summary>
  8. /// 事件消息管理中心
  9. /// </summary>
  10. public class GameEventMgr
  11. {
  12. /// <summary>
  13. /// 存储Hander的字典
  14. /// </summary>
  15. private Dictionary<int, List<IEventHandler>> handlerDic;
  16.  
  17. private static GameEventMgr instance = null;
  18.  
  19. private GameEventMgr()
  20. {
  21. handlerDic = new Dictionary<int, List<IEventHandler>>();
  22. }
  23.  
  24. public static GameEventMgr GetInstance()
  25. {
  26. if (null == instance)
  27. {
  28. instance = new GameEventMgr();
  29. }
  30. return instance;
  31. }
  32.  
  33. /// <summary>
  34. /// 对外提供的注册监听的接口
  35. /// </summary>
  36. /// <param name="handler"></param>监听者(处理回调)
  37. /// <param name="eventTypes"></param>想要监听的事件类型
  38. public void RegisterHandler(IEventHandler handler, params EventType[] eventTypes)
  39. {
  40. for (int i = ; i < eventTypes.Length; i++)
  41. {
  42. RegisterHandler(eventTypes[i], handler);
  43. }
  44. }
  45.  
  46. /// <summary>
  47. /// 内部实际调用的注册监听的方法
  48. /// </summary>
  49. /// <param name="type"></param>要监听的事件类型
  50. /// <param name="handler"></param>监听者(处理回调)
  51. private void RegisterHandler(EventType type, IEventHandler handler)
  52. {
  53. if (null != handler)
  54. {
  55. if (!handlerDic.ContainsKey((int)type))
  56. {
  57. handlerDic.Add((int)type, new List<IEventHandler>());
  58. }
  59. if (!handlerDic[(int)type].Contains(handler))
  60. {
  61. handlerDic[(int)type].Add(handler);
  62. }
  63. }
  64. }
  65.  
  66. /// <summary>
  67. /// 反注册事件监听的接口,对所有类型的事件移除指定的监听
  68. /// </summary>
  69. /// <param name="handler"></param>
  70. public void UnRegisterHandler(IEventHandler handler)
  71. {
  72. using (var enumerator = handlerDic.GetEnumerator())
  73. {
  74. List<IEventHandler> list;
  75. while (enumerator.MoveNext())
  76. {
  77. list = enumerator.Current.Value;
  78. list.Remove(handler);
  79. }
  80. }
  81. }
  82.  
  83. /// <summary>
  84. /// 反注册事件监听的接口,移除指定类型事件的监听
  85. /// </summary>
  86. /// <param name="handler"></param>
  87. /// <param name="types"></param>
  88. public void UnRegisterHandler(IEventHandler handler, params EventType[] types)
  89. {
  90. EventType type;
  91. for (int i = ; i < types.Length; i++)
  92. {
  93. type = types[i];
  94. if (handlerDic.ContainsKey((int)type) && handlerDic[(int)type].Contains(handler))
  95. {
  96. handlerDic[(int)type].Remove(handler);
  97. }
  98. }
  99. }
  100.  
  101. /// <summary>
  102. /// 分发事件
  103. /// </summary>
  104. /// <param name="gameEvent"></param>想要分发的事件
  105. public void DispatchEvent(GameEvent gameEvent)
  106. {
  107. bool eventHandle = false;
  108.  
  109. List<IEventHandler> handlers;
  110. if (null != gameEvent && handlerDic.TryGetValue((int)gameEvent.EventType, out handlers))
  111. {
  112. for (int i = ; i < handlers.Count; i++)
  113. {
  114. try
  115. {
  116. eventHandle = handlers[i].HandleMessage(gameEvent) || eventHandle;
  117. }
  118. catch (Exception e)
  119. {
  120. Debug.LogError(e);
  121. }
  122. }
  123.  
  124. if (!eventHandle)
  125. {
  126. if (null != gameEvent)
  127. {
  128. switch (gameEvent.EventType)
  129. {
  130. case EventType.ServerMsg:
  131. break;
  132. default:
  133. Debug.LogError("消息未处理,类型:" + gameEvent.EventType);
  134. break;
  135. }
  136. }
  137. }
  138. }
  139. }
  140.  
  141. /// <summary>
  142. /// 分发事件
  143. /// </summary>
  144. /// <param name="evt"></param>分发的消息名称
  145. /// <param name="eventType"></param>消息事件类型
  146. /// <param name="para"></param>参数
  147. public void DispatchEvent(string evt, EventType eventType = EventType.UIMsg, params object[] para)
  148. {
  149. GameEvent gameEvent = new GameEvent();
  150. gameEvent.EventType = eventType;
  151. EventData eventData = new EventData();
  152. eventData.Cmd = evt;
  153. if (null != para)
  154. {
  155. eventData.ParaList = new List<object>();
  156. for (int i = ; i < para.Length; i++)
  157. {
  158. eventData.ParaList.Add(para[i]);
  159. }
  160. }
  161. gameEvent.Para = eventData;
  162.  
  163. this.DispatchEvent(gameEvent);
  164. }
  165.  
  166. /// <summary>
  167. /// 分发事件
  168. /// </summary>
  169. /// <param name="evt"></param>分发的消息名称
  170. /// <param name="eventType"></param>消息事件类型
  171. public void DispatchEvent(string evt, EventType eventType = EventType.UIMsg)
  172. {
  173. GameEvent gameEvent = new GameEvent();
  174. gameEvent.EventType = eventType;
  175. EventData eventData = new EventData();
  176. eventData.Cmd = evt;
  177. eventData.ParaList = null;
  178. gameEvent.Para = eventData;
  179.  
  180. this.DispatchEvent(gameEvent);
  181. }
  182. }

我们在其内部维护了一个handlerDic字典,它的key是int类型的,对应的其实就是我们在上面定义的EventType 这个枚举,它的value是一个元素为IEventHandler类型的列表,也就是说我们按照不同的事件类型,将监听者分为了几类进行处理。监听者是可以监听多个消息类型的,也就是说一个监听者实例可以存在于多个列表中,这样并不会产生冲突。我们就从RegisterHandler(IEventHandler handler, params EventType[] eventType)这个对外提供的注册监听的接口入手,逐步的分析一下它的工作流程:

  1. 调用RegisterHandler方法,传入监听者和需要监听的事件类型(可以是数组,支持多个事件类型),然后遍历事件类型,依次调用RegisterHandler(EventType type, IEventHandler handler)接口,将监听者逐个的注册到每个事件类型对应的监听者列表中;
  2. 当需要分发事件的时候,调用DispatchEvent方法,传入一个GameEvent类型的参数gameEvent,它包含了需要派发的事件属于什么类型,和对应的事件消息需要传递的参数,其中这个参数又包含了字符串具体的事件名称和一个参数列表;
  3. 在DispatchEvent中,会根据事件类型来判断内部字段中是否有注册了该事件的监听者,如果有就取到存有这个监听者的列表;
  4. 然后依次遍历每个监听者,调用其HandleMessage方法,进行具体消息的处理,该函数还会返回一个bool值,表示是否处理了该消息。如果遍历了所有的监听者以后,发现没有处理该消息的监听者,则会打印一个错误消息进行提示;
  5. DispatchEvent(string evt, EventType eventType = EventType.UIMsg, params object[] para)和DispatchEvent(string evt, EventType eventType = EventType.UIMsg)这两个接口是对DispatchEvent接口的进一步封装,方便用户进行无参消息派发和含参数消息派发;

最后我们再来看一下具体的监听者应该如何实现IEventHandler接口,以 ColaFrameWork框架 中的UI基类——UIBase举例,在UIBase内部维护了一个Dictionary<string, MsgHandler> msgHanderDic 结构,用它来保存具体的事件名称对应的回调函数,然后再去具体地实现HandleMessage和IsHasHandler接口中的抽象方法,代码如下:

  1. /// <summary>
  2. /// 处理消息的函数的实现
  3. /// </summary>
  4. /// <param name="gameEvent"></param>事件
  5. /// <returns></returns>是否处理成功
  6. public bool HandleMessage(GameEvent evt)
  7. {
  8. bool handled = false;
  9. if (EventType.UIMsg == evt.EventType)
  10. {
  11. if (null != msgHanderDic)
  12. {
  13. EventData eventData = evt.Para as EventData;
  14. if (null != eventData && msgHanderDic.ContainsKey(eventData.Cmd))
  15. {
  16. msgHanderDic[eventData.Cmd](eventData);
  17. handled = true;
  18. }
  19. }
  20. }
  21. return handled;
  22. }
  23.  
  24. /// <summary>
  25. /// 是否处理了该消息的函数的实现
  26. /// </summary>
  27. /// <returns></returns>是否处理
  28. public bool IsHasHandler(GameEvent evt)
  29. {
  30. bool handled = false;
  31. if (EventType.UIMsg == evt.EventType)
  32. {
  33. if (null != msgHanderDic)
  34. {
  35. EventData eventData = evt.Para as EventData;
  36. if (null != eventData && msgHanderDic.ContainsKey(eventData.Cmd))
  37. {
  38. handled = true;
  39. }
  40. }
  41. }
  42. return handled;
  43. }

为了使用更加简洁方便,我们还可以再封装一些函数出来,以便随时注册一个消息和取消注册一个消息,主要是RegisterEvent和UnRegisterEvent接口,代码如下:

  1. /// <summary>
  2. /// 初始化注册消息监听
  3. /// </summary>
  4. protected void InitRegisterHandler()
  5. {
  6. msgHanderDic = null;
  7. GameEventMgr.GetInstance().RegisterHandler(this, EventType.UIMsg);
  8. msgHanderDic = new Dictionary<string, MsgHandler>()
  9. {
  10. };
  11. }
  12.  
  13. /// <summary>
  14. /// 取消注册该UI监听的所有消息
  15. /// </summary>
  16. protected void UnRegisterHandler()
  17. {
  18. GameEventMgr.GetInstance().UnRegisterHandler(this);
  19.  
  20. if (null != msgHanderDic)
  21. {
  22. msgHanderDic.Clear();
  23. msgHanderDic = null;
  24. }
  25. }
  26.  
  27. /// <summary>
  28. /// 注册一个UI界面上的消息
  29. /// </summary>
  30. /// <param name="evt"></param>
  31. /// <param name="msgHandler"></param>
  32. public void RegisterEvent(string evt, MsgHandler msgHandler)
  33. {
  34. if (null != msgHandler && null != msgHanderDic)
  35. {
  36. if (!msgHanderDic.ContainsKey(evt))
  37. {
  38. msgHanderDic.Add(Name + evt, msgHandler);
  39. }
  40. else
  41. {
  42. Debug.LogWarning(string.Format("消息{0}重复注册!", evt));
  43. }
  44. }
  45. }
  46.  
  47. /// <summary>
  48. /// 取消注册一个UI界面上的消息
  49. /// </summary>
  50. /// <param name="evt"></param>
  51. public void UnRegisterEvent(string evt)
  52. {
  53. if (null != msgHanderDic)
  54. {
  55. msgHanderDic.Remove(Name + evt);
  56. }
  57. }

关于C#版的事件分发机制大概就介绍到这里了,马三在这里只是大概地讲了下思路,更细致的原理和使用方法大家可以去马三的 ColaFrameWork框架 中找一下相关代码。

三、Lua版的事件分发机制

  Lua版本的事件分发机制相对C#版的来说就简单了很多,Lua中没有接口的概念,因此实现方式和C#版的也大有不同,不过总的来说还是对外暴露出以下几个接口:

  • Instance():获取单例
  • RegisterEvent():注册一个事件
  • UnRegisterEvent():反注册一个事件
  • DispatchEvent():派发事件
  • AddEventListener():增加监听者
  • RemoveEventListener():移除监听者

  照例还是先上一下核心代码EventMgr.lua,然后再逐步解释:

  1. require("Class")
  2. local bit = require "bit"
  3.  
  4. EventMgr = {
  5. --实例对象
  6. _instance = nil,
  7. --观察者列表
  8. _listeners = nil
  9. }
  10. EventMgr.__index = EventMgr
  11. setmetatable(EventMgr, Class)
  12.  
  13. -- 构造器
  14. function EventMgr:new()
  15. local t = {}
  16. t = Class:new()
  17. setmetatable(t, EventMgr)
  18. return t
  19. end
  20.  
  21. -- 获取单例接口
  22. function EventMgr:Instance()
  23. if EventMgr._instance == nil then
  24. EventMgr._instance = EventMgr:new()
  25. EventMgr._listeners = {}
  26. end
  27. return EventMgr._instance
  28. end
  29.  
  30. function EventMgr:RegisterEvent(moduleId, eventId, func)
  31. local key = bit.lshift(moduleId, ) + eventId
  32. self:AddEventListener(key, func, nil)
  33. end
  34.  
  35. function EventMgr:UnRegisterEvent(moduleId, eventId, func)
  36. local key = bit.lshift(moduleId, ) + eventId
  37. self:RemoveEventListener(key, func)
  38. end
  39.  
  40. function EventMgr:DispatchEvent(moduleId, eventId, param)
  41. local key = bit.lshift(moduleId, ) + eventId
  42. local listeners = self._listeners[key]
  43. if nil == listeners then
  44. return
  45. end
  46. for _, v in ipairs(listeners) do
  47. if v.p then
  48. v.f(v.p, param)
  49. else
  50. v.f(param)
  51. end
  52. end
  53. end
  54.  
  55. function EventMgr:AddEventListener(eventId, func, param)
  56. local listeners = self._listeners[eventId]
  57. -- 获取key对应的监听者列表,结构为{func,para},如果没有就新建
  58. if listeners == nil then
  59. listeners = {}
  60. self._listeners[eventId] = listeners -- 保存监听者
  61. end
  62. --过滤掉已经注册过的消息,防止重复注册
  63. for _, v in pairs(listeners) do
  64. if (v and v.f == func) then
  65. return
  66. end
  67. end
  68. --if func == nil then
  69. -- print("func is nil!")
  70. --end
  71. --加入监听者的回调和参数
  72. table.insert(listeners, { f = func, p = param })
  73. end
  74.  
  75. function EventMgr:RemoveEventListener(eventId, func)
  76. local listeners = self._listeners[eventId]
  77. if nil == listeners then
  78. return
  79. end
  80. for k, v in pairs(listeners) do
  81. if (v and v.f == func) then
  82. table.remove(listeners, k)
  83. return
  84. end
  85. end
  86. end

  在实际使用的时候主要是调用 RegisterEvent、UnRegisterEvent 和 DispatchEvent这三个接口。RegisterEvent用来注册一个事件,UnRegisterEvent 用来反注册一个事件,DispatchEvent用来派发事件。先从RegisterEvent接口说起,它需要传入3个参数,分别是ModuleId,EventId和回调函数func。ModuleId就是我们不同模块的id,他是一个模块的唯一标识,在实际应用中我们可以定义一个全局的枚举来标识这些模块ID。EventId是不同的消息的标识,它也是数字类型的枚举值,并且因为有了模块ID的存在,不同模块可以使用相同的EventId,这并不会导致消息的冲突。在RegisterEvent内部操作中,我们首先对ModuleId进行了左移16位的操作,然后再加上EventID组成我们的消息key,左移16位可以避免ModuleID直接与EventId组合后会产生Key冲突的问题,一般来说左移16位已经可以满足定义很多模块和事件id的需求了。然后调用 self:AddEventListener(key, func, nil) 方法,将计算出来的key和回调函数进行注册。在EventMgr的内部其实还是维护了一个监听者列表,注册消息的时候,就是把回调和参数添加到监听者列表中。反注册消息就是把对应key的回调从监听者列表中移除。派发事件的时候就是遍历key所对应的监听者列表,然后依次执行里面的回调函数。好了,接着说AddEventListener这个函数的操作,它首先会去获取key对应的监听者列表,结构为{func,para},如果没有就新建一个table,并把它保存为key所对应的监听者列表。得到这个监听者列表以后,我们首先会对其进行遍历,如果里面已经包含func回调函数的话,就直接return掉,过滤掉已经注册过的消息,防止重复注册。如果通过了上一步检查的话,就执行 table.insert(listeners, { f = func, p = param })操作,加入监听者的回调和参数。对于UnRegisterEvent方法,我们依然会计算出key,然后调用 RemoveEventListener 操作,把监听者从监听者列表中移除。在使用DispatchEvent接口进行事件派发的时候,我们依然会先计算出Key,然后取出key对应的监听者列表。接着依次遍历这些监听者,然后执行其中保存着的回调函数,并且把需要传递的事件参数传递进去。具体的使用方法,可以参考下面的Main.lua:

  1. require("EventMgr")
  2.  
  3. local function TestCallback_1()
  4. print("Callback_1")
  5. end
  6.  
  7. local function TestCallback_2(param)
  8. print("Callback_2")
  9. print(param.id)
  10. print(param.pwd)
  11. end
  12.  
  13. local EventMgr = EventMgr:Instance()
  14. EventMgr:RegisterEvent(, , TestCallback_1)
  15. EventMgr:RegisterEvent(, , TestCallback_2)
  16. EventMgr:DispatchEvent(, )
  17. EventMgr:DispatchEvent(, , { id = "abc", pwd = "" })

支持含参数事件分发和无参数事件分发,上面代码的执行结果如下,可以发现成功地监听了注册的消息,并且也获取到了传递过来的参数:

 图1:代码执行结果

四、总结

通过本篇博客,马三和大家一起学习了如何在Unity中使用C#和Lua分别实现事件分发机制,希望本篇博客能为大家的工作过程中带来一些帮助与启发。

本篇博客中的样例工程已经同步至Github:https://github.com/XINCGer/Unity3DTraining/tree/master/lua/LuaEventMgr,欢迎大家Fork!

马三的开源Unity客户端框架 ColaFramework框架:https://github.com/XINCGer/ColaFrameWork

如果觉得本篇博客对您有帮助,可以扫码小小地鼓励下马三,马三会写出更多的好文章,支持微信和支付宝哟!

       

作者:马三小伙儿
出处:https://www.cnblogs.com/msxh/p/9539231.html 
请尊重别人的劳动成果,让分享成为一种美德,欢迎转载。另外,文章在表述和代码方面如有不妥之处,欢迎批评指正。留下你的脚印,欢迎评论!

我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=3s3rkmf0oback

【Unity游戏开发】用C#和Lua实现Unity中的事件分发机制EventDispatcher的更多相关文章

  1. 【Unity游戏开发】浅谈Lua和C#中的闭包

    一.前言 目前在Unity游戏开发中,比较流行的两种语言就是Lua和C#.通常的做法是:C#做些核心的功能和接口供Lua调用,Lua主要做些UI模块和一些业务逻辑.这样既能在保持一定的游戏运行效率的同 ...

  2. 【Unity游戏开发】记一次解决 LuaFunction has been disposed 的bug的过程

    一.引子 RT,本篇博客记录的是马三的一次解决 LuaFunction has been disposed 的bug的全过程,事情还要从马三的自研框架 ColaFrameWork 说起.最近,马三在业 ...

  3. 关于Unity游戏开发方向找工作方面的一些个人看法

     这是个老生常谈,却又是谁绕不过去的话题,而对于每个人来说,所遇到的情况又不尽相同,别人的求职方式和路线不一定适合你,即使是背景很相似的两个人,有时候机遇也很重要. 我本人的工作经验只有一年,就业方式 ...

  4. C# Unity游戏开发——Excel中的数据是如何到游戏中的 (二)

    本帖是延续的:C# Unity游戏开发——Excel中的数据是如何到游戏中的 (一) 上个帖子主要是讲了如何读取Excel,本帖主要是讲述读取的Excel数据是如何序列化成二进制的,考虑到现在在手游中 ...

  5. C# Unity游戏开发——Excel中的数据是如何到游戏中的 (三)

    本帖是延续的:C# Unity游戏开发——Excel中的数据是如何到游戏中的 (二) 前几天有点事情所以没有继续更新,今天我们接着说.上个帖子中我们看到已经把Excel数据生成了.bin的文件,不过其 ...

  6. C# Unity游戏开发——Excel中的数据是如何到游戏中的 (四)2018.4.3更新

    本帖是延续的:C# Unity游戏开发--Excel中的数据是如何到游戏中的 (三) 最近项目不算太忙,终于有时间更新博客了.关于数据处理这个主题前面的(一)(二)(三)基本上算是一个完整的静态数据处 ...

  7. 2017年Unity游戏开发视频教程(入门到精通)

    本文是我发布的一个Unity游戏开发的学习目录,以后我会持续发布一系列的游戏开发教程,都会更新在这个页面上,适合人群有下面的几种: 想要做独立游戏的人 想要找游戏开发相关工作的人 对游戏开发感兴趣的人 ...

  8. Re:Unity游戏开发有哪些让你拍案叫绝的技巧?

    这是我在知乎一个问题: <Unity游戏开发有哪些让你拍案叫绝的技巧?> 下面的回答,觉得蛮有趣的,贴在这里和博客的朋友们分享下. ----- 分享一个比较好玩的内容吧. 大家都知道Uni ...

  9. Unity游戏开发常用的一些函数用法

    Unity游戏开发常用函数 本文提供全流程,中文翻译. Chinar 坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 -- 高分辨率用户请根据需求调整网页缩放比例) Chinar -- 心分享. ...

随机推荐

  1. django 时区和系统(ubuntu)时区修改

    django时区默认使用UTC,中国人使用CST东八区. settings.py改为上海时区 #settings.py TIME_ZONE = 'Asia/Shanghai' # True:使用UTC ...

  2. web安全—sql注入漏洞

    SQL注入-mysql注入 一.普通的mysql注入 MySQL注入不像注入access数据库那样,不需要猜.从mysql5.0以上的版本,出现一个虚拟的数据库,即:information_schem ...

  3. 剑指offer:调整数组顺序使奇数位于偶数前面

    题目 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分. 分析 事实上,这个题比较简单,很多种方式都可以实现,但是其时间复杂度或空间复 ...

  4. 父页面内获取获取iframe内的变量或者是获取iframe内的值

    前提:页面不可跨域访问,必须同一域名下,否则返回值为空 父页面 <!DOCTYPE html> <html lang="en"> <head> ...

  5. WinForms 快速开发的工具类。

    下面是我本人在 WinForms 开发中积累的一些心得. 1. 在父窗体中打开子窗体 直接贴代码: private void btnCompare_Click(object sender, Event ...

  6. handsontable的基础应用

    handsontable是一款页面端的表格式交互插件,可以通过她加载显示表格内容,能够支持合并项.统计.行列拖动等. 同时,支持对加载后的表格页面的处理:添加/删除行/列,合并单元格等操作. 我在项目 ...

  7. 专业的web打印插件

    Lodop是什么?参考官方网站:http://www.lodop.net/ 有人说她是Web打印控件,因为她能打印.在浏览器中以插件的形式出现,用简单一行语句就把整个网页打印出来: 有人说她是打印编程 ...

  8. Asp.Net Core配置的知识总结

    配置在Asp.Net Core中由四个核心的对象组成: IConfiguration:配置的最终产出物,它代表了整个asp.net core应用的配置树,这棵树有根节点,子节点和叶子节点,根节点由IC ...

  9. mysql时间比较

    ' and ZXBZ ='Y' AND SQRQ >= '2017-04-28 00:00:00' AND SQRQ <= '2017-04-28 23:59:59'; ;

  10. 内核调试打印dump_stack

    https://blog.csdn.net/dragon101788/article/details/9419175 在函数中加入dump_stack函数 需要包含的头文件: #include < ...