2024年夏季《移动软件开发》实验报告
姓名和学号? | 冯欣怡,22020006029 |
---|---|
本实验属于哪门课程? | 中国海洋大学24夏《移动软件开发》 |
实验名称? | |
博客地址? | 实验6:推箱子小程序-优快云博客 |
Github仓库地址? | Fxinyi/6_24_summer_WeiXinMiniprogram_PushBox (gitee.com) |
(备注:将实验报告发布在博客、代码公开至 github 是 加分项,不是必须做的)
一、实验目标
1、综合所学知识创建完整的推箱子游戏;
2、能够在开发过程中熟练掌握真机预览、调试等操作。
二、实验步骤
0. 项目准备
(1)图片
创建项目,不使用云服务,不使用模板。
然后在文件夹中创建一个空文件夹images用于存放图片,将下载的图片放入:
1. 首页展示
首页用于展示游戏选关,展示4个关卡的图片
(1)window
先在app.js中设置window样式:
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#00b5c7",
"navigationBarTitleText": "推箱子",
"navigationBarTextStyle":"black"
},
(2) 关卡图片
在index.js中设置data:
data: {
levels:[
"level01.png",
"level02.png",
"level03.png",
"level04.png"
]
},
在index.wxml中用wxfor循环每个都显示:
<!--index.wxml-->
<view class="container">
<!-- 标题 -->
<view class="title">游戏选关</view>
<!-- 关卡列表 -->
<view class="levelBox">
<view class="box" wx:for="{{levels}}" wx:key="level{{index}}"data-level="{{index}}">
<image src="/images/{{item}}"></image>
<text>第{{index+1}}关</text>
</view>
</view>
</view>
2. 关卡页
点开每一关后是关卡页,即游戏页,包括2个部分
游戏窗口/控制区
game.wxss
/* pages/game/game.wxss */
/**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;
}
game.wxml:
<!--pages/game/game.wxml-->
<view class="container">
<!-- 关卡提示 -->
<view class="title">第{{level}}关</view>
<!-- 游戏画布 -->
<canvas canvas-id="myCanvas"></canvas>
<!-- 方向键 -->
<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>
<!-- 重新开始 -->
<button type="warn" bindtap="restartGame">重新开始</button>
</view>
展示第level关,然后用canvas展示游戏画面,最后是控制区,上下左右箭头button对应不同的函数,最后有一个重新开始按钮。
3. 跳转关卡
给每一个index.js的图片绑定跳转函数:
<view class="box" wx:for="{{levels}}" wx:key="level{{index}}" bindtap="chooseLevel" data-level="{{index}}">
<image src="/images/{{item}}"></image>
<text>第{{index+1}}关</text>
</view>
chooseLevel函数,获取选择的level,携带level信息跳转到相应的关卡页,
chooseLevel:function(e){
let level=e.currentTarget.dataset.level
wx.navigateTo({
url: '../game/game?level='+level
})
},
4. 游戏逻辑
(1)游戏窗口
游戏窗口要用到关卡数据,咱们先把地图数据存放到公用的utils中‘
定义data.js,存放我们的map数据们,并将他们暴露给外界。
a.数据准备
//================================================
//地图数据map1-map4
//================================================
//关卡1
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]
]
//关卡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]
]
module.exports = {
maps: [map1, map2, map3, map4]
}
b.游戏画面
思路:每一步画面都会改变,我们用onLoad函数进行画面的更新。
首先获取当前的关卡,关卡是0-3,所以显示的level应该是level+1,这里加一了,注意之后再用level找地图的话要减一(笑死了),然后创建canvas画布,调用initMap初始化地图数据。
onLoad: function(options) {
//获取关卡
let level= options.level
//更新页面关卡标题
this.setData({
level: parseInt(level)+1
})
//创建画布上下文
this.ctx = wx.createCanvasContext('myCanvas')
//初始化地图数据
this.initMap(level)
//绘制画布内容
this.drawCanvas()
},
通过level获取到对应的data中的数据,在文件开头引用utils data.js
var data = require('../../utils/data.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
编写initMap函数:
1墙,2路,3终点,4箱子,5人物,0墙的外围
获取mapdata,mapdata表示的是每个格子是什么,而我们用两个数组存放,更新map和data;map表示能不能走,1不能走,2能走。我们把map的其他数字都更新成1/2,并且记录小鸟的初始坐标
initMap: function(level) {
// 读取原始的游戏地图数据
let mapData = data.maps[level]
//使用双重for循环记录地图数据
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
}
}
}
},
drawCanvas函数,画出我们的游戏图
8*8的格子,每个格子边长40,我们绘制320的画布
通过map给img赋值,默认是ice,就是2-路,然后如果是1-墙:img=stone.3-终点:img=pig,
知道了每个点对应什么图像,接下来用drawImage在对应位置画出即可
ctx.drawImage('/images/icons/' + img + '.png', j * w, i * w, w, w)
然后通过box再在对应的位置叠加绘制箱子
再通过col row在对应的位置叠加绘制小鸟
这样叠加绘制就可以很便利的实现我们的动态画面,底层是静止的,小鸟和箱子是动态的:
drawCanvas: function() {
let ctx = this.ctx
//清空画布
ctx.clearRect(0, 0, 320, 320)
//使用双重for循环绘制8x8的地图
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()
},
(2)控制区
a.上下左右
已经说到控制按键bingtap移动按键
<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>
下面定义这4个方向函数:
上:
如果不在最顶端才考虑上移,如果上方不是墙或箱子,可以移动小鸟并更新当前小鸟坐标
如果上方是箱子,不在最顶端才能考虑推动;
如果箱子上方不是墙或箱子更新小鸟坐标+箱子坐标
调用写的drawCanvas()函数,更新坐标
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
* 自定义函数--方向键:左
*/
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
/**
* 自定义函数--方向键:右
*/
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()
}
},
这样我们就实现了移动箱子。
b. 游戏成功
之后就来到一个问题,如何判断游戏成功,何时判断游戏成功。
游戏成功:每个箱子都在终点处
每一次移动地图都会变化,所以应该每次移动后都判断箱子是否在终点处了
先写判断函数:checkWin+Iswin
isWin: function() {
//使用双重for循环遍历整个数组
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
},
/**
* 自定义函数--游戏成功处理
*/
checkWin: function(level) {
if (this.isWin()) {
wx.showModal({
title: '恭喜',
content: '游戏成功!',
showCancel: false
})
}
}
},
每次移动都判断是否成功:
c.重新开始
游戏期间需要重新开始,我们只需要从utils/data.js加载原始地图即可:
restartGame: function() {
//初始化地图数据
this.initMap(this.data.level - 1)
//绘制画布内容
this.drawCanvas()
},
5. 跳转下一关
我玩的过程中想到游戏成功后可以跳转下一关,给checkwin加上一条navigateto就好:
记得我们的level已经加了1了,下一关直接就是
wx.navigateTo({
url: '../game/game?level='+le
})
不用再+1.
最后一关不用跳
checkWin: function(level) {
if (this.isWin()) {
wx.showModal({
title: '恭喜',
content: '游戏成功!',
showCancel: false
})
if(this.data.level!=4){
console.log(this.data.level)
var le=this.data.level
console.log(le)
wx.navigateTo({
url: '../game/game?level='+le
})
}
}
},
结果可以跳转:
三、程序运行结果
跳转到下一关
四、问题总结与体会
在进行推箱子游戏的实验过程中,canvas绘图逻辑对我来说是比较复杂的,学会后发现这是简单而有效的方法。确保每个元素的位置准确,能够随着游戏逻辑的推进而动态变化。判断箱子的可推动性、玩家的移动路径需要细心。这些都让我不断学习和探索
此外,我成功实现了游戏成功后自动跳转到下一关的功能,这为玩家带来了更加流畅的游戏体验。
ta.level!=4){
console.log(this.data.level)
var le=this.data.level
console.log(le)
wx.navigateTo({
url: ‘…/game/game?level=’+le
})
}
}
},
结果可以跳转:
[外链图片转存中...(img-ZAwdODZ2-1725371695071)]
## 三、程序运行结果
[外链图片转存中...(img-L91xWrVV-1725371695072)]
[外链图片转存中...(img-Q7FnGXWd-1725371695072)]
[外链图片转存中...(img-iylkyiUr-1725371695072)]
[外链图片转存中...(img-giP79Du0-1725371695072)]
### 跳转到下一关
[外链图片转存中...(img-zSmz8SZV-1725371695072)]
## 四、问题总结与体会
在进行推箱子游戏的实验过程中,canvas绘图逻辑对我来说是比较复杂的,学会后发现这是简单而有效的方法。确保每个元素的位置准确,能够随着游戏逻辑的推进而动态变化。判断箱子的可推动性、玩家的移动路径需要细心。这些都让我不断学习和探索
此外,我成功实现了游戏成功后自动跳转到下一关的功能,这为玩家带来了更加流畅的游戏体验。