2023年夏季《移动软件开发》实验报告
姓名和学号? | |
---|---|
本实验属于哪门课程? | 中国海洋大学23夏《移动软件开发》 |
实验名称? | 实验6:推箱子游戏 |
博客地址? | XXXXXXX |
Github仓库地址? | XXXXXXX |
一、实验目标
1、综合应用所学的知识创建完整的推箱子游戏;2、熟练掌握和绘图API。
二、实验步骤
本项目一共需要两个页面,即首页和游戏页面,其中首页用于呈现关卡菜单,点击对应难度的关卡后进人游戏画面。
首页功能需求如下:
- 首页需要包含标题和关卡列表。
- 关卡至少要有 64个关卡选项,每个关卡显示预览图片和第几关。
- 点击关卡列表可以打开对应的游戏画面。
游戏页功能需求如下:
- 游戏页面需要显示游戏第几关、游戏画面、方向键和“重新开始”按钮。
- 点击方向键可以使游戏主角自行移动或推动箱子前进。
- 游戏画面由8*8的小方块组成,主要包括地板、围墙、箱子、游戏主角和目的地。
- 点击“重新开始”按钮可以将箱子和游戏主角回归初始位置并重新开始游戏。
1.项目创建
- 如图所示:
2.页面配置
1.创建页面文件
-
项目创建完毕后,在根目录中会生成文件夹pags用于存放页面文件。一般来说首页默认命名为index,表示小程序运行的第一个页面;其他页面名回称可以自定义。本项目有两个页面文件,需要创建index(首页页面)和game(游戏页面)。
-
具体操作如下:
-
将app.json文件内pages属性中的“pags/logs/logs改成“pages/game/game”。
-
按快捷键Ctrl+S保存修改后会在page文件夹下自动生成game目录。
-
2.删除和修改文件
-
具体操作如下:
-
删除utils文件夹及其内部所有内容。
-
删除pages文件夹下的logs目录及其内部所有内容。
-
删除index.wxml和index.wxss中的全部代码。
-
删除index,.js中的全部代码,并且输入关键词page找到第二个选项按回车键让其自动补全函数。
-
删除app.wxss中的全部代码。
-
删除app.js中的全部代码,并且输入关键词app找到第一个选项按回车键让其自动补全函数。
-
3.创建其他文件
创建其他自定义文件,本项目还需要以下两个文件夹。
- images:用于存放关卡图片素材
- utils:用于存放公共JS文件。
单击目录结构左上角的十号创建文件夹,并分别命名为images和utils。
1.添加图片文件
- 本项目在首页需要用到4幅关卡图片,图片素材位于images中。
- 右击目录结构中的images文件夹,选择“硬盘打开”,在该文件夹中将关卡图片文件全部复制、粘贴进去。
2.创建公共JS文件
-
右击utils文件夹,选择“新建”->JS,输入data后按回车键创建公共文件data.js,如图所示。
-
完成后的目录结构如图所示。
-
配置布局至此全部完成,进行页面和视图设计。
4.视图设计
1.导航栏设计
-
小程序默认导航栏是黑底白字的效果,因此需要在app.json中自定义导航栏标题和背景颜色。更改后的app.json文件代码如下:
"pages":[ "pages/index/index" ], "window":{ "backgroundTextStyle":"light", "navigationBarBackgroundColor": "#E64340", "navigationBarTitleText": "推箱子游戏", "navigationBarTextStyle":"white" },
-
上述代码可以更改所有页面的导航栏标题文本为“推箱子游戏”、背景颜色为珊瑚红色(#E64340)、字体为白色。
可得效果如图所示:
2.页面设计(margin:表示上下左右的距离)
1.公共样式设计:
首先在app.wxss中设置页面窗口和顶端标题的公共样式,代码如下:
/**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;
}
- 效果展示如图所示:![
- ](https://img-blog.csdnimg.cn/1c308f57e1064ab1a9470340f4e7aff8.png)
2.首页设计:
首页主要包含两部分内容,即标题和关卡列表。
计划使用如下组件:
-
顶端标题:容器;
-
关卡列表:容器,内部使用数组循环。
当前效果如图所示:
由图可见,此时可以显示标题和一个临时关卡。由于尚未获得关卡数据,只所以暂时无法显示完整的关卡列表,仅供作为样式参考。
**
**
3.游戏页面设计:
-
游戏页面需要用户点击首页的关卡列表;
-
然后在新窗口中打开该页面口游戏页面包括游戏标题、游戏画面、方向键和“重新开始”按钮;
-
由于暂时没有做点击跳转的逻辑设计,所以可以在开发工具顶端选择“普通编译”下的“添加编译模式”,并携带临时测试参数level=0如图所示。
-
此时预览就可以直接显示game页面了,设计完毕后再改回“普通编译”模式即可重新显示首页。
-
计划使用如下组件:
-
:整体容器和顶端标题;
-
:四个方向键和一个“重新开始”按钮。
-
表示游戏画布
-
-
-
当前效果如图所示:
- 由图可见,此时可以显示完整样式效果。由于尚未获得游戏数据,所以暂时无法根据用户点击的关卡入口显示对应的游戏内容,仅供作为样式参考。此时页面布局与样式设计就已完成。
5.逻辑实现
1.公共逻辑
-
在公共文件中(utils/data.js)中配置游戏地图的数据
-
代码如下
//========================= //地图数据map1~map4 //地图数据:1为墙,2为路,3为终点,4为箱子,5为人物,0为墙的外围。 //关卡一 var map1=[ [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] ] //关卡二 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] ] //关卡三 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] ] //关卡四 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] ]
-
-
在data.js中使用module.exports语句暴露出口
-
代码如下
module.exports={ maps:[map1,map2,map3,map4] }
-
-
最后在各页面JS文件顶端引用公共JS文件
-
代码如下
var data=require('../../utils/data.js');
-
1.首页逻辑(index)
- 首页主要有两个功能需要实现,一是展示关卡列表,二是点击图片能够跳转到游戏页面。
(1)关卡列表展示:
-
在JS文件的 data 中录入关卡图片的数据信息,这里以4个关卡为例 。
- 相关的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'> <!--标题--> <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)代码相关片段修改如下:
- (表示为关卡添加了自定义点击事件函数chooseLevel,并且使用data–level属性携带了关卡图片信息)
<view class='container'> <!--标题--> <view class='title'>游戏选关</view> <!--关卡列表--> <view class='levelBox'> <view class='box' wx:for='{{levels}}' wx:key='levels{{index}}' bindtap='chooseLevel' data-level='{{index}}'> <image src='/images/{{item}}'></image> <text>第{{index+1}}关</text> </view> </view> </view>
-
然后在对应的index.js文件中添加chooseLevel函数的内容,代码片段如下:
chooseLevel: function (e) { let level = e.currentTarget.dataset.level wx.navigateTo({ url: '../game/game?level=' + level }) },
-
此时已经可以点击跳转到game页面,并且成功携带了关卡图片数据,但是仍需在game页面进行携带数据的接收处理才可显示正确的游戏画面。
-
(点击第二关)
-
-
2 游戏页逻辑(game)
- 游戏页主要有几个功能需要实现:
- 一是显示当前是第几关;
- 二是游戏地图的绘制;
- 三是4个方向键可以移动游戏主角;
- 四是点击“重新开始”按钮可以使游戏图还原成最初的状态。
(1)显示当前第几关:
-
在首页逻辑中已经实现了页面跳转并携带了关卡对应的图片信息,现在需要在游戏页面接收关卡信息,并显示对应的图片内容。
-
相关JS(pages/game/game.js)代码片段如下:
onLoad(options) { let level = options.level this.setData({ level: parseInt(level) + 1 }) },
-
修改WXML(pages/game/game.wxml)代码片段如下:
<view class='container'> <!--关卡提示--> <view class='title'>第{{level}}关</view>
-
-
此时重新从首页点击不同的关卡图片跳转就可以发现已经能够正确显示对应的内容了。
-
运行效果如图所示:(点击第4关)
-
(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和drawCanvsa。
- 对应的JS(pages/game/game.js)代码片段添加如下:
Page({
// 命周期函数--监听页面加载
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个方向键,为其绑定点击事件。
-
WXML(pages/game/game.wxml)代码修改后如下:
<!--方向键--> <view class='btnBox'> <button type='warn' bindtap='up'>↑</button> <view> <button type='warn' bindtap='left'>←</button> <button type='warn' bindtap='down'>↓</button> <button type='warn' bindtap='right'>→</button> </view> </view>
-
-
在game.js文件添加自定义函数 up\down\left和right,分别用于实现游戏主角(小鸟)在上、下、左、右四个方向的移动,每次点击在条件允许的情况下移动一格。
-
对应的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() } }, 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() } }, 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() } }, 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() } },
-
- 当前效果如图所示。
-
(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代码对应片段添加如下:
checkWin:function(){ if(this.isWin()){ wx.showModal({ title:'恭喜', content:'游戏成功', showCancel:false }) } },
-
-
然后修改game.js中的4个方向键函数中追加关于游戏成功判断的函数,以up函数为例:(其他三个同)
-
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() } },
可得成功后的效果如图所示:
-
(4)重新开始游戏
-
修改game.wxml代码,为“重新开始”按钮追加自定义函数的点击事件。
-
WXML(pages/game/game.wxml)代码片段修改如下:
<!--pages/game/game.wxml--> <view class="container"> <!--提示图区域--> <view class='title'>提示图</view> <image src='{{url}}'></image> <!--游戏画布--> <canvas canvas-id='myCanvas' bindtouchstart='touchBox'></canvas> <!--“重新开始”按钮--> <button type='warn' bindtap='restartGame'>重新开始</button> </view>
-
-
在game.js文件中添加 restartGame函数,用于重新开始游戏。
-
对应的JS(pages/game/game.js)代码片段添加如下:
restartGame:function(){ this.initMap(this.data.level-1) this.drawCanvas() },
-
此时页面效果如图所示。
点击**“重新开始”**后如图所示:
三、程序运行结果
列出程序的最终运行结果及截图。
四、问题总结与体会
一.问题
1.width失去作用—优先级问题
-
解决方法1:将app.json中的"style":"V2"删除
-
原因:小程序一直以来采用的都是 AppService 和 WebView 的双线程模型,基于 WebView 和原生控件混合渲染的方式,小程序优化扩展了 Web 的基础能力,保证了在移动端上有良好的性能和用户体验。官方在 WebView 渲染之外新增了一个渲染引擎 Skyline,但是这个新的引擎还不是特别完善,很多样式不能够兼容,会有很多问题
-
解决方法2:采用id选择器而不是class来使用,优先级会更高一些
二.总结与体会
- 小游戏极其有趣,学习到了很多相关js以及各种的知识,而且更加了解了相关代码的含义,且对于各种组件进行了进一步的理解,收获满满。
- 除此之外,设计出的推箱子小游戏很有意思,可以一直玩,有趣也充满了成就感。