canvas+js從0開始擼一個俄羅斯方塊

最近工做之餘看了一個俄羅斯方塊小遊戲實現的視頻教程,以爲這個小遊戲的算法挺有意思,打算整理出來學習參考一下。html

雖然標題標着canvas,其實這個小遊戲用到的canvas知識點甚少,其核心仍是js算法問題。教程會比較枯燥,可能對實際工做中也沒太大用處(不過能夠用俄羅斯方塊作一個營銷小活動),寫出來目的主要是爲了學習一下這款小遊戲的算法思想,以及鍛鍊解決問題的能力。前端

前言

本篇文章將分爲幾個模塊逐步講解:node

  1. 遊戲玩法介紹
  2. 算法思想
  3. 建立遊戲地圖
  4. 方塊的建立
  5. 生成不一樣形狀方塊
  6. 方塊下落
  7. 方塊碰撞
  8. 方塊左右移動
  9. 方塊變形
  10. 方塊向下加速
  11. 消除整行
  12. 遊戲結束

遊戲雖小,功能還挺複雜,須要的就是思惟和耐心。下面直接進入正題。react

遊戲玩法介紹

俄羅斯方塊想必你們都玩過,大致玩法簡單說一下,在一個格子地圖上隨機下落不一樣形狀的方塊,經過←→方向鍵去控制方塊的移動,以及↑鍵改變方塊的形狀,↓鍵加速其下落,當方塊落下後滿一行時消除該行。git

來看一下咱們最終實現的效果圖:github

tetris

算法思想

咱們能夠把俄羅斯方塊一分爲二的看,地圖+方塊。咱們把地圖當作一個二維的數組:算法

[
    [0,0,0,0,0,0...],
    [0,0,0,0,0,0...],
    [0,0,0,0,0,0...],
    [0,0,0,0,0,0...],
    ...
]

複製代碼

方塊也看做一個二維數組canvas

如Z形狀的方塊可看做爲:小程序

[
    [1,1,0],
    [0,1,1]
]
複製代碼

倒T形狀的方塊可看做爲:微信小程序

[
    [0,1,0],
    [1,1,1]
]
複製代碼

等等 而後兩個數組結合起來:

[
    [0,0,1,1,0,0,0,0],
    [0,0,0,1,1,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],
]
複製代碼

上面是一個Z字形方塊在第一行。

從上面數組能夠一目瞭然的看到,咱們渲染canvas的時候將數組值爲0的元素渲染成地圖的顏色,而元素值爲1的渲染成方塊的顏色便可。 那麼方塊的移動又是怎麼作呢,很簡單就是去改變方塊左右的元素從0改成1,再將原本的位置重置爲0便可,在視覺上形成方塊的移動。

以下圖一個Z字方塊的移動:

arr

知道其核心思想以後,能夠碼代碼開發了。

建立遊戲地圖

首先咱們先準備一個canvas畫布:

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>canvas</title>
    <style>
        #myCanvas {
            position: absolute;
            left: 0;
            top: 0;
            right: 0;
            bottom: 0;
            margin: auto;
            background: #000;
        }
    </style>
</head>
<body>
<canvas id="myCanvas" height="500" width="500"></canvas>
<button id="stop">中止</button>
<button id="score">得分: 0</button>
<script src="/js/tetris/index.js"></script>
</body>
</html>
複製代碼

首先咱們須要瞭解一下canvas的簡單操做,在畫布上畫一個小方塊:

index.js

let Tetris = {
    init(){
        //初始化
        const canvas = document.getElementById('myCanvas');
        this.gc = canvas.getContext('2d');
        this.render();
    },
    //渲染畫布
    render(){
        //在座標爲100,100的位置畫一個40*40的紅色方塊
        this.gc.fillStyle = 'red';
        this.gc.fillRect(100,100,40,40);
    }
};

Tetris.init();
複製代碼

如圖:

canvas

作俄羅斯方塊所需的canvas知識點你只須要知道fillStyle,fillRect便可,沒有那麼複雜。

接下來,咱們要畫出N*N的格子地圖了。

先生成一個N*N的二維數組,值都爲0:

function map(r,c){
    let data = [];
    for(let i = 0; i < c; i++){
        data.push([]);
        //每行長度爲 r
        data[i].length = r;
        //全部元素默認值 0
        data[i].fill(0);
    }
    return data;
}
console.table(map(20,10));
複製代碼

20*10

根據上面的方塊的渲染方式和地圖數組的生成結合,來渲染出一個20*20的地圖:

完整代碼以下:

index.js

let Tetris = {
    init(){
        //初始化
        const canvas = document.getElementById('myCanvas');
        this.gc = canvas.getContext('2d');
        //20*20的格子
        let data = this.map(20,20);
        this.render(data);
    },
    map(r,c){
        let data = [];
        for(let i = 0; i < c; i++){
            data.push([]);
            //每行長度爲 r
            data[i].length = r;
            //全部元素默認值 0
            data[i].fill(0);
        }
        return data;
    },
    render(data){
        //計算每一個方塊的寬高 方塊之間間隔爲4
        let w = 500/20 - 4;
        let h = 500/20 - 4;

        //計算方塊的行列
        let r = data.length;
        let c = data[0].length;

        for(let i = 0; i < r; i ++){
            for (let j = 0; j < c; j++){
                //判斷數組裏的值 若爲1 則渲染爲紅色 0 則渲染爲白色
                this.gc.fillStyle = data[i][j] === 0 ? 'white' : 'red';
                this.gc.fillRect(
                    (w+4)*j+2,
                    (h+4)*i+2,
                    w,
                    h
                );
            }
        }
    }
};

Tetris.init();
複製代碼

分析一下上面稍微難以理解的是每一個方塊x,y軸值的計算。

詳細分析以下:

畫布寬高500 * 500 要平均分配20*20個方塊,
那麼方塊的最大寬度爲 500/20 = 25 
可是這樣的話方塊會擠在一塊 所以咱們再給每一個方塊之間增長4個單位的間距
因而 單個方塊的 w/h = 500/20 - 4 = 21;
座標的算法:
20個方塊之間有19個間隙 剩下一個4單位的距離 分配到小方塊距離左右兩側的間隙
因而能夠列出:

n列   x座標
0     w*0 + 2
1     w*1 + 2 + 4*1
2     w*2 + 2 + 4*2
3     w*3 + 2 + 4*3
...
n     w*n + 2 + 4*n 
因此第j列的x座標能夠概括爲 (w+4)*j + 2
y座標亦然
複製代碼

執行完代碼看效果以下:

map

長征已經成功走出第一步,接下來就是方塊的建立了。

方塊的建立

方塊有多種類型,如一字型,L字型,Z字型,倒T字型,田字型等等,根據上面算法中提到的,每種類型能夠用一個二維數組描述出來:

blockType: [
    [[1,1,1,1]],
    [[1,1],[1,1]],
    [[1,1,0],[0,1,1]],
    [[0,1,1],[1,1,0]],
    [[0,1,0],[1,1,1]],
    [[1,0,0],[1,1,1]],
    [[0,0,1],[1,1,1]]
]
複製代碼

那怎麼把一個方塊再地圖上顯示出來呢?

也很簡單,只須要把方塊的二維數組插入到地圖的數組中就能夠了。

簡單實現以下:

//更新data數組
draw(block){
    /*
    * 假如block爲Z字型的方塊 [[1,1,0],[0,1,1]]
    */
    for (let i = 0; i < block.length; i++){
        for (let j = 0; j < block[0].length; j++){
            this.data[i][j + this.y] = block[i][j];
        }
    }
    console.table(this.data);
    //再次調用render方法更新畫布
    this.render(this.data);
}
複製代碼

生成不一樣形狀方塊

要隨機生成一個不一樣形狀的方塊,調用Math.random()便可。

貼完整的代碼:

index.js

let Tetris = {
    //初始化
    init(){
        const canvas = document.getElementById('myCanvas');
        this.gc = canvas.getContext('2d');
        //20*20的格子
        this.data = this.map(20,20);
        //X軸的偏移量 之因此保存爲變量 是之後咱們作左右移動的是須要經過改變這個值來實現 
        this.x = 7;
        //隨機生成一個方塊
        this._block = this.block();
        this.draw(this._block);
    },
    //地圖數據
    map(r,c){
        let data = [];
        for(let i = 0; i < c; i++){
            data.push([]);
            //每行長度爲 r
            data[i].length = r;
            //全部元素默認值 0
            data[i].fill(0);
        }
        return data;
    },
    //隨機生成一個類型的方塊
    block(){
        let index = Math.floor(Math.random()*7);
        return this.blockType[index];
    },
    //方塊的類型
    blockType: [
        [[1,1,1,1]],
        [[1,1],[1,1]],
        [[1,1,0],[0,1,1]],
        [[0,1,1],[1,1,0]],
        [[0,1,0],[1,1,1]],
        [[1,0,0],[1,1,1]],
        [[0,0,1],[1,1,1]]
    ],
    //重繪畫布
    draw(block){
        for (let i = 0; i < block.length; i++){
            for (let j = 0; j < block[0].length; j++){
                //要向x軸偏移 須要爲j加一個偏移量便可
                this.data[i][j + this.x] = block[i][j];
            }
        }
        console.table(this.data);
        this.render(this.data);
    },
    //渲染
    render(data){
        //計算每一個方塊的寬高 方塊之間間隔爲4
        let w = 500/20 - 4;
        let h = 500/20 - 4;

        //計算方塊的行列
        let r = data.length;
        let c = data[0].length;

        for(let i = 0; i < r; i ++){
            for (let j = 0; j < c; j++){
                //判斷數組裏的值 若爲1 則渲染爲紅色 0 則渲染爲白色
                this.gc.fillStyle = data[i][j] === 0 ? 'white' : 'red';
                /*
                 * 座標算法
                 * 畫布寬度500 小方格寬度21 個數20 則 留下的空隙寬度爲 500 - 21*20 = 80 其中 20個小方塊可分4單位的間隙
                 * 20個方塊之間有19個間隙 剩下一個4單位的距離 分配到小方塊距離左右兩側的間隙
                 * 總結一下規律
                 * n行     x座標
                 * 0       w*0 + 2
                 * 1       w*1 + 2 + 4
                 * 2       w*2 + 2 + 4*2
                 * 3       w*3 + 2 + 4*3
                 * ...
                 * n       w*n + 2 + 4*n
                 * 因此第j列的x座標能夠概括爲 (w+4)*j + 2
                 * y座標亦然
                 */
                this.gc.fillRect(
                    (w+4)*j+2,
                    (h+4)*i+2,
                    w,
                    h
                );
            }
        }
    }
};

Tetris.init();
複製代碼

刷新頁面會出現隨機的方塊以下圖:

生成方塊

方塊下落

方塊下落,其實就是要改變方塊在畫布上的Y軸位置,怎麼持續下落呢,很顯然須要開一個定時器。

[未完待續。。。]

方塊碰撞

[未完待續。。。]

方塊左右移動

[未完待續。。。]

方塊變形

[未完待續。。。]

方塊向下加速

[未完待續。。。]

消除整行

[未完待續。。。]

遊戲結束

[未完待續。。。]

後言

最近時間比較緊,剩下模塊慢慢更新。大致思想了解以後,其餘問題均可以迎刃而解,無非就是須要耐心和不斷的調試。

喜歡就贊一波,給點更新的動力😆

參考文獻

俄羅斯方塊遊戲實戰

人人貸大前端技術博客中心

最後廣而告之。 歡迎訪問人人貸大前端技術博客中心

裏面有關nodejs react reactNative 小程序 前端工程化等相關的技術文章陸續更新中,歡迎訪問和吐槽~

上一篇: 微信小程序打怪之定時發送模板消息(node版)

相關文章
相關標籤/搜索