2023年夏季《移动软件开发》实验报告
姓名:*** 学号:21020007***
姓名和学号? | ***,21020007*** |
---|---|
本实验属于哪门课程? | 中国海洋大学23夏《移动软件开发》 |
实验名称? | 实验6:推箱子小游戏 |
博客地址? | 用户Id: m0_62135967 用户名称:Pane |
Github仓库地址? | XXXXXXX |
(备注:将实验报告发布在博客、代码公开至 github 是 加分项,不是必须做的)
一、实验目标
1、创建一个可以推箱子的小游戏
二、实验步骤
1.项目创建:本项目创建选择空白文件夹boxGame,效果如图所示。单击“新建”按钮完成项目创建,然后准备手动修改页面配置。
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中全部代码
(3)创建其他文件:创建2个新的文件夹用于存放图片素材和存放公共JS文件,文件夹名称命名为images和utils,并将所需要用到的图片放进文件夹里
①添加图片文件:本项目将在首页中用到4幅图片,将图片复制到文件夹中,并在images文件夹下面新建二级文件夹icons,将全部的图标素材放进去
②在utils文件夹下新建data.js文件
3.设计
(1)导航栏设计:小程序默认导航栏是黑底白字的效果,可以通过在app.json中对window属性进行重新配置来自定义导航栏效果。可将导航栏背景色设置为珊瑚红色,字体为白色。更改后的app.json文件代码如下:
.container{ height:100vh; color:#E46340; font-weight:bold; display:flex; flex-direction:column; align-items:center; justify-content:space-evenly; } .title{ font-size:18pt; }
(2)页面设计: ①公共样式设计:首先在app.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; }
②首页设计:首页主要包含两部分内容,即标题和关卡列表,页面设计如下图所示:计划使用<view>容器,内部使用数组循环
③游戏页面设计:游戏页面需要用户点击首页的关卡列表,然后再新窗口中打开该页面。游戏页面包括游戏关卡标题,游戏画面。方向键和“重新开始”按钮,页面设计如下图所示:可以添加编译模式预览game页面
<!--game.wxml--> <view class='container'> <!-- 关卡提示 --> <view class='title'>第{{level}}关</view> <!-- 游戏画布 --> <canvas canvas-id='myCanvas'></canvas>
4.逻辑实现:
(1)公共逻辑:在公共JS文件中进行游戏地图数据的配置,这里分别使用map1~map4代表4个不同的关卡地图数据,以二维数组的形式存放。当前的地图均由8*8的放个组成,每个位置的数字代表着对应的图标素材,并且需要在data.js中使用module.exports语句暴露数据出口,代码如下
//================================================ //地图数据map1-map4 //地图数据:1墙,2路,3终点,4箱子,5人物,0墙的外围 //================================================ //关卡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] }
(2)首页逻辑:首页主要是由两个功能:一个是展示关卡列表,一个是点击图片可以跳转到游戏页面
①关卡列表展示:在JS文件中的data目录下录入关卡图片的数据信息,接着为关卡对应的<view>组件添加wx:for属性循环显示关卡列表数据和图片
<!--pages/menu/menu.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='{{index}}'> <image src='/images/{{item}}'></image> <text>第{{index+1}}关</text> </view> </view> </view>
(3)游戏页逻辑:主要有以下几个功能:显示当前是第几关、游戏地图的绘制、4个方向键可以移动游戏主角、点击“重新开始”按钮可以使游戏地图还原成最初的状态
①显示当前在第几关:在首页逻辑中已经实现了页面跳转并携带了关卡对应的图片信息,现在需要再游戏页面接收关卡信息,并且显示对应的图片内容。修改完代码后从首页点击不同关卡图片跳转就可以正确显示对应的内容了
onLoad: function(options) { //获取关卡 let level= options.level //更新页面关卡标题 this.setData({ level: parseInt(level)+1 }) //创建画布上下文 this.ctx = wx.createCanvasContext('myCanvas') //初始化地图数据 this.initMap(level) //绘制画布内容 this.drawCanvas() },
②游戏逻辑实现:
a.准备工作:首先在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
b.初始化游戏画面:首先需要根据当前是第几关读取对应的游戏地图信息,并更新到游戏初始数据中。在game.js文件中添加initMap函数,用于初始化游戏地图数据;在game.js中添加自定义函数用于将地图信息绘制到画布上
/** * 自定义函数--初始化地图数据 */ 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 } } } },
c.方向键逻辑实现:修改game.wxml页面中的4个方向键<button>,为其绑定点击事件;在game.js文件中添加自定义函数up、down、left和right,分别用于实现游戏角色在上下左右4个方向的移动,每次点击在条件允许的情况下移动一格(以“上”为例)
/** * 自定义函数--方向键:上 */ 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() } },
③判断游戏成功:在game.js文件中添加自定义函数isWin,用于判断游戏是否已经成功(判断逻辑是只要有一个箱子没有在重点位置就判断游戏尚未成功)最后在game.js的4个方向键函数中追加关于游戏成功判断的函数,这里以UP函数为例,对应的JS代码也要进行修改
/** * 自定义函数--判断游戏成功 */ 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() { if (this.isWin()) { wx.showModal({ title: '恭喜', content: '游戏成功!', showCancel: false }) } },
④重新开始游戏:修改game.wxml代码,为“重新开始”按钮追加自定义函数的点击事件
/** * 自定义函数--重新开始游戏 */ restartGame: function() { //初始化地图数据 this.initMap(this.data.level - 1) //绘制画布内容 this.drawCanvas() },
三、程序运行结果
1.选关界面
2.进行第1关
3.进行游戏
4.游戏成功
四、问题总结与体会
在编写推箱子游戏微信小程序代码的过程中,我遇到了一些问题,同时也学到了一些有关编程的知识和经验。
首先,我需要学习如何设计游戏的逻辑和界面。通过阅读相关教程和参考示例代码,我了解到推箱子游戏需要设计地图、角色和箱子等元素,并通过用户的操作来移动角色和推动箱子。这个过程让我对游戏设计有了更深入的了解。
其次,我遇到了如何处理用户操作和游戏逻辑的问题。我需要学习如何监听用户的按键事件,并根据用户的操作来移动角色和箱子。同时,我也学到了如何判断游戏胜利的条件,即箱子是否都被推到目标位置。这个过程让我更加熟悉了游戏逻辑的处理和判断。
在编写代码的过程中,我学到了如何处理游戏界面的渲染和更新。我需要学习如何使用<view>和<image>等组件来显示游戏的地图和元素,并通过更新元素的位置和状态来实现游戏的交互和动画效果。
另外,我还学到了如何处理游戏关卡的设计和切换。推箱子游戏通常有多个关卡,我需要学习如何设计关卡的地图和目标位置。通过学习相关技术和算法,我学到了如何使用二维数组来表示地图,以及如何根据玩家的操作来动态改变地图的状态。这个过程让我更加关注游戏的可扩展性和可玩性。
总的来说,通过编写推箱子游戏微信小程序代码,我不仅学到了游戏设计和逻辑处理的知识,还学到了如何处理用户操作、界面渲染和关卡设计等问题。这个实验让我更加熟悉了编程的一些基本概念和技巧,并提升了我的实践能力和问题解决能力。我相信这些经验和知识将对我今后的编程学习和开发工作有所帮助。
附录:源代码
//game.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
Page({
/**
* 页面的初始数据
*/
data: {
level: 1
},
/**
* 自定义函数--初始化地图数据
*/
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: 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()
},
/**
* 自定义函数--方向键:上
*/
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()
}
},
/**
* 自定义函数--判断游戏成功
*/
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() {
if (this.isWin()) {
wx.showModal({
title: '恭喜',
content: '游戏成功!',
showCancel: false
})
}
},
/**
* 自定义函数--重新开始游戏
*/
restartGame: function() {
//初始化地图数据
this.initMap(this.data.level - 1)
//绘制画布内容
this.drawCanvas()
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function(options) {
//获取关卡
let level= options.level
//更新页面关卡标题
this.setData({
level: parseInt(level)+1
})
//创建画布上下文
this.ctx = wx.createCanvasContext('myCanvas')
//初始化地图数据
this.initMap(level)
//绘制画布内容
this.drawCanvas()
},
})
<!--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>
/**game.wxss**/
/* 游戏画布样式 */
canvas {
border: 1rpx solid;
width: 300px;
height: 300px;
}
/* 方向键按钮整体区域 */
.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;
}
// pages/index/index.js
Page({
/**
* 页面的初始数据
*/
data: {
levels: [
'level01.png',
'level02.png',
'level03.png',
'level04.png'
]
},
/**
* 自定义函数--游戏选关
*/
chooseLevel: function(e) {
let level = e.currentTarget.dataset.level
wx.navigateTo({
url: '../game/game?level=' + level
})
},
})
<!--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='{{index}}'>
<image src='/images/{{item}}'></image>
<text>第{{index+1}}关</text>
</view>
</view>
</view>
/* 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;
}
data.js
//================================================
//地图数据map1-map4
//地图数据:1墙,2路,3终点,4箱子,5人物,0墙的外围
//================================================
//关卡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]
}