1. 程式人生 > >HTML5 Canvas 射擊類小遊戲 平滑的移動 思路

HTML5 Canvas 射擊類小遊戲 平滑的移動 思路

這篇部落格主要講了如何處理HTML5 Canvas 遊戲中的角色移動問題。

筆者這幾天做了一個 HTML5 Canvas 的射擊類小遊戲,嗯,名字叫做《DroppingBalls》,大概就是自己控制一個坦克在介面的最下面左右移動,然後上面會有怪向下移動,我們必須趕在怪完全掉下來之前將它擊斃,否則我們會掉血。怪會越來越多越來越強,我們最後一定會死掉的,這沒關係,因為我們的目前是在死之前拿更多的分數。

這個遊戲目前還沒有完全做完,也僅僅是練習作。遊戲掛在筆者的個人網站上了,大家可以去看一下。

該部落格中的程式碼僅為了表明思路,並不保證可以直接執行。若需要可以直接執行的程式碼,可以到該遊戲頁面 f12 檢視:

好了,今天的話題就是 如何實現這樣的射擊類小遊戲的第一步,完美的控制己方坦克移動。

##主要技術

JS + Canvas

程式碼難度低,只要自己寫過一遍就感覺非常非常簡單了。程式碼僅是工具,但將許多程式碼組合在一起並讓它們高效的工作,這就是一門藝術了。

##遊戲分析

射擊類遊戲,比如坦克大戰、打飛機等等,基本元素幾乎相同。我們從最簡單的入手來看一下。我們就以坦克舉例。

首先得有玩家的坦克,然後玩家會通過鍵盤來控制自己的坦克移動,通過鍵盤控制射擊,射擊後會發出子彈,子彈在射出後會自動飛行,直到擊中敵方坦克或者飛出地圖後消失。敵方坦克的移動路線可以是隨機的,也可以是設定好的。在《DroppingBalls》中,己方坦克、敵方坦克、子彈 全都是一個球Ball, 這樣做的目的是因為相比於正方形等,兩個圓形之間的距離是最好判斷的,這將使【子彈是否能夠擊毀坦克】的判斷非常容易。

遊戲中,玩家通過wasd來控制移動,j來射擊。

HTML頁面中的Canvas標籤id=CanvasGame, 寬=600,高=600

##分步驟實現 第1步

我們要畫一個球(這裡的球和圓是一個意思),表示己方坦克。

這個可以在w3school上自己看一下,這個可以做到之後就可以進行第二步了。

w3school的Canvas教程連結:

##分步驟實現 第2步

實現己方坦克的粗糙的移動。這裡我們只說向右移動,其他方向的類似。

經過第1步的學習,球可以畫出來了,但是怎樣實現球的移動呢?這裡就涉及到動畫的原理了,即球是不會動的,球的移動只是看起來在移動而已,實際上,我們要做的是不斷的清空遊戲畫面,之後,在球的原本位置靠右一點點的位置畫一個球,當我們每秒重複幾十次這樣的清空重畫後,在視覺上,球就動起來了。

總結一下我們要做的事情: 捕獲鍵盤按鍵‘D’時,球的x座標增大,清空遊戲畫面,重新畫出這個球。

這時為了方便各種變數的操作,我們本著面向物件的原則將球封裝一下:

// Base class for myTank 、 enemy 、bullet .etc

function Ball(paraX,paraY,paraR,paraSpeed,paraColor,paraMap) {

    var ball = {
        x:paraX,
        y: paraY,
        r:paraR,
        speed:paraSpeed,
        color: paraColor,
        map: paraMap

    }

    return ball;

}

這裡的speed、map可以先不用管。

通過這樣的封裝,我們很容易實現球的移動:

// Base class for myTank 、 enemy 、bullet .etc

function Ball(paraX,paraY,paraR,paraSpeed,paraColor,paraMap) {

    var ball = {
        x:paraX,
        y: paraY,
        r:paraR,
        speed:paraSpeed,
        color: paraColor,
        map: paraMap,
    }
    ball.move=function(){
        this.x+=this.speed;
    };

    return ball;

}

通過在ball這個物件裡面增加move方法,我們想向右移動該球時,只需要呼叫ball.move()就可以了。

接下來我們要做的就是捕獲鍵盤按鍵‘D’時,呼叫ball.move()

var myTank=new Ball(200,200,20,3,"red",map);//map先不用管
document.onkeydown = function (event) {
    var e = event || window.event || arguments.callee.caller.arguments[0];
    if (!e) return;
    console.log(String.fromCharCode(e.keyCode) + " down");
    switch (e.keyCode) {
        case "d".charCodeAt(0):
        case "D".charCodeAt(0):
            myTank.move();
            break;


    } // end switch

};// end onkeydown

這樣的話就實現了myTank的向右移動。但此時的移動僅僅是在記憶體完成的,我們需要將它畫到Canvas上才能看到。

    function clear() {
        var ctx =document.getElementById('CanvasGame').getContext('2d');
        ctx.clearRect(0, 0, 600, 600);
    }
    function paintMyTank(){
        var ctx = document.getElementById('CanvasGame').getContext('2d');
        ctx.beginPath();
        ctx.fillStyle = myTank.color;
        ctx.arc(myTank.x,myTank.y, myTank.r, 0, 2*Math.PI);
        ctx.fill();
    }

有了這兩個方法後,我們就可以在捕獲鍵盤按鍵‘D’後呼叫了。區域性程式碼如下:

switch (e.keyCode) {
        case "d".charCodeAt(0):
        case "D".charCodeAt(0):
            myTank.move();
            clear();
            paintMyTank();
            break;


    } // end switch

這樣基本實現了myTank的向右移動了。

補全一下ball.move()及document.onkeydown()後,即可實現4個方向的移動。

##分步驟實現 第3步

整理程式碼,實現myTank的完美移動。

經過第二步可以實現非常粗糙的移動,即捕獲方向按鍵後,更新myTank的座標值,清空Canvas,重新畫出myTank。 但很遺憾,真正的程式碼並不是這樣寫的。上面的程式碼僅僅是為了便於理解。第2步實現後,移動非常粗糙,且不能斜方向移動。而且程式碼難以整合。

所在這裡我們要整理一下程式碼,同時解決這個移動的問題。

程式碼的結構上,我們希望每個變數、每個方法都有自己的域,讓程式碼最大可能的受控,說白了,就是面向物件的方法,一層又一層的封裝。這裡我們將遊戲的所有變數及方法封裝到game物件裡面。

function Game() {
    var game={};
    game.controlKeys=new ControlKeys(); // 控制按鈕, 構造方法在下面
    game.models = {
        myTank: new MyTank(), //類似的方法實現myTank的構造
    };
    game.func = {}; //後面會補充
    game.event={};  //後面會補充
}

myTank的移動處理上,我們不希望每個鍵盤事件時重新整理一次畫面。而且js無法處理兩個方向鍵同時按下的時候的斜方向移動,所以我們要換一種方式。我們設定一些標誌變數,並將他們封裝到controlKeys物件中:

//game.controlKeys
function ControlKeys() {
    var controlKeys = {
        //方向
        w: false,
        a: false,
        s: false,
        d: false,
    }
    return controlKeys;
}

鍵盤事件需要捕獲onkeydown和onkeyup, down時,controlKeys對應的標誌變為true;up時變為false;

遊戲時鐘設定為30毫秒, 即每30毫秒,我們根據controlKeys的標誌值,若w為true則myTank.move(“w”),即向上移動;若d為true則myTank.move(“d”),即向右移動。這些判斷之間相互不干涉,這樣就實現了斜方向的移動。

game.event = {
    beginControl: function () {
        document.onkeydown = function (event) {
            var e = event || window.event || arguments.callee.caller.arguments[0];
            if (!e) return;
            console.log(String.fromCharCode(e.keyCode) + " down");
            switch (e.keyCode) {

                case "w".charCodeAt(0):
                case "W".charCodeAt(0):
                    game.controlKeys.w = true;
                    break;
                //a s d 類似
            } // end switch

        };// end onkeydown

        document.onkeyup = function (event) {
            var e = event || window.event || arguments.callee.caller.arguments[0];
            if (!e) return;
            console.log(String.fromCharCode(e.keyCode) + " up");
            switch (e.keyCode) {

                case "w".charCodeAt(0):
                case "W".charCodeAt(0):
                    game.controlKeys.w = false;
                    break;
                //a s d 類似
            } // end switch
        };//end onkeyup
    }
}

game.func = {
    //遊戲時鐘; 遊戲週期;
    clock: function () {
        //處理myTank移動
        if (game.controlKeys.w) {
            game.models.myTank.move("w");
        }
        if (game.controlKeys.a) {
            game.models.myTank.move("a");
        }
        //s d 類似
        game.painter.paintGame();

    }//end game.func.clock()

}; //end game.func


game.event = {
      //開啟遊戲時鐘,設定為30毫秒執行一次
       start: function () {
           setInterval(game.func.clock, 30);
           game.func.beginControl();
       },
}

這樣,即可實現myTank的平滑的、8個方向的完美移動了。

##結語

《DroppingBalls》 遊戲程式碼目前500行左右,雖然不大,但若想詳細的在一篇部落格講完還是有些困難的。 這篇部落格將myTank的完美移動的思路講了一下,部落格中所給程式碼上可能存在一些bug,因為這些程式碼是從遊戲原始碼中抽出來的部分,僅僅為了表達思路,直接copy過去是不能執行的。

遊戲連結會給到大家,大家可以直接f12看一下原始碼,這樣更方便快捷: