游戏大厅 从基础开始(2)
——最基础的交流:聊天
从前有个国王叫做混沌
他没有七窍
没办法与外界交流
两个朋友希望他开心 就给他凿了七窍
于是他就死了。
所以我们这一章来给用户添加七窍,让用户和房间具有最基本的聊天功能。
什么?我前面的故事什么意思?
。。。。。。。
。。。。。
。。。。
。。。
。。
。
我也不知道。
其实,网络游戏交流的最基础,就是聊天室。
如果我们把任何一个网络游戏高度抽象化,把所有的非共性的部分全部去掉,我们会得到这样一个抽象的流程。
- 客户端发包 给服务器。
- 服务器处理包。
- 客户端收取广播数据。
刚刚好 聊天室恰恰实现了这样一个最简化的流程。
- 用户说话。
- 服务器作简单的转向处理。
- 客户端收取广播数据。
一些经过扩展的聊天室 甚至已经具有了游戏的雏形。
经过1999年互联网泡沫年代的人,应该还记得有奖抢答的聊天室。
聊天室会定期向用户发送问题,用户根据特殊的命令向服务器发送答案 在指定的时间内完成的,就可以得到相应的经验奖励。
没错这就是传奇的抢怪。
实际上最早的文字mud也是建立在irc协议的聊天室上。
甚至我们现实世界的游戏 也是一群想喝酒聊天而苦于没什么话题的人,在聊天的基础上建立的。
我们可以从结构里、从历史上、从YY中得出以下一系列引申结论。
- 网路游戏的操作命令就是特殊的聊天内容。
- 网络游戏的返回结果就是特殊的聊天结果广播
- 网络游戏,就是一个个特殊的聊天室。
也就是说 我们完成一个全功能聊天室的时候,我们已经完成了一个游戏的80%了。
那么这个结论对我们今天的主题有什么关系呢?
。。。。。。。
。。。。。
。。。。
。。。
。。
。
有感而发而已,一毛钱关系都没有。
首先我们来看看
聊天聊的是什么
聊天信息
这个Interface 声明了一个聊天信息必要的所有特性。详细内容可以参考代码注释
聊天频道
定义
频道:聊天者发言的存贮转发器
在这里 我设计的频道把用户发出的信息存储在一个链表中。
为什么我们要把他们存储在频道中呢?
其实并不是每一个通道都很清楚自己的成员在哪里。
如果一个用户进出一个区域都要到对应的频道签个到 告个假 产生的系统消耗是巨大的。
作为一个区域性质的大频道 你总不能每得到一个包 都要这个频道到每个子区域去穷举用户吧,那可真的得不偿失
所以我们把每个频道中的信息按照顺序排好,所有用户都可以把自己最后一次读取的节点当成凭据 得到所有和自己相关 未读取过的信息。
聊天者 IChater 聊天的人
- 能够发出信息(其实是一个带Ichatmessage工厂的投递器)
- 能够接收所有相干的信息
- 能够过滤掉自己无关的和有意识屏蔽的人和频道
(这里要特别声明一下 IChater 没有继承IUser 是因为以后可能增加聊天机器人这样的扩展Chater 不过这样的chater如何工作,是否要继承IUser还是单独建立INPC还没有考虑清楚,BS我吧。)
聊天室 IChatroom 能聊天的区域
实现本地频道
补充定义:
地区频道:成员为本区域聊天成员的频道
公共频道:一个大范围内多区域共享的频道,一般是服务级别/服务器级别的聊天频道
继承频道: 一个区域从他的相关频道"学习"到的频道。包括从服务中继承的公共频道 或者从父区域继承的区域广播。
临时频道:用户自己建立的频道 如果长时间没有人访问则自动销毁,如果有人访问则重新建立的随机频道
组队频道:特殊的临时频道,随着组队的存在而存在。
考虑一下目前最复杂最华丽的mmo角色扮演聊天室,魔兽世界的功能现状,我们作出如下需求分析.
- 每个区域都有相应的若干地域性的聊天频道。
- 每个服务器都有相应的若干公共频道 传递系统公告和密语。
- 每个工会都有自己的频道
- 每个小队都有自己的临时频道
- 每个用户都可以创建自己的临时频道
这就麻烦了,如何定义channel在各个对象中的布局 才能满足如此变态需求呢?
我们给最上层的服务 增加一个publicchannels 频道集合 来处理公告/交易/密语 因为他们是全服务器范围的 (甚至跨服务器范围的)
在这个服务内所有的区域 在创建的时候 都把这些公共频道引用当成自己的初始成员。对于有父区域向子区域广播的类型 我们也要把父区域的channel加入这个集合
这就好像是事件监听机制一样。这表示'这些频道 我这个区域的所有聊天者参与监听了'
同样的,我们对聊天者也可以有同样的处理 可以把临时频道、工会频道、小队频道 注册到用户监听频道列表中。
这样我们在Ichater.listen()中 只需要对自己身边几个重要集合中的IChatchannel 进行检查 就可以收集到一切自己没有听到的 与自己相关的消息。
整体图如下
我们在实现IChatchannel的时候 可以用代理模式做成远程web service的代理
这样就可以实现部分负载分离,listen()的时候偶尔读取一次罢了
如果客户端的Ichater是可以直接连接到远程聊天服务进程的另一个代理(比如udp协议代理),那么整个聊天流程就完整地脱离大厅了。
在聊天流量巨大,已经喧宾夺主的情况下 这种分离不失为一种选择
当然这是后话。
参考代码
IService
IChatChannel
IChater
IChatroom