[关闭]
@nullcc 2015-11-11T17:54:15.000000Z 字数 4801 阅读 3520

游戏客户端和服务端逻辑概述

版本列表

版本 作者 备注 更新日期
v0.0.1 (init) zjy 用户登录、创建新角色、进入游戏、聊天、战斗逻辑 2015.11.10
v0.0.2 zjy 增加游戏热更新描述 2015.11.11

备注

本文主要描述游戏客户端和服务器端的交互过程,但其中可能会涉及一些技术原理和细节,有需要者可以有选择地查看。

索引


------杂项------

游戏开发基础情况

客户端:cocos2d-js (js)
服务端:网易开源游戏框架pomelo (js)
数据库:MySQL, redis

游戏热升级

本游戏使用cocos2d-js开发客户端,由于cocos2d-js的跨平台特性,利用jsb技术绑定javascript和C++,可以在只编写javascript代码的情况下,使游戏可以在不同平台上运行而几乎不需要做任何与平台有关的工作(要引入第三方库的情况下除外,这需要不同平台手动绑定一些函数,在之后会详细介绍)。这种技术还可以使游戏进行热更新,在游戏已经上架的情况下,热更新可以使开发者无需提交新版本到发布平台(app store/安卓各种应用商店等),直接利用网络进行游戏客户端代码的实时更新。以下将简要介绍这种热更新的原理、本游戏热更新服务系统的设计实现方式和注意事项(坑)。

工作原理

在客户端中,使用jsb.AssetsManager这个系统提供的对象,首先加载位于"res/project.manifest"文件,该文件描述了请求服务端版本信息和更新文件信息的方法,project.manifest文件内容如下:

  1. {
  2. "packageUrl" : "http://192.168.1.211:8000/res",
  3. "remoteManifestUrl" : "http://192.168.1.211:8000/check/project",
  4. "remoteVersionUrl" : "http://192.168.1.211:8000/check/version",
  5. "version" : "1.0.0",
  6. "engineVersion" : "3.6",
  7. "assets" : {
  8. },
  9. "searchPaths" : [
  10. ]
  11. }
1. remoteVersionUrl指定了获取最新版本的方法,客户端在启动时会先访问这个URL。
2. remoteManifestUrl指定了游戏升级文件的列表和其他一些相关信息
3. packageUrl指定了升级文件在服务器端的根目录

更新过程:
1. 客户端启动,使用jsb.AssetsManager对象加载project.manifest文件,利用remoteVersionUrl请求当前最新版本信息,服务器查询数据库返回这个信息。
2. 如果服务器返回的最新版本比客户端当前的新,就利用remoteManifestUrl请求需要更新的文件列表,服务器查询数据库返回更新文件的列表。如果最新版本和当前客户端版本一样就跳过更新过程,直接进入游戏。
3. 客户端收到更新文件列表,把packageUrl和文件路径拼接起来形成完整URL,再请求下载各个文件并保存在本地。
4. 客户端完成下载,加载jsList.js文件,进入游戏。

热更新细节请查看api-server的代码。

注意事项

1. 原始包里的文件和通过热更新增加的新文件的更新方式不同

在热更新时,原始包中的文件可以直接通过指定文件名和MD5值来更新,这不会有任何问题。但如果是通过热更新增加的新文件就有所不同。经测试发现,对于一个通过热更新新增的文件new.js,假如在v1.0.1版本加入了此文件,在下个版本v1.0.2中,不管此文件是否有更新,在返回的project.manifest中的assets键下都必须指出此文件名和其相应的MD5值。

如果不这么做,在更新完文件后assetsManager中调用loadGame()时,require文件会失败。目前原因不明。

2. 文件的MD5值

待更新文件的MD5值直接关系到此文件会不会被真正下载到客户端进行更新,客户端进行热更新时会检查project.manifest文件中各个文件的文件名和MD5值,然后和本地的相应文件(路径相同的同名文件)进行MD5值的比对,如果project.manifest文件中相应的文件的MD5值和本地的不同,则真正进行下载更新,如果相同就忽略。可以猜想到的是,coco2d-js在客户端维护了一个文件列表和对应文件的MD5值,以方便比对。

也就是说,热更新时一个文件会不会被更新其实和这个文件本身的新旧无关,而只与其MD5值有关,如果文件是新的,但是MD5还是旧文件的MD5,即使检测到了此文件需要更新也依然不会真正去下载更新,这点需要注意,否则会导致更新失败。

3. 服务端该做些什么?

针对一个新版本,首先需要在api.g_versions表中增加一个新行,version和internalVersion字段都必须唯一且要按照递增的方式。api.g_files表负责存储所有在新版本中要更新的文件信息,其中version字段关联api.g_versions的version字段。path和md5字段分别是文件在客户端的对应路径和其MD5值。

------游戏逻辑部分------

【1】用户登录

本游戏的用户账户系统使用第三方平台授权登录的方式(OAuth2.0),不提供典型的账号密码登录机制。OAuth2.0的机制和原理在此不再赘述, 目前支持的第三方平台有:
1. 微信(ShareSDK平台代码22)
2. QQ(ShareSDK平台代码6)
3. 新浪微博(ShareSDK平台代码1)

用户登录流程图如下:

Created with Raphaël 2.1.2游戏客户端游戏客户端第三方平台第三方平台认证服务器(api)认证服务器(api)游戏服务器(pomelo)游戏服务器(pomelo)①认证请求客户端第三方平台认证②accress_token,openid③accress_token,openid,type认证服务器与第三方平台进行身份确认④登录token⑤请求游戏服务器列表⑥游戏服务器列表⑦选择相应的gate server连接,请求connector服务器信息⑧connector服务器的host,port⑨正式登录游戏玩家进入游戏

登录流程结束后,根据玩家在该服务器分区是否拥有角色分别进入【3】进入游戏或【2】创建新角色


【2】创建新角色

每个账号在每个游戏服务器分区只能创建至多一个角色。

1. 前提条件

<1> 用户通过登录认证
<2> 用户在该服务器分区尚未建立角色

2. 过程

<1> 客户端选择角色性别、职业、昵称(可随机也可自己设置,但需要服务端验证唯一性),设置好后发送至服务端
<2> 服务端验证新角色数据,若成功则把新角色信息数据落地并返回新角色信息给客户端,否则返回错误信息
<3> 客户端收到创建成功信息和角色数据后,转到【3】进入游戏


【3】进入游戏

1. 前提条件

<1> 用户通过登录认证
<2> 用户在该服务器分区已建立角色

2. 过程

<1> 用户认证成功后,服务器把当前用户加入游戏世界(世界聊天频道、游戏主频道等),读取用户在该区的角色数据并返回给客户端
<2> 客户端收到角色数据后,进入游戏主界面,并用角色数据初始化UI


【4】离开游戏

玩家在以下两种情况下会离开游戏(长连接断开):
1. 应用被切换到后台
2. 应用被kill

1. 前提条件

<1> 用户在线
<2> 客户端和服务端的长连接断开

2.服务端操作

<1> 把角色从所有已加入的频道中kick
<2> cache当前角色数据到redis,并设置expire时间(待定),数据包括:
1. 角色当前数据
2. 角色当前战斗数据
expire到期时进行数据落地
<3> 清除服务器内存中当前角色的信息


【5】聊天系统

服务端负责维护聊天频道的信息,可以把角色加入频道或从频道中移除,角色只有在加入某个频道后才能从那个频道上收到服务器推送的数据。

聊天系统中客户端和服务端的信息发送方式:
1. 客户端: notify
2. 服务端: push

聊天频道类型

  1. 世界聊天(全服可见)
  2. 私人聊天(目前只支持单对单)

【6】物品

物品在数据库中分为物品模板表(静态)和物品实例表(动态),当需要生成物品时,服务器从物品模板表中读取相应物品信息并初始化一个新物品,此新物品的数据需要存储到物品实例表中。


【7】宠物系统


【8】游戏战斗

模式:
1. 手动模式
2. 自动(挂机)模式

手动模式

用户实际操作的战斗模式,需要用户参与交互,如果用户在战斗界面不进行任何操作,则全部以默认值计算战斗情况。

1. 前提条件

<1> 用户在线
<2> 用户进入战斗界面

2. 准备

<1> 客户端首先向服务器发送开始战斗请求(地图id)
<2> 服务端判断该客户端当前是否可以开始战斗,如果可以,生成一个随机key(每场战斗一个),并初始化战斗数据(地图数据、角色属性值、技能数据、连连看数据),然后返回相应数据给客户端
<3> 客户端收到随机key和战斗初始化数据后,初始化战斗UI,开始发送战斗回合请求

3. 过程

<1> 每个回合中,客户端进行倒计时,用户在倒计时结束前可以操作连连看,据此获得战斗加成,客户端进行连连看的同时,通过notify的方式通知服务器每一个连连看操作,服务端需做验证,若发现非法操作则直接结束战斗
<2> 在倒计时结束时,服务端使用当前战斗数据和战斗加成数据计算本回合战斗情况,使用push的方式传给客户端
<3> 客户端显示战斗过程(战斗动效、数据变化等)
<4> 本回合结束,如果敌我双方有一方HP全部为0,战斗结束,服务端统计最终战斗结果,push给客户端,客户端展示最终结果。否则开始下一个回合,重复<1>~<4>的过程

4. 结算

<1> 战斗结束后,服务端计算本次战斗的结果(成果/失败)和收益(金钱/经验/物品等)

5. 流程图

Created with Raphaël 2.1.2客户端客户端服务器(pomelo)服务器(pomelo)①request: 开始战斗请求(地图id)验证合法性(非法请求直接结束)②response: 随机key,战斗场景数据,角色数据③初始化战斗界面,战斗回合开始④战斗回合,倒计时,游戏前端操作,需要和服务端同步连连看操作(notify)验证连连看合法性(非法请求直接结束)⑤倒计时结束⑥push: 回合结果数据(如果战斗未结束则通知客户端可以开启下一回合)若战斗未结束,回到③,否则结束战斗,显示战斗结果

自动模式(挂机)

用户不参与任何战斗的实际交互,全部由服务器代劳的战斗模式。

1. 前提条件

<1> 用户离线
<2> 用户在某一张战斗地图上

2. 准备

<1> 从用户离线开始就进入到自动战斗模式,保存用户当前战斗地图和离开时间点

3. 过程

<1> 用户离线状态下服务器不进行任何战斗计算

4. 结算

<1> 用户再次上线时,统计用户这段时间内的战斗情况和收益(具体算法待定)

5. 服务器的操作

用户离线时,把用户当前战斗地图和离开时间点cache到redis里,设置expire时间(具体时间待定),服务端订阅此expire。当用户在expire时间之内再次登录,则直接从redis中取数据。当expire时间到时,服务端会收到订阅消息,把该数据落地。


添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注