2023年夏季《移动软件开发》实验报告
一、实验目标
1、综合应用所学知识创建完整的拼图游戏项目;2、熟练掌握组件。
注意事项:提供项目需要的图片资源下载,链接:https://gaopursuit.oss-cn-beijing.aliyuncs.com/2023/images.zip
二、实验步骤
1)项目需求:
本项目一共需要两个页面,即首页和游戏页面,其中,首页用于呈现关卡菜单,点击对应难度的关卡后进入游戏画面。
首页功能需求:
首页功能需求如下:(1) 首页需要包含标题和关卡列表。(2) 关卡至少要有 6 个关卡选项,每个关卡显示预览图片和第几关。(3) 点击关卡列表可以打开对应的游戏画面。
游戏页功能需求:
游戏页功能需求如下:(1)游戏页面需要显示游戏提示图、游戏画面和“重新开始”按钮。(2)每关游戏提示图显示对应的图片预览。(3)游戏画面随机将原图打乱为3×3的小方块,并且可移动被点击的方块。(4)点击“重新开始”按钮可以重新随机打乱小方块并开始游戏。
2)项目创建:
创建项目如下:
3)页面配置:
1、创建页面文件
项目创建完毕后,在根目录中会生成文件夹pags用于存放页面文件。一般来说首页默认命名为index,表示小程序运行的第一个页面;其他页面名回称可以自定义。本项目有两个页面文件,需要创建index(首页页面)和game视频讲解(游戏页面)。
具体操作如下:
(1)将app.json文件内pages属性中的“pags/logs/logs改成“pages/game/game”。
(2)按快捷键Ctrl+S保存修改后会在page文件夹下自动生成game目录。
2、删除和修改文件
具体操作如下:
(1)删除utils文件夹及其内部所有内容。
(2)删除pages文件夹下的logs目录及其内部所有内容。
(3)删除index.wxml和index.wxss中的全部代码。
(4)删除index.js中的全部代码,并且输入关键词page找到第二个选项按回车键让其自动补全函数
(5)删除app.wxss中的全部代码。
(6)删除app.js中的全部代码,并且输入关键词app找到第一个选项按回车键让其自动补全函数
3、创建其他文件
单击目录结构左上角的+号创建文件夹并命名为images,将所下载的图片素材复制粘贴进去
此时文件配置全部完成,效果图如下:
4)视图设计:
1、导航栏设计
在app.json中对window属性进行重新配置来自定义导航栏效果,更改后的app.json文件代码及效果图如下:
{
"pages":[
"pages/index/index",
"pages/game/game"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#E64340",
"navigationBarTitleText": "拼图游戏",
"navigationBarTextStyle":"black"
},
"style": "v2",
"sitemapLocation": "sitemap.json"
}
上述代码更改导航栏内背景色为珊瑚红
2、页面设计
(1)公共样式设计:
首先在app.wxss中设置页面窗口和顶端标题的公共样式,代码如下:
/* 页面容器样式 */
.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)首页设计:
首页主要包含两部分内容,即标题和关卡列表。计划使用如下组件:
-
顶端标题:容器;
-
关卡列表:容器,内部使用数组循环。
WXML(pages/index/index.wxml)代码如下:
<view class='container'>
<!--标题-->
<view class='title'>游戏选关</view>
<!--关卡列表-->
<view class='levelBox'>
<view class='box'>
<image src='/images/pic01.jpg'></image>
<text>第一关</text>
</view>
</view>
</view>
wxss(pages/index/index.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;
}
当前效果如图12-8所示。由图可见,此时可以显示标题和一个临时关卡。由于尚未获得关卡数据,只所以暂时无法显示完整的关卡列表,仅供作为样式参考。
(3)游戏页面设计:
游戏页面需要用户点击首页的关卡列表,然后在新窗口中打开该页面口游戏页面包括游戏提示图、游戏画面和“重新开始”按钮,页面设计如图12-9。
由于暂时没有做点击跳转的逻辑设计,所以可以在开发工具顶端选择“普通编译”下的“添加编译模式”,并携带临时测试参数level=pic01.jpg,如图12-10所示。
此时预览就可以直接显示game页面了,设计完毕后再改回“普通编译”模式即可重新显示首页
计划使用如下组件:
- :整体容器和顶端标题
- :提示图
- canvas:游戏画布
- button:"重新开始"按钮
wxml(pages/game/game.wxml)代码如下:
<!--pages/game/game.wxml-->
<view class='container'>
<!--提示图区域-->
<view class='title'>提示图</view>
<image src='/images/pic01.jpg'></image>
<!--游戏画布-->
<canvas camvas-id='warn'>重新开始</canvas>
</view>
wxss(pages/game/game.wxss)页面代码如下:
/* pages/game/game.wxss */
/* 提示图样式 */
image{
width:250rpx;
height:250rpx;
}
/* 画布样式 */
canvas{
border:1rpx solid;
width:300px;
height:300px;
}
此时可以显示完整样式效果。由于尚未获得游戏数据,所以暂时无法根据用户点击的关卡入口显示对应的游戏内容,仅供作为样式参考
此时页面布局与样式设计已完成
5)逻辑实现
1、首页逻辑
首页主要有两个功能需要实现,一是展示关卡列表,二是点击图片能够跳转到游戏页面。
(1)关卡列表展示:
在JS文件的 data 中录入关卡图片的数据信息,这里以6个关卡为例 。相关的JS(pages/index/index.js)代码片段如下:
data: {
levels:[
'pic01.jpg',
'pic02.jpg',
'pic03.jpg',
'pic04.jpg',
'pic05.jpg',
'pic06.jpg'
]
},
接着为关卡对应的组件添加wx:for属性循环显示关卡列表数据和图片。修改后的WXML(pages/index/index.wxml)代码如下:
<view class='container'>
<!--标题-->
<view class='title'>游戏选关</view>
<!--关卡列表-->
<view class='levelBox'>
<view class='box'wx:for='{{levels}}'wx:key='levels{{index}}'>
<image src='/images/{{item}}'></image>
<text>第{{index+1}}关</text>
</view>
</view>
</view>
页面效果如下:
(2)点击跳转游戏页面:
若希望用户点击关卡图片即可实现跳转,需要首先为关卡列表项目添加点击事件。相关WXML(pages/index/index.wxml)代码相关片段修改如下:
<view class='container'>
<!--标题-->
<view class='title'>游戏选关</view>
<!--关卡列表-->
<view class='levelBox'>
<view class='box'wx:for='{{levels}}'wx:key='levels{{index}}'bindtap='chooseLevel'data-level='{{item}}'>
<image src='/images/{{item}}'></image>
<text>第{{index+1}}关</text>
</view>
</view>
</view>
上述代码表示为关卡添加了自定义点击事件函数chooseLevel,并且使用data–level属性携带了关卡图片信息。然后在对应的index.js文件中添加chooseLevel函数的内容,代码片段如下:
/**
* 自定义函数:游戏选关
*/
chooseLevel:function(e){
let level=e.currentTarget.dataset.levelwx.navigateTo({
url:'../game/game?level='+level
})
},
此时已经可以点击跳转到game页面,并且成功携带了关卡图片数据,但是仍需在game页面进行携带数据的接收处理才可显示正确的游戏画面。
2、游戏页逻辑
游戏页主要有两个功能需要实现,一是显示提示图;二是游戏逻辑实现。
(1)显示提示图:
在首页逻辑中已经实现了页面跳转并携带了关卡对应的图片信息,现在需要在游戏页面接收关卡信息,并显示对应的图片内容。相关JS(pages/game/game.js)代码片段如下:
onLoad(options) {
//更新图片路径地址
url='/images/'+options.level
//更新提示图地址
this.setData({url:url})
},
修改WXML(pages/game/game.wxml)代码片段如下:
<!--提示图区域-->
<view class='title'>提示图</view>
<image src='{{url}}'></image>
此时重新从首页点击不同的关卡图片跳转就可以发现已经能够正确显示对应的内容了。运行效果如图所示。
(2)游戏逻辑实现:
1)准备工作。首先在game.js文件的顶端记录一些游戏初始数据信息。对应的JS(pages/game/game.js)代码片段添加如下:
var num=[
['00','01','02'],
['10','11','12'],
['20','21','22']
]
//方块宽度
var w=200
//图片初始地址
var url='/images/pic01.jpg'
2)初始化拼图画面。传统做法是随机抽取画面中的任意两个方块,然后交换彼此位置,在进行足够多次数的交换后基本可以实现随机打乱的效果。但是这种方法有一个弊端,就是有时候会陷入无解的死局。因此可以考虑从空白方块的所在位置入手,每次随机让它和周围的邻近方块交换位置,这样可以通过方块反向移动回到最初始状态(确保本局有解),并且在交换足够多的次数后也可以实现随机打乱的效果。
在game.js文件中添加shuffle函数,用于重新开始游戏。对应的JS(pages/game/game.js)代码片段添加如下:
// pages/game/game.js
//方块初始位置
var num=[
['00','01','02'],
['10','11','12'],
['20','21','22']
]
//方块宽度
var w=200
//图片初始地址
var url='/images/pic01.jpg'
Page({
// 自定义函数-随机打乱方块顺序
shuffle:function(){
//先令方块回归初始位置
num=[
['00','01','02'],
['10','11','12'],
['20','21','22']
]
//记录当前空白方块的行和列
var row=2
var col=2
//打乱方块顺序100次
for(var i=0;i<100;i++){
//随机产生其中一个方向:上(0),下(1),左(2),右(3)
var direction=Math.round(Math.random()*3)
//上:0
if(direction==0){
//空白方块不在最上面一行
if(row!=0){
//交换位置
num[row][col]=num[row-1][col]
num[row-1][col]='22'
//更新空白方块的行
row-=1
}
}
//下:1
else if(direction==1){
//空白方块不在最下面一行
if(row!=2){
//交换位置
num[row][col]=num[row+1][col]
num[row+1][col]='22'
//更新空白方块的行
row+=1
}
}
//左:2
else if(direction=2){
//空白方块不在最左侧
if(col!=0){
//交换位置
num[row][col]=num[row][col-1]
num[row][col-1]='22'
//更新空白方块的列
col-=1
}
}
//右:3
else if(direction==0){
//空白方块不在最右侧
if(col!=2){
//交换位置
num[row][col]=num[row][col+1]
num[row][col+1]='22'
//更新空白方块的行
col+=1
}
}
}
},
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad:function(options) {
//更新图片路径地址
url='/images/' + options.level
//更新提示图地址
this.setData({url:url})
},
})
上述代码表示使用for循环进行了100次打乱,开发者可以根据自己的需求更改循环次数。每次使用Math.random()方法从上下、左、右4个方向中随机产生一个方向,之后如果符合条件则交换空白方块和图片方块的位置。
然后在game.js中添加自定义函数drawCanvas,用于将打乱后的图片方块绘制到画布上。对应的JS(pages/game/game.js)代码片段添加如下:
//自定义函数--绘制画布内容
drawCanvas:function(){
let ctx=this.ctx
//清空画布
ctx.clearRext(0,0,600,600)
//使用双重fo循环绘制3×3的拼图
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的onLoad函数中调用自定义函数shuffle和drawCanvas。对应的JS(pages/game/game.js)代码片段添加如下:
onLoad:function(options) {
//更新图片路径地址
url='/images/' + options.level
//更新提示图地址
this.setData({url:url})
//创建画布上下文
this.ctx=wx.createCanvasContext('myCanvas')
//打乱方块顺序
this.shuffle()
//绘制画布内容
this.drawCanvas()
},
当前效果图如下:
3)移动被点击的方块。修改game.wxml页面中的画布组件(),为其绑定触摸事件。WXML(pages/game/game.wxml)代码修改后如下:
<!--游戏画布-->
<canvas canvas-id='myCanvas'bindtouchstart='touchBox'></canvas>
在game.js文件添加自定义函数 touchBox,用于实现图片方块的移动,对应的JS(pages/game/game.js)代码片段添加如下:
Page({
//自定义函数-监听点击方块事件
touchBox:function(e){
//如果游戏成功,不做任何操作
if(this.data.isWin){
return
}
//获取被点击方块的坐标x和y
var x=e.changedTouches[0].x
var y=e.changedTouches[0].y
//console.log('x:'+x+',y:'+y)
//换算成行和列
var row =parseInt(y/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(60)
ctx.fillText('游戏成功',150,150)
ctx.draw()
}
}
},
//自定义函数--移动被点击的方块
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
}
}
},
})
当前效果图如下:
(3)判断游戏成功
首先在game.js文件中的data中添加初始数据 isWin,用于标记游戏成功与否。对应的JS(pages/game/game.js)代码片段添加如下:
data: {
isWin:false
},
在上述代码中isWin为false表示游戏尚未成功,当成功时会重置为true。在game.js文件中添加自定义函数isWin,用于判断游戏是否已经成功,对于的js(pages/game/game.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){
//返回false,游戏尚未成功
return false
}
}
}
//更新游戏成功状态
this.setData({isWin:true})
//返回true,表示游戏成功
return true
},
游戏成功效果如图:
在这里插入图片描述
(4)重新开始游戏
修改game.wxml代码,为“重新开始”按钮追加自定义函数的点击事件。WXML(pages/game/game.wxml)代码片段修改如下:
<!--重新开始按钮-->
<button type='warn'bindtap='restartGame'>重新开始</button>
在game.js文件中添加 restartGame函数,用于重新开始游戏。对应的JS(pages/game/game.js)代码片段添加如下:
//自定义函数-重新开始游戏
restartGame:function(){
//更新游戏成功状态
this.setData({isWin:false})
//打乱方块顺序
this.shuffle()
//绘制画布内容
this.drawCanvas()
},
点击重新开始后,更新页面,重新开始游戏,页面效果如图:
试验完成
三、程序运行结果
首页:
游戏页面:
游戏成功:
重新开始:
四、问题总结与体会
此次实验过程较为复杂,要实现首页和游戏界面,其中游戏界面的逻辑函数很多,较为复杂。在实验过程中遇到了:(1)输错变量名导致页面无法正常显示;(2)点击跳转到游戏界面的函数写错;(3)画布图片的位置打乱和点击移动函数较为复杂导致写错等问题,但在经过仔细比对实验文档检查并修正后,顺利完成实验。
经过这次实验,了解到了做一个小游戏的基本流程,也意识到了其中步骤的复杂,对自己是一次很大的提升。