@wsd1
2017-11-12T11:20:15.000000Z
字数 10008
阅读 1259
ucast
android
201708
uControl:是otu和实时引擎的整合;
uLogic:在uControl之上提供应用逻辑的角色;
Edge:是管理具体物联网应用的逻辑角色;
uLogicManager:负责收敛uLogic数据的cache层,微信登录,用户信息;
player\viewer: 玩家或观众
1、场景事件添加 “IDLE” 用于表示 当前机器转变空闲。
2、enter RPC 返回值中 添加 driver字段,用于表示当前游戏正在进行,并且带有司机信息。
3、事件处理流程 补充处理数组代码
1、补充“操作状态事件推动流程”章节,描述游戏过程。
2、将webview地址添加上了 html后缀。
1、Enter RPC调用返回值中的driver字段做了调整
2、场景事件 添加了 "ERROR" 事件,用于提示用户出现问题。
app名称:互动抓娃娃
codeName:appClaw
android包名:cc.ucast.claw
glbAccount 对象,存储用户相关信息
glbRoundInfo 对象,存储游戏信息
GET portal/appclaw/cover.json
{img: "http://ucast.oss-cn-beijing.aliyuncs.com/covers/appclaw_splash_1726.png", hold: 2} 两字段分别表示图片地址和持续时间(秒)。
登录过程参见微信开放平台登录文档:
https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&id=open1419317851
1、APP带上“appid”“scope”和“state”调用SDK请求微信开放平台。
用户点击允许后,微信返回:
ErrCode 错误码 0为同意
code 用户换取access_token的code,仅在ErrCode为0时有效
state 第三方程序发送时用来标识其请求的唯一性的标志,不能超过1K
lang 微信客户端当前语言
country 微信用户当前国家信息
2、APP将code交给ucast后台,后台处理后会返回账户信息。
具体是GET 路径:https://ucast.cc/portal/wxopen_claw?code=xxxx
(20170902:该接口Ready)
可以得到json串,类如:
{
nickname:"",
avatar:"http://xxxx.jpg",
userID: "xccdsdsd",
jwt:"Long string about 300bytes"
}
建议赋值给 glbAccount
若之前登录过,本地缓存了glbAccount,可以越过微信登录,访问https://ucast.cc/api/v1/me(20170902:该接口Ready),验证令牌,并更新之。
若没有glbAccount,或者验证时失败,就要重新微信登录了。
使用js示例逻辑:
//若没有发现账户信息
if(!glbAccount || !glbAccount.jwt){
//微信登录逻辑
}
//更新账户信息,
$.ajax({
type: 'GET',
url: "https://ucast.cc/api/v1/me",
dataType: 'json',
beforeSend: function(xhr) {
xhr.setRequestHeader('Authorization', "bearer " + glbAccount.jwt);
}
})
.done((dat) => {
console.log(dat);
/* 结果类如:
{
nickname: "ss",
avatar: "http://sss.jpg",
userID: "xxaa",
jwt: "a.b.c" //更新的令牌
}
*/
glbAccount.jwt = dat.jwt; //这里表示更新令牌
})
.fail(function(jqXHR, textStatus) {
//若失败,可能是过期,请重新进行微信登录流程
...
});
列表页采用webview,web页面请求需要添加账户令牌,示例代码:
...
webView.loadUrl("https://ucast.cc/portal/appclaw/index.html#/list?jwt="+glbAccount.jwt);
其内部依赖后台接口:
GET /api/v1/appclaw/games
用户点击其中之一项目,会调用native代码,提供数据,示例如下:
//call native method
window.WebViewJavascriptBridge.callHandler(
'onListItemSelect'
, item.data
, (responseData)=>{console.log(responseData);}
);
/* item.data 数据结构如下:
{
//场景侧(现场)信息
scene:{
sceneDID: 123,
state: "busy"/"ready"/"sleep",
ctrlUrl: "wss://xxx",
uLogicName: "clawSimple"
},
//游戏信息
profile:{
profileName: "claw_basic_profile",
images:["http://img14.360buyimg.com/n1/g16/M00/01/13/rBEbRlNrQy4IAAAAAAMSAcUO_u8AAAUEwPB7PQAAxIZ643.jpg"],
title: "自带乾坤袋哆啦A梦",
fee: 30
}
}
*/
观众界面下方是游戏详情,其入口地址为:
https://ucast.cc/portal/appclaw/index.html#/detail?sdid={item.data.scene.sceneDID}&jwt={glbAccount.jwt}
1、登录总线:
ds = deepstream(selectedItem.ctrlUrl).login({
username: glbAccount.userID,
password: '',
token: glbAccount.jwt
});
2、关注场景的事件(后文统称“场景事件”):
说明:这是本场景公共信息发布通道,比如,观众数量变化了、当前有人开始(退出)游戏之类的
ds.event.subscribe(`event/uLogic/${selectedItem.scene.uLogicName}/${selectedItem.scene.sceneDID}`,(evts)=>{
//得到的是数组
for(var i = 0; i < evts.length; i++){
let evt = evts[i];
switch(evt.event){
case "VIEWER": //客户端更新观众状态
/*
evt.value类如:
{
count: 12
faces:[
"http://xxx.jpg",
"http://xxx.jpg",
"http://xxx.jpg"
]
}
*/
//更新UI
break;
case "START": //有人开始新的一局
/*
evt.value类如:
{
driverNicks: ["nick1", ...],
driverAvatars: ["avatar1.jpg", ...]
}
*/
//此处可以disable 参与按钮,并显示“稍候”。
break;
case "IDLE": //机器空闲
/*
evt.value {} 不关注
*/
//此处可以enable 参与按钮,并显示“参加”。
break;
case "ERROR": //检测到故障
/*
evt.value类如:
{
msg: "当前机器可能出现问题,请退出房间,稍候再进来",
}
*/
//此处可以toast msg。
break;
}
}
...
})
3、关注场景发给自己的事件(后文统称“场景个人事件”):
说明,本通道主要是用来为用户参与游戏服务,用户可以得到类如:COINS、PLAY、CATCH、RESULT 这些事件,其作用见下面相关专题。
ds.event.subscribe("event/uLogic/${selectedItem.scene.uLogicName}/${selectedItem.scene.sceneDID}/${glbAccount.userID}",(evts)=>{
//得到的是数组
for(var i = 0; i < evts.length; i++){
let evt = evts[i];
switch(evt.event){
case "COINS": //客户端更新本地金币数量
glbAccount.coins = evt.value;
//更新UI
break;
case "PLAY": //客户端得到确认消息,机器已经ready
glbRoundInfo.startTs = utc_now();
glbRoundInfo.endTs = glbRoundInfo.startTs + evt.value.gameTime;
//安排下抓定时器,倒计时
//播放"ready,go"的背景声
break;
case "CATCH": //机器侧传回下抓事件
//倒计时停止
//操控界面禁止
break;
case "SUCCESS": //机器传回结果
//弹出成功对话框
//evt.value.reserveTime 指定保留期时间
break;
case "FAIL": //机器传回结果
//弹出失败对话框
//evt.value.reserveTime 指定保留期时间
break;
}
}
...
})
/*
事件数据类如:
[ { event:"evtName",
value:{...} //根据具体情况确定
},
{},
...
]
*/
4、关注场景弹幕事件(统称“弹幕事件”):
弹幕事件就是用来得到当前场景弹幕的,很简单。
ds.event.subscribe(`event/uLogic/danmaku/${selectedItem.scene.sceneDID}`)
//弹幕事件数据也是数组,类如:
[{ bbb:"吾从未看过如此....",
from:"xxx"
},
{ bbb:"啦啦啦😝....",
from:"xxx"
},...
]
5、调用RPC "enter"
ds.rpc.make(
`rpc/uLogic/${selectedItem.scene.uLogicName}/enter`, {
sceneDID: selectedItem.scene.sceneDID,
userID: glbAccount.userID,
nickname: glbAccount.nickname,
avatar: glbAccount.avatar
}, (err, res) => {
if(!err){
/* 得到res类如:
res: {
//该字段用于播放流,若streams只有1路,可以不用显示切换视角按钮
streams:["rtmp://xxx","rtmp://xxx"],
//该字段初始化渲染观众栏
viewer: {
count: 12
faces:[
"http://xxx.jpg",
"http://xxx.jpg",
"http://xxx.jpg"
]
},
//该字段初始化渲染当前司机,若无,参加按钮激活
driver: {
drivers: ["name1"],
driverNicks: ["nick1"],
driverAvatars: ["avatar1.jpg"]
}
}
*/
}
});
1、调用RPC "exit"
ds.rpc.make(
`rpc/uLogic/${selectedItem.scene.uLogicName}/exit`, {
sceneDID: selectedItem.scene.sceneDID,
userID: glbAccount.userID
};
2、取消订阅
//取消订阅弹幕事件
ds.event.unsubscribe(`event/uLogic/danmaku/${selectedItem.scene.sceneDID}`)
//取消订阅本人场景事件
ds.event.subscribe("event/uLogic/${selectedItem.scene.uLogicName}/${selectedItem.scene.sceneDID}/${glbAccount.userID}")
//取消订阅场景事件
ds.event.subscribe(`event/uLogic/${selectedItem.scene.uLogicName}/${selectedItem.scene.sceneDID}`)
调用RPC enter,得到数据:
result.streams 类如:
["rtmp://xxx", "rtmp://xxx"]
初始数据来源:调用RPC enter,得到数据;
result.viewer 类如:
{
count: 100
faces:[
"http://xxx.jpg",
"http://xxx.jpg",
"http://xxx.jpg"
]
}
场景"VIEWER"事件随后也会提供更新该元素的数据。
类如:
[{ event: "VIEWER",
value: {
count: 100
faces:[
"http://xxx.jpg",
"http://xxx.jpg",
"http://xxx.jpg"
]
}
},
...
]
接收:
数据来自于之前订阅弹幕路径:
初版,位置随机放置。
发送:
let rpc_path = `rpc/uLogic/danmaku/fire`;
let rpc_data = {
userID: "xxx",
sceneDID: xxx,
nickname: "xxx",
bb:"哈哈哈😆"
}
ds.rpc.make(rpc_path, rpc_data, (err, ret)=>{
if(!err)
console.log("发送成功");
})
流程:
1、用户进入观众界面后,会调用 enter RPC
2、后台会根据用户信息去中心服务器获取用户最新金币数据,之后会通过场景个人事件"COINS"发送给APP。
3、APP仅通过该方式获得金币数量,在获取之前为零。
4、获取之后,放在全局对象 glbAccount.coins,并渲染UI
参加按钮 有两种状态,
使能状态,其显示“参与”
禁止状态,其显示“稍候”
流程:
1、用户进入观众界面后,会调用 enter RPC,获得结果内有 driver字段则表示当前忙,建议设置按钮为 禁止状态。若无,则反之。
2、之后,场景事件会有 "START" 和 "IDLE"事件,这两个事件分别禁止和使能该按钮。
3、在使能状态下,如果用户金币不足,则提示请充值,再次点击该按钮进入“我的”界面。若金币富余,则进入操作状态流程。
播放器界面下方是游戏详情,界面使用Webview,加载地址为:
...
webView.loadUrl("https://ucast.cc/portal/appclaw/detail.html?jwt="+glbAccount.jwt);
其内部依赖后台接口:
GET /api/v1/appclaw/games/:sceneDID
用户点击 "参与"按钮,实施如下伪代码:
//在本地数据比较金币和费用
if(glbAccount.coin > selectedItem.game.fee){
//调用参与RPC
result = ds.rpc.make(
`rpc/uLogic/${selectedItem.scene.uLogicName}/participate`, {
sceneDID: selectedItem.scene.sceneDID,
userID: glbAccount.userID
},
//结果处理回调(暂时比较简单,只有成功和失败区别,细节再细化)
(err, res) => {
if (err) {
//参与失败
return;
}
//切换到控制方向盘和抓取按钮
//切换到驾驶状态
});
}
类如:error: {code:xx, message:"xxx"}
participate关键返回错误解释:
error: {code:xx, message:"xxx"}
ReturnCode.PARAM_INVALID 参数不合法
ReturnCode.PARAM_ERROR 错误的参数
ReturnCode.NOT_IN_ROOM 不在房间 所以没法参与
ReturnCode.BUSY 司机在忙,无法参与
ReturnCode.NOT_READY 机器准备或者后台条件不足
ReturnCode.LESS_MONEY 没钱玩毛
错误码定义见:
module.exports = {
OK: 0,
PARAM_INVALID: 1, //参数格式错误,不合调用需求
PARAM_ERROR: 2, //错误参数,无法执行
NOT_IN_ROOM: 3, //当前用户不在房间内(有些接口需要保证用户在房间内)
DRIVER_BUSY: 4, //司机开车中
NOT_READY: 5, //哪里条件不允许
LESS_MONEY: 6, //没钱
};
调用participate成功进入操作状态之后,后续过程都会通过 “场景个人事件”推动完成。如下,大致示例:
uLogic为后台游戏逻辑。
额外说明:
1、APP本地倒计时自然结束处理
若,APP本地倒计时自然结束后,禁止控制界面,等候CATCH事件即可。
2、保留期
后端发出(SUCCESS或FAIL)个人事件时,会携带一个reserveTime,单位为秒。
司机得到游戏结果后,建议结果界面保留reserveTime后自动dismiss。
保留期之后,"再次参与"按钮就没有参与优势了。下面详解
APP侧基本约定:
作为观众,收到 START事件,会禁止参与按钮。收到IDLE事件,使能参与按钮。
后端发布游戏结果流程:
1、后端判断当前游戏结果
2、发出个人事件SUCCESS或FAIL给司机
3、等候reserveTime指定时间(保留期)
4、发布场景事件"IDLE"。
可见,对于观众来说,保留期之后才能继续参与游戏。
其实,后台在得知结果之后(保留期之前),就已经准备好被participate。
司机得到结果后(保留期内),会弹出提示图,其中有"再来一局"的按钮,此按钮是司机优先参与的机会(即使背景参与按钮还是禁止状态)。
司机参与之后,后台不再发出IDLE事件。
这就是保留期的原理,它是为司机准备的一个优先参与的时间窗。
建议APP 处理"再来一局"按钮逻辑时,忽略IDLE事件的影响,直接实施Participate流程。
操作界面仅仅替换操控按键和抓取按钮。
!
!
!
!
!
!
我的页采用webview,web页面请求需要添加账户令牌,示例代码:
...
webView.loadUrl("https://ucast.cc/portal/appclaw/index.html#/my/home?jwt="+glbAccount.jwt);
相关文档:
https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1#
预下单过程:
app侧告诉后台想付多少钱,后台从微信侧取得一个“预支付识别标识”,连带着此次支付账单编号,发回给app;
细节:
1、用户在"我的"->"获取金币" 界面选取支付项目,选中10元(1000分)点击充值;
2、webview内JS调用后台https://ucast.cc/api/v1/appclaw/wxpay?fee=1000 接口
3、ucast后台从微信接口获取预支付识别标识prepayid后,连带签名信息,一同返回给前端,数据结构类如:
/* 类如
{
"wxpay_param": {
"appId": "wx2d8...9ce587", //应用APPID
"partnerId": "149...92", //微信支付商户号
"prepayId": "wx20171...70924263783",// <--预支付识别标识
"packageValue": "Sign=WXPay",
"nonceStr": "xWLXur4...1VFDyd8bKk3aHS",
"timeStamp": "151...488",
"sign": "6BA44262...545A56AD1AA4511"
},
"bill_id": "5a082a0...284f6160", // <-- 商户内订单编号
"fee": 1
}
*/
4、通过jsBridge 将该信息交给native代码,后者调用SDK,唤起微信支付界面
//这里是关键代码,将选取的游戏信息提交给native
window.WebViewJavascriptBridge.callHandler(
'onWechatPayPreOrder',
res.body, // <-- 这个就是 上面描述的数据结构
(responseData) => {
console.log(responseData);
}
);
android调起过程详见:
https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_5
支付过程在微信中完成,且后台会得到微信支付后台的通知,并加以记录。
支付确认的过程需要native支持的内容如下:
native须在上面第一条描述的情况下,为webview的URL 额外添加参数字段 bill_id=xxxx
示例:正常情况下(用户点击'我的'),Native打开的webview地址为:
https://ucast.cc/portal/appclaw/index.html#/my/home?jwt=x.y.z
若native从微信支付返回,那么需要打开的webview地址为:
https://ucast.cc/portal/appclaw/index.html#/my/home?jwt=x.y.z&bill_id=xxx
可见,额外多出一个bill_id参数。
如此,webpage可以获知最近有支付行为,并作出相应表现。