打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
基于id的游戏客户端事件分发(消息队列)

基于id的游戏客户端事件分发(消息队列)

分类: 游戏客户端 IOS游戏开发 260人阅读 评论(0) 收藏 举报

主要目的是完成游戏客户端内的事件处理。这里要明确几件事情:

1、同样一个方案在小项目中可能是漂亮的解决方案,但是在大项目中就是很不灵活的处理方法。游戏客户端无论如何都是小项目,不会像windows一样几千位工程师负责上千个dll,一起开发一个操作系统。所以,对我的需求而言,把模块分的很细,很独立意义不是很大,点到即止。

2、这样一个事件分发机制主要是处理业务逻辑对gui的控制,或者是不同的界面之间的交互。比如服务器下发一个消息给客户端,然后客户端要刷新物品界面;或者是竞技场界面打开后,通知打开玩家列表界面等等。单纯的点击一个按钮,然后响应按钮事件不需要用到这个功能。

3、这个模块就像ios的通知中心一样,可以最大程度的解耦合,与此对应的还有一个方案是基于接口观察者模式事件通知。我个人更倾向于id的方案,如果是接口的话,一方面要维护接口的一致性,另一方面要经常使用到c++最恶心,最容易出问题的多继承(我碰到过好几个非常隐晦的bug就是多继承内存排布引发的)。


基本实现思路是这样的:

1、先实现一个委托机制以方便的实现函数回调,这里我用的是fast_delegate,也可以选择boost::function。

2、写一个CommandQueueMgr来管理所有的id(一个枚举,由开发者进行统一维护,多个开发者可以分配多个id段,也可以写在各自的头文件里面以避免冲突),并提供注册事件,解除注册,响应事件等功能

3、每个需要事件响应的模块(界面)自己在初始化的时候进行注册(如果没有统一的入口,就写个静态变量来调用初始化函数)。这样就有了一个id和函数的对应关系。

4、原本需要调用函数的地方,执行PostMessage函数,发送一个id事件。调用者根本不需要知道被调用者是什么,如果这个id有对应函数就执行,否则就忽略(也可以写上log)。

5、根据实际需要可以写一个异步的PostMessage和一个同步的SendMessage

6、PostMessage可以只支持一个int参数,就像windows一样,这样会更加清晰。也可以支持多个参数,这样就要求开发者在写id的时候把对应参数的注释写好,否则会很混乱


  1. #pragma once  
  2.   
  3. #include <deque>  
  4. #include <boost/any.hpp>  
  5.   
  6. #ifdef __APPLE__  
  7. #include <tr1/functional>  
  8. using std::tr1::function;  
  9. using std::tr1::bind;  
  10. using namespace std::tr1::placeholders;  
  11.   
  12. namespace std {  
  13.     template<class _Ty> struct _Remove_reference  
  14.     {  
  15.         typedef _Ty _Type;  
  16.     };  
  17.       
  18.     template<class _Ty> inline typename _Remove_reference<_Ty>::_Type&& move(_Ty&& _Arg)  
  19.     {  
  20.         return ((typename _Remove_reference<_Ty>::_Type&&)_Arg);  
  21.     }  
  22. };  
  23. #else  
  24. #include <functional>  
  25. using std::function;  
  26. using std::bind;  
  27. using namespace std::placeholders;  
  28. #endif  
  29.   
  30. #include "MySingleton.h"  
  31. #include "MyThread.h"  
  32.   
  33.   
  34.   
  35. class Parameter  
  36. {  
  37. public:  
  38.     #define PARAM_IMPL_INIT(t) Parameter(t para) { m_data = para; }  
  39.   
  40. #ifdef WIN32  
  41.     // windows下面类型转换失败抛出异常  
  42.     #define PARAM_IMPL_RET(t, it,value) operator t () { t ret = value;  ret = boost::any_cast<it>(m_data);  return std::move(ret); };  
  43. #else  
  44.     #define PARAM_IMPL_RET(t, it, value) operator t () { t ret = value; try { ret = boost::any_cast<it>(m_data); } catch (...) {} return std::move(ret); };  
  45. #endif  
  46.       
  47.     PARAM_IMPL_INIT(int);  
  48.     PARAM_IMPL_INIT(long long);  
  49.     PARAM_IMPL_INIT(double);  
  50.     PARAM_IMPL_INIT(const std::string&);  
  51.     PARAM_IMPL_INIT(boost::any);  
  52.   
  53.     PARAM_IMPL_RET(boolintfalse);  
  54.     PARAM_IMPL_RET(charint, 0);  
  55.     PARAM_IMPL_RET(unsigned charint, 0);  
  56.     PARAM_IMPL_RET(shortint, 0);  
  57.     PARAM_IMPL_RET(unsigned shortint, 0);  
  58.     PARAM_IMPL_RET(intint, 0);  
  59.     PARAM_IMPL_RET(unsigned intint, 0);  
  60.     PARAM_IMPL_RET(longint, 0);  
  61.     PARAM_IMPL_RET(unsigned longint, 0);  
  62.     PARAM_IMPL_RET(long longlong long, 0);  
  63.     PARAM_IMPL_RET(unsigned long longlong long, 0);  
  64.     PARAM_IMPL_RET(floatdouble, 0.0f);  
  65.     PARAM_IMPL_RET(doubledouble, 0.0);  
  66.     PARAM_IMPL_RET(std::string, std::string, "");  
  67.   
  68.     Parameter(const char* param)  
  69.     {  
  70.         m_data = std::string(param);  
  71.     }  
  72.   
  73.     operator boost::any() {  
  74.         return std::move(m_data);  
  75.     }  
  76. private:  
  77. //  boost::variant<std::string, double, float, int> m_data;  
  78.     boost::any m_data;  
  79. };  
  80.   
  81. typedef function<void(uint32, Parameter, Parameter, Parameter, Parameter)> FuncCallback;  
  82. #define LOCK_QUEUE MyLock l(&m_mutex)  
  83. class CommandQueue : public MySingleton<CommandQueue>  
  84. {  
  85. public:  
  86.     CommandQueue() {};  
  87.     ~CommandQueue() {};  
  88.   
  89.     void registerHandler(uint32 command, FuncCallback callback)  
  90.     {  
  91.         m_eventMap[command] = callback;  
  92.     }  
  93.   
  94.     void unRegisterHandler(uint32 command)  
  95.     {  
  96.          auto itr = m_eventMap.find(command);  
  97.          if (itr != m_eventMap.end())  
  98.          {  
  99.              m_eventMap.erase(itr);  
  100.          }  
  101.     }  
  102.   
  103.     void post(uint32 dwCommand)  
  104.     {  
  105.         COMMAND_DATA cmdData;  
  106.         cmdData.dwCommand = dwCommand;  
  107.   
  108.         LOCK_QUEUE;  
  109.         m_queue.push_back(std::move(cmdData));  
  110.     }  
  111.   
  112.     template<typename Type>  
  113.     void post(uint32 dwCommand, Type data)  
  114.     {  
  115.         COMMAND_DATA cmdData;  
  116.         cmdData.dwCommand = dwCommand;  
  117.         cmdData.parameters.push_back(std::move(data));  
  118.   
  119.         LOCK_QUEUE;  
  120.         m_queue.push_back(std::move(cmdData));  
  121.     };  
  122.   
  123.     template<typename Type1, typename Type2>  
  124.     void post(uint32 dwCommand, Type1 data1, Type2 data2)  
  125.     {  
  126.         COMMAND_DATA cmdData;  
  127.         cmdData.dwCommand = dwCommand;  
  128.         cmdData.parameters.push_back(std::move(data1));  
  129.         cmdData.parameters.push_back(std::move(data2));  
  130.   
  131.         LOCK_QUEUE;  
  132.         m_queue.push_back(std::move(cmdData));  
  133.     };  
  134.   
  135.     template<typename Type1, typename Type2, typename Type3>  
  136.     void post(uint32 dwCommand, Type1 data1, Type2 data2, Type3 data3)  
  137.     {  
  138.         COMMAND_DATA cmdData;  
  139.         cmdData.dwCommand = dwCommand;  
  140.         cmdData.parameters = {data1, data2, data3};  
  141.   
  142.         LOCK_QUEUE;  
  143.         m_queue.push_back(std::move(cmdData));  
  144.     };  
  145.   
  146.     template<typename Type1, typename Type2, typename Type3, typename Type4>  
  147.     void post(uint32 dwCommand, Type1 data1, Type2 data2, Type3 data3, Type4 data4)  
  148.     {  
  149.         COMMAND_DATA cmdData;  
  150.         cmdData.dwCommand = dwCommand;  
  151.         cmdData.parameters = {data1, data2, data3, data4};  
  152.   
  153.         LOCK_QUEUE;  
  154.         m_queue.push_back(cmdData);  
  155.     };  
  156.   
  157.     void send(uint32 dwCommand)  
  158.     {  
  159.         auto itr = m_eventMap.find(dwCommand);  
  160.         if (itr != m_eventMap.end()) {  
  161.             itr->second(dwCommand, 0, 0, 0, 0);  
  162.         }  
  163.     };  
  164.   
  165.     template<typename Type>  
  166.     void send(uint32 dwCommand, Type data)  
  167.     {  
  168.         auto itr = m_eventMap.find(dwCommand);  
  169.         if (itr != m_eventMap.end()) {  
  170.             itr->second(dwCommand, data, 0, 0, 0);  
  171.         }  
  172.     };  
  173.   
  174.     template<typename Type1, typename Type2>  
  175.     void send(uint32 dwCommand, Type1 data1, Type2 data2)  
  176.     {  
  177.         auto itr = m_eventMap.find(dwCommand);  
  178.         if (itr != m_eventMap.end()) {  
  179.             itr->second(dwCommand, data1, data2, 0, 0);  
  180.         }  
  181.     };  
  182.   
  183.     template<typename Type1, typename Type2, typename Type3>  
  184.     void send(uint32 dwCommand, Type1 data1, Type2 data2, Type3 data3)  
  185.     {  
  186.         auto itr = m_eventMap.find(dwCommand);  
  187.         if (itr != m_eventMap.end()) {  
  188.             itr->second(dwCommand, data1, data2, data3, 0);  
  189.         }  
  190.     };  
  191.   
  192.     template<typename Type1, typename Type2, typename Type3, typename Type4>  
  193.     void send(uint32 dwCommand, Type1 data1, Type2 data2, Type3 data3, Type4 data4)  
  194.     {  
  195.         auto itr = m_eventMap.find(dwCommand);  
  196.         if (itr != m_eventMap.end()) {  
  197.             itr->second(dwCommand, data1, data2, data3, data4);  
  198.         }  
  199.     };  
  200.   
  201.     void dispatchAll()  
  202.     {  
  203.         LOCK_QUEUE;  
  204.         while (!m_queue.empty()) {  
  205.             COMMAND_DATA& cmdData = m_queue.front();  
  206.             int size = cmdData.parameters.size();  
  207.             auto itrCall = m_eventMap.find(cmdData.dwCommand);  
  208.             switch (size)  
  209.             {  
  210.             case 0:  
  211.                 itrCall->second(cmdData.dwCommand, 0, 0, 0, 0);  
  212.                 break;  
  213.             case 1:  
  214.                 itrCall->second(cmdData.dwCommand, cmdData.parameters[0], 0, 0, 0);  
  215.                 break;  
  216.             case 2:  
  217.                 itrCall->second(cmdData.dwCommand, cmdData.parameters[0], cmdData.parameters[1], 0, 0);  
  218.                 break;  
  219.             case 3:  
  220.                 itrCall->second(cmdData.dwCommand, cmdData.parameters[0], cmdData.parameters[1], cmdData.parameters[2], 0);  
  221.                 break;  
  222.             case 4:  
  223.                 itrCall->second(cmdData.dwCommand, cmdData.parameters[0], cmdData.parameters[1], cmdData.parameters[2], cmdData.parameters[3]);  
  224.                 break;  
  225.             }  
  226.   
  227.             m_queue.pop_front();  
  228.         }  
  229.     }  
  230.   
  231. private:  
  232.     struct COMMAND_DATA {  
  233.         uint32 dwCommand;  
  234.         std::vector<Parameter> parameters;  
  235.     };  
  236.   
  237.     MyMutex m_mutex;                            // 递归锁,处理多线程异步消息抛送  
  238.     std::deque<COMMAND_DATA> m_queue;         // post异步command处理队列(都在主线程处理,每帧结束的时候执行)  
  239.     std::map<uint32, FuncCallback> m_eventMap;    // 注册的命令和回调函数映射  
  240. };  
  241.   
  242. inline void reg_command(uint32 command, FuncCallback callback)  
  243. {  
  244.     CommandQueue::getSingleton().registerHandler(command, callback);  
  245. }  
  246.   
  247. inline void unreg_command(uint32 command)  
  248. {  
  249.     CommandQueue::getSingleton().unRegisterHandler(command);  
  250. }  
  251.   
  252. inline void post_command(uint32 dwCommand)  
  253. {  
  254.     CommandQueue::getSingleton().post(dwCommand);  
  255. }  
  256.   
  257. template<typename Type>  
  258. inline void post_command(uint32 dwCommand, Type data)  
  259. {  
  260.     CommandQueue::getSingleton().post(dwCommand, data);  
  261. }  
  262.   
  263. template<typename Type1, typename Type2>  
  264. inline void post_command(uint32 dwCommand, Type1 data1, Type2 data2)  
  265. {  
  266.     CommandQueue::getSingleton().post(dwCommand, data1, data2);  
  267. }  
  268.   
  269. template<typename Type1, typename Type2, typename Type3>  
  270. inline void post_command(uint32 dwCommand, Type1 data1, Type2 data2, Type3 data3)  
  271. {  
  272.     CommandQueue::getSingleton().post(dwCommand, data1, data2, data3);  
  273. }  
  274.   
  275. template<typename Type1, typename Type2, typename Type3, typename Type4>  
  276. inline void post_command(uint32 dwCommand, Type1 data1, Type2 data2, Type3 data3, Type4 data4)  
  277. {  
  278.     CommandQueue::getSingleton().post(dwCommand, data1, data2, data3, data4);  
  279. }  
  280.   
  281.   
  282.   
  283.   
  284. inline void send_command(uint32 dwCommand)  
  285. {  
  286.     CommandQueue::getSingleton().send(dwCommand);  
  287. }  
  288.   
  289. template<typename Type>  
  290. inline void send_command(uint32 dwCommand, Type data)  
  291. {  
  292.     CommandQueue::getSingleton().send(dwCommand, data);  
  293. }  
  294.   
  295. template<typename Type1, typename Type2>  
  296. inline void send_command(uint32 dwCommand, Type1 data1, Type2 data2)  
  297. {  
  298.     CommandQueue::getSingleton().send(dwCommand, data1, data2);  
  299. }  
  300.   
  301. template<typename Type1, typename Type2, typename Type3>  
  302. inline void send_command(uint32 dwCommand, Type1 data1, Type2 data2, Type3 data3)  
  303. {  
  304.     CommandQueue::getSingleton().send(dwCommand, data1, data2, data3);  
  305. }  
  306.   
  307. template<typename Type1, typename Type2, typename Type3, typename Type4>  
  308. inline void send_command(uint32 dwCommand, Type1 data1, Type2 data2, Type3 data3, Type4 data4)  
  309. {  
  310.     CommandQueue::getSingleton().send(dwCommand, data1, data2, data3, data4);  
  311. }  


        这个东西的好处就是完全解耦合,写起来也非常方便,用到什么功能就添一个id就好了。假设服务器下发一个消息通知物品改变了,那么我们需要刷新物品界面。这个时候定义一个id CMD_REFRESH_GOODS  ,然后在界面的初始化代码里面注册这个id (reg_command(CMD_REFRESH_GOODS, std::bind(&MyGoodsDialog::Refresh, this))),Refresh函数可以是任意形式,任意参数。消息处理代码里面只需要调用  send_command(CMD_REFRESH_GOODS),就可以完成界面刷新,无论是界面还是逻辑都不需要包含或继承任何东西,它们只需要知道这个id就可以了。 如果有多个消息会刷新界面那也非常简单,在各自的消息里面调用send_command代码就可以了。


       这里也顺便说一下我对游戏客户端各模块依赖关系的看法,说真心话,游戏客户端真的是小项目。那么作为这个小项目,没有必要过分关注耦合,模块化之类的东西。凡是能让我的代码变得整洁,能让我写代码写的顺手方便的功能就是好功能,即便是约束也乐于遵守。但是如果为了模块化,而让我写代码时写个Impl,再写个provider,那会让人恶心透了,事实证明,有些模块化纯粹是杞人忧天。有些复用性的想法纯粹是自作多情,比如这种--你看我的代码模块化的多好,逻辑模块一行代码不用改就可以用到其他项目。我想说,如果你的游戏赚钱了,我们要再做一个类似的项目,那么我就算把你的ui模块也搬过来又有什么问题。如果我们要做的是一个新的项目,那么我还是要从那一坨代码中找到我想要的可以复用的东西,那还不如让代码变得简单,直接些,我更容易理解。

       作为游戏客户端,有三个主要模块就足够了,逻辑模块、渲染模块、ui模块,所有跟引擎打交道的地方都停留在渲染模块,渲染模块是对引擎的再封装,即便有少量东西扩散到ui模块也应该只停留在ui控件内部。逻辑模块只负责并且负责完全的逻辑,这也是为什么逻辑层不能引入ui元素的原因。

      逻辑层的东西就是一个一个的管理类,负责游戏的各个业务逻辑。  渲染层是对引擎的再封装,比如封装一个人物渲染类,负责渲染人物(逻辑层里还应该有一个人物类来处理业务逻辑,比如姓名、帮派,这个是组合关系)。  ui层就是一个一个的界面。   渲染层封装好后可以几个月不动,逻辑层和ui层一一对应,完成一个业务逻辑就是逻辑层添加一个管理类以及ui层添加几个对话框。他们之间相对独立,有交互就靠上面提到的事件中心来调度。

      这样一个事件中心,看着是非常简单的东西,但是却是整个客户端的基础架构,一个好的架构可以让人更加快速的完成功能(而当架构搭建好后,90%的时间都是在写业务逻辑和界面逻辑),而不好的架构可能让我们写代码又慢,bug又多。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
C++串口编程实例
久别重逢的 std::bad_alloc
std::unique_ptr使用incomplete type的报错分析和解决
bind与lambda
一些瞠目结舌的 C/C++ 代码技巧
【洛谷日报#110】浅谈C IO优化——读优输优方法集锦
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服