20岁的大三男青年,在这个特殊(超长)的寒假入门游戏开发的历程。
本人在校学的是物联网工程,可惜对游戏开发怀着一腔热血,于是在19年年底初次接触微信小游戏的开发(那时候天真,妄想直接用微信开发者工具写),后来在与前辈的聊天中,初识Cocos Creator引擎。从此入坑小游戏开发至今。恰逢前段时间腾讯面向大学生举办了一个2020中国高校计算机大赛-微信小程序应用开发赛,我就进去当混子了…今天写这篇文章记录以及讲解这个项目的基本思路(主要提供开发思路及部分代码),
这个项目主要就是一个2048的改版,玩家控制小飞船来左右移动控制生成小球,并在玩家手指离开屏幕时小球掉落,与场上其他小球进行合成(当小球合成到2048时,消除小球),目前主要是合成玩法(有个在职策划小哥哥推荐把这个项目做成解压+关卡玩法,但由于时间关系,最终没有实现,各位开发者有兴趣的都可以自己试试)。下面为这个项目的小程序码,感兴趣的可以去体验一下,希望为更多入门开发者提供良好思路。这个项目主要就是一个2048的改版,玩家使用小飞船来左右移动控制生成小球,并在玩家手指离开屏幕时小球掉落,与场上其他小球进行合成(当小球合成到2048时,消除小球),目前主要是合成玩法(有个在职策划小哥哥推荐把这个项目做成解压+关卡玩法,但由于时间关系,最终没有实现,有兴趣的可以自己试试)。下面为这个项目的小程序码,感兴趣的可以去试一试(体验一下),希望为更多开发者提供良好思路。
1:微信方面
在和微信开发者工具搭配使用方面主要使用微信的云开发和微信授权登录,用户分享转发等功能,其中,创建全屏登录按钮的部分代码如下。
let width = window.wx.getSystemInfoSync().screenWidth;//获取设备宽屏幕 let height = window.wx.getSystemInfoSync().screenHeight;//获取设备屏幕高 > var button = wx.createUserInfoButton({ type: 'text', text: '', style: { left:0,//距左0 top:0,//距右0 width:width,//按钮宽度 height:height,//按钮高度 } }); button.show();
2:新手教程
新手教程主要用到了一个本地缓存的功能,玩家第一次进入游戏时,判断一个本地缓存数据是否存在(由于玩家首次进入,肯定是不存在的啦),不存在则新手教程开启,当玩家做出了指定操作的时候(玩家手指离开屏幕成功生成一个小球后),往本地缓存写入一个数据,那么,当下次玩家游戏的时候会进行该本地缓存数据判断,这个时候,本地存在该数据,则新手教程关闭,新手教程的节点随之销毁。
2.1:判断是否存在本地缓存数据
maskBtn() { var playerP = cc.sys.localStorage.getItem('oldPlayer');//拿出本地缓存的数据 if(playerP==1){//如果本地缓存的数据存在且为指定数据 this.hand.destroy()//新手教程节点销毁 }else{ this.hand.active=true;//新手教程节点开启(刚开始进入为关闭) } },
2.2:若不存在本地缓存数据,则在touchEnd后,往本地写入数据
onTouchEnd() { this.hand.destroy();//新手指引销毁 if(!playerP){//如果是新用户 var p = 1; cc.sys.localStorage.setItem('oldPlayer',p);//写入数据 }; var playerP = cc.sys.localStorage.getItem('oldPlayer'); console.log(playerP);//打印一下看是否正确 } },
3:飞船移动
在玩家控制飞船的左右移动事件中,主要是开始触摸屏幕,移动以及结束触摸操作,在开始移动时,飞船下生成小球,移动过程中获取触摸点的位置变化然后引起飞船的位置变化,触摸结束将小球重力缩放设置为1(小球会向下掉落),并将掉落的小球绑定到另一父节点上。在这些操作中,可能会存在飞船飞出屏幕左右边缘的情况,于是用一个scene()函数控制飞船处于屏幕内(方法较为简单,有其他好方法建议替换,并告诉我这个小菜鸡一声,谢谢)。
3.1:飞船移动控制
onTouchStart(e) { if (this.state == 1) { // console.log("start"); //记录数值 this.thisNum = this.nextNum.string; //初始化球 this.initBallNode(); // 球赋值 this.ballNode.getComponent('ballNode').updatestr(this.thisNum) //position this.ballNode.setPosition(e.getDelta()); //-44 this.ballNode.y = -44 } }, onTouchEnd() { if (this.state == 1) { //开启重力 this.ballNode.getComponent('ballNode').initGra(2) // console.log("end"); /**数值更新 */ this.updateNum(); //换绑父节点 this.ballNodeFall(); //获取最大的数值 for (var a = 0; a < gNumArr.length; a++) { this.arr = Math.max.apply(null, gNumArr) } // console.log(gNumArr) // console.log(this.arr) if (this.arr == 8) { x = 1 }// 一个小球生成机制 if (this.arr == 16) { x = 2 } if (this.arr == 32) { x = 3 } if (this.arr >= 64) { x = 4 } // console.log(x) this.hand.destroy();//新手指引销毁 if(!playerP){//如果是新用户 var p = 1; cc.sys.localStorage.setItem('oldPlayer',p); }; var playerP = cc.sys.localStorage.getItem('oldPlayer'); // console.log(playerP); } }, onTouchMove(e) { if (this.state == 1) { // console.log("move"); //区域控制 this.scene(); this.plane.setPosition(e.getDelta()); //position this.ballNode.setPosition(e.getDelta()); this.ballNode.y = -44 } },
3.2:scene()函数
scene() { let PX = this.player.position.x; let PY = this.player.position.y; let BX = (cc.find("bg"), this.node).width / 2 - this.player.width / 2; if (PX < -BX) { this.player.runAction(cc.moveTo(0, -BX, PY)); } if (PX > BX) { this.player.runAction(cc.moveTo(0, BX, PY)); } },
4:小球合成
在小球碰撞合成的过程中,我使用的是引擎自带的碰撞组件(强烈不建议),建议各位开发者能自己写碰撞的一定要自己写,由于我自己还是初学者(还很菜,不会自己写碰撞组件),所以就只能用引擎自带的了,这也就导致游戏存在一些问题,比如当场景中小球过多会引起“抖动”,小球合成视觉效果差(可能也和我没有美术有关,哈哈哈哈哈哈哈哈哈),各位开发者可以试着将它做成一种融合效果。言归正传,在这里,是进行判断两个碰撞体上Label节点上的数字是否相等,相等则进行小球合成操作,分数进行累加,小球合成以及消失动画(没有美术,动画有亿点点丑)。然后这里还存在一个小球合成后,小球的大小,颜色要进行更新,以及若合成的小球是场中最大的球,要将这个球的数据保存下来(后续要进行小球生成机制)。
onBeginContact: function (contact, selfCollider, otherCollider) { // console.log('begin') if(otherCollider.node.group == 'default'){ let selfNum = selfCollider.node.getChildByName('thisNum').getComponent(cc.Label) // let self = selfCollider.node.getPosition(); // console.log(self) let otherNum = otherCollider.node.getChildByName('thisNum').getComponent(cc.Label) var selfAnim = selfCollider.node.getComponent(cc.Animation); var otherAnim = otherCollider.node.getComponent(cc.Animation); //小球累加 if(selfNum.string == otherNum.string){ selfNum.string = selfNum.string*2//碰撞球数据更新 // otherNum.string = 0;//被碰撞球数据清零 //分数score var score = cc.find("Canvas/bg/score/scoreLabel").getComponent(cc.Label) this.s=selfNum.string score.string=Number(this.s)+Number(score.string) /*var gameScript = cc.find("Canvas/bg").getComponent('game') gameScript.updateScore(selfNum.string);*/ //最大数据更新/存入最大数据 this.max = Math.max.apply(null,gNumArr) if(Number(this.s)>this.max){ gNumArr.push(Number(this.s)) } /*** 2048销毁*/ if(Number(this.s)==2048){ this.node.destroy(); // boomNum+=1; } //Scale大小,颜色 for(var i = 0;i<gBall.length;i++){ if(selfNum.string==gBall[i].pro){ this.node.setScale(gBall[i].scale) this.node.color=gBall[i].color; } }; cc.audioEngine.playEffect(this.toucheffect,false); //动画播放 selfAnim.playAdditive('self') otherAnim.playAdditive('other') // otherCollider.destroy() setTimeout(() => { otherCollider.node.destroy() }, 300); } } },
5:球大小及颜色的操作
球的大小和颜色这里用一个全局数组进行保存,然后通过小球Label组件上的数据和数组中的pro进行比较,然后获得颜色和大小。
5.1:数组代码
window.gBall=[ {pro:2,color:new cc.Color(129,236,236),scale:0.55}, {pro:4,color:new cc.Color(0, 206, 201),scale:0.6}, {pro:8,color:new cc.Color(122,255,116),scale:0.65}, {pro:16,color:new cc.Color(162,155,254),scale:0.7}, {pro:32,color:new cc.Color(108,92,231),scale:0.75}, {pro:64,color:new cc.Color(255,116,224),scale:0.8}, {pro:128,color:new cc.Color(255,116,160),scale:0.85}, {pro:256,color:new cc.Color(255,116,116),scale:0.9}, {pro:512,color:new cc.Color(255,39,39),scale:0.95}, {pro:1024,color:new cc.Color(16, 172, 132),scale:1.0}, {pro:2048,color:new cc.Color(150,89,60),scale:1.05}, ]
5.2:合成的主要部分
//Scale/color for(var i = 0;i<gBall.length;i++){ if(selfNum.string==gBall[i].pro){ this.node.setScale(gBall[i].scale);//Scale this.node.color=gBall[i].color;//color } };
6:球生成机制
在飞船移动生成小球的时候,存在一定的生成机制(当场上存在8才会生成小球4,存在16才会生成小球8…),即需要判断当前场上存在的最大的小球的Label数据(生成即存放),然后才能随机生成新数据的小球。数据存入时一定存在一个判断,否则数组数据会非常多,不利于小球生成的数据获取。
6.1:数据存入
//最大数据更新/存入最大数据 this.max = Math.max.apply(null,gNumArr)//获取数组最大数据 if(Number(this.s)>this.max){//若最大数据小于当前生成球的数据 gNumArr.push(Number(this.s))//将当前球的数据存入数组 }
6.2:小球生成机制
//获取最大的数值 for (var a = 0; a < gNumArr.length; a++) { this.arr = Math.max.apply(null, gNumArr)//获取数组内最大数据 } // console.log(gNumArr) // console.log(this.arr) if (this.arr == 8) { x = 1 } if (this.arr == 16) { x = 2 } if (this.arr == 32) { x = 3 } if (this.arr >= 64) { x = 4 }
6.3:小球生成
updatestr(str){ // console.log('data') this.Num.string = str for(var a = 0;a<gBall.length;a++){ if(this.Num.string==gBall[a].pro){ this.node.setScale(gBall[a].scale) this.node.color=gBall[a].color; } }; },
使用math函数求得(x+1)的2次方即可完成小球生成机制。
7:分数保存
在死亡(小球超出一定高度)后,游戏会进行分数保存,将用户所得的分数保存到本地并显示当前用户的最高分,这里不做过多解释,就是一个数据保存以及基本的判断。
saveScore() { //当前分数 var currentScore = this.score.string; var scoreData = { 'score': currentScore }; var preData = cc.sys.localStorage.getItem('score');//获取数据 var highScore = cc.sys.localStorage.getItem('topScore');//获取数据 if (!highScore || parseInt(highScore) < parseInt(currentScore)) {//若不存在或最高分小于当前分数 cc.sys.localStorage.setItem('topScore', currentScore);//存入数据 } if (!preData) { preData = []; preData.unshift(scoreData); } else { preData = JSON.parse(preData); if (!(preData instanceof Array)) { preData = []; } preData.unshift(scoreData); } cc.sys.localStorage.setItem('currentScore', currentScore);//存入 cc.sys.localStorage.setItem('score', JSON.stringify(preData));//存入 console.log(preData) console.log(highScore) if (highScore < currentScore) { highScore = currentScore; } //最高分 this.topScore.string = highScore; preTopScore = highScore; //当前分 this.nowScore.string = currentScore; },
8:道具
最后一个也就是我们的道具,炸弹道具,道具的主要功能即当小球高度偏高时销毁屏幕中所有的小球,功能较为简单(也可以加入其他道具,如万能小球(可以与任意球合成),扔掉当前小球(随机新生成一个小球)),在这里程序部分也比较简单,即销毁其中一个节点下的所有子节点。由于这里我没有其他的炸弹生成机制,因此,道具只存在1个或0个的情况,如果想把生成2048后获得一个炸弹,这里可以做成自加自减的方式。
boomTouch() { if (this.state == 1) { if (boomNum > 0) { console.log('boom') boomNum = 0; // boomNum-=1; this.ballNodeDown.destroyAllChildren() } else { this.videoPlay() console.log('视频广告') } } },
以上就是这个项目的基本实现了,也算是研发小白交的一个作业吧!