【俄罗斯方块】单机游戏-微信小程序项目开发入门
这是一个仿俄罗斯方块小游戏的微信小程序,只需要写一小段代码就实现出来了,有兴趣的同学完全可以自己动手开发,来看看实现过程是怎样的呢,边写边做,一起来回忆小时候玩过的经典俄罗斯方块游戏吧。
文章目录
- 创建小程序
- 页面设计
- 页面逻辑
- 初始化
- 定时刷新
- 完善细节
- 运行小程序
💡 给读者一个小小提示
需要有HTML和CSS基础
还有JavaScript基础
电脑有安装好微信小程序开发工具满足以上三个条件就能看懂接下来的一些内容了
创建小程序
首先,打开微信小程序开发工具,如下图所示,
新建一个小程序项目minigame-tetris,就选择小程序,注意不要选择使用云服务和模板,然后点确定即可,
这时项目在开发者工具中自动创建好了,会发现生成的文件不会太多,这是为方便精简项目源码,避免新建的项目自动生成一大堆对新手来说不懂的东西,此项目开发对新手非常友好的!
页面设计
在项目根目录下,打开一个页面布局文件,文件路径为pages/index/index.wxml
,如下图所示,这是我们要做的页面显示效果,准备编写布局标签实现它吧
可能新手想不太明白,那么说下原理,整个页面的布局只用到了一个画布Canvas
组件和两个图片image
,然后设置css
样式层层叠加放置就可以实现效果,布局文件内容参考如下,简单就好!
<!--index.wxml-->
<view class="container">
<view class="canvas-box">
<image src="{{bgImg}}" class="canvas-bg" />
<image src="{{foreImg}}" class="canvas-bg" />
<canvas type="2d" id="canv" class="canvas"></canvas>
</view>
<view class="footer">
<view class="btngroup">
<!-- 此处省略更多 -->
</view>
</view>
</view>
页面逻辑
接下来,想到需要编写游戏的逻辑,这下会不会感到难写了,以我们对俄罗斯方块游戏互动逻辑的了解,似乎实现起来不太难吧,别担心,站在巨人的肩膀上,看看怎么写得呢,
打开文件路径为pages/index/index.js
,开始编写代码,如下所示,理清一下逻辑,一边写一边思考
// index.js
const canvasId = 'canv';
//...
Page({
data:{
context:{},//存放其它的值
bgImg:'',//背景图
//...
},
//页面加载时,从此处开始执行
onLoad(){
//先获取canvas画布组件 大小size 和 节点node
wx.createSelectorQuery().in(this).select(`#${canvasId}`).fields({
size:true, node:true
},res=>{
const canvas = res.node;
//这里需要设置一下画布的宽高,不然画布实际大小会与实际不一致
canvas.width = res.width;
canvas.height = res.height;
//获取绘制2d画布的工具集合对象ctx
const ctx = canvas.getContext('2d');
//设置到页面的局部变量context里去,方便在其它方法中使用
Object.assign(this.data.context,{ canvas, ctx });
const base64 = this.initGrid();
this.initTimer();
//页面显示背景图
this.setData({
bgImg: base64
});
}).exec()
},
//页面卸载时
onUnload(){
//...
},
//初始化网格,或绘制游戏背景图
initGrid(){
//...
},
//初始化定时器
initTimer(){
//...
},
//用户按下按键,或点击的事件
onclickkey(e){
//...
},
//用户松开按键事件
onclickend(){
//...
}
})
初始化
初始化方法initGrid()
,这里面做的是初始化一些属性,和绘制背景图,还有网格数据,大致的实现逻辑如下,可以这样理解,游戏中的一些方块都是有自己位置的,需要网络来作为参照物,定方位
// index.js
import Tetris from '../../static/utils/tetris';//导入一个模块
Page({
data:{
context:{},
tetris:null,//方块相关的对象
//...
},
//...
//初始化网格,或绘制游戏背景图
initGrid(){
const { ctx, canvas } = this.data.context;
const cols = 28;//这里设定每一行内的方块数量
const gs = Math.trunc(canvas.width/cols);
const rows = Math.trunc(canvas.height/gs);
const padding = Math.trunc(canvas.width%cols/2);
//计算好了一些属性,如果要用到,就在局部变量context中存上去
Object.assign(this.data.context,{ padding, gs, rows, cols });
//这里我们用到了一个模块,这是对处理方块相关的封装,使用前需要先导入它
const tetris = new Tetris({rows,cols});
//先保存一下对画布的设置
ctx.save();
ctx.fillStyle='#000000';
ctx.strokeStyle='#fff';
ctx.rect(0,0,canvas.width,canvas.height);
ctx.fill();
ctx.fillStyle='#fff';
ctx.strokeStyle='#000';
ctx.beginPath();
// 这两行是绘制网格显示的,在测试中可以参照,游戏中我们会将它隐藏起来
// const grids = tetris.getGrids();
// grids.forEach(g=>this.drawGrid(g));
ctx.rect(padding,0,cols*gs,rows*gs);
ctx.fill();
ctx.stroke();
//绘制完,可以恢复设置了
ctx.restore();
this.data.tetris = tetris;
const data = canvas.toDataURL();
ctx.clearRect(0,0,canvas.width,canvas.height);//需要清除了,下次绘制用
return data;//绘制好了,返回图片信息
},
//...
}
定时刷新
初始化定时器方法initTimer()
,这方法做的是让游戏运动起来的意思,因为是用定时器实现刷新游戏状态的,如果对此不太了解,那么实现过程会是难以理解的,这个实现逻辑是较复杂的,大致代码如下
const TIME = 900;
//...
Page({
data:{
foreImg:'',//前置图,在背景图上一层
tetris:null,//方块相关的对象
timer:null,//定时器
isContinue:true,//游戏开始或暂停
downKey:null,//按下按键的信息
speed:1,//刷新速度调节阈值
score:0,//游戏得分
time:0,//游戏时长
//...
},
//游戏更新逻辑
refresh(offset = 0){
//...
},
//游戏结束
gameEnd(){
//...
},
//画方块(格子)
drawGrid(g,fill){
//...
},
//初始化定时器
initTimer(){
const { tetris } = this.data;
//使用模块对象的方法 这里先创建一个随机方块
let tetri = tetris.createReadomTetris();
//定义一个重绘的方法,实现它的逻辑
const redraw=()=>{
const { ctx, canvas } = this.data.context;
const { isContinue, downKey, speed, score, time } = this.data;
//如果游戏可以继续 并且 在没有按下键时
if(isContinue && !downKey){
let offset = 1;//向下移动1格
//方块在下落了,然后刷新显示,最后判断下落是否触底
if(this.refresh(offset)){
//方块无法向下移动 就判断游戏结束
if(tetri.gt==0){
this.gameEnd();
return;
}
//将方块加入到网格集合中,就是合并入,装下去的意思
tetris.joinGrids(tetri);
//接着尝试处理清理逻辑,会自动判断底下的所有方块是否装满,如果装满了一行会自动消除,返回成功消除后的得分
let scope = tetris.clearFillGrids();
//重新创建一个随机方块
tetri = tetris.createReadomTetris();
//获取网格集合
let grids = tetris.getGrids(true);
//清空画布
ctx.clearRect(0,0,canvas.width,canvas.height);
//将网格集合中所有方块绘制出来
grids.forEach(g=>this.drawGrid(g,true));
//绘制好了,弄成一个图片数据,放在页面前置背景图中显示
this.setData({
foreImg: canvas.toDataURL(),
score: score+scope,//更新显示得分加分
});
ctx.clearRect(0,0,canvas.width,canvas.height);
}else{
//方块下落一格
tetri.gt+=offset;
}
}
//需要调用这个方法,在不需要的时候会自动挂起,节省CPU处理开销
canvas.requestAnimationFrame(()=>{
let t = Math.trunc(TIME*speed);
//等待下一次重绘
this.data.timer = setTimeout(redraw,t);
this.setData({
time:time+t
})
});
};
//开始重绘
redraw();
},
//...
}
完善细节
上面讲得一些的方法都是比较详细的,应该能看得明白吧,对了,那个模块文件只需要实现一些方法就可以了,文件路径为/static/utils/tetris.js
,具体怎样写得,参考代码大致如下
//定义一个基本方块宽度,所占格数
const COLS = 4;
//定义一些基本方块,形状各不相同
const RetrisMini = [
[0,1,5,9],[0,1,2,5],[0,4,8,12],[0,1,4,5],[2,4,5,6],[1,4,5,9],[0,1,2,3],[0,4,8,9],[1,4,5,6],[0,1,2,4],[0,4,5,8]
];
/**
* 方块方法封装对象类
**/
export default class Tetris{
//一些私有属性
#grids;
#cols;
#rows;
#coordis;
#tetri;
constructor(config){
const { rows, cols } = config;//传参数两个属性,分别为行数和列数
const grids = new Array(rows*cols);
for(let i=0; i<rows; i++){
for(let j=0; j<cols; j++){
grids[i*cols+j]={ l:j, t:i };
}
}
this.#grids=grids;//初始化一些属性
this.#cols=cols;
this.#rows=rows;
this.#coordis=[];
}
moveTetriAtHorizontal(l,success){
//...左右移动方块
}
getGrids(filter){
//...获取那些方块占用的网格集合
}
getGridsIndex(l,t){
//...根据表格坐标计,算出一格的索引值返回
}
joinGrids(titri,l,t){
//...将一个方块并入网格集合中
}
clearFillGrids(){
let scope=0;
//...如果有,清理填平的一行方块,加分
return scope;
}
isIntoTetris(g){
//...判断方块位置是否与其它实物发生重叠,也就是说是否发生碰撞
return true;
}
isTouchTetris(titri,l,t){
//...判断方块位置是否触底,也就是说是否还能往下落
}
createCoordis(cs){
//...给方块创建一个位置坐标
}
createReadomTetris(){
//...创建一个随机的方块
}
getTetris(){
return this.#tetri;
}
rotateRightAngle(success){
//...方块顺时针方向旋转角度
}
}
💡一个问题
可能有注意到了,代码中的RetrisMini
是什么东西,看不懂正常,来解说一下,第一个[0,1,5,9]
,表格形式填充如下,仔细看一看,好像是一个方块形状的图案
A | B | C | D |
---|---|---|---|
0 | 1 | x | x |
x | 5 | x | x |
x | 9 | x | x |
这就是基本方块形状,方便后面绘制的,
还有一些实现细节,剩下的方法这里就不讲了,由于篇幅有限,没法在这里将每个实现细节都讲得清楚,项目代码写得是不多,就讲到这了,请自己动手完善,相信自己,努力就会有收获。
如果想要看完整项目源码的请"点击这里",点击进去展开资源一栏往下仔细找一找就有了。
运行小程序
下面是游戏项目源码运行的效果动图,是否心动了呢,欢迎尝试,项目源码完整,运行无问题,请放心下载学习,谢谢!
💡关于游戏剧情
俄罗斯方块游戏原本是没有剧情的,让本人想起来了,跟两个神话故事有异曲同工之妙呢,分别是《精卫填海》和《女娲补天》故事,这故事来做游戏剧情好像可以,还有其它剧情吗,这里不多说了,剧情怎么写,请自己脑补。