新建项目
新建一个空的微信小程序项目。
配置文件
-
在pages页面下配置home、game相关页面文件;
-
在app.js、app.json和app.wxss文件中进行全局配置;
-
新建images文件夹,在文件夹下导入所需图片素材;
-
在utils文件夹下的data.js文件夹中配置关卡数据。
功能分析
基础功能:
(1) 首页需要包含标题和关卡列表。 (2) 关卡至少要有4个关卡选项,每个关卡显示预览图片和第几关。 (3) 点击关卡列表可以打开对应的游戏画面。
添加功能:
(1) 为每个关卡添加倒计时功能,限时60秒,超时后若没有通关则提醒用户选择继续游戏或者重新开始。
(2) 为每个关卡添加步数记录功能,每移动一步步数加1。
(3) 为全局添加背景音乐。
(4) 为每个关卡添加撤回功能,用户可以撤回每一步,撤回时计时不暂停,步数不增加。
(5) 为全局添加背景,进一步优化界面。
页面设计与实现
首页
-
使用view、image和text标签实现首页标题和关卡列表展示及点击跳转功能:
<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>
对应JavaScript函数实现功能:
Page({ data: { levels: [ '../../images/level01.png', '../../images/level02.png', '../../images/level03.png', '../../images/level04.png' ] }, chooseLevel: function(e) { let level = e.currentTarget.dataset.level wx.navigateTo({ url: '../game/game?level=' + level }) },
关卡页面
-
使用view,image和text标签实现新闻内容的显示。
<image id="lb" src="../../images/喇叭.png"></image> <view class='title'>第 {{level}} 关</view> <view class="container2"> <view class="timer">还剩 {{timeLeft}} 秒</view> <text class="walk">步数: {{steps}}</text> </view>
onLoad: function(options) { //获取关卡 const backgroundAudioManager = wx.getBackgroundAudioManager(); backgroundAudioManager.src = '../../music/back.mp3'; backgroundAudioManager.play(); //监听背景音频播放结束事件 backgroundAudioManager.onEnded(() => { console.log('背景音乐播放结束'); }); //监听背景音频播放错误事件 backgroundAudioManager.onError((res) => { console.error('背景音乐播放出错', res.errMsg); }); let level= options.level //更新页面关卡标题 this.setData({ level: parseInt(level) + 1 }) //创建画布上下文 this.ctx = wx.createCanvasContext('myCanvas') //初始化地图数据 this.initMap(level) //绘制画布内容 this.drawCanvas(), // 开始倒计时 this.startTimer(); }, startTimer: function() { let timeLeft = this.data.timeLeft; let timerId = setInterval(() => { if (timeLeft > 0) { timeLeft--; this.setData({ timeLeft: timeLeft }); } else { // 当timeLeft不大于0时,停止倒计时 this.timeOut(); } }, 1000); // 将timerId保存到页面的data中,以便可以在其他地方访问它 this.setData({ timerId: timerId }); }, timeOut: function() { clearInterval(this.data.timerId); wx.showModal({ title: '超时', content: '时间耗尽,您没有过关!是否重新开始?', success: (res) => { if (res.confirm) { this.restartGame(); } else if (res.cancel) { console.log('用户点击取消'); } } }); },
-
使用canvas标签实现游戏画布可视化功能。
<canvas canvas-id='myCanvas'></canvas>
通过addFavorites和addFavorites函数,结合if判断语句和isAddsc的状态实现收藏和取消收藏功能。
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, timeLeft: 60, // 初始倒计时60秒 steps: 0, history: [], }, updateSteps: function() { this.setData({ steps: this.data.steps + 1 }); }, 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 } } } }, 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() },
-
使用button组件实现移动功能。
<view class='btnBox'> <button type='warn' bindtap='up'>↑</button> <view> <button type='warn' bindtap='left'>←</button> <view class="noUse"> </view> <button type='warn' bindtap='down'>↓</button> <view class="noUse"> </view> <button type='warn' bindtap='right'>→</button> </view> </view>
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.updateSteps(); this.saveState(); //重新绘制地图 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.updateSteps(); this.saveState(); //重新绘制地图 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.updateSteps(); this.saveState(); //重新绘制地图 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.updateSteps(); this.saveState(); //重新绘制地图 this.drawCanvas() //检查游戏是否成功 this.checkWin() } },
-
使用button组件实现重新开始和撤回功能。
<button type='warn' bindtap='restartGame'>重新开始</button> <button bindtap="undo">撤回</button>
restartGame: function() { this.setData({ timeLeft: 60, // 假设初始时间为60秒 // 重置其他游戏状态... }); //初始化地图数据 this.initMap(this.data.level - 1) //绘制画布内容 this.drawCanvas(); this.startTimer(); }, saveState: function() { // 假设 getState 是一个函数,返回当前游戏状态的对象 let state = this.getState(); // 将当前状态添加到历史记录中 this.data.history.push(state); // 更新当前步骤 this.setData({ currentStep: this.data.history.length - 1 }); }, // 获取当前游戏状态 getState: function() { return { map: JSON.parse(JSON.stringify(map)), // 深拷贝地图 box: JSON.parse(JSON.stringify(box)), // 深拷贝箱子 row: row, col: col }; },
-
检测游戏是否结束。
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 }) } },
效果: