【方块消消乐】方块消除游戏-微信小程序开发流程详解

有做过俄罗斯方块游戏小程序的经验,这次有做了一个消灭方块的游戏,实现过程很顺利,游戏看着和之前做的俄罗斯方块游戏很像,这里调整了玩法,试玩感觉还可以,接下来给大家讲一讲消灭方块游戏开发过程。

俄罗斯方块游戏文章 【俄罗斯方块】单机游戏-微信小程序项目开发入门

这里的消灭方块游戏,也叫方块消消乐游戏,

小程序

用微信开发工具打开,新建一个小程序项目,例如项目名miniprogram-BoxDesalination

如下图,依次选择即可

  • AppID 选自己申请的测试号
  • 不使用云服务(不免费)
  • JavaScript - 基础模板

这里选创建小程序项目,因为这开发比小游戏项目开发难度低了不少,很适合新手学习

初始页面

初始页面文件位置在/pages/index/index

在页面index.wxml布局文件中添加按钮,添加内容如下,点击按钮进入游戏,

<view>
   <button type="default" bindtap="onClickKey" data-key="enter">进入游戏</button>
</view>

然后在页面index.js逻辑文件中添加进入游戏的点击事件

Component({
    
    
	methods: {
    
    
        // 事件处理函数
		onClickKey(e) {
    
    
			wx.navigateTo({
    
    
		    	url: '/pages/game/game',
		    })
		}
	}
}

现在这个时间点,微信开发工具自动创建的小程序项目的初始页面跟旧版的不同了,
初始页面以前是用页面Pages对象,
现在发现是用的自定义组件Component展示,添加的方法是在methods里面的

游戏页面

创建一个游戏页面,文件位置在/pages/game/game

打开页面game.wxml布局文件,布局内容如下

<!--pages/game/game.wxml-->
<view class="page">
    <canvas class="canvas" type="2d" id="zs1028_csdn" bindtouchstart="onTouchStart" bindtouchmove="onTouchMove" bindtouchend="onTouchEnd" disable-scroll="{
     
     {true}}"></canvas>
</view>

只需要放一个组件canvas就好

游戏逻辑

接下来,准备写游戏逻辑,首先思考如何初始化页面,

打开页面game.js逻辑文件,在里面的onReady()方法里写初始化代码,得到一个canvas

// pages/game/game.js
import ZS1028_CSDN from '../../utils/zs1028_CSDN.js'
import BLOCK from '../../utils/block.js'

const app = getApp()

Page({
    
    
	/**
     * 生命周期函数--监听页面初次渲染完成
     */
    async onReady() {
    
    
        const {
    
     node:canvas, width, height } = await ZS1028_CSDN.query('#zs1028_csdn')
        Object.assign(canvas, {
    
     width, height })
        //创建小游戏引擎(框架)对象,传入canvas
        const game = ZS1028_CSDN.createMiniGameEngine({
    
    
            canvas,
            // isTest: true //性能测试用途
        })
        this.game = game
		// 初始化游戏数据...
		// 背景和网格数据
		const bgGridsData = {
    
    ...}
		// 所有的方块数据
		const blocksData = {
    
    ...}
		// 游戏状态数据
		const stateData = {
    
    ...}
		// 初始化游戏数据都放到gameData中,方便下次获取
		this.gameData = {
    
    
		    bgGridsData,
		    blocksData,
		    stateData
		}
		
		// 调用这个会在画布中划分一块区域在顶部显示游戏状态
		game.initTopBar({
    
    
		    data:stateData,
		    reset(){
    
    ...},
		    redraw(data){
    
    ...}
		})
		// 将上面的方法都加入到游戏引擎对象中...
		game.addBgObject({
    
    
		    data:bgGridsData,
		    reset(){
    
    ...},
		    redraw(data){
    
    ...},
		    methods:{
    
    ...}
		})
		game.addBgObject({
    
    
		    data:blocksData,
		    reset(){
    
    ...},
		    redraw(data){
    
    ...},
		    methods:{
    
    ...}
		})
		// 调用开始游戏方法
		this.restart()
    },
    /**
    * 以下都是触摸产生事件调用的方法
    */
    onTouchStart(e) {
    
    ...},
    onTouchMove(e) {
    
    ...},
    onTouchEnd(e) {
    
    ...},
)}

看上面,用了两个模块,省了很多代码,这样实现和读代码很容易;

  • 模块ZS1028_CSDN,是小游戏引擎(框架)对象,154行代码;
  • 模块BLOCK,是封装了方块的一些处理方法,186行代码;

游戏背景

初始化的游戏背景对象是bgGridsData,如下代码,看看有哪些数据

const bgGridsData = {
    
    
	grids1:[],//上面的网格
    grids2:[],//下面的网格
    cols: 20,//网格列数
    isShowGrids: false //是否展示网格,调试用途,默认不展示
}

将背景对象添加到game对象的属性data中,

如下代码,在方法redraw()实现绘制背景

game.addBgObject({
    
    
    data:bgGridsData,
    reset(){
    
    ...}, //这里是可实现重置背景数据的方法
    redraw(data){
    
    
        const {
    
     topBar, canvas, context:ctx } = data
        // 如果没有背景图片,就需要绘制出来,也是背景初始化的逻辑
        if (!this.cacheImg) {
    
    
            //省略更多...在这里实现绘制背景和网格
            
            //绘制好了,就导出为图像对象
            let img = canvas.createImage()
            img.onload = () => this.cacheImg = img
            img.src = canvas.toDataURL()
            
            //最后要处理保存背景数据...
            return
        }
        // 有背景图像了,这里就把图像绘制出来
        ctx.drawImage(this.cacheImg,0,0,canvas.width,canvas.height)
        // 这里判断初始化的网格大小
        if (bgGridsData.gridSize>0) {
    
    
        	// 设定方块的颜色
            ctx.strokeStyle = '#000000'
            ctx.fillStyle = '#000000'
            // 上面的网格有数据以后
            this.data.grids1.forEach((grid)=>{
    
    
                if (!grid.isBlock) return
                //有方块的话,就调用方块模块的绘制方法,绘制出来方块
                BLOCK.drawBlockAtGrid(ctx,bgGridsData.gridSize,grid.x,grid.y)
            })
        }
    },
    methods:{
    
    
        //类似组件的方法...都在这里添加实现,在内部调用
    }
})

网格背景图像是静态的,可以当作缓存来绘制,
未变化的图形是不建议重新初始化处理的,这会消耗CPU计算资源

游戏方块

初始化游戏方块的是在blocksData对象里,

如下代码,看看有哪些数据

const blocksData = {
    
    
    blocks:[],// 所有方块列表数据
    // 选择方块的
    select:{
    
    
        index:-1, // 执行方块列表数据的索引
        // 点击开始点
        startPoint:{
    
    
            x:-1,
            y:-1
        },
        // 拖动位置点
        movePoint:{
    
    
            x:-1,
            y:-1
        },
        // 位置变化差
        offsetX:0,
        offsetY:0
    }
}

能看懂上面的一些定义吗,看到后面就会知道有什么用了吧

将方块对象添加到game对象的属性data中,

如下代码,在方法redraw()实现绘制所有方块

const that = this
game.addBgObject({
    
    
    data:blocksData,
    //重置方法
    reset(){
    
    
        const {
    
     blocks } = this.data
        blocks.length = 0
        //调用下面的方法,分别设置三个随机方块在下面的网格中,传入的是索引
        that.setRandomBlock()
        that.setRandomBlock(1)
        that.setRandomBlock(2)
    },
    redraw(data){
    
    
        const {
    
    canvas,context:ctx} = data
        const {
    
    blocks,select}=this.data
        // 遍历所有方块
        blocks.forEach((block,index)=>{
    
    
            let offsetX=0
            let offsetY=0
            //如果选择了下面网格的一个方块
            if (index==select.index){
    
    
            	//调用模块的方法,查找此方块的索引
                let gIndex = BLOCK.findIndexFormGrid(bgGridsData.cols,block,0)
                //计算方块的开始点和结束点
                let startPoint = ...
                let endX = ...
                let endY = ...
                //此处省略了...
                //再绘制选中的方块背景...
                ctx.strokeStyle = '#000000'
                ctx.fillStyle = '#777777'
                ctx.beginPath()
                ctx.rect(startPoint.x,startPoint.y,endX,endY)
                ctx.fill()
                ctx.stroke()
                //调用模块的更新选择方法,就是改变select.offsetX和select.offsetY
                BLOCK.updateSelectBlockLocation(this.data.select)
                //拖动改变了位置
                offsetX = this.data.select.offsetX
                offsetY = this.data.select.offsetY
                //获取下面方块在网格的数据
                let grid = bgGridsData.grids2[block.index]
                if (grid) {
    
    
                	//调用模块的方法,获取拖动的方块在上面投影出来网格的方块
                    let mapBlock = BLOCK.findMappingBlock(bgGridsData.cols,bgGridsData.gridSize,bgGridsData.grids1,block,grid.x+offsetX,grid.y+offsetY)
                    //如果有的话,就绘制出来
                    if (mapBlock) {
    
    
                        ctx.strokeStyle = '#000000'
                        ctx.fillStyle = '#999999'
                        //调用此方法绘制方块mapBlock,方法是在下面的methods定义的
                        this.drawBlock(ctx,bgGridsData.grids1,mapBlock)
                    }
                    //最后将投影出来的方块存到起
                    this.data.mapBlock = mapBlock
                }
            }
            ctx.strokeStyle = '#000000'
            ctx.fillStyle = '#000000'
            //调用此方法绘制方块block,方法是在下面的methods定义的
            this.drawBlock(ctx,bgGridsData.grids2,block,offsetX,offsetY)
        })
    },
    methods:{
    
    
        drawBlock(ctx,grids,block,offsetX=0,offsetY=0){
    
    
        	//方块有个属性list,这里记录方块的每一块相对坐标位置
            block.list.forEach((g,i)=>{
    
    
                //此处省略了...绘制方块的每一块
            })
        }
    }
})

开始游戏

在开始的游戏方法里,调用game的方法run()即可,

在调用游戏开始前,需要调用reset()就可以重置游戏数据,代码如下

restart() {
    
    
   const {
    
    game}=this
    const {
    
    stateData}=this.gameData
	//关闭定时器
    this.closeTimer()
    //重置游戏
    game.reset()
    //运行游戏
    game.run()
    //定时更新
    this.timer = setInterval(()=>{
    
    
        stateData.timerNum--
        if (stateData.timerNum==0){
    
    
            stateData.isGameEnd = true
            this.closeTimer()
            app.setMaxScope(stateData.scope)
            wx.showModal({
    
    
              title: '系统提示',
              content: '游戏结束!当前得分'+stateData.scope,
              showCancel: true,
              cancelText: '返回游戏',
              confirmText: '重新开始',
              complete: (res) => {
    
    
                if (res.confirm) {
    
    
                  this.restart()
                }
              }
            })
            return
        }
    },1100)
},

写到这里,游戏基本就可以运行了,界面如下图
在这里插入图片描述

还没完呢,需要实现游戏交互,在触摸事件里实现

选择方块

开始触摸时,会调用方法onTouchStart(e)

代码如下,这里实现选择底部的方块

onTouchStart(e) {
    
    
    const touch = e.touches[0]
     const {
    
     bgGridsData, blocksData, stateData } = this.gameData
	//先判断游戏是否结束
     if (stateData.isGameEnd) return
	//获取触摸到方块的索引
     let blockIndex = blocksData.blocks.findIndex(block=>{
    
    
         return block.list.find((g,i)=>{
    
    
             //...调用模块的方法,查找触摸到的方块索引
             let index = BLOCK.findIndexFormGrid(bgGridsData.cols,block,i)
             let grid = bgGridsData.grids2[index + block.index]
             //判断指定网格位置
             return grid.x<touch.x && grid.x+bgGridsData.gridSize>=touch.x && grid.y<touch.y && grid.y+bgGridsData.gridSize >= touch.y
         })
     })
     //如果没有触摸到,重置选择返回
     if (blockIndex<0) {
    
    
         blocksData.select.index = -1
         return
     }
     //执行到这里,就是触摸到了,设置以下选择数据
     blocksData.select.index = blockIndex
     Object.assign(blocksData.select.startPoint,{
    
    
         x: touch.x,
         y: touch.y
     })
 },

看上面的代码,游戏交互的实现,只需要自己实现修改游戏数据就可以了,
具体的绘制逻辑,之前就有讲过,当数据变化,绘制的状态也会跟着变化

拖动方块

触摸移动时,也就是拖动操作,会调用方法onTouchMove(e)

代码如下,这里实现拖动方块

onTouchMove(e) {
    
    
   const touch = e.touches[0]
    const {
    
     bgGridsData, blocksData } = this.gameData
    if (blocksData.select.index<0) return
	//选择到方块,然后移动,就是拖动方块的操作了,边移动边更新它的位置
    Object.assign(blocksData.select.movePoint,{
    
    
        x: touch.x,
        y: touch.y
    })
},

消灭方块

不再触摸时,也就是拖动取消了,会调用方法onTouchEnd(e)

代码如下,这里实现放置方块

 onTouchEnd(e) {
    
    
   const {
    
     bgGridsData, blocksData, stateData } = this.gameData
    if (blocksData.select.index<0) return
    const {
    
     select, mapBlock } = blocksData
    //如果有投影出来的方块
    if (mapBlock) {
    
    
    	//这里就处理,把投影的方块设置到上面的网格中,就真的放置上去了
        mapBlock.list.forEach((value,index)=>{
    
    
            if (value<1) return
            let gIndex = BLOCK.findIndexFormGrid(bgGridsData.cols,mapBlock,index)
            let grid = bgGridsData.grids1[gIndex + mapBlock.index]
            grid.isBlock = true
        })
        //放置好了,接下来,再出一个随机方块
        this.setRandomBlock(select.index)
        //调用模块的方法,算出消灭方块的数量,同时会修改网格的方块数据(清空操作)
        let count = BLOCK.decreaseBlocksFormGrids(bgGridsData.grids1,bgGridsData.cols)
        if (count>0) {
    
    
        	//更新游戏状态,加分,加时间
            stateData.scope += count
            stateData.timerNum += 60
            //弹出提示
            wx.showToast({
    
    
              title: `消灭方块,加60秒`,
              icon: 'none'
            })
        }
    }
	//以下是重置选择数据
    select.index = -1
    Object.assign(select.startPoint, {
    
    
        x: -1,
        y: -1
    })
    Object.assign(select.movePoint, {
    
    
        x: -1,
        y: -1
    })
},

还有对象game有个方法game.initTopBar(),是初始化顶部显示的游戏状态,看看游戏效果里有,

其它方法,以及模块的一些方法,这里不多讲了,不是重点,

建议看源代码吧,代码不多,值得研究学习

游戏测试

写到这里,方块消消乐游戏就算完成了,可以运行编译测试,

运行效果图如下,点鼠标拖动底部的方块,任选一个,拖放到上面的框中,
在这里插入图片描述

  • 当某行或某列没有留空的话,就会被消灭了,会奖励时间;
  • 底部出现的方块是随机的,玩家根据自己的想法去拖放方块,
  • 看谁消灭的多,得分就越高哦,同时会记录下来;

想看项目源码的前往下载请点这里查找,若不方便查找请在右侧输入关键词方块消消乐搜索即可,本博客站内请放心下载,感谢支持!

可能手机上看不到下载点,请改用电脑浏览器查看

请添加图片描述

猜你喜欢

转载自blog.csdn.net/zs1028/article/details/134621988