跳到主内容

接入指南

游戏服务接入指南

面向研发同事的完整接入文档:客户端加载、JS Bridge 通信、服务端部署、签名接口与回调。

一、游戏接入模式

Gamefans 游戏侧有两种接入模式,即游戏自带大厅模式和业务侧组局模式,两种模式下的业务流程和需要调用的接口有所不同。

1.1 游戏自带大厅模式

该模式下的时序图如下。在该模式下,游戏会主动维护座位状态,玩家加入或退出游戏时,游戏会主动调用服务端相关接口;业务侧也可以通过相关接口进行玩家上下座、添加机器人等操作。

1.2 业务侧组局模式

该模式下,需要业务侧维护游戏玩家状态,同时在需要开始游戏时通过调用游戏服务相关接口来告知游戏玩家列表并开始游戏。该模式接入后的时序图如下。

二、客户端接入

游戏包为静态文件,以网页方式部署,通过URL加载即可。游戏与业务侧客户端的交互通过JS Bridge完成。通过游戏加载URL中的相关参数,可以指定游戏的相关行为;通过 JS Bridge 相关方法和事件,可以完成游戏和业务侧客户端的相关交互,游戏预定义了一部分常用的事件和方法,同时可以根据业务侧需求,添加对应事件或方法。

2.1 客户端部署

交付的游戏客户端为静态文件,需要以网页的部署形式,部署到对应服务器、对象存储等位置,并可以按照需求开启 CDN 等功能。

2.2 客户端加载

游戏部署完成后,可以通过 URL 方式进行加载。游戏加载需要以下参数:

参数类型说明
app_idstringAPP ID
room_idstring房间 ID
tokenstring用户标识
languagestring游戏语言代码
extraJSON其他信息

其中 extra 字段包含以下参数:

字段类型是否必须默认值说明
http_urlstring是--游戏调用 http 接口 url
ws_urlstring是--游戏调用的 websocket 接口 url
topint否0安全区上边界
bottomint否0安全区下边界
leftint否0安全区左边界
rightint否0安全区右边界
widthint否--游戏宽度
heightint否--游戏高度
auto_scaleint否0是否自动缩放,0关闭自动缩放,1开启自动缩放

以如下参数值为例:

参数值
app_id5147182126419264
room_id89945
tokenHWUEv2uXigqYEG0t
languagezh-CN
extra{"http_url":"https://example.com/api","ws_url":"wss://example.com/websocket-endpoint","top":100,"bottom":300,"auto_scale":0}

则最终构建游戏 URL 如下所示:

plain
https://example.com/ludo?app_id=5147182126419264&room_id=89945&token=HWUEv2uXigqYEG0t&language=zh-CN&extra=%7B%22http_url%22%3A%22https%3A%2F%2Fexample.com%2Fapi%22%2C%22ws_url%22%3A%22wss%3A%2F%2Fexample.com%2Fwebsocket-endpoint%22%2C%22top%22%3A100%2C%22bottom%22%3A300%2C%22auto_scale%22%3A0%7D

其中 extra 参数为经过 url encode 后的 json 字符串,解码后为:

json
{"http_url":"https://example.com/api","ws_url":"wss://example.com/websocket-endpoint","top":100,"bottom":300,"auto_scale":0}

2.3 客户端接口

游戏与业务侧通过 JS Bridge 方式进行通信,业务侧 APP 需要注册 JS Bridge 以完成相关功能,以下文件中包含 Android 与 iOS 端接入的相关代码示例:

2.3.1 Android 接入

1. 添加 JS 接口
java
mViewModel = new GameViewModel(mWebView);
mWebView.addJavascriptInterface(mViewModel, "GameJSBridgeAndroid");
2. GameViewModel.java

通讯集中在此文件中,可直接复用此文件

1. 向游戏发送消息
java
String state = "app_common_android";
String json = "{\"key\":\"value\"}";
mViewModel.sendMessageToGame(state, json, new GameViewModel.GameMessageCallback() {
    @Override
    public void onCallbackMessage(String json) {
        LogUtils.d("收到了游戏的消息回调:" + json);
    }
});
2. 设置监听,接收游戏发送的数据
java
mViewModel.setOnGameMessageListener(new GameViewModel.OnGameMessageListener() {
    @Override
    public void onMessage(String state, String json, GameViewModel.GameMessageHandler handler) {
        LogUtils.d("收到游戏发送过来的消息:" + state + " json:" + json);
        // 给游戏回调应答消息,必须要调用,格式和内容请查看文档定义
        String backJson = "{\"msg\":\"Android收到消息了\"}";
        handler.completed(backJson);
    }
});

2.3.2 iOS 接入

1. 添加JS的消息处理
swift
// 添加消息处理,其中onMessageFromGame和onCallbackMessage是必须的
userContentController.add(context.coordinator, name: "onMessageFromGame")
userContentController.add(context.coordinator, name: "onCallbackMessage")

// 在Coordinator的userContentController方法中,将消息传递给GameViewModel
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    parent.viewModel.userContentController(name: message.name,body: "\(message.body)")
}
2. GameViewModel.swift

通讯集中在此文件当中,可直接复用此文件

1. 向游戏发送消息
swift
let state = "app_common_ios"
let json = "{\"testKey\":\"haha\"}"
viewModel.sendMessageToGame(state:state,json:json){ json in
    print("count:\(count) 游戏给的回调数据为:\(json ?? "")")
}
2. 设置监听,接收游戏发送的数据
swift
// 设置游戏消息监听
viewModel.setOnGameMessageListener(listener: MyGameMessageListener())

class MyGameMessageListener: OnGameMessageListener {
    func onMessage(state: String, json: String, handler: (String) -> Void) {
        print("iOS收到游戏发来的消息,state:\(state) json:\(json)")

        // 给游戏回调应答消息,必须要调用,格式和内容请查看文档定义
        let backJson = "{\"msg\":\"iOS收到消息了\"}"
        handler(backJson)
    }
}

2.4 客户端事件

游戏和 APP 通过 JS Bridge 双向通信时,均需要传递 state 和 data 参数,以及 callback 作为应答回调。

以 sendMsgToApp 为例:

javascript
bridge.sendMsgToApp = function (state, data, callback) { ... }
参数类型说明
statestring命令标识,通信中所传递的事件
dataJSON string通信中所传递的数据,格式为合法的JSON字符串
callbackcallable应答回调

2.4.1 游戏发送给 APP 的事件

1. 游戏加载完成

mg_common_notify_load_completed 通知 app 游戏加载完成

参数内容说明
statemg_common_notify_load_completed游戏加载完成时将发送此消息
json---
callback---
2. 游戏结算

mg_common_game_settlement 通知 app 游戏的结束数据

参数内容说明
statemg_common_game_settlement游戏结算界面出现的同时通知此消息
json具体结构如下
callback---

data 字段结构如下:

json
{
    "results": IResult[], // 玩家信息列表
    "reason": number,     // 游戏结束原因 0: 正常结束  3: 提前结束
    "roundId": string,    // 局id
    "chessNum": number,   // 当局玩法棋子个数 4 或者 2
    "item": number,       // 当局游戏是否有道具 0:无 1:有
    "model": number       // 当局游戏是快速模式还是经典模式 0:快速 1:经典
}

IResult 接口定义:

typescript
interface IResult{
    uid: string,
    appId: string,
    userId: string,    // 对应app侧的玩家id    
    name: string,
    avatar: string,
    gender: string,
    score: number,    // 得分
    rank: number,
    isEscaped: number,
    isAI: number,
    isManaged: number, // 是否托管完成游戏 1:托管完成 0:不是托管完成
    extras: string,
}

3. 其他事件

游戏可以根据业务侧需求,在游戏流程中发送相关事件。

2.4.2 APP 发送给游戏的事件

该部分事件可以根据业务侧需求灵活定义,例如通过原生界面对游戏进行背景音乐、音效等操作,或显示相关游戏 UI 等。注册 JS Bridge 后,可通过以下调用来向游戏发送事件:

javascript
bridge.sendMsgToGame = function (message) {}

三、服务端部署

服务端交付编译后的二进制文件,需要业务侧按照文档提供相关接口,完成服务端配置和部署。

3.1 服务器资源

服务端部署需要满足以下最小服务器要求:

编号配置数量用途
11核2G1台etcd 服务
22核4G由请求量决定游戏服务

若需要单机部署,可将 etcd 服务与游戏服务部署在同一台服务器上,配置保持 2 核 4G 即可,但出于环境管理与可扩展性考虑,尽量将其独立出来

除上述服务器资源外,业务侧还需要准备 redis 服务

3.2 部署流程

  • 文件准备:交付的游戏服务端二进制包 gfs.zip
  • 接口准备:游戏服务端所需的获取用户信息业务接口和游戏事件通知回调接口,具体见下一节相关接口描述
  1. 按照以上表格中的服务器需求部署 etcd 服务
  2. 准备 redis 服务
  3. 将 gfs.zip 上传至游戏部署服务器并解压
  4. 修改 httpgate/config/config.yaml 中 etcd 对应的配置,回到 httpgate 目录,使用命令 bash service.sh start 启动服务
  5. 修改 tcpgate/config/config.yaml 中 etcd 和 redis 对应的配置,回到 tcpgate 目录,bash service.sh start 启动服务
  6. 修改 timer/config/config.yaml 中 etcd 和 redis 对应的配置,回到 timer 目录,bash service.sh start 启动服务
  7. 修改 {{game_name}}/config/config.yaml 中 etcd 和 redis 对应的配置,填写 get_user_info_url 和 notify_url 地址,回到 ludo 目录,bash service.sh start 启动服务
  8. 如需支持分布式,需要重复第3步到第7步

注意事项:

  • service.sh 脚本需要使用 bash 执行

    配置文件中需要修改的配置项包括:

    • etcd_urls:ectd 服务地址
    • redis:redis 服务配置
    • app_info.app_id
    • app_info.secret
    • app_info.get_user_info_url:游戏服务端调用的获取用户信息接口地址
    • app_info.notify_url:游戏服务端调用的事件通知接口
  • 启动服务前请先检查 service.sh 脚本的可执行权限,若无执行权限则通过 chmod +x service.sh 来添加对应权限

四、服务端接入

服务端接口均遵守以下规则:

  1. 所有接口使用 UTF-8 编码的 JSON 字符串格式进行数据交互
  2. 接口响应的基本格式如下,业务侧所提供的回调接口也应按照该格式提供。本文档中其余部分若非特殊说明,则默认接口响应为该格式,不再单独列出
json
{
    "ret_code": 0,        // 接口响应码,用来判断本次请求是否成功
    "ret_msg": "ok",      // 接口响应信息,可提供简单的
    "data": []            // 响应数据,由具体业务确定其类型
}

4.1 业务方提供游戏回调接口

该部分所有的接口需要由业务侧提供,游戏服务端会在游戏运行时的适当节点调用这些接口,以获取相关信息,完成对应功能。

4.1.1 获取用户信息

游戏服务端将通过此接口获取游戏中用户的基本信息,如用户名、头像等。

- 请求定义

http
POST  业务侧提供URL
Accept: application/json  
Content-Type: application/json

{
    "token": "{{token}}", 
    "app_id": "{{app_id}}"
}    
参数类型说明
tokenstring用户唯一标识
app_idstringAPP 唯一标识

- 响应定义

json
{
    "ret_code": 0,
    "ret_msg": "",
    "data": {
        "user_id": "123456",  
        "name": "Username",  
        "avatar": "https://example.com/avatar.png",  
        "gender": "male",
        "is_ai": 0,
        "extras": "{\"skin_type\": 0}"
    }
}
`data`类型定义
字段类型说明
user_idstring用户唯一标识
namestring显示名称
avatarstring头像URL
genderstring性别
is_aiint机器人 0:真人 1-3:机器人等级
extrasstring扩展字段json字符串

`extras`字段定义,该字段类型为`string`,即经过转义的`JSON`字符串,其具体数据接口需要根据具体业务需求定义。

4.1.2 游戏事件回调

当游戏状态改变时,游戏服务端将通过调用此接口同步游戏状态。

通用接口信息

所有游戏回调接口均会提供本节所描述的各项参数,其中 data 信息由各个事件分别决定,定义须参考具体事件的请求定义部分。

- 请求定义

http
POST 业务侧提供URL
Accept: application/json  
Content-Type: application/json

{ 
    "event": "{{game_event}}", 
    "notify_id": "{{notify_id}}", 
    "game_name": "{{game_name}}", 
    "app_id": "{{app_id}}", 
    "room_id": "{{room_id}}", 
    "timestamp":"{{timestamp}}", 
    "data": {} 
}
参数类型说明
eventstring游戏事件类型
notify_idstring事件消息唯一标识
game_namestring游戏名
app_idstringAPP唯一标识
room_idstring房间唯一标识
timestampstring毫秒时间戳字符串
dataobject其他相关信息,具体定义见不同事件中的请求定义

- 响应定义

json
{
    "ret_code": 0,
    "ret_msg": "",
    "data": null
}
游戏开始事件:game_start

游戏正式开始时,服务端将调用该接口通知 game_start 事件。

- 请求定义

json
{ 
    // 通用字段
    "event": "game_start", 
    ...
    "data": {
        "mode": 1,
        "round_id": "{{round_id}}",
        "start_time": 11230182038080,
        "players": [{
            "user_id": "{{user_id}}",
            "seat_index": 0,
            "status": "IDLE",
            "is_ai": 0
        }],
        "extras": "",
        "app_extras": ""
    } 
}
`data`类型定义
参数类型说明
modeint模式
round_idstring局ID
start_timeint游戏开始时间戳
playersPlayerInfo[]参与游戏的玩家列表
extrasstring扩展字段
app_extrasstring业务侧透传字段
`PlayerInfo` 类型定义
参数类型说明
user_idstring用户ID
seat_indexint座位索引
statusstring状态 IDLE:空闲 READY:已准备
is_aiint是否为机器人
游戏结束事件:game_end

游戏结束时,服务端将调用该接口通知 game_end 事件。

- 请求定义

json

{ 
    // 通用字段
    "event": "game_end", 
    ...
    "data": {
        "mode": 1,
        "round_id": "{{round_id}}",
        "start_time": 11230182038080,
        "end_time": 11230182038080,
        "duration": 5000,
        "results": [{
            "user_id": "{{user_id}}",
            "is_ai": 0,
            "rank": 1,
            "score": 10,
            "is_win": 1,
            "is_escaped": 0,
            "is_managed": 0,
            "extras": ""
        }],
        "extras": "",
        "app_extras": ""
    } 
}
`data`类型定义
参数类型说明
modeint模式
round_idstring局ID
start_timeint游戏开始时间戳
end_timeint游戏结束时间戳
durationint游戏持续时间(毫秒)
resultsResult[]玩家列表
extrasstring扩展字段
app_extrasstring业务侧透传字段
`Result`类型定义
参数类型说明
user_idstring用户ID
is_aiint是否为机器人
rankint排名
scoreint得分
is_winint输赢 1:输,2:赢,3:平局
is_escapedint是否逃跑 0:未逃跑 1:逃跑
is_managedint是否托管 0:未托管 1:托管
extrasstring扩展字段

4.2 调用游戏服务端接口

4.2.1 接口签名机制

调用游戏服务端接口需要进行签名验证,本节介绍接口签名所需的参数与签名方法。如非特殊说明,所有接口均需要进行签名验证。

1. 签名所需的参数
参数类型说明
auth_typestring认证类型 gfs
app_idstringAPP 唯一标识
secretstringAPP 密钥
timestampstring请求时间戳
noncestring随机字符串
bodyjson stringJSON格式的请求体
2. 获取签名的步骤

按照如下格式拼装各参数,共四行,每行以\n结尾,包含最后一行

plain
{{app_id}}\n
{{timestamp}}\n
{{nonce}}\n
{{body}}\n

以`secret`为 key,对上一步得到的字符串进行`HmacSHA1`加密,得到请求签名

plain
sign = hmac_sha1(origin, secret)

为请求添加`Authorization`请求头,内容应书写在在一行内

http
POST url
Authorization: {{auth_type}} app_id="{{app_id}}",timestamp={{timestamp}},nonce={{nonce}},signature={{sign}}
3. 接口签名示例

使用以下数据进行签名演示

参数示例值
auth_typegfs
app_id123456
secretabcdef
timestamp1718777147021
noncewHwZk4veHDFSpiw5
body--

构建签名字符串 `origin`

plain
123456
1718777147021
wHwZk4veHDFSpiw5
{"event":"user_enter","app_id":"123456","room_id":"200071","timestamp":"1718777146975","data":{"user_info":{"user_id":"200001","avatar":"https://gfs-static.cyouth.cn/upload/avatar/133482529220956160.png","name":"CYouth","gender":"0","is_ai":0}}}

# 最后一行也有换行符\n

获取签名

plain
sign = hmac_sha1(origin, secret)
# secret = abcdef
# sign = 24e819bb9a6bac0c02f141e0ff41ed1bf342db79

添加请求头

http
Authorization: gfs app_id="123456",timestamp="1718777147021",nonce="wHwZk4veHDFSpiw5",signature="24e819bb9a6bac0c02f141e0ff41ed1bf342db79"

请求接口

4.2.2 接口响应码

响应码说明
0成功
100000通用错误
100001code 创建失败
100002code 效验失败
100003code 解析失败
100004code 非法
100005code 过期
100006请求 get_user_info 失败
100007解析 get_user_info 数据失败
100008get_user_info 接入方错误,返回http状态码非200
100009http 缺失code 参数
100010http 缺失appId 参数
100101登录错误
100102加入错误
100103游戏中的房间不能上座
100104房间人数已满
100105重复加入
100106位置上有人
100107机器人(AI)不能成为队长
100108退出错误
100109不在游戏位
100110非空闲状态不准离开
100111准备错误
100112取消准备错误
100113开始错误
100114已开始
100115队长才能开始游戏
100116有人未准备
100117开始游戏的人数不足
100118踢人错误
100119队长才能踢人
100120游戏中的房间不能踢人
100121不能踢自己
100122换队长错误
100123逃跑错误
100124逃跑时游戏已结束
100125逃跑时玩家已不在游戏中
100126解散错误
100127解散时游戏已结束
100128队长才能解散

4.2.3 游戏服务端接口

1. 通用接口信息

本节所提供的信息为调用游戏服务端接口的通用信息,如非特殊说明,所有接口均需以本节所描述的方法进行请求,并携带本节所定义的数据字段。

- 通用请求定义

http
POST {{baseURL}}/{game}/app_event
Accept: application/json
Content-Type: application/json

{  
    "event": "user_enter",  
    "app_id": "app_id_1",  
    "room_id": "room_id_1",  
    "timestamp": "1657770493152",  
    "data": {}
}
字段位置类型说明
gameURLstring游戏名
eventbodystring事件类型
app_idbodystringAPP 唯一标识
room_idbodystring房间唯一标识
timestampbodystring毫秒时间戳
databodyobject接口所需数据,不同接口数据不同

- 通用响应定义

字段类型说明
ret_codeint响应码
ret_msgstring响应信息
data----
2. 玩家上座接口

通过调用该接口,将指定玩家添加到游戏位,需要提供加入游戏的用户信息。

- 请求定义

json
{
    "event": "user_enter",
    "app_id": "{{app_id}}",
    "room_id": "{{room_id}}",
    "timestamp": "{{timestamp}}",
    "data": {
        "user_info": {
            "user_id": "",
            "avatar": "",
            "name": "",
            "gender": "",
            "extra": ""
        }
    }
}

- `UserInfo`定义

字段类型说明
user_idstring用户唯一标识
avatarstring用户头像URL
namestring用户显示名称
genderstring性别
extrasJSON string扩展字段

- `UserInfo.extras`定义

字段类型说明
skin_typeint皮肤类型,取值:1~4
3. 结束游戏或用户逃跑

通过调用该接口,结束一局游戏,或者用户逃跑。

- 请求定义

json
{  
    // 通用字段
    "event": "game_end",  
    "app_id": "{{app_id}}",  
    "room_id": "{{room_id}}",  
    "timestamp": "{{timestamp}}",
    "data": {  
        "user_id": ""
    }
}
`data`类型定义
字段类型说明
user_idstring用户ID 指定用户逃跑时携带。默认为空,表示游戏提前结束
4. 添加机器人

通过调用该接口,可以向房间内添加机器人玩家。机器人不能作为队长,因此添加机器人前需要有玩家加入游戏。

- 请求定义

json
{  
    // 通用字段
    "event": "ai_add",  
    "app_id": "{{app_id}}",  
    "room_id": "{{room_id}}",  
    "timestamp": "{{timestamp}}",
    "data": {  
        "user_infos": [
            {
                "user_id": "",
                "avatar": "",
                "name": "",
                "gender": "",
                "extra": ""
            }
        ],
        "is_ready": true
    }
}
`data`类型定义
字段类型说明
user_infosuserInfo[]指定多个机器人玩家信息
is_readyboolean机器人玩家是否为已准备状态

- 响应定义

当请求成功(即`ret_code`为0)时,data为以下结构:

`data`类型定义
字段类型说明
user_idsstring[]添加成功的机器人uid数组

五、常见问题

游戏通过安全区相关配置来避免 UI 遮挡问题,使得在游戏全屏的情况下为业务客户端 UI 保留空间,只需要在加载游戏的 URL 中提供安全区相关参数(即 top, bottom, left, right 四个参数)即可,参数值为需要保留的安全区大小。