大概第四天了,开始大屏端的开发。
大屏端也是分为3个场景 enroll(扫码场景) game(游戏场景显示蜗牛) ranking(结束后排行榜)
扫码节点怎么创建呢?
创建一个sprite(精灵)渲染节点。默认情况下,精灵节点会有一个默认的sprite Frame ,我把他删除(在属性检查器中操作),为什么删除呢?因为二维码是后台动态生成的。
脚本怎么写呢?在enroll.js 的onload 中写如下代码:
//生产二维码
cc.loader.load({url: code_url, type: 'png'}, function (err, texture) {
// Use texture to create sprite frame
var frame=new cc.SpriteFrame(texture);
cc.director.getScene().getChildByName('Canvas').getChildByName('scan').getChildByName('code').getComponent(cc.Sprite).spriteFrame = frame;
});
说明: code_url 是全局变量 由php渲染index.php创建
接下来看看game 场景怎么开发?
因为蜗牛 是动态生成的,所以要创建预制资源 我创建了 snail01, snail02, snail03 ....snail10。为什么要创建10个预制资源呢因为,每种蜗牛的颜色不同,使用的贴图不一样。每个蜗牛预制资源都一个组件蜗牛动画组件。关于动画组件参考官网的视频教程就可以做出来了。
关于显示头像和昵称的预制资源制作 比蜗牛稍微复杂点。 难点如: 头像是 圆形的,怎么解决用到了 mark 类型节点, 头像,昵称也是动态从后台生成的。
说明: head是空节点类型 mark 是mark类型节点, img 是sprite(精灵)类型节点 但要删除默认sprite Frame贴图,nickname是Lable 类型节点。注意img 的宽高摇等于img的宽高,或者略大一两个像素。
game.js脚本怎么写? 如下:
createSnail : function (wxOpenid,wxNickname,wxHeadimgurl) {
var whichColorSnail = this.getColorSnail();
cc.log('whichColorSnail:'+whichColorSnail);
//并记录下颜色选择
gamedata.cameScene[wxOpenid] = whichColorSnail;
gamedata.snailPrefabUse[whichColorSnail] = 1; //标记为1
gamedata.cameScene[wxOpenid] = whichColorSnail;
gamedata.ingScene[wxOpenid] = cc.instantiate(eval('this.'+whichColorSnail));
gamedata.ingHead[wxOpenid] = cc.instantiate(this.headPrefab);
gamedata.ingHead[wxOpenid].getChildByName('nickname').getComponent(cc.Label).string=wxNickname;
cc.loader.load(wxHeadimgurl, function (err, texture) {
// Use texture to create sprite frame
cc.log('create logo');
var frame=new cc.SpriteFrame(texture);
gamedata.ingHead[wxOpenid].getChildByName('mark').getChildByName('img').getComponent(cc.Sprite).spriteFrame = frame;
//gamedata.ingHead[wxOpenid].getChildByName('mark').getChildByName('img').getComponent(cc.).spriteFrame = frame;
});
this.node.addChild(gamedata.ingScene[wxOpenid]);
this.node.addChild(gamedata.ingHead[wxOpenid])
//gamedata.ingScene[wxOpenid].children[0].string = '李四';
return gamedata.ingScene[wxOpenid];
},
说明: cc.instantiate(eval('this.'+whichColorSnail)); 我怀疑 使用 this[whichColorSnail] 也是可以的。
接下来的就是 ranking场景了。其中头像 节点创建参考 game场景中头像节点创建。区别是 排行榜中节点我是直接作为game.js的属性,不像game是动态this.node.addChild添加的。
enroll.js 中onload中代码如下:
onLoad () {
var self = this;
//设置游戏状态
var state_data = {game_name:'shake',type:'gameState',state : 3}
WebSocketManager._wsObj.send(JSON.stringify(state_data));
var finalPlayerArr10 = gamedata.sortedPlayerArr.slice(0,10);
cc.log('in ranking');
cc.log('finalPlayerArr10:'+JSON.stringify(finalPlayerArr10));
var ranking_num = 0;
var nameArr = ['first','second','third','forth','fifth','sixth','seventh','eighth','ninth','tenth'];
for(var i=0; i< finalPlayerArr10.length ; i++){
//异步加载
cc.loader.load(finalPlayerArr10[i].wxHeadimgurl, function (err, texture) {
// Use texture to create sprite frame
var frame=new cc.SpriteFrame(texture);
self[nameArr[ranking_num]].spriteFrame = frame;
cc.log('which'+nameArr[ranking_num]);
ranking_num++;
});
//显示分数
self[nameArr[i]+'Score'].string = finalPlayerArr10[i].score;
//显示名称
self[nameArr[i]+'Name'].string = finalPlayerArr10[i].wxNickname;
// //显示蜗牛 不显示了
// var finalSnail = this.createFinalSnail(finalPlayerArr10[i].wxOpenid,finalPlayerArr10[i].wxNickname);
// finalSnail.setPosition(this.getFinalPosition(finalPlayerArr10[i].wxOpenid));
}
说明: 为什么不用 i代替ranking_num 呢?确实刚开始我是用i的但是经测试我发现第一名总是显示到第二名的位置。所以推测
cc.loader.load是异步加载的,所以不能用i ,我就定义一个ranking_num变量。
接下来说说game.js中业务逻辑 ,蜗牛怎么更新实时的位置? 这个就属于纯纯的业务逻辑了,看博客的朋友可以不用看下面的内容!
逻辑要求: 编号1 的跑道 是第一名的跑道,编号2 是第二名 ,编号3是第三名,以此类推,总共 10个跑道显示前十名。那是不是就能10个人玩呢,也不是,可以同时超过十个的人玩。比如100个人,大屏幕上只显示前十名,0.5秒间隔重新计算实时排名。假如我是第十三名,再次计算我是第九名了,就能显示在编号9的跑道位置。
接下来要解决的是移动蜗牛到新跑道问题。有人说,那不简单吗,0.5 时,把原来的屏幕上的蜗牛全部销毁,再重新生成前十名的蜗牛。我认为此方式不可取原因有二。一: 销毁,再重建,耗资源,也许10秒,8秒内总是这十个人时前十名呢,太浪费资源了。二:感官上不友好,玩家本来是第一名,突然闪现到第九名,显然对玩家体验太差。
这块逻辑比较绕,所以先写写伪代码,思路理清了,再写代码不迟!
//1。保存老的前十名的玩家,留着分析 old -10; 并且创建oldPlayerArr10 和 一个标记对象isDealOldPlayer10
//2。对sortedPlayerArr 重新排列 取出前10 new-10; 并创建newPlayerRanking10
//3。从老old -10开始for分析 如果 不在ingScene ;在 isDealOldPlayer10 中del这个属性 处理
// 如果在ingScene; 不在new -10 进行节点销毁,ingScene更新
// 在 new-10中,获得新的位置,并移动到新位置
//4。从new-10开始for分析,
// 在old中的进一步在看是否处理过;已经处理过的1就不管了end; 没有处理过的(可能只有一种情况就是3。中在old new 都存在但是ingScene中)没有. 这个作为新点来处理 和下面处理方式一样(创建新的snailPrefab,ingScene更新)
// 不在old中 创建新的snailPrefab
说明: 3.步其实还是有很多细节的,一开想着就是 for遍历,首先处理第一名,接着是第二名。。。但是有一种情况可以想象的到。假如。 第一名新位置 是第10名。那么 处理完第一名。第一名变到新位置10 和 原来第十名同时存在一个跑道上!!!。接下来处理第二名假如新位置是第9名。那么处理完第二名,编号9跑道也是同时存在两个蜗牛!!!。 分析到这里大家都明白了吧,不能这么处理!
比较合适的方式是:处理完第一名,假如新位置是 10 ,那么接下来处理 old 第十名 ,看新位置假如是 4,接下来再处理old第四名.....明白了吧。到什么情况下终止处理呢?就是当前处理的老蜗牛,一新位置和老位置一样,二,新的名次已经在前十名之外了。
有人说 ,傻呀,就不能让10只蜗牛同时移动到新位置,或没有位置的就销毁。的确这种方式更合理些。但是我还没有找到同时执行那些代码的方法,如果大神知道这种方法,烦请告诉我,谢谢!
下面重点分析下第3步的代码。通过以上伪代码,隐约要递归方式(特点是 会循环,不知道具体的循环次数,内容类似),但是怎么写呢?还是比较困难的,先不管了,我先写第一名的处理,不考虑递规步递规问题。代码如下:
for(var c=0;c < oldPlayerArr10.length ; c++) {
// if(! oldPlayerArr10[c].wxOpenid in gamedata.ingScene){
// continue;
// }
//
// if (isDealOldPlayer10[oldPlayerArr10[c].wxOpenid] == 1) {
// continue;
// }
//
// if(! oldPlayerArr10[c].wxOpenid in newPlayerRanking10 ){ //节点销毁,ingScene更新,标记为已处理
// gamedata.ingScene[oldPlayerArr10[c].wxOpenid].node.destroy();
// delete gamedata.ingScene[oldPlayerArr10[c].wxOpenid];
//
// }else { //在new-10中
// gamedata.ingScene[oldPlayerArr10[c].wxOpenid].runAction(cc.moveTo(2,this.getSnailPosition(oldPlayerArr10[c].wxOpenid)));
// //在新位置上需要动的点
//
// if(oldPlayerRanking10[oldPlayerArr10[c].wxOpenid] == newPlayerRanking10[oldPlayerArr10[c].wxOpenid]){ //名次没有变化,跑道没有变化
//
// }else{
// var nextTrack = newPlayerRanking10[oldPlayerArr10[c].wxOpenid];
// //查看nextTrack跑道是否有 老蜗牛,没有则end 若有则开始处理它
// oldPlayerRanking10
// var nextWxOpenid = this.getWxOpenidByTrack(nextTrack,oldPlayerRanking10);
//
// if(nextWxOpenid != ''){
// //在new-10中
// //不在new-10中
// }
//
// }
// }
//
// isDealOldPlayer10[oldPlayerArr10[c].wxOpenid] = 1; //标记为已处理
}
写到if(nextWxOpendi != '')....就发现是时候改成递规了。
continue---> return false ; 把 newPlayerRanking10, oldPlayerRanking10 等这些改为参数:改好的递规函数如下:大家如果遇到写递规时,有困难也可以采取这种间接方式,把第一个过程写下来然后再改为递规的方式。
recurrenceDealOldPlayer: function (wxOpenid, newPlayerRanking10, oldPlayerRanking10,isDealOldPlayer10,ingScene_tmp) {
cc.log('in recurreceDealOldPlayer:');
// cc.log('in gamedata.ingScene:');
// cc.log(ingScene_tmp);
cc.log('wxOpenid:'+wxOpenid);
var aa = wxOpenid in ingScene_tmp;
cc.log('wxOpenid in ingScene_tmp:'+aa);
if(!( wxOpenid in ingScene_tmp)){
return false;
}
if (isDealOldPlayer10[wxOpenid] == 1) {
return false;
}
cc.log('ingScene_tmp【wxopenid]:'+ ingScene_tmp[wxOpenid]);
if(! (wxOpenid in newPlayerRanking10) ){ //节点销毁,ingScene更新,标记为已处理
var deadNode = ingScene_tmp[wxOpenid];
delete ingScene_tmp[wxOpenid];
cc.log('nodeDestroy():');
//deadNode.node.destroy();
deadNode.destroy();
//更新snailPrefabUse 中值
gamedata.snailPrefabUse[gamedata.cameScene[wxOpenid]] = 0;
//销毁logo节点
var logoNode = gamedata.ingHead[wxOpenid];
delete gamedata.ingHead[wxOpenid];
cc.log('logonodeDestroy():');
//logoNode.node.destroy();
logoNode.destroy();
cc.log('2222isDealOldPlayer10:'+JSON.stringify(isDealOldPlayer10));
isDealOldPlayer10[wxOpenid] = 1; //标记为已处理
}else { //在new-10中
ingScene_tmp[wxOpenid].runAction(cc.moveTo(config.runActionTime,this.getSnailPosition(wxOpenid)));
cc.log('2222111isDealOldPlayer10:'+JSON.stringify(isDealOldPlayer10));
isDealOldPlayer10[wxOpenid] = 1; //标记为已处理
if(oldPlayerRanking10[wxOpenid] == newPlayerRanking10[wxOpenid]){ //名次没有变化,跑道没有变化
return false;
}else{
//名次发生变化才需要变更head位置
gamedata.ingHead[wxOpenid].runAction(cc.moveTo(config.headRunActionTime,this.getLogoPosition(wxOpenid)));
var nextTrack = newPlayerRanking10[wxOpenid];
//查看nextTrack跑道是否有 老蜗牛,没有则end 若有则开始处理它
var nextWxOpenid = this.getWxOpenidByTrack(nextTrack,oldPlayerRanking10);
if(nextWxOpenid != ''){
//在new-10中
//不在new-10中
this.recurrenceDealOldPlayer(nextWxOpenid,newPlayerRanking10,oldPlayerRanking10,isDealOldPlayer10,ingScene_tmp);
}
}
}
},
至此,大屏端的主要逻辑已经写完了,下一篇说说微信扫码,php 渲染index.php(cocos打包后是index.html),与php socket服务,整个流程是怎么样的,怎样衔接的。
未完待续。。。。。