pomelo(五) Tutorial 2 Treasures

来源:互联网 发布:百度推广搜索合作网络 编辑:程序博客网 时间:2024/06/04 20:14

pomelo(五) Tutorial 2 Treasures

#Tutorial 2 -- Treasures ##描述 Treasures 游戏是从 LordOfPomelo 中抽取出来,去掉了大量的游戏逻辑,用以更好的展示 Pomelo 框架的用法以及运作机制。

Treasures 很简单,输入一个用户名后,会随机得到一个游戏角色,进入游戏场景。在游戏场景中地上会散落一些宝物,每个宝物都有分数,玩家操作游戏人物去捡起地上的宝物,然后就能得到相应的分数。

##安装和运行 安装 pomelo

npm install -g pomelo

获取源码

git clone https://github.com/NetEase/treasures.git

安装 npm 依赖包(先进入项目目录)

sh npm-install.sh

启动 web-server (先进入web-server目录)

node app.js

启动 game-server (先进入game-server目录)

pomelo start

在浏览器中访问 http://localhost:3001 进入游戏

如有问题,可以参照 pomelo快速使用指南

也可以参照 tutorial 1

##架构 Treasures 分为 web-Server 和 game-Server 两部分。

  • web-server 是用 Express 建立的最一个基础的 http 服务,用来支撑浏览器页面的访问。

  • game-server 是 WebSocket 服务器,用来运行整个游戏的逻辑。

首先,通过配置文件,来看 game-server 的具体架构 game-server/config/server.json

{  "development": {    "connector": [      {"id": "connector-server-1", "host": "127.0.0.1", "port": 3150, "clientPort": 3010, "frontend": true},      {"id": "connector-server-2", "host": "127.0.0.1", "port": 3151, "clientPort": 3011, "frontend": true}    ],    "area": [      {"id": "area-server-1", "host": "127.0.0.1", "port": 3250, "areaId": 1}    ],    "gate": [      {"id": "gate-server-1", "host": "127.0.0.1", "clientPort": 3014, "frontend": true}    ]  }}

可以看出,服务端是由以下几个部分构成:

  • 2 个 connector 服务器,主要用于接受和发送消息。
  • 1 个 gate 服务器,主要用于负载均衡,将来自客户端的连接分散到两个 connector 服务器上。
  • 1 个 area 服务器,主要用于驱动游戏场景,和游戏逻辑

服务器之间的关系,如下图:

treasure-arch

##源码分析 通过游戏流程来分析代码。

###1. 连接服务器 客户端 web-server/public/js/main.js 中 entry 方法中

pomelo.request('gate.gateHandler.queryEntry', {uid: name}, function(data) {  //...});

服务端 game-server/app/servers/gate/handler/gateHandler.js 中

Handler.prototype.queryEntry = function(msg, session, next) {  // ...  // 返回要连接的 connector 服务器的 host 和 port  next(null, {code: Code.OK, host: res.host, port: res.wsPort});};

这样客户端就能连接到分配的 connector 服务器上。

###2. 进入游戏 在与 connector 服务器建立连接之后,开始进入游戏

pomelo.request('connector.entryHandler.entry', {name: name}, function(data) {  // ...});

在客户端第一次向 connector 服务器发送请求时,服务器会将 session 信息进行初始化和绑定

// session 与 playerId 绑定session.bind(playerId);// 设置玩家 areaIdsession.set('areaId', 1);

进入游戏场景,客户端向服务端发起进入场景请求:

pomelo.request("area.playerHandler.enterScene", {name: name, playerId: data.playerId}, function(data) {  // ...});

客户端向服务端发送请求后,先到达 connector 服务器,然后 connector 服务器根据 game-server/app/util/routeUtil.js 中转发规则,将请求路由到相应的 area 服务器(本例子中只有一个area服务器),area 服务器中的 playerHandler 再处理相应的请求。这样玩家就加入到游戏场景中了。

在一个玩家加入到游戏场景之后,其他玩家必须能即时的看到这个玩家的加入,所以服务端必须将消息广播到在此游戏场景中的所有玩家。 建立 channel,所有加入此游戏场景的玩家都会加入到这个 channel 中

// 获取 channel,如果没有就创建一个channel = pomelo.app.get('channelService').getChannel('area_' + id, true);// 将玩家加入 channelchannel.add(e.id, e.serverId);

当 area 中有玩家加入,或其他状态发生改变时,这些信息都会被推送到在这个 channel 中的每个玩家。比如有玩家加入时:

channel.pushMessage({route: 'addEntities', entities: added});

这些消息都是通过 connector 服务器发送到客户端。而 area 中的消息是通过 session.frontendId 来决定是由哪个 connector 服务器发出去。

客户端接受消息:

// 当有新玩家加入时,服务端会广播消息给所有玩家。客户端通过这个路由绑定,来获取消息pomelo.on('addEntities', function(data) {  // ...});

###3. Area 服务器 area 服务器是一个由 tick 驱动的游戏场景。每个 tick 都会对场景中的 entity 的状态进行更新,如果状态有发生改变,这些改变会被推送到客户端。

function tick() {  //run all the action  area.actionManager().update();  // update entities  area.entityUpdate();  // update rank  area.rankUpdate();}

比如玩家发起一个 move 动作:

客户端

// 向服务端发送 move 请求通知pomelo.notify('area.playerHandler.move', {targetPos: {x: entity.x, y: entity.y}, target: targetId});

服务端 playerHandler 接受请求:

handler.move = function(msg, session, next) {  // ...  // 产生一个 move action  var action = new Move({    entity: player,    endPos: endPos,  });});

然后这个 action 会在每个 tick 中更新。

###4. 客户端发送和接受消息 客户端和服务端的通讯有以下几种方式:

  • Request - Response 方式
// 向 connector 发送请求,参数 {name: name}pomelo.request('connector.entryHandler.entry', {name: name}, function(data) {  // 回调函数得到请求返回结果  // do something});
  • Notify (向服务端发送通知)
// 向服务端发送 move 请求通知pomelo.notify('area.playerHandler.move', {targetPos: {x: entity.x, y: entity.y}, target: targetId});
  • Push (服务端主动发送消息到客户端)
// 当有新玩家加入时,服务端会广播消息给所有玩家。客户端通过这个路由绑定,来获取消息pomelo.on('addEntities', function(data) {  // ...});

###5. 离开游戏 就是在玩家离开游戏时,connector 服务器会先收到断开的消息,这时,它需要在 area 服务器中将用户剔除,并广播消息给其他在线玩家。 因为服务器之间的进程都是独立的,所以这就涉及到一个 RPC 调用,好在 Pomelo 框架对 RPC 做了很好的封装,做法如下: area 服务器想要提供一系列的 Remote 接口供其他服务器进程调用,只需要在 servers/area 目录下,创建一个 remote 目录,在这个目录下的文件暴露出的接口,都可以作为 RPC 调用接口。 比如,玩家离开:

// connector 中对 session 绑定事件,当 session 关闭时,触发事件session.on('closed', onUserLeave.bind(null, self.app));var onUserLeave = function (app, session, reason) {  if (session && session.uid) {    // rpc 调用    app.rpc.area.playerRemote.playerLeave(session, {playerId: session.get('playerId'), areaId: session.get('areaId')}, null);  }};

对应的 area/remote/playerRemote.js 中 playerLeave 方法

exports.playerLeave = function(args, cb) {  // 发出通知  area.getChannel().pushMessage({route: 'onUserLeave', code: consts.MESSAGE.RES, playerId: playerId});  // ...};

这样就轻易的完成了一个跨进程的调用

##数据压缩 pomelo 0.3 版本开始增加了传输数据的压缩特性,数据采用 protobuf 方式压缩,二进制格式传输,大幅降低了网络传输流量。

数据压缩参考 Pomelo 数据压缩协议

原创粉丝点击