写点什么

深入浅出 node.js 游戏服务器开发——基于 Pomelo 的 MMO RPG 开发

  • 2013-03-26
  • 本文字数:4942 字

    阅读完需:约 16 分钟

在上一篇文章中,我们介绍了如何使用 Pomelo 来搭建聊天服务器。在这篇文章中,我们为大家介绍如何使用 Pomelo 框架来搭建 MMO RPG 服务器,并分析其设计思路和实现方法。以此来帮助大家更好的理解和使用 Pomelo 框架,理解 Pomelo 框架游戏开发的基础流程,使用方法和设计理念。

本文中的游戏服务端架构,只是为了说明 Pomelo 的开发理念和设计思路,并不是基于 Pomelo 开发的唯一方案,开发者完全可以根据自己的实际应用环境设计不同的服务端架构。

开始之前

Pomelo 框架与 MMO RPG

我们曾在本系列第一篇文章曾介绍过 pomelo 的架构。我们先简单回顾一下 Pomelo 为我们的游戏开发提供了什么:

  • 可扩展的服务器架构。Pomelo 中对服务器端进行了抽象,将服务器分为承载链连接的前端服务器和负责业务逻辑的后端服务器,并提供了便利,高效的分布式扩展支持,让使用者可以在少改甚至不改的前提下实现对服务端的扩展。
  • 完整的通信框架:Pomelo 对客户端与服务器之间,服务器与服务器之间的通信进行了完整的封装。开发者只需要按照默认规则来填写服务代码就可以完成接口的发布和调用,而不用考虑内部实现。
  • 大量游戏开发基础库:除了基本的服务器框架之外,Pomelo 还提供了很多游戏开发需要用到的基础库,如任务调度,AI 控制,寻路等,而这些库还会随着 Pomelo 的发展进一步完善。
  • 基于 Node.JS 轻量级的开发环境,以及大量的模块。相对于传统的开发语言,Node.JS 有着轻量,快捷的特性(0.3 版中启动一个包括 10 几个服务器集成的服务端只需要不到 4S)。而活跃的开源社区也提供了大量的第三方模块。

作为 Pomelo 游戏开发的入门导引,本文的重点将放在游戏基础架构的搭建上,因此本文将主要介绍下面三个方面的内容:游戏服务端的构建,与客户端的通讯,服务器的扩展。

本文的参考示例

我们使用 demo Treasures 作为本文的参考示例,游戏的截图如下:

从上图可以看出,在 treasures 中,玩家会进入一张遍布宝物的地图中,通过拾取宝物来获得积分。所有玩家的积分在右上角会有一个排名。下面是这个 demo 的关键点:

  • 每个玩家的行动对其他玩家来看都是实时的。
  • 在获取宝物时积分会更新积分榜,这个更新对所有玩家实时可见。
  • 宝物会定时刷新。

相对于一般的的 MMO RPG,这个 demo 显得十分简陋:没有持久化,没有战斗,没有 AI。。。但是,其中实现了 MMO RPG 中最核心的亮点功能:一个可以容纳多个在线玩家的游戏场景,以及玩家之间的实时互动。那些功能的确实可以让系统的结构更加清晰明确,成为一个非常好的项目导引。

搭建游戏服务端

由于游戏逻辑十分简单,我们后端采用一台单独的场景服务器来运行整个游戏逻辑,同时加入一台连接服务器来承载用户连接,系统的设计如下:

下面,我们就按照这个设计来搭建游戏服务端。

编写场景服务

游戏场景是玩家所处的虚拟环境,而场景服务器就是游戏场景在服务端的抽象,根据游戏类型和内容的不同,场景服务器的复杂程度也会千差万别。在我们的例子中,场景的构成十分简单:一张开放的游戏地图,地图中的玩家,以及定时刷新的宝物。

首先,作为一个场景服务器,需要能够储存用户和宝物的信息,这里我们直接使用一个放在内存中的 map 来储存场景中的所有实体,同时,我们将所有场景中的实体都抽象为一个 Entity 对象,放在这个 map 中。

为了能够操纵这些数据,还需要暴露出对外接口,我们使用的接口有下面三种:

  • 初始化的接口: 我们在 init 方法中会设置场景信息,配置参数等, 并启动场景中的时钟循环。
  • 实体访问接口:如 AddEntity 和 RemoveEntity 接口等,我们使用这些接口来访问和修改场景中的实体。
  • 刷新场景中的宝物:当满足条件时,外部事件会调用该接口来刷新地图中的宝物。

我们通过一个无限循环的 tick 来驱动场景服务,在每一个 tick 中会更新场景中所有实体的状态信息,我们最终设计如下:

搭建场景服务器

在完成场景服务的代码之后,我们还需要提供一个场景服务运行的平台,在 Pomelo 中,我们通过搭建一个场景服务器来实现。

Pomelo 中的服务器分为两类,负责承载用户连接的前端服务器和运行逻辑的后端服务器。作为负责核心逻辑的场景服务器,自然是属于后端服务器,因此,我们在 /game-sever/config/server.json 中加入以下配置:

"area": [ {"id": "area-server-1", "host": "127.0.0.1", "port": 3250, "areaId": 1} ]

其中的“area”是我们为场景服务器类型所起的名称,其对应的内容就是场景服务器的列表,可以看出,现在我们只加入了单台场景服务器。与聊天服务器相比,场景服务器的配置并没有明显区别,只是多了一个 areaId 的属性。我们使用这一属性来标明这个场景服务器对应的场景 id,我们的建议设计是一个游戏服务器对应一个独立的游戏场景,这样可以大大减少场景管理的开销,并提高单场景的负载能力。Pomelo 中每一个服务器就是一个独立的进程,相对于一个场景服务的开销,单独的服务器造成的开销是可以接受的。当然,这只是建议设计,框架本身完全支持一个游戏服务器对应多个游戏场景的设计。开发者可以根据具体的应用情况调整场景服务器的配置,通过加入场景管理逻辑,实现一个场景服务器和场景之间的自由配置。

启动场景服务

在加入场景服务器之后,我们还需要对服务端的运行环境进行配置,在场景服务器启动时运行对应的场景服务。为了实现这一目标,我们需要在 app.js 中加入如下配置:

app.configure('production|development', 'area', function(){ var areaId = app.get('curServer').areaId; area.init(dataApi.area.findById(areaId)); });

app.configure 方法用来对指定的服务器进行配置,包括三个参数,前两个参数分别是运行环境和服务器类型的 filter,第三个参数是在满足前面两个 filter 的情况下需要运行的代码。在上面的配置中,第一个参数"production|development"表示是针对线上和开发两种环境,之后的‘area’参数表示的是针对 area 服务器类型。 关于 app.js 初始化的更多内容,见 Pomelo 启动流程

与客户端通讯

建立连接服务器

要与客户端通信,我们需要建立一个前端服务器,用来维护与客户端之间的连接。server.json 中的配置如下:

"connector": [ {"id": "connector-server-1", "host": "127.0.0.1", "port": 3150, "clientPort": 3010, "frontend": true} ] 其中的标志位“frontend:true”表示这是一个前端服务器,“clientPort:3010”则表示该服务器对外暴露前端。对于前端服务器,在启动时就会默认加载连接组件,因此我们不需要在 app.js 中进行额外的配置。在 pomelo 0.3 中,如果需要使用原生 websocket 等非默认的连接方式,则需要在 app.js 中加入相应配置。在客户端,我们在启动时连接对应的接口,就可以建立起与服务端的连接。

处理客户端请求

Pomelo 中,我们提供了两种客户端向服务端发送请求的方法,request/response 模式和 notify 模式。

request/response 模式与 web 中的请求模式相似,是标准的请求 / 响应模式,对于客户端的一个请求,服务端会给出一个响应。以玩家的移动为例,当需要移动时,会发送一个请求给服务端,服务端会验证客户端的请求,并返回结果客户端,下图是具体请求流程:

最上面的是客户端代码,在这里,我们向服务端发送一个移动请求。 之后,服务端会根据请求的 route(area.playerHandler.move)找到对应的处理方法(/game-server/area/handler/playerHander.move),然后调用该方法来处理客户端的请求。当处理完成之后,会并在 next 方法中传入处理结果,结果会发回客户端,并作为回调函数的参数传回。

notify 模式的运行流程与 request/response 类似,只是当服务端处理请求后不会发送任何响应。客户端通过 pomelo.notify 方法来发出 notify 请求,notify 请求的参数与 pomelo.request 相似,只是不需回调函数,notify 方法的实例如下:

```` pomelo.notify(‘area.playerHandler.pick’, params); ``` `### 服务端消息推送

与 web 服务不同,game 服务端会有大量的推送消息,要实现这一功能,我们使用 pomelo 中的 push 模式来实现。下图以“move”为例,展示了服务端的推送流程:

Treasures 中的广播功能是通过一个全局的 channel 来实现的,在游戏中的所有玩家在加入游戏后都会加入一个全局的 channel 中。当需要广播消失时,服务端就会调用这个全局的 channel 来对所有用户进行消息推送。

扩展游戏服务端

在前面两节中,我们使用 pomelo 搭建了一个单节点的游戏服务器。在这一节中,将介绍如何使用 Pomelo 来对服务端进行扩展,搭建分布式的游戏服务。

由于游戏服务器的复杂性,像 web 服务器简单的水平扩展是不现实的,而根据业务逻辑的不同,不同的服务器也有着不同的扩展方案。因此我们分别以前面介绍的连接服务器和场景服务器为例,来对他们进行扩展。

连接服务器的扩展

连接服务器作用是负责维护所有客户端的连接,负责客户端消息的接受和推送,在 MMO RPG 中,连接服务器往往是性能的热点之一,因此对连接服务器的扩展对于提高系统负载有很重要的现实意义。 在例子 treasures 中,我们通过加入一个负载均衡服务器(gate 服务器),来实现连接服务器的扩展:当客户端登录时,会首先连接 gate 服务器,来分配一个连接服务器,之后,客户端会断开与 gate 服务器的连接,在与其对应的连接服务器建立连接,如下图所示:

要实现这一功能,在服务端,我们首先要在 server.config 中加入新的前端服务器,代码如下:

```` “gate”: [ {“id”: “gate-server-1”, “host”: “127.0.0.1”, “clientPort”: 3014, “frontend”: true} ] ``` `之后,在 gate 服务器上编写对应的负载均衡接口,在例子中,我们采用了使用 uid 的 crc 值对服务器数目取模的方式, 代码如下:

```` module.exports.dispatch = function(uid, connectors) { var index = Math.abs(crc.crc32(uid)) % connectors.length; return connectors[index]; }; ``` `最后,在客户端加入对应的连接代码,就完成了对连接服务器的扩展。 在我们的例子中只用到了两台连接服务器,而在实际应用中,可以根据实际环境,编写自己的负载均衡算法,加入更多的连接服务器。

场景服务器的扩展

与连接服务器不同,场景服务器中包含着大量的状态信息,如果对单一的场景进行扩展,需要复杂的同步机制和大量远程调用。因此,在 Pomelo 中,我们的场景扩展是通过加入新的场景来进行的。 在设计游戏时,整个世界就被分为多个不同的场景。而在服务器端,一个场景则与一个独立的场景服务器相对应,场景服务的扩展可以通过加入新的场景来完成。下面,就以 treasure 为例,介绍一下场景服务的的扩展方法: 因为场景扩展是通过加入新的场景来完成的,所以首先要在 server.json 中加入新的场景服务器:

在加入场景服务器之后,我们还要保证发往场景服务器的请求可以被分发到正确的场景去。而 Pomelo 中,对于同一类型的服务器,默认的分法方法是采用随机分配的方式,这是不能满足我们要求的。因此,我们需要加入自己的路由算法,流程如下:

首先,我们编写了新的路由算法:根据用户所属的场景 id 进行投递。之后,我们在 app.js 中使用 app.route 方法来将我们的路由算法配置为针对 area 服务器的默认方法。app.route 方法接受两个参数,第一个是需要自定义 route 的服务器类型,第二个参数就是具体的路由算法。我们分别将‘area’服务类型和我们编写的算法传入,就可以实现自定义的路由功能了。

跨平台客户端

除了原有的 JS 客户端之外,pomelo 还提供了多种其他的客户端,而不同的客户端可能会对应着不同的长连接协议。但是经过 pomelo 封装之后,在 Pomelo 服务端,不同客户端在连接上连接上是完全对等的。因此,你可以使用 pomelo 框架实现跨平台联机对战的(前提是你开发了针对多个平台的游戏客户端)。

最终架构

经过扩展后,我们的最终服务器架构如图所示:

总结

在本文中,我们介绍了如何使用 Pomelo 框架来构建了一个包括连接服务器和场景服务器的 MMO RPG 服务端。并介绍了如何使用 pomelo 特性,来对游戏服务端进行扩展,并构建出了一个分布式的 MMO RPG 服务。 但是,这只是游戏服务端开发中最为基础的部分,MMO RPG 中很多基础功能在这里都没有实现,如数据持久化,AI 控制,寻路系统等。在下面的文章中,我们会进一步介绍 Pomelo 在游戏开发中的应用。

参考示例

捡宝demo

Pomelo 启动流程

Pomelo 框架

2013-03-26 03:3740240

评论

发布
暂无评论
发现更多内容

一文读懂 TsFile

Apache IoTDB

QIP 25周年

亚马逊云科技 (Amazon Web Services)

量子计算 量子技术 量子纠错

揭露sealer背后实现整个集群一键交付的奥秘 | 龙蜥技术

OpenAnolis小助手

开源 sealer 龙蜥技术 镜像集群

同事删库跑路后,我连表名都不能修改了?

码农参上

数据库 数据恢复 权限 删库 4月月更

2022年全新FFmpeg/WebRTC/RTMP/RTSP/HLS/RTP播放器-音视频流媒体高级开发学习大纲

赖猫

音视频开发 音视频技术

16 张图 | Nacos 架构原理①:一条注册请求会经历什么?

悟空聊架构

nacos 注册中心 4月日更 悟空聊架构 4月月更

知识管理的目的及意义——提高社会资源的配置效率

小炮

知识管理

IoTDB 服务绍兴安瑞思90%业务,助其提升百倍查询速度

Apache IoTDB

五大应用示范,为社区/企业防疫管理减负提效

明道云

在线CSV转SQL工具

入门小站

工具

程序员=沉闷无趣?都是刻板印象…

LigaAI

程序员人生 程序猿

博睿数据入选首批欧拉技术测评方案,为欧拉生态开发者应用体验保驾护航

博睿数据

hyengine - 面向移动端的高性能通用编译/解释引擎

阿里巴巴终端技术

编译 移动端 引擎

一文带你使用 Apache IoTDB 系统监控模块

Apache IoTDB

让我们开始使用 Amazon Lambda

亚马逊云科技 (Amazon Web Services)

Serverless 云原生 亚马逊云科技 云技能

这款神器大大提升了协作效率!

Liam

后端 Jmeter Postman swagger Mock

linux之iconv命令

入门小站

Linux

在线字节转换工具

入门小站

工具

易观分析:三大协会发声NFT,未来监管走向几何?

易观分析

区块链 NFT

社区动态|Apache Doris 迎来第 300 位 Contributor !

ApacheDoris

大数据 开源 OLAP MPP apache doris

虎符研究院|币圈后浪MetaAds——现实与元宇宙的展示平台

区块链前沿News

虎符交易所

Prime Video 如何使用机器学习来确保视频质量

亚马逊云科技 (Amazon Web Services)

计算机视觉 Amazon Prime Video WACV

JS/TS项目里的Module都是什么?

华为云开发者联盟

js Module loaders bundlers

Tiger DAO VC:将你的风险投资变成DAO组织协同

BlockChain先知

5. 堪比JMeter的.Net压测工具 - Crank 实战篇 - 接口以及场景压测

MASA技术团队

C# .net 微软 测试 压测

Docker 实战教程之从入门到提高 (五)

汪子熙

Docker 容器 虚拟化 容器镜像 4月月更

3月券商App行情刷新及交易体验评测报告,国泰君安再夺领导者冠军

博睿数据

从容器化到资源池化,数栈云原生技术实践探索之路

袋鼠云数栈

大数据 flink 云原生 k8s

TASKCTL ETL作业类型的插件与维护管理

敏捷调度TASKCTL

kettle 元数据 ETL 自动化运维 调度任务

审核和审批的区别

秋去冬来春未远

14年软件开发经历IT:低代码已成为企业管理的核心引擎

一只大光圈

钉钉 低代码 IT 数字化 钉钉宜搭

深入浅出node.js游戏服务器开发——基于Pomelo的MMO RPG开发_架构/框架_张小刚_InfoQ精选文章