实验6:推箱子游戏

一、实验目标

1、综合所学知识创建完整的推箱子游戏;

2、能够在开发过程中熟练掌握真机预览、调试等操作。

二、实验步骤

1.功能需求

本项目一共需要两个页面,即首页和游戏页面,其中首页用于呈现关卡菜单,点击对应难度的关卡后进入游戏画面。

(1)首页功能需求
  • 首页需要包含标题和关卡列表。

  • 关卡至少要有4个关卡选项,每个关卡显示预览图片和第几关。

  • 点击关卡列表可以打开对应的游戏画面。

(2) 游戏页功能需求
  • 游戏页面需要显示第几关、游戏画面、方向键和“重新开始”按钮。

  • 点击方向键可以使游戏主角自行移动或推动箱子前进。

  • 游戏画面由8x8的小方块组成,主要包括地板、围墙、箱子、游戏主角和目的地。

  • 点击“重新开始”按钮可以将箱子和游戏主角回归初始位置并重新开始游戏。

2.创建项目

  • 新建项目,选择空白文件夹,创建小程序,选择不使用云服务,模板选择JS基础。

  • app.json中的pages属性下的pages/logs/logs改为pages/game/game,保存后pages文件夹下自动生成game页面。

  • 删除logs目录及其所有内容。

  • 删除utils文件夹下内容,新建data.js文件。

  • 删除 index.wxmlindex.wxss全部代码。

  • 删除index.js中全部代码,输入关键词page,找到Page选项回车自动补全函数。

  • 删除app.wxss中全部代码。

  • 删除app.js中全部代码,输入关键词app,找到App选项回车自动补全函数。

  • 下载游戏图片链接: https://gaopursuit.oss-cnbeijing.aliyuncs.com/course/mobileDev/boxgame_images.zip,解压,图片素材放在项目中创建的images文件夹。

3.视图设计

3.1 导航栏设计

修改app.json文件中的 window属性,定义导航栏的效果,修改导航栏的背景色为红色,字体为白色

 "window":{
    "navigationBarBackgroundColor": "#E64340",
    "navigationBarTitleText": "推箱子游戏",
    "navigationBarTextStyle":"white"
  },

3.2 公共样式设计

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;
}
3.3 首页设计

首页主要包含两部分内容,即标题和关卡选择列表,使用<view>容器,内部使用数组循环。

  1. 容器视图 (container):

    • 使用 <view class='container'> 创建一个主要内容的容器。

  2. 标题视图 (title):

    • 使用 <view class='title'> 显示标题“选择关卡”。

  3. 关卡盒子视图 (levelBox):

    • 使用 <view class='levelBox'> 创建一个包含多个关卡盒子的容器。

    • 使用 for 循环遍历 levels 数组,为每个关卡生成一个盒子。每个盒子包含一个 <image src='/images/{{item}}'/> 标签,用于显示关卡的图片。每个盒子还包含一个 <text> 标签,显示关卡编号(例如,第1关)。

注意,levels 是数据源,应该在页面的 JavaScript 文件中定义和赋值。

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;
}

3.4游戏页面设计

用户点击首页的关卡列表,然后跳转到游戏页面,其包括游戏关卡标题,游戏画面,方向键和“重新开始”按钮,使用<view><canvas><button>组件用于实现关卡标题,图形绘制和重新开始按钮。

  1. 容器视图 (container):

    • 使用 <view class="container"> 创建一个主要内容的容器。

  2. 标题视图 (title):

    • 使用 <view class='title'> 显示当前关卡的标题,例如“第{{level}}关”。

  3. 画布 (canvas):

    • 使用 <canvas canvas-id="sokoban"></canvas> 创建一个画布,用于显示游戏内容。

  4. 按钮盒子视图 (btnBox):

    • 使用 <view class='btnBox'> 创建一个包含控制按钮的容器。

    • 包含一个向上的按钮 <button type='warn' bind:tap="up">⬆</button>

    • 包含一个嵌套的视图<view>,其中有四个方向按钮:

      • 向左按钮 <button type='warn' bind:tap="left">⬅</button>

      • 向下按钮 <button type='warn' bind:tap="down">⬇</button>

      • 向右按钮 <button type='warn' bind:tap="right">➡</button>

  5. 重新开始按钮 (restart):

    • 使用 <button type='warn' bind:tap="restart">重新开始</button> 创建一个重新开始游戏的按钮。

game.wxml代码:

<view class="container">
  <!--关卡提示-->
  <view class="title">第1关</view>
    
  <!--游戏画布-->
  <canvas canvas-id="myCanvas"></canvas>
  <!--方向键-->
  <view class="btnBox">
    <button type="warn">↑</button>
    <view>
      <button type="warn">←</button>
      <button type="warn">↓</button>
      <button type="warn">→</button>
    </view>
  </view>
  <!--重新开始按钮-->
  <button type="warn">重新开始</button>
</view>

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;
}

4.逻辑实现

4.1 公共逻辑

data.js文件中配置游戏地图的数据,分别使用map1~map4代表4个不同的关卡地图数据,以二维数组的形式存放。当前的地图均由8*8的放个组成,每个位置的数字代表着对应的图标素材,并且需要在data.js中使用module.exports语句暴露数据出口。

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]
]
​
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]
}

game.js中引用公共的js文件:

var data = require('../../utils/data.js')
4.2 首页逻辑
(1)关卡列表展示

index.js中的data数据中添加关卡的图片数据信息。

 data: {
    levels:[
      'level01.png',
      'level02.png',
      'level03.png',
      'level04.png',
    ]
  },
(2)点击跳转游戏页面

在首页点击游戏关卡,需要跳转到对应的游戏页面,所以为关卡列表绑定点击事件。

index.wxml部分代码:

    <view class="box" wx:for="{{levels}}" wx:key="index" bind:tap="chooseLevel" data-level="{{index}}">
        <image src="/images/{{item}}" />
        <text>第{{index+1}}关</text>
    </view>
  • 定义一个 chooseLevel 函数,处理关卡选择事件。

  • 函数通过 e.currentTarget.dataset.level 获取被点击关卡的索引。

  • 使用 wx.navigateTo 方法导航到游戏页面,并传递关卡索引作为参数。

  chooseLevel:function(e){
    let level = e.currentTarget.dataset.level
    wx.navigateTo({
      url:'../game/game?level='+level
    })
  },
4.3 游戏页逻辑
(1)显示当前第几关

game.js中接受关卡数据:

/**
   * 页面的初始数据
   */
  data: {
    level: 1
  },
​
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad(options) {
    let level = options.level
    this.setData({
      level: parseInt(level) + 1
    })
  },

game.wxml相关代码:

  <view class="title">第{{level}}关</view>

(2)游戏逻辑实现
(i)初始化游戏画面

首先 在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;

需要根据当前是第几关读取对应的游戏地图信息,并更新到游戏的初始数据中。

game.js 文件中添加initMap 函数,用于初始化游戏地图数据。data.js 文件中应该定义了 maps 数据源。

  1. 读取地图数据:

    • 使用 data.maps[level] 读取对应关卡的原始游戏地图数据。

  2. 双重循环解析地图数据:

    • 使用双重 for 循环遍历地图的每一个位置(8x8的地图)。

    • 初始化 bos 数组中的每个位置为 0

    • mapData 中的值赋给 map 数组。

  3. 更新地图和箱子数据:

    • 如果 mapData[i][j] 的值为 4,表示该位置有箱子,将 box[i][j] 设为 4,并将 map[i][j] 设为 2

    • 如果 mapData[i][j] 的值为 5,表示该位置是主角的位置,将 map[i][j] 设为 2,并记录主角的行列位置 rowcol

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++){
        bos[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;
        }
      }
    }
  },

然后在game.js中添加自定义函数drawCanvas用于将地图数据绘制画布上:

  1. 清除画布:

    • 使用 ctx.clearRect(0, 0, 320, 320) 清除画布内容。

  2. 绘制地图:

    • 使用双重 for 循环遍历地图的每一个位置(8x8的地图)。

    • 根据 map[i][j] 的值选择不同的图片(icestonepig),并使用 ctx.drawImage 绘制到画布上。

    • 如果 box[i][j] 的值为 4,表示该位置有箱子,绘制箱子图片。

  3. 绘制主角:

    • 使用 ctx.drawImage 在主角的位置绘制主角图片。

  4. 刷新画布:

    • 使用 ctx.draw() 刷新画布,显示绘制的内容。

  drawMap(){
    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/' + img + '.png', j * w, i * w, w, w)
        if(box[i][j] == 4){
          ctx.drawImage('/images/box.png', j * w, i * w, w, w)
        }
      }
    }
    ctx.drawImage('/images/bird.png',col * w, row * w, w, w)
​
    ctx.draw();
​
  },

最后在game.jsonLoad函数中创建画布上下文,并调用initMapdrawCanvas函数:

onLoad(options) {
    let level = options.level
    this.setData({
      level: parseInt(level) + 1
    })
    //创建画布上下文
    this.ctx = wx.createCanvasContext('myCanvas')
    //初始化地图数据
    this.initMap(level)
    //绘制画布内容
    this.drawCanvas()
  },

(ii)方向键逻辑实现

game.wxml中的4个方向键<button>,为其绑定点击事件:

<!--方向键-->
<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>

之后在game.js文件中添加自定义函数updownleftright分别用于实现游戏主角的上下左右四个方向的移动。

  1. 玩家移动:

    • 玩家可以通过上下左右按钮移动。

    • 每次移动时,检查目标位置是否为空地或目标位置,如果是,则更新玩家位置。

    • 如果目标位置是箱子,还需要检查箱子前方的格子是否为空地或目标位置,如果是,则同时移动箱子。

  2. 碰撞检测:

    • 玩家不能移动到墙壁或箱子前方是墙壁的格子。

    • 箱子不能被推到墙壁或其他箱子前方是墙壁的格子。

  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[box + 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
            boxd[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
            boxd[row][col + 1] = 0
            col = col + 1
          }
        }
      }
      this.drawCanvas()
      this.checkWin();
    }
  },
(3)判断游戏成功

胜利的逻辑是当所有箱子都被推到目标位置时,游戏即告胜利。遍历所有目标位置,检查每个目标位置上是否都有一个箱子。如果所有目标位置上都有箱子,则判定游戏胜利。

game.js文件中添加自定义函数isWin用于判断游戏是否已经成功。

isWin:function(){
    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函数用于弹出游戏成功提示对话框。

checkWin:function(){
    if(this.isWin()){
      wx.showModal({
        title: '恭喜',
        content: '游戏成功!',
        showCancel: false
      })
    }
  },

(4)重新开始游戏

game.wxml文件中,为“重新开始”按钮添加自定义点击事件:

<button type="warn" bindtap="restartGame" >重新开始</button>

game.js文件中添加restartGame函数,用于重新开始游戏:

restartGame: function () {
    //初始化地图数据
    this.initMap(this.data.level - 1)
    //绘制画布内容
    this.drawCanvas()
  },

三、程序运行结果

游戏首页:

点击第1关进入关卡:

点击上下左右按钮移动小鸟:

通关成功:

点击确定:

点击重新开始:

点击左上角返回首页:

点击第2关进入关卡:

四、问题总结与体会

        在本次实验中,我综合运用了所学的前端开发知识,成功创建了一个完整的推箱子游戏。通过这个项目,我不仅巩固了对微信小程序开发的理解,还提升了实际操作能力。

        在项目开始之前,我首先进行了详细的规划和设计。明确了项目的功能需求,包括地图布局、玩家移动、箱子推送、碰撞检测和胜利条件等。通过绘制游戏流程图和界面原型图,我对项目的整体结构有了清晰的认识。这一步骤帮助我在开发过程中有条不紊地进行,避免了不必要的返工。

        在代码编写过程中,我充分利用了微信小程序提供的各种组件。例如,使用 <view><button> 组件构建游戏界面,通过数据绑定和事件处理,实现了玩家的移动和箱子的推送功能。每次玩家移动时,我都进行了碰撞检测,确保玩家和箱子不会穿过墙壁或重叠。

        在调试过程中,我学会了使用微信开发者工具进行真机预览和调试。通过实时预览,我能够及时发现和修复页面布局和功能上的问题。此外,利用开发者工具提供的调试功能,我可以查看控制台输出、断点调试代码,进一步提高了调试效率。

        在项目中,我还学习了如何管理和存储游戏数据。通过二维数组表示游戏地图,记录玩家、箱子、墙壁和目标位置的坐标。每次玩家移动或推送箱子后,我都更新地图数据,并检查胜利条件。当所有箱子都被推到目标位置时,游戏即告胜利。

        在开发过程中,我遇到了一些技术难题,但通过查阅文档、参考示例代码和请教他人,我逐步解决了这些问题。这让我深刻体会到,前端开发是一项需要不断学习和实践的工作。未来,我将继续学习更多的前端技术,不断提升自己的开发能力。

        总的来说,这次实验不仅让我掌握了微信小程序开发的基本技能,还培养了我解决问题的能力和团队合作精神。通过这个项目,我对前端开发有了更深入的理解,也为今后的学习和工作打下了坚实的基础。

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

whyz蒟蒻

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值