首先要知道游戏类型是什么,然后知道承载人数是多少,以及开发周期多少。需要根据这些来决定游戏架构和技术选型。
网络和数据协议
- 网络传输tcp VS udp
tcp可靠保序,而udp则不是,但是吞吐量会大很多,一般来说tcp在端游时代是比较常用的一个选择,但是手游时代并不是很适合,特别是一些竞技类游戏对延迟比较敏感的,而tcp设计的初衷是‘全局最优’的,所以有了指数退避重传,慢启动等策略,竞技类游戏往往采用基于udp的一些可靠协议来实现,比如kcp等。其他对延迟不敏感的游戏采用tcp还是个比较通用的选择。 - IO模型
游戏服务器采用的多路复用技术不同系统提供的策略不同,select,epoll,kqueue,iocp等。如果需要支持大量连接的地方,比如游戏的gate服务器往往可以采用epoll,kqueue,iocp等等,如果需要支持不多连接的服务器,比如不是直连客户端的服务器,也可以使用select和poll等,但是大部分情况下网络库做成通用的形式,很少提供不同服务的服务器进程采用不同的io模型。 - 数据协议
网络传输过程中采用tcp这种流式协议的方式,因为有mtu的缘故,数据协议往往通过(长度+协议编号+内容)的方式编码,接收 的时候需要检查数据包的完整性。通过预估协议数量来决定2个还是4个字节长度来表示协议号长度,一般游戏2个字节足够了,协议数量一般不超过1000 - pack/unpck
这里需要注意大小端的问题,不过现在一般对于游戏服务器来说一般都是小端的结构,python和lua在struct的pack和unpack中可以指定大小端的。
玩家数据存储
- 文件存储和数据库存储
数据库的存储是个格式比较固定,但是别的系统很容易连进来使用,方便统计,也方便分shard,而且数据库本身做了LRU这种操作,可以做到比较高效的读取和写入。
文件存储比较容易扩展,灵活,但是别的系统不容易连进来使用,性能相对较 差。比如每个玩家的属性结构 一个文件或者一个bindata的形式,其实现在用mongodb算结合了2者的优点 - 存储间隔
这个不同游戏实现方式也不同,如果落地时间较长服务器故障后会回档,反正带来大量的序列化的开销以及数据库压力的开销。想做到即时存盘一般都是用类似数据库的策略oplog的形式增量存盘。
gameplay设计
对于gameplay来说,本身就是个大循环,一定频率进行tick,接收来客户端或者其他服务器的rpc,处理逻辑,然后数据落地以及发送数据给客户端或者其他服务器,一般gameplay来说在同一个进程里都是同步的方式去编写,同步的实现大多数是单线程的,或者使用coroutine来实现actor这种模式。大部分游戏交互都是比较多,所以不论service和service之间的交互还是玩家和玩家之间的交互,如果考虑多线程的同步的问题,会非常复杂以及很容易做错,所以一个service内同一个时刻都是在一个线程中执行的。
场景管理
针对mmo或者一些竞技类游戏往往有场景管理的概念,就是游戏AOI,比如一个玩家移动,需要告诉周围所有的玩家,复杂度在n*n,如果减少这个n,就有了AOI算法,比如九宫格,十字链表等,如果刚开服的时候很多人挤到一个主城中,就算采用九宫格和十字链表等AOI等算法,往往同屏内玩家数量还是很大,客户端渲染的单位数量比服务器少一个数量级的,所以场景管理这里还可以有个分线的做法,玩家多的时候,不同线不可见,玩家少的时候进行合并。
消息同步
- 状态同步
状态同步大多数游戏都比较合适的,一个场景内人数比较多,状态同步的好处是天然防作弊,因为所有的东西都是服务器计算。mmo一般都是基于状态同步实现的,一般是影子跟随,而一些射击类游戏甚至采用向前模拟的方式来做。 - 帧同步
对延迟要求比较高entity数量不多的游戏采用的方法,常用的是moba用帧同步比较多,比如王者荣耀。针对状态同步,帧同步的优点网络带宽比较小,服务器几乎没有运算量。缺点是同一个场景中支持的entity数量不多,状态不同步时比较难查以及重连的过程中需要追帧。
如果做帧同步一些关键点为表现要和逻辑分离,随机算法和随机种子的一致性,数学库浮点换定点,三角函数采用泰勒展开或者查表法,需要保序的容器,timer不能基于钟表时间而需要帧timer,以及防作弊(一般都是投票法,或者服务器跑个验证端)
游戏维护和热更新
现在很多游戏在线更新bug甚至不停服更新慢慢变成一种强需求了,实现这种方式主要使用脚本热更新,热重启+逻辑内存以及ab服切换来实现。
网友评论