阅读 780

【游戏开发】游戏服务器设计思考

1.游戏业务开发思考

在游戏开发中,为了可以进一步加深对引擎底层代码的理解,通晓上层业务逻辑是非常必要的。往往游戏业务逻辑做的优秀的开发人员,也对引擎逻辑有非常独到的理解。这里将我对游戏业务相关的粗浅理解整理一下。游戏业务逻辑分为客户端部分和服务器部分。其中客户端部分逻辑主要负责游戏的表现,用来使得游戏的卖相更好。服务器部分逻辑是整个游戏的灵魂,绝大部分的游戏的逻辑、数值计算都发生在服务器部分(这里以状态同步为例,不考虑帧同步网游)。

客户端开发和服务器开发需要两种截然不同的开发思路,两者都需要一种所谓的“框架”。那么问题来了,框架是什么呢?

所谓开发框架,以我的粗浅理解,我觉得更可以比喻成台式机的主板。一个牛逼的主板,可以往上插入更大的内存,更好的显卡,更多核心的CPU。而差的主板却支撑不住很多强大的组件。而游戏框架也是一样的,一个优秀的框架,可以具有更强的扩展性,可以上更多的功能。相反的,一个很弱小的框架,往往在上一些新功能,整个代码框架就会乱掉,后续随着耦合性越来越强,整个项目几乎就会崩溃掉。因为扩展的空间往往微乎其微了。

然而,优秀框架并非是软件工程的银弹。一个优秀框架只会极大减缓项目衰老的过程,但是并不会阻止项目的死亡。项目大到一定程度的时候,单纯的投入人力物力重构往往已经不能解决问题。成功的项目只会逐渐庞大,成为一个利维坦级别的怪物。这部分内容可以参照软工圣经《人月传说》中的软件工程管理哲学,此处不再赘述。

1.1客户端部分

客户端部分作为游戏表现的核心开发内容,可以说是整个游戏卖相的关键。客户端逻辑决定了游戏的玩法机制、表现内容、画面质量等一系列关键内容。这部分的开发内容也是绝大部分人所理解的“开发游戏”。一个游戏客户端往往需要考虑更多内容,这里整理一下我认为基于一个基础的引擎,所需要考虑的,构造一个游戏所需要的内容:

游戏资源加载 。如何加载游戏中的资源,如模型、贴图等。 游戏物体材质系统 。游戏内的材质格式,着色器编写,渲染管线是怎么去渲染的等等。 脚本框架 。游戏内逻辑的编写框架,机制是什么。 模型动画系统 。模型动画状态机、动作切换、二维混合树、IK动画等。 技能特效系统 。角色的技能释放、判定、特效表现等。 AI状态机、行为树 。角色的AI设计,基于寻路算法的种种表现。 物理碰撞检测 。基于刚体碰撞的实时物理表现。 UI管理器 。UI部分逻辑,各种回调函数的编写、各种模块设计。 输入模块 。检测硬件输入,与之联动。 游戏计时器 。游戏内的计时器。 网络模块 。 用于解析服务器消息、向服务器发消息。 当然还有一些常用的框架:

GameObject Component框架 。这种框架基于游戏对象和组件之间的依托关系,以全员组合的方式构造整个游戏。 ECS 框架。Entity Component System,私以为是GameObject Component框架的升级版。充分利用了计算机的空间局部性质。

1.2服务器部分

服务器端开发作为整个游戏开发的灵魂,可以说整个游戏的玩法机制都是由这部分决定的。同时,服务器端开发的思路与客户端不同。客户端开发是线性思维,也就是,如何能在当前机器上以更少的资源占用来获取更好更炫酷的游戏效果。而服务器端需要时刻考虑,做了一个新功能,是要给千千万万台机器跑的,这就需要考虑更多更多性能问题。因此,这里列出一下我所理解作为服务器应该掌握的最基础内容:

Socket 。熟练使用传输层的API。 同步方式 。如状态同步的实现、帧同步的实现等。 RPC协议 。如何设计一个优雅的RPC协议是服务器逻辑的关键。 消息机制 。基于消息的信息传输。 计时器 。用于计算游戏服务器内的时间记录。 数学库 。往往游戏逻辑判定为了安全,都放在服务器进行。 协议设计。TCP/UDP/KCP。 当然还有很多更深层次的主题:如负载均衡,帧同步的浮点数、随机数等等问题。这里不再赘述。掌握上面的内容,就足够写出一个基础的游戏服务器了。

2.关于一个基础游戏服务器的设计思路

这里分享一下关于我从零搭建一个状态同步服务器的基础思路。整体游戏服务器的框架可以分为三个部分:

基础Socket层。这部分是由Socket api构成层级。基本上没啥可说的。 消息接收与发送层。当前层用于处理链接到服务器的客户端逻辑。整合第一层编辑好的Socket API,为第三层提供封装好的接收、发送数据接口。管理当前游戏中唯一的客户端ID、Socket列表。 服务器核心逻辑层。所有的服务器逻辑都写在这一层。 我认为,一个基础游戏服务器的设计思路应该尽可能做到:

功能解耦。

数据解耦。

因此,针对第二层的客户端列表,可以将客户端socket封装成不同类型的entity,每一个entity具有不同的功能(功能解耦)。同时,不同客户端通过entity去访问数据都是互相独立的操作(数据解耦)。以我设计的基于建房间开始游戏的状态同步服务器为例:

链接实体。客户端链接到服务器后,就成为一个链接实体。链接实体的功能有限,只有登录注册。 登录实体。登录后,增加选人、商城、进入房间界面等等功能。失去登录、注册功能。 房间实体。进入房间界面后,增加开房、进房、准备、进入游戏等功能。失去第二层功能。 游戏实体。进入游戏后,增加游戏逻辑功能(如放技能、攻击、走位等)、退出游戏功能等。失去其他功能。 我们可以看到,不同层级的实体,其具有完全不同的功能。也就是说,客户端在不同阶段,其开放的功能也是不同的。这极大的避免了功能耦合。我们可以维护一个字典,来快速查找当前客户端究竟是在哪一层实体上。同时,实体内逻辑调用采用RPC协议,进而快速调用对应方法。

当然除了游戏实体的功能解耦外,整个服务器维护的其实是一个房间管理器。房间管理器下属存在很多房间。每一个房间都可以被实体所创建调用。同时,一个房间对应一个独一无二的世界。采用多进程的方式可以使得多个房间同时无压力运行。具体结构如下:

class RoomManager
{
public:
vector<Room> roomList;
};

class Room
{
public:
World world;
};


复制代码

时,每一个房间内,维护着当前游戏内的客户端列表,以快速的同步游戏内消息。房间内的World设计,就和客户端的思路比较接近了。一个比较形象的比喻是,在服务器内复现整个逻辑世界。设计游戏内部的AI、地形(A**算法)等,就可以完成服务器内的世界编写。

简而言之,游戏开发是客户端和服务器端互相打配合的产物。以状态同步为例,服务器运行时,只需要给客户端发送该客户端的状态属性,当前游戏其他玩家的状态属性,再由客户端依据这些属性进行相应表现,就可以完美进行多人同步游戏。

3.一些思考 上面这个服务器思路可以说是玩具级别了,但是更多的是说明设计思路。服务器的坑非常深,以后有机会再慢慢填坑。私以为,作为一名工程师,最重要的不是会什么语言,什么框架,熟练使用XXX;最重要的是解决问题的能力。希望上述的思路分享可以帮助到一些入坑游戏开发的同学。与各位共勉。

文章分类
后端
文章标签