实验六-推箱子游戏
一、实验目标
1、综合应用所学的知识创建完整的推箱子游戏;
2、熟练掌握和绘图API。
二、实验步骤
列出实验的关键步骤、代码解析、截图。
1、需求分析
本项目一共需要两个页面.即首页和游戏页面,其中首页用于呈现关卡菜单,点击对应难度的关卡后进人游戏画面。
首页功能需求如下:
(1)首页需要包含幻灯片播放效果和新闻列表;
(2)幻灯片至少要有3幅图片自动播放;
(3)点击新闻列表可以打开新闻全文。
新闻页功能需求如下:
(1)阅读新闻全文的页面需要显示新闻标题、图片、正文和日期;
(2)允许 点击按钮将当前阅读的新闻添加到本地收藏夹中;
(3)已经收藏过的新闻也可以点击按钮取消收藏。
个人中心页功能需求如下:
(1)未登录状态下显示登录按钮,用户点击以后可以显示微信头像和昵称。
(2)登录后读取当前用户的收藏夹,展示收藏的新闻列表。
(3)收藏夹中的新闻可以直接点击查看内容。
(4)未登录状态下收藏夹显示为空。
2、创建项目
3、页面配置
3.1 创建页面
项目创建完毕后,在根目录中会生成文件夹pages用于存放页面文件。一般来说首页默 认命名为index,表示小程序运行的第一个页面;其他页面名称可以白定义。本项目有两个页 面文件,需要创建index(首页页面)和game(游戏页面)。 具体操作如下: (1)将app.json文件内pages属性中的"pages/logs/logs"改成pages/game/game”。 (2)按快捷键Ctrl十S保存修改后会在pages文件夹下自动生成game目录。
3.2 删除修改页面
删除 index.wxml和index.wxss全部代码
删除index.js中全部代码,输入关键词page,找到Page选项回车自动补全函数
删除app.wxss中全部代码
删除app.js中全部代码,输入关键词app,找到App选项回车自动补全函数
3.3添加文件
在根目录下创建images和util文件夹,分别用于存储图片素材和JS文件
①添加图片文件,如图所示为所用图标文件,将图片拖入images文件夹即可
②创建公共JS文件
右击utils文件夹,选择“新建文件”,输入"data.js"回车后即创建公共函数data.js
图片为创建images和utils后的目录结构
4、代码展示
utils/data.js
//墙:1; 路:2; 终点:3; 箱子:4;人物:5 墙外围:0
var map1 = [
[0, 1, 1, 1, 1, 1, 0, 0],
[0, 1, 2, 2, 1, 1, 0, 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]
]
module.exports = {
maps: [map1, map2, map3, map4]
}
app.js
App({
/**
* 当小程序初始化完成时,会触发 onLaunch(全局只触发一次)
*/
onLaunch: function () {
},
/**
* 当小程序启动,或从后台进入前台显示,会触发 onShow
*/
onShow: function (options) {
},
/**
* 当小程序从前台进入后台,会触发 onHide
*/
onHide: function () {
},
/**
* 当小程序发生脚本错误,或者 api 调用失败时,会触发 onError 并带上错误信息
*/
onError: function (msg) {
}
})
app.json
{
"pages":[
"pages/index/index",
"pages/game/game"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#E64340",
"navigationBarTitleText": "推箱子游戏",
"navigationBarTextStyle":"black"
},
"sitemapLocation": "sitemap.json"
}
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;
}
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
})
}
})
index.json
{
"usingComponents": {
}
}
index.wxml
<view class="container">
<!-- 标题 -->
<view class="title">游戏选关</view>
<!-- 关卡列表 -->
<view class="levelBox">
<view class="box" wx:for="{{levels}}" wx:key="levels{{index}}" bind:tap="chooseLevel" data-level="{{index}}">
<image src="/images/{{item}}"/>
<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;
}
game.js
//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.json
{
"usingComponents": {}
}
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
/**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;
}
三、程序运行结果
列出程序的最终运行结果及截图。
四、问题总结与体会
问题总结
在完成推箱子游戏实验的过程中,我遇到了几个主要问题,并逐一进行了解决:
-
图片资源加载问题:
- 问题描述:在游戏开始时,我尝试从服务器加载图片资源,但发现图片加载速度较慢,导致游戏初始化时界面显示不完整。
- 解决方案:我首先检查了图片资源的URL是否正确,并确认服务器响应正常。随后,我优化了图片资源的大小和格式,采用压缩后的PNG格式图片,以减少加载时间。同时,在游戏逻辑中增加了图片加载完成的回调函数,确保所有图片资源加载完成后再进行游戏界面的渲染。
-
canvas绘图性能问题:
- 问题描述:在游戏进行过程中,尤其是当箱子和角色移动时,canvas的绘图性能成为瓶颈,导致游戏画面出现卡顿现象。
- 解决方案:我优化了canvas的绘图逻辑,尽量减少不必要的绘图操作。例如,只在箱子或角色位置发生变化时才重新绘制相关区域,而不是每次都重绘整个画布。此外,我还采用了双缓冲技术,即先在一个隐藏的canvas上绘制,然后再将绘制好的内容一次性复制到显示canvas上,以减少屏幕刷新次数,提高绘图性能。
-
游戏逻辑错误:
- 问题描述:在游戏开发初期,由于逻辑判断不够严谨,导致玩家在某些情况下可以非法移动箱子或角色,如穿过墙壁等。
- 解决方案:我仔细检查了游戏的移动逻辑,确保每次移动都符合游戏规则。通过增加边界检查和状态管理,我修正了这些逻辑错误,使得游戏行为符合预期。
收获与体会
通过本次实验,我深刻体会到了以下几点:
-
综合应用能力:实验要求我们将所学的组件、绘图API以及小程序界面API等知识进行综合运用,这极大地锻炼了我的知识整合和应用能力。
-
问题解决能力:在实验中遇到问题时,我学会了如何分析问题、查找资料、尝试解决方案并最终解决问题。这种能力对于未来的学习和工作都至关重要。
-
团队协作与沟通:虽然本次实验是个人完成的,但我意识到在软件开发过程中,团队协作与沟通的重要性。未来在参与团队项目时,我将更加注重与团队成员的协作与沟通。
-
对课程安排的建议:
- 建议课程在介绍新知识时,能够结合更多的实际案例和项目,以便我们更好地理解和应用所学知识。
- 适当增加实践环节的比例,让我们有更多的机会亲自动手实践,从而加深对知识的理解和掌握。
- 可以在课程中引入一些前沿的技术和工具介绍,以拓宽我们的视野和知识面。