2024年夏季《移动软件开发》实验六-推箱子游戏

实验六-推箱子游戏

一、实验目标

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

三、程序运行结果

列出程序的最终运行结果及截图。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

四、问题总结与体会

问题总结

在完成推箱子游戏实验的过程中,我遇到了几个主要问题,并逐一进行了解决:

  1. 图片资源加载问题

    • 问题描述:在游戏开始时,我尝试从服务器加载图片资源,但发现图片加载速度较慢,导致游戏初始化时界面显示不完整。
    • 解决方案:我首先检查了图片资源的URL是否正确,并确认服务器响应正常。随后,我优化了图片资源的大小和格式,采用压缩后的PNG格式图片,以减少加载时间。同时,在游戏逻辑中增加了图片加载完成的回调函数,确保所有图片资源加载完成后再进行游戏界面的渲染。
  2. canvas绘图性能问题

    • 问题描述:在游戏进行过程中,尤其是当箱子和角色移动时,canvas的绘图性能成为瓶颈,导致游戏画面出现卡顿现象。
    • 解决方案:我优化了canvas的绘图逻辑,尽量减少不必要的绘图操作。例如,只在箱子或角色位置发生变化时才重新绘制相关区域,而不是每次都重绘整个画布。此外,我还采用了双缓冲技术,即先在一个隐藏的canvas上绘制,然后再将绘制好的内容一次性复制到显示canvas上,以减少屏幕刷新次数,提高绘图性能。
  3. 游戏逻辑错误

    • 问题描述:在游戏开发初期,由于逻辑判断不够严谨,导致玩家在某些情况下可以非法移动箱子或角色,如穿过墙壁等。
    • 解决方案:我仔细检查了游戏的移动逻辑,确保每次移动都符合游戏规则。通过增加边界检查和状态管理,我修正了这些逻辑错误,使得游戏行为符合预期。

收获与体会

通过本次实验,我深刻体会到了以下几点:

  1. 综合应用能力:实验要求我们将所学的组件、绘图API以及小程序界面API等知识进行综合运用,这极大地锻炼了我的知识整合和应用能力。

  2. 问题解决能力:在实验中遇到问题时,我学会了如何分析问题、查找资料、尝试解决方案并最终解决问题。这种能力对于未来的学习和工作都至关重要。

  3. 团队协作与沟通:虽然本次实验是个人完成的,但我意识到在软件开发过程中,团队协作与沟通的重要性。未来在参与团队项目时,我将更加注重与团队成员的协作与沟通。

  4. 对课程安排的建议

    • 建议课程在介绍新知识时,能够结合更多的实际案例和项目,以便我们更好地理解和应用所学知识。
    • 适当增加实践环节的比例,让我们有更多的机会亲自动手实践,从而加深对知识的理解和掌握。
    • 可以在课程中引入一些前沿的技术和工具介绍,以拓宽我们的视野和知识面。
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值