2023年夏季《移动软件开发》实验报告
一、实验目标
1、综合应用所学的知识创建完整的推箱子游戏;2、熟练掌握canvas和绘图API。
游戏图片下载链接:https://gaopursuit.oss-cn-beijing.aliyuncs.com/course/mobileDev/boxgame_images.zip
二、实验步骤
1、需求分析
(1)首页功能需求
首页功能需求如下:
(1)首页需要包含标题和关卡列表。
(2)关卡至少要有4个关卡选项,每个关卡显示预览图片和第几关。
(3)点击关卡列表可以打开对应的游戏画面。
(2)游戏页功能需求
游戏页功能需求如下:
(1)游戏页面需要显示第几关、游戏画面、方向键和“重新开始”按钮。
(2)点击方向键可以使游戏主角自行移动或推动箱子前进。
(3)游戏画面由8×8的小方块组成,主要包括地板、围墙,箱子,游戏主角和目的地。
(4)点击“重新开始”按钮可以将箱子和游戏主角回归初始位置并重新开始游戏。
2、项目创建
本项目创建选择空白文件夹boxGame,效果如图如下所示。
单击“新建”按钮完成项目创建,然后准备手动修改页面配置文件。
3、页面配置
(1)创建页面文件
项目创建完毕后,在根目录中会生成文件夹pages用于存放页面文件。一般来说首页默认命名为index,表示小程序运行的第一个页面:其他页面名称可以自定义。本项目有两个页面文件,需要创建index(首页页面)和game(游戏页面)。
具体操作如下:
(1)将app.json文件内pages属性中的“pages/,logs/logs"改成“pages/game/game”。
(2)按快捷键Ctrl十S保存修改后会在pages文件夹下自动生成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:用于存放图片素材:
- utils:用于存放公共JS文件。
单击目录结构左上角的十号创建文件夹,并分别命名为images和utils。
1)添加图片文件
在文件夹images中新建文件夹icons,将游戏页面的5个游戏图标素材放入其中
2)创建公共JS文件
右击utils文件夹,选择“新建”→JS,输入data后按回车键创建公共文件data.js
此时文件配置完成
4、页面设计
(1)导航栏设计
更改app.json文件,更改后的代码如下
{
"pages":[
"pages/index/index"
],
"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'>
<text class='title'>游戏选关</text>
<view class='levelBox'>
<view class='box'>
<image src='/images/level01.png'></image>
<text>第一关</text>
</view>
</view>
</view>
WXSS(pages/index/index.wxss)代码如下
.levelBox{
width:100%;
}
.box{
width:50%;
float:left;
margin:20rpx 0;
display:flex;
flex-direction: column;
align-items: center;
}
image{
width:300rpx;
height:300rpx;
}
效果图如下:
此时可以显示标题和一个临时关卡。
3)游戏页面设计
游戏页而需要用户点击首页的关卡列表,然后在新窗口中打开该页面。游戏页面
包括游戏关卡标题、游戏画面、方向键和“重新开始”按钮
由于暂时没有做点击跳转的逻辑设计,所以可以在开发工具顶端选择“普通编译”下的“添加编译模式”,并携带临时测试参数level=0,如图所示:
此时预览就可以直接显示game页面了,设计完毕后再改回“普通编译”模式即可重新显示首页。
计划使用如下组件:
- :整体容器和顶端标题;
- canvas游戏画布
- button:四个方向键和一个重新开始按钮
WXML(pages/game/game.wxml)代码如下:
<view class='container'>
<view class='title'>第一关</view>
<canvas canvas-id='myCanvas'></canvas>
<view class='btnBox'>
<button type='warn'>↑</button>
<view>
<button type='warn'>←</button>
<button type='warn'>↓</button>
<button type='warn'>→</button>
</view>
</view>
<button type='warn'>重新开始</button>
</view>
WXSS(pages/game/game.wxss)代码如下:
canvas{
border:1rpx solid;
width:320px;
height:320px;
}
.btnBox{
display:flex;
flex-direction: column;
align-items: center;
}
.btnBox view{
display:flex;
flex-direction:row;
}
.btnBox button{
width:90rpx;
height:90rpx;
}
button{
margin:10rpx;
}
此时效果图如下:
此时页面布局与样式设计已经完成
5、逻辑实现
(1)公共逻辑
在公共JS文件(utils/data.js)中配置游戏地图的数据,代码如下:
//地图数据napl~map4
//地图数据:1为墙、2为路、3为终点、4为箱子、5为人物、0为墙的外围
//=================================
//关卡1
var mapl =[
[0,1,1,1,1,1,0,0],
[0,1,2,2,1,1,1,0],
[0,1,5,4,2,2,1,0],
[1,1,1,2,1,2,1,1],
[1,3,1,2,1,2,2,1],
[1,3,4,2,2,1,2,1],
[1,3,2,2,2,4,2,1],
[1,1,1,1,1,1,1,1]
]
//关卡2
var map2 =[
[0,0,1,1,1,0,0,0],
[0,0,1,3,1,0,0,0],
[0,0,1,2,1,1,1,1],
[1,1,1,4,2,4,3,1],
[1,3,2,4,5,1,1,1],
[1,1,1,1,4,1,0,0],
[0,0,0,1,3,1,0,0],
[0,0,0,1,1,1,0,0]
]
//关卡3
var map3=[
[0,0,1,1,1,1,0,0],
[0,0,1,3,3,1,0,0],
[0,1,1,2,3,1,1,0],
[0,1,2,2,4,3,1,0],
[1,1,2,2,5,4,1,1],
[1,2,2,1,4,4,2,1],
[1,2,2,2,2,2,2,1],
[1,1,1,1,1,1,1,1]
]
//关卡4
var map4=[
[0,1,1,1,1,1,1,0],
[0,1,3,2,3,3,1,0],
[0,1,3,2,4,3,1,0],
[1,1,1,2,2,4,1,1],
[1,2,4,2,2,4,2,1],
[1,2,1,4,1,1,2,1],
[1,2,2,2,5,2,2,1],
[1,1,1,1,1,1,1,1]
]
这里分别使用map1~map4代表4个不同关卡的地图数据,以二维数组的形式存放。当前地图均由8×8的方格组成,每个位置的数字代表对应的图标素材。
当前地图数据和图片素材仅供参考,开发者也可以自行修改游戏布局和图片。
然后需要在data.js中使用module.exports语句暴露数据出口,代码片段如下:
module.exports={
maps:[map1,map2,map3,map4]
}
现在就完成了公共逻辑处理的部分,最后需要在game页面的JS文件顶端引用公共JS文件,代码如下:
var data=require('../../utils/data.js');
(2)首页逻辑
首页主要有两个功能需要实现,一是展示关卡列表;二是点击图片能跳转到游戏页面。
1)关卡列表展示
在JS文件的data中录入关卡图片的信息,相关JS(pages/index/index.js)代码如下
data: {
levels:[
'level01.png',
'level02.png',
'level03.png',
'level04.png'
]
},
接着为关卡对应的组件添加wx:for属性循环显示关卡列表数据和图片。
修改后的WXML(pages/index/index.wxml)代码如下:
<view class='container'>
<text class='title'>游戏选关</text>
<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='box'wx:for='{{levels}}'wx:key='levels{{index}}'bindtap='chooseLevel'data-level='{{index}}'>
上述代码表示为关卡添加了自定义点击事件函数chooseLevel,并且使用data-level属性携带了关卡图片下标信息。
然后在对应的index.js文件中添加chooseLevel函数的内容,代码片段如下:
chooseLevel:function(e){
let level=e.currentTarget.dataset.level
wx:navigateTo({
url:'../game/game?level='+level
})
},
此时已经可以点击跳转到game页面,并且成功携带了关卡图片数据,但是仍需在game页面进行携带数据的接收处理才可显示正确的游戏画面。
(3)游戏页逻辑
游戏页主要有以下几个功能需要实现:
- 显示当前是第几关;
- 游戏地图的绘制;
- 4个方向键可以移动游戏主角:
- 点击“重新开始”按钮可以使游戏地图还原成最初状态。
1)显示当前第几关
在首页逻辑中已经实现了页面跳转并携带了关卡对应的图片信息,现在
需要在游戏页面接收关卡信息,并显示对应的图片内容。
相关JS(pages/game/game.js)代码片段如下:
data: {
level:1
},
onLoad:function(options) {
let level=options.level
this.setData({
level:parseInt(level)+1
})
},
修改WXML(pages/game/game.wxml)代码如下:
<view class='title'>第{{level}}关</view>
2)游戏逻辑实现
1)准备工作
首先在game.js文件的顶湍记录一些游初始数据信息。
对应的JS(pages/game/game.js)代码段添加如下:
//地图图层数据
var map =[
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
]
//箱子图层数据
var box =[
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0]
]
var w=40
var row=0
var col=0
2)初始化游戏画面
首先需要根据当前是第几关读取对应的游戏地图信息,并更新到游戏初始数据中。
在game.js文件中添加initMap函数,用于初始化游戏地图数据。
对应的JS(pages/game/game.js)代码片段添加如下:
initMap:function(level){
let mapData=data.maps[level]
for(var i=0;i<8;i++){
for(var j=0;j<8;j++){
box[i][j]=0
map[i][j]=mapData[i][j]
if(mapData[i][j]==4){
box[i][j]=4
map[i][j]=2
}else if(mapData[i][j]==5){
map[i][j]=2
row=i
col=j
}
}
}
},
上述代码首先从公共函数文件data.js中读取对应关卡的游戏地图数据,然后使用双重for循环对每一块地图数据进行解析,并更新当前游戏的初始地图数据、箱子数据以及游戏主角(小鸟)的所在位置。
然后在game.js中添加自定义函数drawCanvas,用于将地图信息绘制到画布上。
对应的JS(pages/game/game.js)代码片段添加如下:
drawCanvas:function(){
let ctx=this.ctx
ctx.clearRect(0,0,320,320)
for(var i=0;i<8;i++){
for(var j=0;j<8;j++){
let img='ice'
if(map[i][j]==1){
img='stone'
}else if(map[i][j]==3){
img='pig'
}
ctx.drawImage('/images/icons/'+img+'.png',j*w,i*w,w,w)
if(box[i][j]==4){
ctx.drawImage('/images/icons/box.png',j*w,i*w,w,w)
}
}
}
ctx.drawImage('/images/icons/bird.png',col*w,row*w,w,w)
ctx.draw()
},
最后在game.js的onload函数中创建画布上下文,并依次调用自定义函数initMap和drawCanvas。
对应的JS(pages/game/game.js)代码片段添加如下:
onLoad:function(options) {
let level=options.level
this.setData({
level:parseInt(level)+1
})
this.ctx=wx.createCanvasContext('myCanvas')
this.initMap(level)
this.drawCanvas()
},
效果如图
3)方向键逻辑实现
修改game.wxml页面中的4个方向键button,为其绑定点击事件
WXML.(pages/game/game.wxml)代码修改后如下:
<button type='warn'bindtap='up'>↑</button>
<view>
<button type='warn'bindyap='left'>←</button>
<button type='warn'bindtap='down'>↓</button>
<button type='warn'bindtap='right'>→</button>
</view>
在game,js文件中添加自定义函数up、down,left和right,分别用于实现游戏主角(小鸟)在上、下、左、右4个方向的移动,每次点击在条件允许的情况下移动一格。
对应的JS(pages/game/game.js)代码片段添加如下:
up:function(){
if(row>0){
if(map[row-1][col]!=1&&box[row-1][col]!=4){
row=row-1
}
else if(box[row-1][col]==4){
if(row-1>0){
if(map[row-2][col]!=1&&box[row-2][col]!=4){
box[row-2][col]=4
box[row-1][col]=0
row=row-1
}
}
}
this.drawCanvas()
this.checkWin()
}
},
down:function(){
if(row<7){
if(map[row+1][col]!=1&&box[row+1][col]!=4){
row=row+1
}
else if(box[row+1][col]==4){
if(row+1<7){
if(map[row+2][col]!=1&&box[row+2][col]!=4){
box[row+2][col]=4
box[row+1][col]=0
row=row+1
}
}
}
this.drawCanvas()
this.checkWin()
}
},
left:function(){
if(col>0){
if(map[row][col-1]!=1&&box[row][col-1]!=4){
col=col-1
}
else if(box[row][col-1]==4){
if(col-1>0){
if(map[row][col-2]!=1&&box[row][col-2]!=4){
box[row][col-2]=4
box[row][col-1]=0
col=col-1
}
}
}
this.drawCanvas()
this.checkWin()
}
},
right:function(){
if(col<7){
if(map[row][col+1]!=1&&box[row][col+1]!=4){
col=col+1
}
else if(box[row][col+1]==4){
if(col+1<7){
if(map[row][col+2]!=1&&box[row][col+2]!=4){
box[row][col+2]=4
box[row][col+1]=0
col=col+1
}
}
}
this.drawCanvas()
this.checkWin()
}
},
3)判断游戏成功
在game.js文件中添加白定义函数isWin,用于判断游戏是否已经成功。
对应的JS(pages/game/game.js)代码片段添加如下:
isWin:function(){
for(var i=0;i<8;i++){
for(var j=0;j<8;j++){
if(box[i][j]==4&&map[i][j]!=3){
return false
}
}
}
return true
},
上述代码的判断逻辑是只要有一个箱子没有在终点位置就判断游戏尚未成功。
然后在game.js中添加白定义函数checkWin,.要求一旦游戏成功就弹出提示对话框。
对应的JS(pages/game/game.js)代码片段修改如下:
checkWin:function(){
if(this.isWin()){
wx.showModal({
title:'恭喜',
content:'游戏成功',
showCancel:false
})
}
},
最后在game.js的4个方向键函数中追加关于游戏成功判断的函数,这里以up函数为例,对应的JS(pages/game/game.js)代码片段修改如下:
this.drawCanvas()
this.checkWin()
其他3个方向的函数的修改方式和up函数完全一致,这里不再一一赘述。游戏成
功后的画面如图所示。
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.initMap(this.data.level-1)
this.drawCanvas()
},
此时页面效果如下:
点击重新开始会回复画布初始状态
三、程序运行结果
见以上实验过程中的图片
四、问题总结与体会
本次实验相较于之前的实验复杂了很多,但也从中能学到很多。学会了运用utils存储我们所需要的数据;进一步熟悉了wx:for的用法,让代码更加简洁;进一步巩固了点击跳转页面的实现;实验中的逻辑判断up,down,left,right这部分的实现和之前的实验拼图很相似,其中需要考虑的情况很多,要注意将所有的情况都要考虑到,不然可能会出现逻辑错误。