游戏大厅 从基础开始(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
Code
'-----------------------------
' Wayne Wang
' 个人研究
' 不是什么了不起的东西
' 有错误的话还请告诉我
' 努力奋斗
' Yeah!
'-----------------------------
Namespace CommonNamespace Common
Public Interface IServiceInterface IService
ReadOnly Property PublicChannels()Property PublicChannels() As IDictionary(Of String, IChatChannel)
ReadOnly Property Areas()Property Areas() As IDictionary(Of String, IArea)
End Interface
End Namespace
IChatChannel
Code
'-----------------------------
' Wayne Wang
' 个人研究
' 不是什么了不起的东西
' 有错误的话还请告诉我
' 努力奋斗
' Yeah!
'-----------------------------
Namespace CommonNamespace Common
/**/''' <summary>
''' 消息频道
''' </summary>
Public Interface IChatChannelInterface IChatChannel
/**/''' <summary>
''' 消息内容
''' </summary>
ReadOnly Property MessageLinkList()Property MessageLinkList() As LinkedList(Of IChatMessage)
/**/''' <summary>
''' 消息过期时间
''' </summary>
Property TimeoutSecond()Property TimeoutSecond() As Integer
/**/''' <summary>
''' 本频道中 某个用户收到的最后一个节点
''' </summary>
ReadOnly Property LastReceivedMessage()Property LastReceivedMessage() As Generic.IDictionary(Of String, LinkedListNode(Of IChatMessage))
/**/''' <summary>
''' 本频道的类别
''' </summary>
ReadOnly Property Type()Property Type() As ChannelType
/**/''' <summary>
''' 频道显示名
''' </summary>
ReadOnly Property Name()Property Name() As String
/**/''' <summary>
''' 频道唯一ID
''' </summary>
ReadOnly Property ID()Property ID() As String
/**/''' <summary>
''' 清除用户的最后收到信息
''' </summary>
Sub Clear()Sub Clear()
/**/''' <summary>
''' 发送信息给本频道 根据信息的类型自动匹配发送的方式
''' </summary>
Sub Send()Sub Send(ByVal message As Common.IChatChannel)
/**/''' <summary>
''' channel的类别
''' </summary>
Enum ChannelTypeEnum ChannelType
/**/''' <summary>
''' 公共频道
''' </summary>
PublicChannel = 0
/**/''' <summary>
''' 本地频道
''' </summary>
AreaChannel = 1
/**/''' <summary>
''' 工会频道
''' </summary>
GuildChannel = 2
/**/''' <summary>
''' 临时频道
''' </summary>
TemporyChannel = 3
/**/''' <summary>
''' 小队频道
''' </summary>
TeamChannel = 4
End Enum
End Interface
End Namespace
IChater
Code
1
2 '-----------------------------
3 ' Wayne Wang
4 ' 个人研究
5 ' 不是什么了不起的东西
6 ' 有错误的话还请告诉我
7 ' 努力奋斗
8 ' Yeah!
9 '-----------------------------
10
11
12 Namespace Common
13
14 Public Interface IChater
15 ''' <summary>
16 ''' 聊天者的唯一ID
17 ''' </summary>
18 ReadOnly Property ChaterID() As String
19 ''' <summary>
20 ''' 除了区域和公共频道 额外加入的频道 比如工会、小队、自定义频道
21 ''' </summary>
22 ReadOnly Property ExtraMountedChannels() As IDictionary(Of String, Common.IChatChannel)
23
24 ''' <summary>
25 ''' 被屏蔽的频道列表
26 ''' </summary>
27 Property MutedChannelNameList() As IEnumerable(Of String)
28
29 ''' <summary>
30 ''' 被屏蔽的用户列表
31 ''' </summary>
32 Property MutedUIDList() As System.Collections.Generic.IEnumerable(Of String)
33
34
35
36 ''' <summary>
37 ''' 对目标说话
38 ''' </summary>
39 ''' <param name="message">信息文本</param>
40 ''' <param name="toChater">目标用户</param>
41 Sub Say(ByVal message As String, ByVal toChater As IChater)
42
43 ''' <summary>
44 ''' 在当前位置公开说话
45 ''' </summary>
46 Sub Say(ByVal message As String)
47
48 ''' <summary>
49 ''' 在当前位置公开发送信息
50 ''' </summary>
51 ''' <param name="message">信息文本</param>
52 ''' <param name="type">信息类型</param>
53 Sub Say(ByVal message As String, ByVal type As Common.IChatMessage.MessageRenderType)
54
55 ''' <summary>
56 ''' 对目标发送信息
57 ''' </summary>
58 ''' <param name="message">信息文本</param>
59 ''' <param name="toChater">目标用户</param>
60 ''' <param name="type">信息类型</param>
61 Sub Say(ByVal message As String, ByVal toChater As IChater, ByVal type As Common.IChatMessage.MessageRenderType)
62
63 ''' <summary>
64 ''' 详细的发送信息
65 ''' </summary>
66 ''' <param name="message">信息文本</param>
67 ''' <param name="toChater">目标用户</param>
68 ''' <param name="type">消息类型</param>
69 ''' <param name="channel">发送消息的频道</param>
70 Sub Say(ByVal message As String, ByVal toChater As IChater, ByVal type As Common.IChatMessage.MessageRenderType, ByVal channel As Common.IChatChannel)
71
72 ''' <summary>
73 ''' 接收聊天信息
74 ''' </summary>
75 Function Listen() As IEnumerable(Of IChatMessage)
76
77
78
79
80
81
82
83
84
85
86 End Interface
87
88 End Namespace
IChatroom
Code
'-----------------------------
' Wayne Wang
' 个人研究
' 不是什么了不起的东西
' 有错误的话还请告诉我
' 努力奋斗
' Yeah!
'-----------------------------
Namespace CommonNamespace Common
Public Interface IChatroomInterface IChatroom
Inherits IArea
/**/''' <summary>
''' 本区域的有效聊天频道
''' </summary>
ReadOnly Property LocalChannels()Property LocalChannels() As IDictionary(Of String, Common.IChatChannel)
/**/''' <summary>
''' 本区域的缺省聊天频道
''' </summary>
ReadOnly Property DefaultLocalChannel()Property DefaultLocalChannel() As Common.IChatChannel
/**/''' <summary>
''' 本区域继承的频道
''' </summary>
ReadOnly Property MountedChannels()Property MountedChannels() As IDictionary(Of String, IChatChannel)
/**/''' <summary>
''' 本区域继承的聊天频道中的公共频道
''' </summary>
ReadOnly Property PublicChannels()Property PublicChannels() As IChatChannel
End Interface
End Namespace