非对战游戏接入文档
1 接入步骤
cp整个接入分为三个大的阶段步骤:
1.技术接入阶段
2.资源包提审阶段
3.测试与发布阶段
1.1 技术接入阶段
该阶段主要的目的是进行技术对接。由我方提供开发版大厅apk,用于cp技术层面接入和连调,需要向我方平台申请AppKey,AppSecret,cp提供产品包名,通过配置开发版apk的配置文件(具体配置方式参见:2.2开发版配置说明),进行游戏功能连调,可采用url方式方便调试。
1.2 资源包提审阶段
开发版apk和游戏功能联调好后,进入提审阶段,将游戏按照文档中要求的方式(具体参见:4游戏资源包打包说明),放入manifest.json ,将游戏资源打包并加入签名,按审核流程要求填写游戏相关信息,并提交加签名后的游戏资源包,由官方人员进行审核通过。
1.3 测试与发布阶段
审核通过后,进入游戏测试阶段,有bug,修改后重新通过提审进行提交。测试通过后,技术层面即可具备上线发布的条件,具体发布方式则由业务产品进行制定和操作。
2 开发调试
2.1 下载调试工具
注意:真机调试时须要使用OPPO手机调试
2.2.1 配置说明
配置文件文件名: /sdcard/Android/data/com.nearme.play/files/debug_config.json
注意: /sdcard/Android/data/com.nearme.play/files在初次运行apk才会创建,拷贝进去配置后,需要杀进程重启才生效(启动时加载该配置)。打开apk后,可点击“进入游戏(单机)”按钮,如果配置正确即可拉起游戏。
{
"pkgName": "",
"appKey": "",
"appSecret": "",
"resourceType": "",
"gameRes": "",
"gpkMd5": "",
"gpkVerify": ""
}
属性 | 类型 | 描述 |
---|---|---|
pkgName | String | 游戏包名,为游戏大厅给游戏分配的指定参数,用来区分校验游戏 |
appKey | String | 为游戏大厅给游戏分配的指定参数,用来区分校验游戏 |
appSecret | String | 为游戏大厅给游戏分配的指定参数,用来区分校验游戏 |
resourceType | String | 资源类型,"1"或"2",填"1"时,表示使用链接的形式加载资源,填"2"时, gameRes 表示使用gpk本地包的形式加载资源,游戏包名pkgName 固定为com.test.zip.nearme.gamecenter |
gameRes | String | 取决于resourceType ,若resourceType 为"1",则代表游戏的url。若resourceType 为"2",这里填入的是gpk本地包的文件名,gpk的打包方法请参考打包说明。 |
gpkMd5 | String | " "或游戏包的md5,取决于gpkVerfiy |
gpkVerify | String | "0"不开启本地游戏资源包验证,"1"开启本地资源包验证,为正式环境下验证方式,需要游戏包打签名,并在gpkMd5 下填写游戏包的md5 |
配置文件的以及gpk的存放路径:
/sdcard/Android/data/com.nearme.play/files/debug_config.json
/sdcard/Android/data/com.nearme.play/files/xxxxx.gpk
使用链接方式加载游戏的debug_config.json配置参考示例(demo)
{
"pkgName": "com.xxxx.game1",
"appKey": "fasdfxdfe",
"appSecret": "sdfesdfc",
"resourceType": "1",
"gameRes": "http://www.xxxxxx.com/gameres/index.html",
"gpkMd5": "",
"gpkVerify": "0"
}
使用gpk本地包方式加载游戏的debug_config.json配置参考示例(demo)
{
"pkgName": "com.test.zip.nearme.gamecenter",
"appKey": "fasdfxdfe",
"appSecret": "sdfesdfc",
"resourceType": "2",
"gameRes": "com.test.zip.nearme.gamecenter.gpk",
"gpkMd5": "",
"gpkVerify": "0"
}
3 JS SDK
3.1 接入方式
游戏页面引入js sdk文件:
https://activity-cdo.heytapimage.com/cdo-activity/static/201809/30/gamehall/sdk/heytap-sdk-v2.js
引入后会有window.OPPO对象。
3.2 API说明
OPPO.setWebviewOrientation(ori)
设置屏幕横/竖屏。
参数
ori
值 | 类型 | 说明 |
---|---|---|
landscape | string | 横屏 |
portrait | string | 竖屏 |
示例:
OPPO.setWebviewOrientation('landscape')
OPPO.setWebviewOrientation('portrait')
OPPO.login(object)
通过登录获取oppo会员信息。手机需要已经安装OPPO会员,OPPO手机内置此应用。
参数
object
属性 | 类型 | 说明 |
---|---|---|
packageName | string | 包名,需要修改成开发者在oppo开放平台填写的包名 才能成功调用此方法 |
callback | function | 接口调用成功的回调函数 |
callback(res)回调函数参数
res
属性 | 类型 | 说明 |
---|---|---|
userId | string | 用户id |
userName | string | 用户名 |
avatar | string | 用户头像地址 |
code | number | 状态码 |
msg | string | 请求返回信息 |
constellation | string | 用户星座 |
sex | string | 性别,"F"代表女、"M"代表男、" "代表未设置 |
location | string | 用户所在地 |
age | string | 年龄 |
token | string | 标识身份的令牌 |
示例:
OPPO.login({
packageName: 'your.package.name',//需要修改成开发者在oppo开放平台填写的包名才能成功调用此方法
callback: function(res) {
console.log(res)
}
})
成功时传递给回调函数的res结构如下:
{
"userId": "2000010000",
"userName": "User2000010000",
"avatar": "http://fs-uc-nearme-com-cn.oss-cn-hangzhou.aliyuncs.com/default.png",
"code": 200,
"msg": "成功",
}
OPPO.checkPay
检查手机是否支持js调用支付功能。
示例:
OPPO.checkPay(function(res) {
console.log(res)
});
OPPO.pay(object)
调用支付。支付成功后,OPPO服务端将会调用游戏发货接口通知发货。
参数
object
属性 | 类型 | 是否必填 | 说明 |
---|---|---|---|
packageName | string | 必填 | 开发者在oppo开放平台填写的包名 |
appName | string | 必填 | 游戏名称 |
appVersion | string | 必填 | 游戏版本 |
appKey | string | 必填 | 在oppo开放平台得到的appKey |
orderId | string | 必填 | 开发者在自己业务系统下的订单号 |
price | number | 必填 | 价格,单位 分 |
productName | string | 必填 | 商品名称 |
productDesc | string | 必填 | 商品描述 |
callbackUrl | string | 必填 | 接收支付平台付款通知的地址, 与oppo android SDK的支付通知处理一致 |
callback | function | 必填 | 接口调用成功的回调函数 |
callback(res)回调函数参数
res
属性 | 类型 | 说明 |
---|---|---|
code | string | 状态码 |
msg | string | 请求返回信息 |
示例:
OPPO.pay({
packageName: 'com.testgame.nearme.gamecenter', //开发者在oppo开放平台填写的包名
appName: '游戏名称',
appVersion: '1.0',
appKey: 'TESTOPPOPAY', //在oppo开放平台得到的appKey
orderId: '20171208001', //开发者在自己业务系统下的订单号
price: 1, //单位 分
productName: '商品名称',
productDesc: '商品描述',
callbackUrl: 'http: //www.yourdomain.com/notify',
//接收支付平台付款通知的地址,与oppo android SDK的支付通知处理一致
callback: function(res) {
console.log(res)
}
});
成功时传递给回调函数的res结构如下:
{
"code":200,
"msg":""
}
JS回调的成功仅表示成功调起支付界面,用户可以在后续步骤取消付款,用户付款完成的话会有通知发送到callbackUrl,开发者应该根据自己的订单号在自己的业务系统查询实际状态。
OPPO.getAppVersion
返回当前客户端的版本号,number类型
示例:
var version = OPPO.getAppVersion()
OPPO.setLoadingProgress
- 要求:客户端版本号 >= 1200
参数为0~100的整数,游戏启动后会拉起Loading界面,游戏需要通过setLoadingProgress把自己的加载进度传回界面。
如果游戏加载起来后5秒内没有调用setLoadingProgress,会自动关闭挡板。为了用户体验统一,务必在游戏加载后第一时间调用setLoadingProgress(0),随后再传回真实的加载进度。
示例:
OPPO.setLoadingProgress(20)
OPPO.loadingComplete
- 要求:客户端版本号 >= 1200
游戏加载完成时调用,关闭挡板界面。
示例:
OPPO.loadingComplete()
OPPO.openRankPage
- 要求:客户端版本号 >= 1300
打开当前游戏的排行榜界面。
示例:
OPPO.openRankPage()
4 打包说明
将游戏打包为签名包,采用jdk自带的keytool 和 jarsigner工具,为标准的jar打签名包方式。
(1)用keytool 生成keystore,如有keystore则跳过该步骤。
(2)在h5资源包中需增加 manifest.json文件(建议utf-8 无bom格式),与index.html同级目录,内容如下:
字段 | 说明 |
---|---|
package | 包唯一标示 |
name | 为外显名称 |
icon | 暂时预留 |
versionName | 外显版本名 |
versionCode | 游戏版本号,必须为整数 |
minPlatformVersion | 对战平台最低能力版本号 |
{
"package": "com.company.unit",
"name": "游戏1",
"icon": "",
"versionName": "1.0",
"versionCode": 1,
"minPlatformVersion": 1
}
(3)用zip工具打包,保证manifest.json等资源为根一级。
(4)使用jarsigner 将zip包和keystore打为签名包,扩展名由zip改为gpk,提交审核。
生成keystore示例
keytool -genkey -keystore testkeypair.keystore -alias testkeypair -keyalg RSA -keysize 2048 -sigalg MD5withRSA -validity 99999
jarsigner 打签名示例
jarsigner -verbose -keystore testkeypair.keystore -signedjar signed.zip unsigned.zip -digestalg SHA1 -sigalg MD5withRSA testkeypair
5 支付功能
支付能力
通过sdk的支付接口支付完成后,平台会通过callbackUrl通知发货。
游戏发货接口
提供方
CP提供callbackURL(游戏在下单的时候自己指定的)
实现功能
用户支付成功后,平台会通过这个地址通知CP给用户发货。
调用方
平台
实现要求
CP在收到回调后应及时(200ms内)回写结果给平台,如果调用超时,那么平台会重试,所以CP发货接口必须具有幂等性,避免多次给用户发货。返回结果数据格式:result=arg0&resultMsg=arg1。
arg0:值为“OK”或“FAIL”,两者选其一,该值为必填字段
arg1:arg0为“OK”时该值可以为空字符串,arg0为“FAIL”时建议提供些有意义的信息,便于查找问题,该值非强制字段。
例如:
result=OK&resultMsg=成功
result=FAIL&resultMsg=网络原因发货失败
注:收到开发者回写结果,当result=OK时,表示CP已正常接收到回调,我方不再重复回调。
请求地址
callbackURL
请求方法
POST请求
游戏发货接口参数
参数名 | 类型 | 是否必填 | 长度限制 | 说明 |
---|---|---|---|---|
notifyId | string | 是 | 50 | 回调通知ID(该值使用系统为这次支付生成的订单号) |
partnerOrder | string | 是 | 50 | 开发者订单号 |
productName | string | 是 | 50 | 商品名称 |
productDesc | string | 是 | 255 | 商品描述 |
price | string | 是 | 商品价格 | |
count | string | 是 | 商品数量(默认1) | |
attach | string | 否 | 255 | 开发者在下单时候传递的附件描述信息 |
sign | string | 是 | 签名 |
验证签名方法&示例
(1) 利用回调的参数生成baseString,方法如下:
private static String getKebiContentString(String url) {
final String[] strings = url.split("&");
final Map < String, String > data = new HashMap < String, String > ();
for (String string: strings) {
final String[] keyAndValue = string.split("=");
data.put(keyAndValue[0], keyAndValue[1]);
}
StringBuilder sb = new StringBuilder();
sb.append("notifyId=").append(data.get("notifyId"));
sb.append("&partnerOrder=").append(data.get("partnerOrder"));
sb.append("&productName=").append(data.get("productName"));
sb.append("&productDesc=").append(data.get("productDesc"));
sb.append("&price=").append(data.get("price"));
sb.append("&count=").append(data.get("count"));
sb.append("&attach=").append(data.get("attach"));
return sb.toString();
}
(2) 对baseString进行验证签名
public static boolean doCheck(String content, String sign, String publicKey) {
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] encodedKey = Base64.base64Decode(publicKey);
PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
java.security.Signature signature = java.security.Signature.getInstance("SHA1WithRSA");
signature.initVerify(pubKey);
signature.update(content.getBytes("utf‐8"));
boolean bverify = signature.verify(Base64.base64Decode(sign));
return bverify;
} catch (Exception e) {
logger.error("验证签名出错.",e);
}
return false;
}
公钥:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCmreYIkPwVovKR8rLHWlFVw7YDfm9uQOJKL89Smt6ypXGVdrAKKl0wNYc3
/jecAoPi2ylChfa2iRu5gunJyNmpWZzlCNRIau55fxGW0XEu553IiprOZcaw5OuYGlf60ga8QT6qToP0/dpiL/ZbmNUO9kUh
osIjEu22uFgR+5cYyQIDAQAB
6 挡板的使用
(游戏必须接入挡板,不接入挡板,将导致游戏加载时,进度条丢失,无法通过测试审核)
非对战游戏加载挡板功能怎么兼容?
通过OPPO.getAppVersion获取到的大厅版本号进行判断,从而进一步做兼容性处理。
若大厅版本号小于1200,则使用自己开发的挡板能力。
若大厅版本号大于等于1200,则使用大厅提供的挡板能力。通过OPPO.setLoadingProgress方法设置加载进度,加载完成后,通过调用OPPO.loadingComplete来关闭挡板。
var t = 0
var interval = setInterval(function() {
// 模拟加载
t+=5
console.log('progress is:' + t)
if (OPPO.getAppVersion() > 1200) {
OPPO.setLoadingProgress(t)
} else {
// 请添加兼容老版本大厅的展示自定义挡板逻辑
}
if (t > 100) {
clearInterval(interval)
console.log('loading complete')
OPPO.loadingComplete()
// 请添加兼容老版本大厅的关闭自定义挡板逻辑代码
}
}, 500)