推箱子小程序

2024年夏季《移动软件开发》实验报告

姓名:冯欣怡 学号:22020006029
姓名和学号?冯欣怡,22020006029
本实验属于哪门课程?中国海洋大学24夏《移动软件开发》
实验名称?
博客地址?实验6:推箱子小程序-CSDN博客
Github仓库地址?Fxinyi/6_24_summer_WeiXinMiniprogram_PushBox (gitee.com)

(备注:将实验报告发布在博客、代码公开至 github 是 加分项,不是必须做的)

一、实验目标

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

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

二、实验步骤

0. 项目准备

(1)图片

创建项目,不使用云服务,不使用模板。

然后在文件夹中创建一个空文件夹images用于存放图片,将下载的图片放入:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1. 首页展示

首页用于展示游戏选关,展示4个关卡的图片

(1)window

先在app.js中设置window样式:

 "window":{
    "backgroundTextStyle":"light",
    "navigationBarBackgroundColor": "#00b5c7",
    "navigationBarTitleText": "推箱子",
    "navigationBarTextStyle":"black"
  },

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(2) 关卡图片

在index.js中设置data:

  data: {
    levels:[
      "level01.png",
      "level02.png",
      "level03.png",
      "level04.png"
    ]
  },

在index.wxml中用wxfor循环每个都显示:

<!--index.wxml-->
<view class="container">
  <!-- 标题 -->
  <view class="title">游戏选关</view>
  <!-- 关卡列表 -->
    <view class="levelBox">
    <view class="box" wx:for="{{levels}}" wx:key="level{{index}}"data-level="{{index}}">
      <image src="/images/{{item}}"></image>
      <text>第{{index+1}}关</text>
    </view>
    </view>
</view>

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2. 关卡页

点开每一关后是关卡页,即游戏页,包括2个部分

游戏窗口/控制区

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

game.wxss

/* pages/game/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;
}
 

game.wxml:

<!--pages/game/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>

展示第level关,然后用canvas展示游戏画面,最后是控制区,上下左右箭头button对应不同的函数,最后有一个重新开始按钮。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3. 跳转关卡

给每一个index.js的图片绑定跳转函数:

<view class="box" wx:for="{{levels}}" wx:key="level{{index}}" bindtap="chooseLevel" data-level="{{index}}">
      <image src="/images/{{item}}"></image>
      <text>第{{index+1}}关</text>
    </view>

chooseLevel函数,获取选择的level,携带level信息跳转到相应的关卡页,

  chooseLevel:function(e){
    let level=e.currentTarget.dataset.level
    wx.navigateTo({
      url: '../game/game?level='+level
    })
  },

4. 游戏逻辑

(1)游戏窗口

游戏窗口要用到关卡数据,咱们先把地图数据存放到公用的utils中‘

定义data.js,存放我们的map数据们,并将他们暴露给外界。

a.数据准备
//================================================
//地图数据map1-map4
//================================================
//关卡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]
}
b.游戏画面

思路:每一步画面都会改变,我们用onLoad函数进行画面的更新。

首先获取当前的关卡,关卡是0-3,所以显示的level应该是level+1,这里加一了,注意之后再用level找地图的话要减一(笑死了),然后创建canvas画布,调用initMap初始化地图数据。

onLoad: function(options) {
    //获取关卡
    let level= options.level
    //更新页面关卡标题
    this.setData({
      level: parseInt(level)+1
    })
    //创建画布上下文
    this.ctx = wx.createCanvasContext('myCanvas')
    //初始化地图数据
    this.initMap(level)
    //绘制画布内容
    this.drawCanvas()
  },

通过level获取到对应的data中的数据,在文件开头引用utils data.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

编写initMap函数:

1墙,2路,3终点,4箱子,5人物,0墙的外围

获取mapdata,mapdata表示的是每个格子是什么,而我们用两个数组存放,更新map和data;map表示能不能走,1不能走,2能走。我们把map的其他数字都更新成1/2,并且记录小鸟的初始坐标

 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函数,画出我们的游戏图
8*8的格子,每个格子边长40,我们绘制320的画布

通过map给img赋值,默认是ice,就是2-路,然后如果是1-墙:img=stone.3-终点:img=pig,

知道了每个点对应什么图像,接下来用drawImage在对应位置画出即可

ctx.drawImage('/images/icons/' + img + '.png', j * w, i * w, w, w)

然后通过box再在对应的位置叠加绘制箱子

再通过col row在对应的位置叠加绘制小鸟

这样叠加绘制就可以很便利的实现我们的动态画面,底层是静止的,小鸟和箱子是动态的:

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()
  },

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(2)控制区

a.上下左右

已经说到控制按键bingtap移动按键

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

下面定义这4个方向函数:

上:

如果不在最顶端才考虑上移,如果上方不是墙或箱子,可以移动小鸟并更新当前小鸟坐标

如果上方是箱子,不在最顶端才能考虑推动;

如果箱子上方不是墙或箱子更新小鸟坐标+箱子坐标

调用写的drawCanvas()函数,更新坐标

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

 * 自定义函数--方向键:左
   */
  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

 /**
   * 自定义函数--方向键:右
   */
  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()
    }
  },

这样我们就实现了移动箱子。

b. 游戏成功

之后就来到一个问题,如何判断游戏成功,何时判断游戏成功。

游戏成功:每个箱子都在终点处

每一次移动地图都会变化,所以应该每次移动后都判断箱子是否在终点处了

先写判断函数:checkWin+Iswin

  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(level) {
    if (this.isWin()) {
      wx.showModal({
        title: '恭喜',
        content: '游戏成功!',
        showCancel: false
      })
    }
    }
  },

每次移动都判断是否成功:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

c.重新开始

游戏期间需要重新开始,我们只需要从utils/data.js加载原始地图即可:

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

5. 跳转下一关

我玩的过程中想到游戏成功后可以跳转下一关,给checkwin加上一条navigateto就好:

记得我们的level已经加了1了,下一关直接就是

wx.navigateTo({
        url: '../game/game?level='+le
      })

不用再+1.

最后一关不用跳

  checkWin: function(level) {
    if (this.isWin()) {
      wx.showModal({
        title: '恭喜',
        content: '游戏成功!',
        showCancel: false
      })
      if(this.data.level!=4){
      console.log(this.data.level)
      var le=this.data.level
      console.log(le)
      wx.navigateTo({
        url: '../game/game?level='+le
      })
    }
    }
  },

结果可以跳转:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

三、程序运行结果

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

跳转到下一关

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

四、问题总结与体会

在进行推箱子游戏的实验过程中,canvas绘图逻辑对我来说是比较复杂的,学会后发现这是简单而有效的方法。确保每个元素的位置准确,能够随着游戏逻辑的推进而动态变化。判断箱子的可推动性、玩家的移动路径需要细心。这些都让我不断学习和探索

此外,我成功实现了游戏成功后自动跳转到下一关的功能,这为玩家带来了更加流畅的游戏体验。

ta.level!=4){
console.log(this.data.level)
var le=this.data.level
console.log(le)
wx.navigateTo({
url: ‘…/game/game?level=’+le
})
}
}
},


结果可以跳转:

[外链图片转存中...(img-ZAwdODZ2-1725371695071)]



## 三、程序运行结果

[外链图片转存中...(img-L91xWrVV-1725371695072)]

[外链图片转存中...(img-Q7FnGXWd-1725371695072)]

[外链图片转存中...(img-iylkyiUr-1725371695072)]

[外链图片转存中...(img-giP79Du0-1725371695072)]

### 跳转到下一关

[外链图片转存中...(img-zSmz8SZV-1725371695072)]

## 四、问题总结与体会

在进行推箱子游戏的实验过程中,canvas绘图逻辑对我来说是比较复杂的,学会后发现这是简单而有效的方法。确保每个元素的位置准确,能够随着游戏逻辑的推进而动态变化。判断箱子的可推动性、玩家的移动路径需要细心。这些都让我不断学习和探索

此外,我成功实现了游戏成功后自动跳转到下一关的功能,这为玩家带来了更加流畅的游戏体验。

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个简单的用JavaScript制作推箱子小程序的例子。 HTML代码: ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>推箱子小游戏</title> <style> .box { width: 50px; height: 50px; background-color: brown; position: absolute; } .player { width: 50px; height: 50px; background-color: blue; position: absolute; } .target { width: 50px; height: 50px; background-color: red; position: absolute; } </style> </head> <body> <div id="container"></div> <script src="app.js"></script> </body> </html> ``` CSS样式中定义了三种元素的样式,包括箱子、玩家和目标元素。 JS代码: ```js // 创建游戏元素 var container = document.getElementById('container'); var map = [ [1, 1, 1, 1, 1], [1, 0, 0, 2, 1], [1, 0, 3, 0, 1], [1, 2, 0, 4, 1], [1, 1, 1, 1, 1] ]; var playerPos = {x: 1, y: 1}; var boxes = []; var targets = []; for (var i = 0; i < map.length; i++) { for (var j = 0; j < map[i].length; j++) { var cell = document.createElement('div'); cell.classList.add('cell'); if (map[i][j] === 1) { cell.classList.add('wall'); } else if (map[i][j] === 2) { cell.classList.add('player'); playerPos.x = j; playerPos.y = i; } else if (map[i][j] === 3) { cell.classList.add('box'); boxes.push({x: j, y: i}); } else if (map[i][j] === 4) { cell.classList.add('target'); targets.push({x: j, y: i}); } cell.style.left = j * 50 + 'px'; cell.style.top = i * 50 + 'px'; container.appendChild(cell); } } // 监听键盘事件 document.addEventListener('keydown', function(event) { var dx = 0, dy = 0; switch (event.keyCode) { case 37: // Left dx = -1; break; case 38: // Up dy = -1; break; case 39: // Right dx = 1; break; case 40: // Down dy = 1; break; } var newPos = {x: playerPos.x + dx, y: playerPos.y + dy}; if (map[newPos.y][newPos.x] !== 1) { // 移动玩家元素 var player = document.querySelector('.player'); player.style.left = newPos.x * 50 + 'px'; player.style.top = newPos.y * 50 + 'px'; playerPos = newPos; // 检查是否推动箱子 for (var i = 0; i < boxes.length; i++) { if (boxes[i].x === newPos.x && boxes[i].y === newPos.y) { var newBoxPos = {x: boxes[i].x + dx, y: boxes[i].y + dy}; if (map[newBoxPos.y][newBoxPos.x] !== 1) { var box = document.querySelectorAll('.box')[i]; box.style.left = newBoxPos.x * 50 + 'px'; box.style.top = newBoxPos.y * 50 + 'px'; boxes[i] = newBoxPos; // 检查是否完成游戏 var finished = true; for (var j = 0; j < targets.length; j++) { if (targets[j].x === newBoxPos.x && targets[j].y === newBoxPos.y) { finished = false; break; } } if (finished) { alert('恭喜你完成游戏!'); } } break; } } } }); ``` JS代码中首先创建游戏元素,并将它们添加到HTML中。然后监听键盘事件,根据玩家按键来移动玩家元素,并检查是否推动了箱子。如果箱子被推动,更新箱子元素的位置,并检查是否完成了游戏。如果完成了游戏,弹出一个提示框。 这是一个简单的推箱子小程序,可以根据需要进一步优化和扩展。希望对你有帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值