@wsd1
2017-11-12T11:20:15.000000Z
字数 10008
阅读 1435
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 methodwindow.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: 12faces:["http://xxx.jpg","http://xxx.jpg","http://xxx.jpg"]}*///更新UIbreak;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;//更新UIbreak;case "PLAY": //客户端得到确认消息,机器已经readyglbRoundInfo.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: 12faces:["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: 100faces:["http://xxx.jpg","http://xxx.jpg","http://xxx.jpg"]}
场景"VIEWER"事件随后也会提供更新该元素的数据。
类如:
[{ event: "VIEWER",value: {count: 100faces:["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){//调用参与RPCresult = 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,唤起微信支付界面
//这里是关键代码,将选取的游戏信息提交给nativewindow.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可以获知最近有支付行为,并作出相应表现。