结对编程作业
| 姓名 | 大体方向 | 具体负责 |
|---|---|---|
| 陈硕 | 前端 | 原型图的设计,美工相关,以及人机对战模块 |
| 林泽熙 | 后端 | 后端相关,负责接口的调用,在线对战,人人对战模块 |
一、原型设计
- 原型作品的链接:https://pxc9yd.axshare.com
1.对该原型的设计说明
- 使用的原型开发工具:Axure Rp9,Axure RP是美国Axure Software Solution公司旗舰产品,是一个专业的快速原型设计工具,能够快速创建应用软件或Web网站的线框图、流程图、原型和规格说明文档,我使用Axure进行猪尾巴游戏的原型设计,Axure相对于墨刀对新手较为友好,可以较为简单的做出高仿真原型图,所以我选择Axure Rp9进行原型设计。
- 功能描述:
(1)首先是猪尾巴游戏的开始界面,点击开始游戏后可以选择游戏模式。

(2)在游戏模式选择界面,选择对应的模式(有在线,人人,人机三种模式)就可以进入对应的界面

(3)人人对战界面,能够实现出牌,翻牌功能,点击牌(以方块2为例)会上滑,点击取消会取消上滑;点击出牌,会将改牌打出到屏幕中央,判断该牌与置牌区第一张卡花色是否相同,根据规则将牌放入对应的区域,点击退出房间返回模式选择界面。

点击方块2,该牌上滑

点击出牌,方块2与置牌区第一张牌花色不同,故将方块2放入置牌区顶部

点击翻牌,翻出黑桃6,与置牌区梅花不同,将该牌放入置牌区顶部

点击退出房间,返回模式选择界面。

(4)人机对战界面,功能与人人对战界面一样,具备出牌,翻牌,判断,退出房间功能,人机对战界面如下。

(5)在线对战界面,功能与人人对战界面类似,具备出牌,翻牌,判断,退出房间功能,在线对战界面如下。

点击梅花10,梅花10上滑

点击出牌,梅花10先被打到页面中央,接着判断与置牌区花色相同,故吃牌,即将置牌区的牌和翻出来的牌移动到手牌区。

点击翻牌按钮,判断结果如下

点击退出房间,返回到模式选择界面

2.遇到的困难及解决方法
- ①找不到好看的棋牌图片背景资源
解决过程:通过在百度,b站上的搜索,找到了一些可以下载高清图片资源的网站,通过这些网站,下载背景图片。至于扑克牌图片实在找不齐一副,就自己利用axure和画图工具做了一副。
收获:知道了一些较为常用的图片下载网站,学会了如何获得想要的资源 - ②完全不熟悉axure的操作,刚开始连原型图的概念都不清楚
解决过程:通过看视频,看博客学习。
收获:知道了什么是原型图,掌握了axure的基础,学会了axure的基本操作,并使用axure制作了猪尾巴的原型图。 - ③没学过TS编程,虽然和java类似,但是在cocos的类的所有类和方法都没有学过
解决过程:首先先看一点讲解cocos的编程视频,然后先尝试做一下基本的项目,比如控制节点的移动等,一步步熟悉cocos的运行流程
二、原型设计实现
1.代码实现思路
-
网络接口的使用
根据作业的接口文档设定以下xhr请求接口- 创建一局游戏
![图片名称]()
- 加入一局游戏
![图片名称]()
- 发送抽牌请求
![图片名称]()
- 发送出牌请求
![图片名称]()
- 请求上一次操作信息
![图片名称]()
- 创建一局游戏
-
代码组织与内部实现设计
![图片名称]()
-
算法的关键与关键实现部分流程图
由于学习ts和cocos的时间关系,尽力完成了人人,在线,人机三种模式设计,没有去想做一个好的AI算法,所以这里只给出了一种最普通的AI出牌思路
即优先打光手牌,然后其次选择抽牌
![图片名称]()
AI代码
function ai(p1now,p2now,putnow,node:cc.Node){ //如果放置区有牌就判断是否手上有可以出的牌 if(putnow.length!=0){ var top:string= putnow[putnow.length-1] top=top[0] var list=['S','H','D','C'] if(p2.length!=0){ var max=0 var chosen='' //遍历花色和手牌,得到最多数量的非顶端牌色 for(var i=0;i<4;i++) { var num=0 for(var j=0;j<p2.length;j++){ if(p2[j][0]==list[i]&&list[i]!=top){ num++ } if(num>max){ max=num chosen=list[i] } } } //如果没有可以出的牌就抽牌然后返回 if(chosen==''){ var temp=cards.pop() put.push(temp) node.getChildByName(temp).setPosition(xp,0,0) node.getChildByName(temp).setSiblingIndex(index) index++ xp+=8 var flag=eat('1',node) paixu('1',node) if (flag==1){ put=[] } node.getChildByName('抽牌').active=true paixu('1',node) return } //如果没有返回则说明有要打的牌 //遍历手牌打出最先遍历到的对应花色牌 for(var i=0;i<p2.length;i++){ if(p2[i][0]==chosen){ put.push(p2[i]) node.getChildByName(p2[i]).setPosition(xp,0,0) node.getChildByName(p2[i]).setSiblingIndex(index) index++ xp+=8 p2.splice(i,1) var flag=eat('1',node) paixu('1',node) if (flag==1){ put=[]} node.getChildByName('抽牌').active=true paixu('1',node) break } }} //如果手牌没牌就直接抽牌 else{ var temp=cards.pop() put.push(temp) node.getChildByName(temp).setPosition(xp,0,0) node.getChildByName(temp).setSiblingIndex(index) index++ xp+=8 var flag=eat('1',node) paixu('1',node) if (flag==1){ put=[] } node.getChildByName('抽牌').active=true paixu('1',node) }} //如果put区没牌则判断 else{ //没手牌就抽牌 if(p2.length==0){ var temp=cards.pop() put.push(temp) node.getChildByName(temp).setPosition(xp,0,0) node.getChildByName(temp).setSiblingIndex(index) index++ xp+=8 var flag=eat('1',node) paixu('1',node) if (flag==1){ put=[]} node.getChildByName('抽牌').active=true paixu('1',node)} //如果有手牌就打第一张 else { put.push(p2[0]) node.getChildByName(p2[0]).setPosition(xp,0,0) node.getChildByName(p2[0]).setSiblingIndex(index) index++ xp+=8 p2.splice(0,1) var flag=eat('1',node) paixu('1',node) if (flag==1){ put=[]} node.getChildByName('抽牌').active=true paixu('1',node) } } } -
重要的代码片段
吃牌函数function eat(p:string,node:cc.Node){ //当最顶层两张牌色相同判定吃牌,把put区所有牌全部加入p手牌中。 if((put.length>=2&&put[put.length-2][0]==put[put.length-1][0])){ if(p=='1') var y=225 else var y=-225 for (var i=0;i<put.length;i++){ node.getChildByName(put[i]).setPosition(0,y) if(p=='1') p2.push(node.getChildByName(put[i]).name) else p1.push(node.getChildByName(put[i]).name) } xp=-60 console.log('吃牌啦') return 1 } }整理手牌函数
function paixu(p:string,node:cc.Node) { var x=-130 //首先判断是那个玩家,然后根据花色放置位置,同时设置越后放进的卡牌显示等级越低 //保证最上层的卡牌可以完全显示,越后放进的卡牌y的值会递增或递减 if(p=='1'){ var y0=225;var x0=-90; var y1=225;var x1=-30; var y2=225;var x2=30; var y3=225;var x3 =90; for (var i=0;i<p2.length;i++){ node.getChildByName(p2[i]).setRotation(180) if(p2[i][0]=='S'){ node.getChildByName(p2[i]).setPosition(x0,y0) node.getChildByName(p2[i]).setSiblingIndex(-i-1) y0+=9 }else if(p2[i][0]=='C'){ node.getChildByName(p2[i]).setPosition(x1,y1) node.getChildByName(p2[i]).setSiblingIndex(-i-1) y1+=9 } else if(p2[i][0]=='H'){ node.getChildByName(p2[i]).setPosition(x2,y2) node.getChildByName(p2[i]).setSiblingIndex(-i-1) y2+=9 }else if(p2[i][0]=='D'){ node.getChildByName(p2[i]).setPosition(x3,y3) node.getChildByName(p2[i]).setSiblingIndex(-i-1) y3+=9 } }} else if(p=='0') {var y0=-225;var x0=-90; var y1=-225;var x1=-30; var y2=-225;var x2=30; var y3=-225;var x3 =90; for (var i=0;i<p1.length;i++){ if(p1[i][0]=='S'){ node.getChildByName(p1[i]).setPosition(x0,y0) node.getChildByName(p1[i]).setSiblingIndex(-i-1) y0-=9 }else if(p1[i][0]=='C'){ node.getChildByName(p1[i]).setPosition(x1,y1) node.getChildByName(p1[i]).setSiblingIndex(-i-1) y1-=9 } else if(p1[i][0]=='H'){ node.getChildByName(p1[i]).setPosition(x2,y2) node.getChildByName(p1[i]).setSiblingIndex(-i-1) y2-=9 }else if(p1[i][0]=='D'){ node.getChildByName(p1[i]).setPosition(x3,y3) node.getChildByName(p1[i]).setSiblingIndex(-i-1) y3-=9 } }} }抽牌函数
draw1(){ var self=this var temp=cards.pop() put.push(temp) self.node.getChildByName(temp).setPosition(xp,0,0) self.node.getChildByName(temp).setSiblingIndex(index) index++ xp+=8 var flag=eat('0',self.node) paixu('0',self.node) if (flag==1){ put=[]} //切换 this.node.getChildByName('抽牌2').active=true this.node.getChildByName('抽牌').active=false paixu('0',self.node) }出牌函数
put1() { for(var i=0;i<this.node.childrenCount;i++) { if(this.node.children[i].y==-175){ var temp=this.node.children[i].name put.push(temp) this.node.getChildByName(temp).setPosition(xp,0,0) this.node.getChildByName(temp).setSiblingIndex(index) index++ xp+=8 var indexx=p1.indexOf(temp) p1.splice(indexx,1) var flag=eat('0',this.node) paixu('0',this.node) if(flag==1){ put=[] } //切换为对方玩家操作 this.node.getChildByName('抽牌2').active=true this.node.getChildByName('抽牌').active=false this.node.getChildByName('出牌').active=false } } } -
性能分析与改进
提交的代码多是前端内容,性能提升空间较少,原本设计的游戏逻辑是分为玩家1手牌区,玩家2手牌区,放置区,卡组区,四个节点单独存放卡牌信息
但是发现当获取节点的层次越多,越容易出现逻辑正确,但是无法获取对应节点的bug(可能是其他问题,但是我无法找到原因),最后改为全部卡牌信息放在一个节点下。 -
展示性能分析
运行人机对战时性能
- 描述你改进的思路(2分)
从原本全部依靠节点存储卡牌子节点,但是频繁处理多层节点的改动极易出现错误,只能降低代码的质量,利用全局数组代替节点存储数据,
2.Github代码签入记录
- 我们之前都是在cocos上调试,然后发布到到微信小游戏,所以只有一个commit记录,后续会改进在线对战功能
![]()
3.遇到的代码模块异常或结对困难及解决方法
-
困难① for循环设置节点显示等级,会出现一个节点显示异常(等级低节点优先显示与高等级节点)
![图片名称]()
![图片名称]()
解决方法①
设置setSiblingIndex参数-i改为-i-1
![图片名称]()
![图片名称]()
收获①
很奇怪的bug,解决完后没时间去细想了,当时只是怀疑是不是设置了特定的数字是最上层显示的就改了一下,但是想想逻辑也不对,但是解决了就不想再管了,
所以说有问题多尝试!这种东西百度都百度不出来,真的是看了很久找不到原因在哪里,借此例子想说明cocos的坑真的太多了 -
困难②
基本实现三个对战后,希望可以在结束后重新加入对局,但是尝试了各种重启游戏的方法,比如重新加载场景,cc.game.restart()方法等,但是都不能实现
解决方法②
希望使用者能重新打开游戏吧
收获②
cocos的很多内容在网络上都找不到答案,如果以后有类似的游戏需求一定要买一本书来研究 -
困难③
cocos的ts编程的一堆坑,仅举一部分例子:
1。 cocos执行代码并不一定是按顺序执行的,其中要考虑很多比如节点的移动动作,周期回调函数的重复执行等,极容易出现异步导致各种奇奇怪怪的bug,比如存在子节点数目但是却找不到子节点。for循环执行同一个操作但是出现各种例外
2。一个变量被赋值一个全局变量后对变量的修改也会传递给全局变量(我猜测是这个原因后面换方法了),导致不能直接函数传参
3。 很多方法执行后没有效果,换一个效果类似的方法后又可正常执行(比如用cc.moveto()和node.setRotation设置节点旋转,前者我用了就没效果)
4。 xhr异步请求接口的时候由于没有设置监听状态码变化等导致疯狂出错。
5。 f12debug时仅显示函数内报错行 不显示对应执行函数的位置,精确定位bug很难。只能不断添加console.log寻找报错位置
6。 学校VNP断开后重连费时,浪费了很多时间
解决过程③:
没有可以请教的人,只能百度,试验别的方法,尽力而为
收获③
明白了cocos的基本运行流程和一些常用方法,学习了ts接口的使用等新知识,虽然很难很累,但是学到了很多新东西,也增长了自己的自学能力
4.评价你的队友
- 对林泽熙的评价:巨能肝,经常熬夜到半夜两三点,对解决问题有着超出常人的执著,学习能力强,能够很快的掌握新语言的用法。
- 对陈硕的评价:美工单人小组,学新东西欲望强烈但没有我能肝,希望之后他能在团队作业中带着我混,
5.结对作业PSP和学习进度条(两人累计时长)
| 第N周 | 新增代码(行) | 累计代码(行) | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 |
|---|---|---|---|---|---|
| 1 | 0 | 0 | 20 | 20 | 了解了原型图的相关概念,初步学习了如何使用axure做出一个原型图,了解微信小游戏制作的相关知识,发现可以用cocos写游戏,最后发布到微信小游戏 |
| 2 | 0 | 0 | 40 | 60 | 学习cocos相关知识,学习typescript相关语法 |
| 3 | 300 | 300 | 40 | 100 | 开始coding,解决coding中遇到的各种困难,学会了接口的使用,cocos场景,结点的构建,动态结点的创建 |
| 4 | 800 | 1100 | 50 | 150 | 开始coding,写博客, |
三、心得
-
陈硕的心得:
-
作业难度:巨难
-
作业感想:
首先是原型图的构建,这部分的实现相对较为简单,做出一个原型图不难,难点在于做出好看的高仿真原型。其次是关于前端的工作,先是学习了一周Typescript,然后开始coding,在出牌发牌的逻辑代码实现卡了壳,各种查询资料也做不出来,后来只好放弃。 -
学习启发:
要在平时多多努力,才能学到足够多的知识来解决工作中的难题
-
-
林泽熙的心得:
-
作业难度:
好难都不会 -
作业感想
这次作业先确定了做微信小程序方向,结果看了半天小程序做法才发现用游戏引擎做简便很多,中途改道用cocos做,coding时间很赶,还要学习新知识,TS编程和cocos的用法都是完全陌生的,同时本次作业编程又需要较大的代码量,所以花费了非常久的时间,天天熬夜做进度也不大,最后两三天才做出一个不完善的粗制品(没有实现托管功能,AI智能度为0),但是我作为一个编程彩笔能够最后做出一个能摆的出来的作品还是不枉两周的天天熬夜,希望在之后的学习生活中能多多遇到 这种富有挑战性的任务,来逼迫自己提升。总而言之,本次结对作业既让我痛苦,也让我收获颇丰。
在线对战还有很多功能待完善,接口的使用不是很熟悉,希望再接再厉 -
学习启发
百度不是万能的,要多多看书学习新知识,相信努力就有回报
-












浙公网安备 33010602011771号