這是一個(gè)仿俄羅斯方塊小游戲的微信小程序,只需要寫(xiě)一小段代碼就實(shí)現(xiàn)出來(lái)了,有興趣的同學(xué)完全可以自己動(dòng)手開(kāi)發(fā),來(lái)看看實(shí)現(xiàn)過(guò)程是怎樣的呢,邊寫(xiě)邊做,一起來(lái)回憶小時(shí)候玩過(guò)的經(jīng)典俄羅斯方塊游戲吧。
?? 給讀者一個(gè)小小提示
需要有HTML和CSS基礎(chǔ)
還有JavaScript基礎(chǔ)
電腦有安裝好微信小程序開(kāi)發(fā)工具滿(mǎn)足以上三個(gè)條件就能看懂接下來(lái)的一些內(nèi)容了
創(chuàng)建小程序
首先,打開(kāi)微信小程序開(kāi)發(fā)工具,如下圖所示,
新建一個(gè)小程序項(xiàng)目minigame-tetris,就選擇小程序,注意不要選擇使用云服務(wù)和模板,然后點(diǎn)確定即可,
這時(shí)項(xiàng)目在開(kāi)發(fā)者工具中自動(dòng)創(chuàng)建好了,會(huì)發(fā)現(xiàn)生成的文件不會(huì)太多,這是為方便精簡(jiǎn)項(xiàng)目源碼,避免新建的項(xiàng)目自動(dòng)生成一大堆對(duì)新手來(lái)說(shuō)不懂的東西,此項(xiàng)目開(kāi)發(fā)對(duì)新手非常友好的!
頁(yè)面設(shè)計(jì)
在項(xiàng)目根目錄下,打開(kāi)一個(gè)頁(yè)面布局文件,文件路徑為pages/index/index.wxml
,如下圖所示,這是我們要做的頁(yè)面顯示效果,準(zhǔn)備編寫(xiě)布局標(biāo)簽實(shí)現(xiàn)它吧
可能新手想不太明白,那么說(shuō)下原理,整個(gè)頁(yè)面的布局只用到了一個(gè)畫(huà)布Canvas
組件和兩個(gè)圖片image
,然后設(shè)置css
樣式層層疊加放置就可以實(shí)現(xiàn)效果,布局文件內(nèi)容參考如下,簡(jiǎn)單就好!
<!--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>
頁(yè)面邏輯
接下來(lái),想到需要編寫(xiě)游戲的邏輯,這下會(huì)不會(huì)感到難寫(xiě)了,以我們對(duì)俄羅斯方塊游戲互動(dòng)邏輯的了解,似乎實(shí)現(xiàn)起來(lái)不太難吧,別擔(dān)心,站在巨人的肩膀上,看看怎么寫(xiě)得呢,
打開(kāi)文件路徑為pages/index/index.js
,開(kāi)始編寫(xiě)代碼,如下所示,理清一下邏輯,一邊寫(xiě)一邊思考
// index.js
const canvasId = 'canv';
//...
Page({
data:{
context:{},//存放其它的值
bgImg:'',//背景圖
//...
},
//頁(yè)面加載時(shí),從此處開(kāi)始執(zhí)行
onLoad(){
//先獲取canvas畫(huà)布組件 大小size 和 節(jié)點(diǎn)node
wx.createSelectorQuery().in(this).select(`#${canvasId}`).fields({
size:true, node:true
},res=>{
const canvas = res.node;
//這里需要設(shè)置一下畫(huà)布的寬高,不然畫(huà)布實(shí)際大小會(huì)與實(shí)際不一致
canvas.width = res.width;
canvas.height = res.height;
//獲取繪制2d畫(huà)布的工具集合對(duì)象ctx
const ctx = canvas.getContext('2d');
//設(shè)置到頁(yè)面的局部變量context里去,方便在其它方法中使用
Object.assign(this.data.context,{ canvas, ctx });
const base64 = this.initGrid();
this.initTimer();
//頁(yè)面顯示背景圖
this.setData({
bgImg: base64
});
}).exec()
},
//頁(yè)面卸載時(shí)
onUnload(){
//...
},
//初始化網(wǎng)格,或繪制游戲背景圖
initGrid(){
//...
},
//初始化定時(shí)器
initTimer(){
//...
},
//用戶(hù)按下按鍵,或點(diǎn)擊的事件
onclickkey(e){
//...
},
//用戶(hù)松開(kāi)按鍵事件
onclickend(){
//...
}
})
初始化
初始化方法initGrid()
,這里面做的是初始化一些屬性,和繪制背景圖,還有網(wǎng)格數(shù)據(jù),大致的實(shí)現(xiàn)邏輯如下,可以這樣理解,游戲中的一些方塊都是有自己位置的,需要網(wǎng)絡(luò)來(lái)作為參照物,定方位
// index.js
import Tetris from '../../static/utils/tetris';//導(dǎo)入一個(gè)模塊
Page({
data:{
context:{},
tetris:null,//方塊相關(guān)的對(duì)象
//...
},
//...
//初始化網(wǎng)格,或繪制游戲背景圖
initGrid(){
const { ctx, canvas } = this.data.context;
const cols = 28;//這里設(shè)定每一行內(nèi)的方塊數(shù)量
const gs = Math.trunc(canvas.width/cols);
const rows = Math.trunc(canvas.height/gs);
const padding = Math.trunc(canvas.width%cols/2);
//計(jì)算好了一些屬性,如果要用到,就在局部變量context中存上去
Object.assign(this.data.context,{ padding, gs, rows, cols });
//這里我們用到了一個(gè)模塊,這是對(duì)處理方塊相關(guān)的封裝,使用前需要先導(dǎo)入它
const tetris = new Tetris({rows,cols});
//先保存一下對(duì)畫(huà)布的設(shè)置
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();
// 這兩行是繪制網(wǎng)格顯示的,在測(cè)試中可以參照,游戲中我們會(huì)將它隱藏起來(lái)
// const grids = tetris.getGrids();
// grids.forEach(g=>this.drawGrid(g));
ctx.rect(padding,0,cols*gs,rows*gs);
ctx.fill();
ctx.stroke();
//繪制完,可以恢復(fù)設(shè)置了
ctx.restore();
this.data.tetris = tetris;
const data = canvas.toDataURL();
ctx.clearRect(0,0,canvas.width,canvas.height);//需要清除了,下次繪制用
return data;//繪制好了,返回圖片信息
},
//...
}
定時(shí)刷新
初始化定時(shí)器方法initTimer()
,這方法做的是讓游戲運(yùn)動(dòng)起來(lái)的意思,因?yàn)槭怯枚〞r(shí)器實(shí)現(xiàn)刷新游戲狀態(tài)的,如果對(duì)此不太了解,那么實(shí)現(xiàn)過(guò)程會(huì)是難以理解的,這個(gè)實(shí)現(xiàn)邏輯是較復(fù)雜的,大致代碼如下
const TIME = 900;
//...
Page({
data:{
foreImg:'',//前置圖,在背景圖上一層
tetris:null,//方塊相關(guān)的對(duì)象
timer:null,//定時(shí)器
isContinue:true,//游戲開(kāi)始或暫停
downKey:null,//按下按鍵的信息
speed:1,//刷新速度調(diào)節(jié)閾值
score:0,//游戲得分
time:0,//游戲時(shí)長(zhǎng)
//...
},
//游戲更新邏輯
refresh(offset = 0){
//...
},
//游戲結(jié)束
gameEnd(){
//...
},
//畫(huà)方塊(格子)
drawGrid(g,fill){
//...
},
//初始化定時(shí)器
initTimer(){
const { tetris } = this.data;
//使用模塊對(duì)象的方法 這里先創(chuàng)建一個(gè)隨機(jī)方塊
let tetri = tetris.createReadomTetris();
//定義一個(gè)重繪的方法,實(shí)現(xiàn)它的邏輯
const redraw=()=>{
const { ctx, canvas } = this.data.context;
const { isContinue, downKey, speed, score, time } = this.data;
//如果游戲可以繼續(xù) 并且 在沒(méi)有按下鍵時(shí)
if(isContinue && !downKey){
let offset = 1;//向下移動(dòng)1格
//方塊在下落了,然后刷新顯示,最后判斷下落是否觸底
if(this.refresh(offset)){
//方塊無(wú)法向下移動(dòng) 就判斷游戲結(jié)束
if(tetri.gt==0){
this.gameEnd();
return;
}
//將方塊加入到網(wǎng)格集合中,就是合并入,裝下去的意思
tetris.joinGrids(tetri);
//接著嘗試處理清理邏輯,會(huì)自動(dòng)判斷底下的所有方塊是否裝滿(mǎn),如果裝滿(mǎn)了一行會(huì)自動(dòng)消除,返回成功消除后的得分
let scope = tetris.clearFillGrids();
//重新創(chuàng)建一個(gè)隨機(jī)方塊
tetri = tetris.createReadomTetris();
//獲取網(wǎng)格集合
let grids = tetris.getGrids(true);
//清空畫(huà)布
ctx.clearRect(0,0,canvas.width,canvas.height);
//將網(wǎng)格集合中所有方塊繪制出來(lái)
grids.forEach(g=>this.drawGrid(g,true));
//繪制好了,弄成一個(gè)圖片數(shù)據(jù),放在頁(yè)面前置背景圖中顯示
this.setData({
foreImg: canvas.toDataURL(),
score: score+scope,//更新顯示得分加分
});
ctx.clearRect(0,0,canvas.width,canvas.height);
}else{
//方塊下落一格
tetri.gt+=offset;
}
}
//需要調(diào)用這個(gè)方法,在不需要的時(shí)候會(huì)自動(dòng)掛起,節(jié)省CPU處理開(kāi)銷(xiāo)
canvas.requestAnimationFrame(()=>{
let t = Math.trunc(TIME*speed);
//等待下一次重繪
this.data.timer = setTimeout(redraw,t);
this.setData({
time:time+t
})
});
};
//開(kāi)始重繪
redraw();
},
//...
}
完善細(xì)節(jié)
上面講得一些的方法都是比較詳細(xì)的,應(yīng)該能看得明白吧,對(duì)了,那個(gè)模塊文件只需要實(shí)現(xiàn)一些方法就可以了,文件路徑為/static/utils/tetris.js
,具體怎樣寫(xiě)得,參考代碼大致如下
//定義一個(gè)基本方塊寬度,所占格數(shù)
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]
];
/**
* 方塊方法封裝對(duì)象類(lèi)
**/
export default class Tetris{
//一些私有屬性
#grids;
#cols;
#rows;
#coordis;
#tetri;
constructor(config){
const { rows, cols } = config;//傳參數(shù)兩個(gè)屬性,分別為行數(shù)和列數(shù)
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){
//...左右移動(dòng)方塊
}
getGrids(filter){
//...獲取那些方塊占用的網(wǎng)格集合
}
getGridsIndex(l,t){
//...根據(jù)表格坐標(biāo)計(jì),算出一格的索引值返回
}
joinGrids(titri,l,t){
//...將一個(gè)方塊并入網(wǎng)格集合中
}
clearFillGrids(){
let scope=0;
//...如果有,清理填平的一行方塊,加分
return scope;
}
isIntoTetris(g){
//...判斷方塊位置是否與其它實(shí)物發(fā)生重疊,也就是說(shuō)是否發(fā)生碰撞
return true;
}
isTouchTetris(titri,l,t){
//...判斷方塊位置是否觸底,也就是說(shuō)是否還能往下落
}
createCoordis(cs){
//...給方塊創(chuàng)建一個(gè)位置坐標(biāo)
}
createReadomTetris(){
//...創(chuàng)建一個(gè)隨機(jī)的方塊
}
getTetris(){
return this.#tetri;
}
rotateRightAngle(success){
//...方塊順時(shí)針?lè)较蛐D(zhuǎn)角度
}
}
??一個(gè)問(wèn)題
可能有注意到了,代碼中的RetrisMini
是什么東西,看不懂正常,來(lái)解說(shuō)一下,第一個(gè)[0,1,5,9]
,表格形式填充如下,仔細(xì)看一看,好像是一個(gè)方塊形狀的圖案
A | B | C | D |
---|---|---|---|
0 | 1 | x | x |
x | 5 | x | x |
x | 9 | x | x |
這就是基本方塊形狀,方便后面繪制的,
還有一些實(shí)現(xiàn)細(xì)節(jié),剩下的方法這里就不講了,由于篇幅有限,沒(méi)法在這里將每個(gè)實(shí)現(xiàn)細(xì)節(jié)都講得清楚,項(xiàng)目代碼寫(xiě)得是不多,就講到這了,請(qǐng)自己動(dòng)手完善,相信自己,努力就會(huì)有收獲。
如果想要看完整項(xiàng)目源碼的請(qǐng)"點(diǎn)擊這里",點(diǎn)擊進(jìn)去展開(kāi)資源一欄往下仔細(xì)找一找就有了。
運(yùn)行小程序
下面是游戲項(xiàng)目源碼運(yùn)行的效果動(dòng)圖,是否心動(dòng)了呢,歡迎嘗試,項(xiàng)目源碼完整,運(yùn)行無(wú)問(wèn)題,請(qǐng)放心下載學(xué)習(xí),謝謝!
??關(guān)于游戲劇情
俄羅斯方塊游戲原本是沒(méi)有劇情的,讓本人想起來(lái)了,跟兩個(gè)神話故事有異曲同工之妙呢,分別是《精衛(wèi)填海》和《女?huà)z補(bǔ)天》故事,這故事來(lái)做游戲劇情好像可以,還有其它劇情嗎,這里不多說(shuō)了,劇情怎么寫(xiě),請(qǐng)自己腦補(bǔ)。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-499409.html
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-499409.html
到了這里,關(guān)于【俄羅斯方塊】單機(jī)游戲-微信小程序項(xiàng)目開(kāi)發(fā)入門(mén)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!