2023年夏季《移动软件开发》实验报告
本实验属于哪门课程? | 中国海洋大学23夏《移动软件开发》 |
实验名称? | 实验4:拼图游戏 |
博客地址? | m0_63773979_-CSDN博客 |
Github仓库地址? | XXXXXXX |
(备注:将实验报告发布在博客、代码公开至 github 是 加分项,不是必须做的)
一、实验目标
1、综合应用所学知识创建完整的拼图游戏项目;2、熟练掌握<canvas>组件。
二、实验步骤
1.首先按照快速启动模板创建小程序的步骤得到一个小程序模板
2.创建页面文件并删除和修改其他文件
将app. json文件内 pages属性中的“pages/logs/logs”改成"pages/game/game", 按快捷键Ctrl+S保存当前修改后会在page文件夹下自动 game目录。
删除 utils文件夹及其内部所有内容。 删除 pages文件夹下的 logs目录及其内部所有内容。 删除 index. wxml和 index. wxss中的全部代码。 删除 index. js中的全部代码,并且输入关键词“page”找到第二个选项按回车键让其自动补全函数
删除 app. wxss中的全部代码。 删除 app. js中的全部代码,并且输入关键词“app”找到第一个选项按回车键让其自动补全函数
还需要创建一个images文件夹用于存放关卡图片
3.视图设计
(1)导航栏设计
在app.json中自定义导航栏标题和背景颜色,文件代码如下
"window":{ "backgroundTextStyle":"light", "navigationBarBackgroundColor": "#E64340", "navigationBarTitleText": "拼图游戏", "navigationBarTextStyle":"white" },
上述代码可以更改所有页面的导航栏标题为“拼图游戏”、背景颜色为珊瑚红色,字体为白色
(2)页面设计:页面主要包括3个区域
1)公共样式设计:首先在app.wsxx中设置页面窗口和顶端标题的公共样式,代码如下:
.container{ height:100vh; color:#E64340; font-weight: bold; display:flex; flex-direction: column; align-items:center; justify-content:space-evenly; } .title{ font-size:18pt; }
2)首页设计:首页主要包含两部分内容,即标题(使用view容器)和关卡列表(view容器,内部使用循环数组),WXML代码如下:
<view class='container'> <!--标题--> <view class='title'>游戏选关</view> <!--关卡列表--> <view class='levelBox'> <view class='box'> <image src='/image/pic01.jpg'></image> <text>第一关</text> </view> </view> </view>
WXSS代码如下:
.levelBox{ width:100%; } .box{ width:50%; float:left; margin:25rpx 0; display:flex; flex-direction: column; align-items: center; } image{ width:260rpx; height:260rpx; }
效果图如下:
3)游戏页面设计:游戏页面需要用户点击首页的关卡列表,然后在新窗口打开该页面口,游戏页面包括游戏提示图,游戏画面和”重新开始“按钮
由于暂时没有做点击跳转的逻辑设计,所以可以在开发工具顶端选择”普通编译“下的”添加编译模式“,并携带临时测试参数level=pic01.jpg
此时预览就可以直接显示game页面了,设计完毕后再改回”普通编译“模式即可重新显示首页
计划使用如下组件:
<view>:整体容器和顶端标题
<image>:提示图
<canvas>:游戏画布
<button>:”重新开始''按钮
WXML代码如下:
<view class='container'> <!--提示图区域--> <view class='title'>提示图</view> <image src='/images/pic01.jpg'></image> <!--游戏画布--> <canvas canvas-id='myCanvas'></canvas> <!--“重新开始”按钮--> <button type='warn'>重新开始</button> </view>
WXSS代码如下:
image{ width:250rpx; height:250rpx; } canvas{ border:1rpx solid; width:300px; height:300px; }
当前效果图如下
4.逻辑实现
(1)首页逻辑
首页主要有两个功能需要实现,一个是展示关卡列表,二是点击图片就能够跳转到游戏页面
1)关卡列表展示:在JS文件的data中录入关卡图片的数据信息,相关的JS代码如下
data: { levels:[ 'pic01.jpg', 'pic02.jpg', 'pic03.jpg', 'pic04.jpg', 'pic05.jpg', 'pic06.jpg' ] },
接着为关卡对应的<view>组件添加wx:for属性循环显示关卡列表数据和图片,修改后的WXML代码如下:
<view class='box' wx:for='{{levels}}'wx:key='levels{{index}}'> <image src='/image/{{item}}'></image> <text>第{{index+1}}关</text>
效果图如下:
2)点击跳转游戏页面:若希望用户点击关卡图片即可实现跳转,首先需要为关卡列表项目添加点击事件,相关WXML代码片段修改如下:
<view class='box' wx:for='{{levels}}'wx:key='levels{{index}}'bindtap='chooseLevel'data-level='{{item}}'>
上述代码表示为关卡添加了自定义点击事件函数chooseLevel,并且使用了data--level属性携带了关卡图片信息,然后我们在对应的JS文件中添加该函数的相关内容,代码片段如下:
chooseLevel:function(e){ let level=e.currentTarget.dataset.level wx.navigateTo({ url:'../game/game?level='+level }) },
现在已经可以点击跳转到游戏页面了,但是不能正确显示对应的游戏画面,仍需在game页面进行携带数据的接受处理才可以显示正确的游戏画面
比如我点进第四关,但是显示的是第一关的游戏画面
(2)游戏页逻辑
游戏页主要有两个功能需要实现,一个是显示提示图,然后就是游戏逻辑实现
1)显示提示图:在首页逻辑中已经实现了页面跳转并携带了关卡对应的图片信息,现在需要实现在游戏界面接收关卡信息并显示对应的图片内容
相关JS代码如下:
onLoad: function(options) { url='/images/'+options.level this.setData({url:url}) },
WXML代码如下:
<!--提示图区域--> <view class='title'>提示图</view> <image src='{{url}}'></image>
可以显示出正确的画面了
2)游戏逻辑实现
a.准备工作:首先在game.js文件的顶端记录一些游戏初始数据信息,对应的JS代码如下:
var num=[ ['00','01','02'], ['10','11','12'], ['20','21','22'] ] var w=100 var url='/images/pic01.jpg'
b.初始化拼图画面,从空白方块的位置入手,每次随机让他和周围的邻近方块交换位置,这样可以通过方块反向移动回到最初状态(确保本局有解),并且在交换足够多的次数后也可以实现随机打乱的效果
在game.js文件中添加shuffle函数用于重新开始游戏,对应JS代码如下:
shuffle:function(e){ num=[ ['00','01','02'], ['10','11','12'], ['20','21','22'] ] var row=2 var col=2 for(var i=0;i<100;i++){ var direction=Math.round(Math.random()*3) if(direction==0){ if(row!=0){ num[row][col]=num[row-1][col] num[row-1][col]='22' row-=1 } } else if(direction==1){ if(row!=2){ num[row][col]=num[row+1][col] num[row+1][col]='22' row+=1 } } else if(direction==2){ if(col!=0){ num[row][col]=num[row][col-1] num[row][col-1]='22' col-=1 } } else if(direction==3){ if(col!=2){ num[row][col]=num[row][col+1] num[row][col+1]='22' col+=1 } } } },
上述代码表示使用for循环进行了100次打乱,开发者可以根据自己的需求更改循环次数,每次使用Math.random()方法从上下左右四个方向中随机产生一个方向,之后如果符合条件则交换空白方块和图片方块的位置
然后我们在game.js中添加一个自定义函数drawCanvas,用于将打乱后的图片方块绘制到画布上,对应的JS代码片段如下:
drawCanvas:function(e){ let ctx=this.ctx ctx.clearRect(0,0,300,300) for(var i=0;i<3;i++){ for(var j=0;j<3;j++){ if(num[i][j]!='22'){ var row=parseInt(num[i][j]/10) var col=num[i][j]%10 ctx.drawImage(url,col*w,row*w,w,w,j*w,i*w,w,w) } } } ctx.draw() },
最后在game.js的inLoad函数中调用自定义函数shuffle和drawCanvas,对应的JS代码如下:
onLoad: function (options) { let url='/images/'+ options.level this.setData({url:url}) this.ctx=wx.createCanvasContext('myCanvas') this.shuffle() this.drawCanvas() },
效果图如下:
c.移动被点击的方块,修改game.wxml页面中的画布组件,为其绑定触摸事件WXML代码修改后如下:
<canvas canvas-id="myCanvas" bindtouchstart="touchBox"></canvas>
在game.js文件添加自定义函数touchBox,用于实现图片方块的移动,对应的JS代码如下:
touchBox:function(e){ // 如果游戏成功,不做任何操作 if(this.data.isWin){ // 终止本函数 return } // 获取被点击方块的坐标x和y var x = e.changedTouches[0].x var y = e.changedTouches[0].y // // 换算成行和列 var row = parseInt(y/w) //将坐标除于小方块的宽度w得出行和列 var col = parseInt(x/w) // 如果点击的不是空白位置 if(num[row][col]!='22'){ //尝试移动方块 this.moveBox(row,col) // 重新绘制画布内容 this.drawCanvas() // 判断游戏是否成功 if(this.isWin()){ // 在画面上绘制提示语句 let ctx =this.ctx // 绘制完整图片 ctx.drawImage(url,0,0) // 绘制文字 ctx.setFillStyle('#e64340')//调节文字颜色 ctx.setTextAlign('center') //表示这个字在这个坐标点上居中显示 ctx.setFontSize(50) //调节字的大小 ctx.fillText('游戏成功',150,150) ctx.draw() } } }, //自定义函数 - 判断游戏是否成功 isWin:function(){ // 使用双重for循环遍历整个数组 for(var i = 0;i < 3;i++ ){ for(var j = 0;j < 3;j++ ){ // 如果有方块位置不对 if(num[i][j] != i*10 + j){ // 返回假,游戏尚未成功 return false } } } // 游戏成功,更新状态 this.setData({isWin:true}) // 返回i真,游戏成功 return true }, // 自定义函数-重新开始游戏 restartGame:function(){ // 更新游戏成功状态 this.setData({isWin:false}) // 打乱方块顺序 this.shuffle() //绘制画布内容 this.drawCanvas() }, /** * 自定义函数--移动方块 */ moveBox: function (i,j) { //情况1:如果被点击的方块不在最上方,检查可否上移 if(i>0){ //如果方块上方是空白的 if(num[i-1][j] == '22'){ // 交换方块与空白的位置 num[i-1][j] = num[i][j] num[i][j] = '22' return } } //情况2:如果被点击的方块不在最下方,检查可否下移 if(i<2){ //如果方块下方是空白的 if(num[i+1][j] == '22'){ // 交换方块与空白的位置 num[i+1][j] = num[i][j] num[i][j] = '22' return } } //情况3:如果被点击的方块不在最左边,检查可否左移 if(j>0){ //如果方块上方是空白的 if(num[i][j-1] == '22'){ // 交换方块与空白的位置 num[i][j-1] = num[i][j] num[i][j] = '22' return } } //情况4:如果被点击的方块不在最右边,检查可否右移 if(j<2){ //如果方块右边是空白的 if(num[i][j+1] == '22'){ // 交换方块与空白的位置 num[i][j+1] = num[i][j] num[i][j] = '22' return } } },
效果图如下:
d.判断游戏成功
首先在game.js文件中的data中添加初始数据isWin,用于标记游戏成功与否,对应的JS代码片段如下:
data: { isWin:false },
在上述代码中isWin为false表示游戏尚未成功,当成功时会重置为true,然后在game.js文件中添加自定义函数isWin,用于判断游戏是否已经成功,对应JS代码如下:
isWin:function(){ // 使用双重for循环遍历整个数组 for(var i = 0;i < 3;i++ ){ for(var j = 0;j < 3;j++ ){ // 如果有方块位置不对 if(num[i][j] != i*10 + j){ // 返回假,游戏尚未成功 return false } } } // 游戏成功,更新状态 this.setData({isWin:true}) // 返回i真,游戏成功 return true },
然后修改game.js中的touchBox函数,要求被触发时追加对游戏成功状态的判断,对应JS代码片段如下:
if(this.data.isWin){ // 终止本函数 returntouchBox:function(e){ // 如果游戏成功,不做任何操作 if(this.data.isWin){ return } //块的坐标x和y var x = e.changedTouches[0].x var y = e.changedTouches[0].y // // 换算成行和列 var row = parseInt(y/w) //将坐标除于小方块的宽度w得出行和列 var col = parseInt(x/w) // 如果点击的不是空白位置 if(num[row][col]!='22'){ //尝试移动方块 this.moveBox(row,col) // 重新绘制画布内容 this.drawCanvas() // 判断游戏是否成功 if(this.isWin()){ // 在画面上绘制提示语句 let ctx =this.ctx // 绘制完整图片 ctx.drawImage(url,0,0) // 绘制文字 ctx.setFillStyle('#e64340')//调节文字颜色 ctx.setTextAlign('center') //表示这个字在这个坐标点上居中显示 ctx.setFontSize(50) //调节字的大小 ctx.fillText('游戏成功',150,150) ctx.draw() } } },
e.重新开始游戏
修改game.wxml代码,为“重新开始”按钮追加自定义函数的点击事件,WXML代码片段修改如下:
<button type="warn" bindtap="restartGame">重新开始</button>
在game.js文件中添加restartGame函数,用于重新开始游戏,对应的JS代码如下:
restartGame:function(){ // 更新游戏成功状态 this.setData({isWin:false}) // 打乱方块顺序 this.shuffle() //绘制画布内容 this.drawCanvas() },
三、程序运行结果
四、问题总结与体会
在实验过程中出现了很多小问题,因为代码量太大而导致经常出现代码上的一些小错误,还有就是在<canvas>的使用上略微有些不熟练,通过此次实验,学会了拼图小游戏的做法,还有一
些函数的用法以及熟悉了<canvas>的用法