2023年夏季《移动软件开发》实验报告
一、实验目标
1、综合应用所学的知识创建完整的推箱子游戏;2、熟练掌握和绘图API。
二、实验步骤
1.需求分析
1.1 首页功能需求
1.首页需要包含标题和关卡列表。
2.至少包括4个关卡选项,每个关卡显示预览图和第几关。
3.关卡列表点击可以跳转到对应的游戏画面。
1.2 游戏页功能需求
1.游戏页需要显示第几关,游戏画面,方向键和重新开始按钮。
2.点击方向键可以使游戏主角自行移动或推动箱子前进。
3.游戏画面为8x8小方块组成,包括地板,围墙,箱子,游戏主角小鸟和目的地。
4.点击重新开始按钮可以使游戏回归到初始位置并重新开始游戏。
2.页面配置
2.1 创建页面文件
2.2 删除和修改文件
2.3创建其他文件
3.视图设计
3.1 导航栏设计
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#E64340",
"navigationBarTitleText": "推箱子游戏",
"navigationBarTextStyle": "white"
},
3.2 页面设计
(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;
}
(2)首页设计
index.wxml
<!--index.wxml-->
<!-- 整体容器 -->
<view class="container">
<!-- 标题 -->
<view class="title">游戏选关</view>
<!-- 关卡列表 -->
<!-- 关卡列表 -->
<view class='levelBox'>
<view class='box' wx:for='{{levels}}' wx:key='index'-level bindtap='chooseLevel' data-level='{{index}}'>
<image src='/images/{{item}}'></image>
<text>第{{index+1}}关</text>
</view>
</view>
</view>
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)游戏页设计
game.wxml
<view class='container'>
<!-- 关卡提示 -->
<view class='title'>第{{level + 1}}关</view>
<!-- 游戏画布 -->
<canvas canvas-id='myCanvas'></canvas>
<!-- 方向键 -->
<view class='btnBox'>
<button type='warn' bindtap='up' size="mini">↑</button>
<view>
<button type='warn' bindtap='left' size="mini">←</button>
<button type='warn' bindtap='down' size="mini">↓</button>
<button type='warn' bindtap='right' size="mini">→</button>
</view>
</view>
<!-- 重新开始按钮 -->
<button type='warn' bindtap='restartGame' size="mini" >重新开始</button>
</view>
game.wxss
/* 游戏画布样式 */
canvas {
border: 1rpx solid;
width: 320px;
height: 320px;
}
/* 方向键按钮整体区域 */
.btnBox {
display: flex;
flex-direction: column;
align-items: center;
}
/* 方向键按钮第二行 btnBox里面的view */
.btnBox view {
display: flex;
flex-direction: row;
}
/* 所有方向键按钮 */
.btnBox button {
width: 90rpx;
height: 70rpx;
}
/* 所有按钮样式 */
button {
margin: 10rpx;
/* background-color: red; */
}
4.逻辑实现
4.1 公共逻辑
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]
}
4.2 首页逻辑
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
})
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
}
})
4.3 游戏页逻辑
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]
]
//方块的宽度 px
var w = 40
//初始化小鸟的行与列
var row = 0
var col = 0
Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 自定义函数--初始化地图数据
*/
initMap: function (level) {
// 读取原始的游戏地图数据
console.log("initMap===>>>:",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()
}
},
/**
* 自定义函数--游戏成功处理
*/
checkWin: function () {
if (this.isWin()) {
wx.showModal({
title: '恭喜',
content: '游戏成功!',
showCancel: false
})
}
},
/**
* 自定义函数--判断游戏成功
*/
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
},
/**
* 自定义函数--重新开始游戏
*/
restartGame: function () {
//初始化地图数据
this.initMap(this.data.level )
//绘制画布内容
this.drawCanvas()
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
//获取关卡
let level = options.level
//更新页面关卡标题
this.setData({
level: parseInt(level)
})
// console.log('onLoad setData', level,typeof(level))
//创建画布上下文
this.ctx = wx.createCanvasContext('myCanvas')
//初始化地图数据
this.initMap(level)
//绘制画布内容
this.drawCanvas()
}
})
三、程序运行结果
首页:
点击第二关,游戏页面如下:
游戏成功:
游戏重新开始:
四、问题总结与体会
此次实验我学会了如何使用组件以及小程序界面API中绘图相关的用法;对小游戏的开发有了深入的了解,同时学会了各种组件的使用。