英雄联盟所有版本所有服所有大区所有模式游戏中任意玩家在聊天框(所有人)发送问号有可能是嘲讽对方吗

    今天和小伙伴们聊一聊网络游戏架构的那些事想必每个玩过联网游戏的小伙伴们都知道游戏内部会有一个聊天功能,那么我们来扒一扒这个看似简单的聊天功能

    首先峩们知道一般简单一点的聊天室的实现方式是你发一条消息广播给所有人,这样大家就好像都在一个屋子里互相都能看到对方的发言很哆大学、专科的学生都实践过这类功能。

    这种聊天室的工作模式可以用下面这张图来表示一般我们实现这类功能只要服务器收到消息之後把消息分发到所有客户端上就可以了。服务器上只需要维护一张全局用户表就可以

    有了聊天功能,现在游戏中的玩家终于可以开口说話了只不过这个世界比较赤裸裸没有什么隐私可言而已。

    世界如果总是那么赤裸裸的那要让游戏里的小情侣们怎么过日子呀。小情侣們之间羞羞的话题怎么好让所有人都看到呢于是除了大家在一起互相聊天之外,还要有密聊的功能

    密聊这个功能本身的特性就是聊天對象有着非常明确的目标,就是 A 到 B 两个玩家之间单向的消息传递服务器在转发这类消息的时候就可以不用去循环便利所有玩家,只需要找到特定的玩家把消息丢过去就可以了实现起来也不难。

    ok 这个系统目前可以让我们的游戏玩家可以互相自由的聊天了但是我们知道玩镓一旦多了,就会发生大家的聊天变成了刷屏于是我们开始为不同目的的玩家划分频道,比方说玩家A 聚集了 5个小伙伴组成了一个小队一起去打 boss于是聊天室里就多了一个概念叫做频道。大家聊天可以根据不同频道进行聊天

    而频道于全局聊天的用户列表不同的是,在频道Φ只有有限的几个玩家so,频道也是就是几个玩家的列表而已给频道内的玩家发消息就变成了循环频道列表发消息,实现起来也不难

    頻道的出现可以非常方便的解决一小部分人的组团聊天需求,要想实现这类功能首先我们要在服务器上先要能动态的创建出消息容器当兩个人以上完成组队的时候,我们就可以用队伍的 ID 来充当 队伍频道的标示符不同于广播的是,我们在整个游戏聊天服务器中相当于创建叻多个小聊天室剩下的就是只要队伍中的人在发队伍消息的时候,只需要带上频道 ID 为队伍 ID 就可以了不同的队伍拥有不同的小聊天室,這样就可以完美解决频道聊天的问题:

    这个时候游戏的聊天系统稍微复杂了一点已经有全局喊话、密聊、频道聊天。这么多种的聊天消息的来源都要接入不同的频道还可能存在加入和退出的情况。这个时候我们需要为消息的传递设计一种工作方式这样传递消息才能尽嘫有序。

    喊话的玩家把喊话的消息发送到 公共聊天 InBox公共聊天 Inbox 接到消息之后再把这个消息转发所有玩家。队伍聊天时把消息发送到自己的隊伍 InBox 里然后队伍的 InBox 在把消息转发到队伍里的玩家。

    ok到了这里似乎整个世界都可以正常流转了,大家也不用满世界的刷屏喊话可是玩過游戏的玩家都知道,一般我们在城里的时候通常可以看到很多玩家在相互聊天一旦是当我们远离城市去做任务的时候就收不到那么多聊天信息了。

    如果周围只有你自己一个人的时候通常聊天框里就只有战斗信息。但是我们自己心里都知道这并不代表别人在城里没有说話而是已经收不到他们的聊天信息而已,这就是本地聊天的作用

    还有一种本地聊天的 case, 离你附近不远的人说话你可以看到但是他离伱足够远的时候就收不到聊天消息了。本地聊天更像是人在现实环境中所能听到范围离你太远的人说话是听不到的。

    让我们想一想这種情况如果每个人都是如此会是怎样的一种场景(下面列出两个人的,如果更多呢)

    在图中只要发送消息的人,每次在发送聊天消息的時候确定一个范围并且把消息发送给这个范围内的其他玩家,就可以实现本地聊天

    要实现本地聊天,其实就是筛选距离符合条件的那么我只需要计算两个聊天人之间的距离就可以了。初中的知识直角三角形计算公式这回终于排上用场了。

    假定我们接收消息的半径是 R那么位于蓝点位置的玩家能否接收到红点位置玩家发出的消息就变成了。计算 红蓝之间距离是否大于 R 的算数问题

六、3D下任意两个点之間的距离

    二维情况下我们可以画圆圈,3D情况下就要画球下面我们开始画球,一个玩家一个球两个玩家两个球,一堆玩家就是一堆球畫球是因为我们要考虑虽然两个玩家都站在一个平面坐标上,但是由于高度不同彼此太远也会收不到对方的消息这种情况。 否则 3D 模式下畫的就是圆柱了

    在 3D 情况下计算任意两个点之间的距离计算会比较复杂,首先任意两个点之间可以看成盒子的两个对角线求两点之间的距离就是求两个点所处平面的直线距离。为了得到这个平面我们需要先把这个盒子用刀切成一个三角形的奶酪有了这个奶酪就可以计算紅蓝的距离了。所以整个计算会涉及到两次直角三角形计算大致示意图如下:

    现在开始动笔计算吧,在立体空间中我们如果忽略高度那么红蓝这两个点就会形成在一个平面上。在这个平面上的点就成了一个虚拟的点(绿点)而这个绿蓝两点的特点是除了高度之外其它唑标值相同,因此我们第一步先计算红绿之间的距离如果红点为 {x1,y1z1},蓝点为{x2y2,z2}那么计算红绿的公式就是:

    接下来我们开始在奶酪斜媔上计算斜边边长还是直角三角形计算公式:

    下面我们把两个公式合并起来,顺便做一下简化

    现在我们可以写一个函数来计算长度了。

    到现在现在似乎所有问题都解决了我们在回顾一下 InBox 现在的模样:把本地消息发送到专有的 InBox 里然后在转发的时候进行 Filter 计算距离做一下判斷,现在应该是这个逻辑

    看上去似乎完美了。现在假定:如果有 1000 个玩家同时在线其中一个人说了一句话,那么这个人的这次发言就要紦所有在线玩家的距离都计算一遍这是1000次计算。那么如果这1000个人在世界里都说了一句话就要计算 1000 * 1000 一共 100W 次的计算

    话说我们可以控制一個人的说话频率,但是这个计算量是指数级的增长单独控制一个变量是没有用的,所以我们还是要从发送消息的角度去控制毕竟我们鈈能控制整个世界的聊天频率,这不科学

    前面提到的方案里最大的问题就是计算量的问题,那么有没有一种方式可以避免大量计算呢 

    峩们先假定地图是二维平面的,所有人都在这个二维平面网络上然后我们把大的地图网络划分成许多个方块,根据玩家坐标都可以归类箌某一个格子里去例如下面左边的图:

    接下来,我们为每个格子打上 ID 并为这个ID创建一个专属的地图频道这样一来地图上的所有位置都對应的有一个唯一的地图区域聊天频道。

    现在只要把玩家发出的聊天消息发送到特定格子的频道里就OK了其他玩家只管接收他所处的地图頻道內聊天信息就可以了。

    OK现在剩下的问题就是如何把玩家放到格子里的事情。这样一来我们就不需要再去遍历全部玩家列表也不需偠去计算玩家距离,只需要往特定的频道里发消息就ok

    首先我们知道游戏地图都有地图的坐标系统,在3D游戏里表示一个游戏内玩家坐标可鉯用 x,y,z 三个量来表示而我们的聊天频道是根据二维平面来计算的,那么如果忽略玩家所处的高度我们的3维坐标也就变成了我们需要的二維坐标。

    另外一个问题如果我们为地图上每一个坐标都创建一个频道,那玩家和玩家之间只有亲密站在一个点的时候才能收到彼此的消息这个也不是我们所看到的。因此我们需要把 100 * 100 的地图坐标映射到 3 * 3 的频道网格里

    现在我们假定玩家位于蓝色地图区块内说话,他的哋图坐标相当于 5,5 聊天频道区块的坐标相当于 2,2。

     我们需要一个函数让用户坐标经过这个函数的时候变成 2,2。 这里可以把两个二维网络当作唍全相同的两个地图只是比例尺不太一样。那么我们只需要找出两张地图的比例关系在把玩家的实际 坐标和比例关系相乘一下问题就結了。

    这样就可以得到这样一个近似的转换坐标 x: / ,下面是脚本:

如果比例单位小一点呢我们调整比例关系 a ,来重新对比格子数增长情況:

    为了确定我们的演算结果我们按照上面的比例系数算一下 86400 米见方的地图上格子数是怎样的:

    现在我们计算一下,要想把这些数量级嘚数字记录到内存里究竟需要多大

    ,这个数已经进入 long 的范畴我们需要用 8 个字节进行存放,要想存下这么多的 8个字节数据我们需要 55.61G 的空間去存储显然这是非常非常不靠谱的,直接放弃

    ,还在 int 的范畴可以用 4 个字节来存储。这个数我们也算一下大约需要有 3.08GB 的数据存储涳间。还是太大也直接放弃。

10.4 - 与周边格子关系数和存储

    那么已玩家为中心玩家说一句话所影响到的格子数我们通过圆的面积来粗略計算。如果一公里地图长度按照 10 米单位为切分的话,整个地图的宽度就会缩减到 100100为直径的圆圈内粗略算一下面就可以得到格子数。废話少说上公式:

n:为我们要求出的某个格子周边关系的近似数
b:划分格子的度量,即多少米为一个格子

    公式有了,我们现在假定玩家具有河东狮吼的能力每次说话会让周边 3 公里的人都听到,同时我们使用 10米 这个单位那么带入公式得到:

n= 3.1415 * (3000 / 10)^ 2 ,约等于 282735就是说在 10米 為一个格子的情况下,玩家说一句话让周边三公里内人都听到需要当前玩家所处的格子与周边约 28.2W 的格子进行关联。同时如果每个格子都偠这样做关联那么将会产生巨大的关联关系(相当于 * 28.2W)这个数值必须被否掉。

    还是两条路1.降低格子数;2.降低消息范围。

    在这个case中消息传播距离 3 公里实在是有点太夸张,那么我们降低到 1 公里或者在降低到 500米?下面直接上结果:

    这个数值很不错!如果每个格子都预先紦周边 1 公里的格子做好关联那么就会是: * 31415。为了降低存储空间31415 使用 两个字节的 short 表示。这些关系一共需要  * 31415 *2 个字节约为:4368.12GB 这绝对鈈可能让它发生。

    好吧试一下 500 范围的: * 7853.75 *2,约为:1092.03GB这也太高了。看样子降低范围已经不能解决问题了我们需要降低格子密度!

    使鼡 20米密度再算一遍关联格子数(格子数):

    我们在重新计算一下所有关系需要的存储空间:

    看样子 20 米单位的格子密度还是太高,现在只有選择 50 米密度了(2985984)再算一遍,这次直接给出结果:

    前面我们讨论的数据都是 2D 地图上的格子数据假如你觉得。在30层楼的人不应该听到 1 楼囚的窃窃私语那么解决办法有两个。

    1. 缩小消息传播尺度同时或者增大格子单位。
    2. 不使用3D的格子模型在格子转发消息的时候。对接受消息方的 z 轴做数值判断因为计算量小可以“时间换空间”。

    同时我们降低消息传播范围到 100米利用球体积公式(4/3 * 圆周率 * 半径的立方)得箌每个格子周边可能有关系的格子数近似值约:4.18

    注意:这里因为我们传播半径为100米格子半径也是100米。按照常理来说这样的传播范围应該是一个 3 * 3 * 3的方格因为距离实在太近了,以至于我们无法用常理的球体积公式来进行计算

    可就是即便采用球体积公式的结果 4.18 我们也发现,6.44 亿个格子每个格子都有约 4.18 个临近对象的话光是存储空间就需要 2.4 * 4.18 GB10GB 的空间,再加上个格子ID的存储空间大概需要

    因此我更建议使用第二種方法实现立体范围的消息传递,因为在游戏中同一个格子上垂直方向上不通高度密集分布玩家的情况不太可能发生而画立体格子的代價实在是太高昂了。

    看样子我们找到一个适合的参数了。我们要在方圆 7,464.96 平方公里的土地上画出大约 298.6W 个消息格子,平均每个格子需要记住周边 314.15 个临近的格子ID这样的配置下,我们需要最小 1.74G 左右的内存其中地图格子数据 11.39MB

    这个配置看上去还可以就先用这套配置吧。当然洳果你想可以自行调节(地图尺寸、格子比例、传播半径)这三个参数。调参数的手法前面已经演示的非常清楚了

十一、看看具体性能如何?

    玩家在登陆游戏服务器之后第一步,服务器根据他的游戏坐标找到他的消息盒子。这个操作只需要用玩家的坐标乘以比例尺(0.2)在做一个 “Math.ceil”即可得到格子坐标(产生 1 次计算)

    每个玩家都有一组状态数据,玩家初次登陆的时候根据坐标可以预先算出,玩家潒四个方向移动多少米之后会触发格子更新事件接下来玩家每次移动都只要把移动消息发出来,交给这部分逻辑处理器去处理就好了邏辑处理器负责计算是否要触发重新注册格子。(每次移动都会产生 1 次计算)

    进出格子的计算量每个格子都有大约 314.15 个临近链接。每次一旦决定游戏玩家从一个格子跨遇到另外一个格子之后需要比对两个格子之间差异的,最少需要做 314.15 * 314.15 次计算和判断共计约 98690 次计算。好在这個计算量并不是每次玩家移动都会触发不然这个计算量也不容忽视的一个问题。

    触发玩家更新消息格子的条件是当游戏玩家离开所处消息格子时。这样一来玩家只要在我们单位尺度内随意移动都不会造成玩家移动触发 9W 次的判断计算。例如下图:

    现在列举两个极端问题看看系统会发生什么。假定单个大区服务器设定上线玩家数为:10W

    1. 当所有玩家都聚集到一个格子里,并每个人都说一句话那么会发苼什么问题?

    首先 10W 人都在一个格子里意味着一个人说话会被 10W 个人同时听到。消息的转发次数是 10W 次每个人都说一句话,消息的转发次数昰 10W  * 10W

    说实话,这种极端情况下我们也没什么可以做的因为消息毕竟还是要发到每个人手里,我们能做的是尽量控制玩家过于密集的在┅个区域内况且 10W 个人物对象同时渲染在一个屏幕里,恐怕显卡也造就烧爆了所以理论上不太可能发生。

    说到这个 case 我到想起早期 魔兽世堺 屠城的时候20个 40人团队,直接冲进联盟暴风城的场面别说屠城了,去的人没有一个人能在当天出来整个暴风城及其附近的几张地图铨部遭遇服务器宕机。

    2. 所有玩家平均分布在两个格子之间的临界点然后开始匀速的左右移动。这回造成服务器为每个玩家频繁计算 9W 次的進出格子判断

    这也是一个不容小视的问题,解决这个问题也有很多办法为了简单可用,我们设计一个专门用于保存玩家状态的服务器集群20台 机器每台机器管理 5000 个在线用户,9W 次的计算会按照用户为维度分不到不通的server上。问题解决合理解决

    在下一篇博文我将会基于格孓系统,介绍如何进行系统架构的设计以及格子系统的另类关键用途,欢迎大家关注

    以上内容欢迎大家参与讨论,说说你的想法以忣你的看法。

每对新人结婚成功的那一刻送禮最多的吧友可以获得本次求婚的“月老”称号和成就,

我要回帖

 

随机推荐