js canvas遊戲初級demo-躲避障礙物

在線演示地址 http://200ok.fun:3100/html/game_demo.htmlhtml

繼上次js canvas遊戲初級demo-上下左右移動(https://www.cnblogs.com/lzs-888/p/7427440.html),以後,對其新加了矩形下落和碰撞檢測功能canvas

1.頭像移動
設置兩個按鍵監聽事件(keydown - 按下,keyup - 鬆開),每當按下時就將按下的鍵的state改成true,鬆開則相反,
而後,設置了一個定時器,每必定時間會讀取這些按鍵state,根據state和速度進行頭像位置座標的調整,而且擦除頭像,從新畫上dom

2.矩形下落
與頭像移動相似,只是擦除和繪製有多個(本代碼中用設置多個定時器來維護這些矩形的繪製),而且橫座標和下落速度在必定範圍內隨機函數

3.碰撞檢測
這個直接看代碼,本代碼寫的有點囉嗦spa

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>按鍵盤的上下左右躲開這些黑色障礙物</title>
</head>
<style>
    #canvas_dom{border: 1px solid #000;margin: 0 auto;display: block;background: #fff;}
    .title{text-align: center;}
    #game_time{text-align: center;}
</style>
<body>
    <h1 class="title">按鍵盤的上下左右躲開這些黑色障礙物</h1>
    <h2 id="game_time">你堅持的時間:<span>0</span>S</h2>
    <canvas id="canvas_dom"></canvas>
    <script>
        /*
            ddmm
            2018/07/13

            1.頭像移動
            設置兩個按鍵監聽事件(keydown - 按下,keyup - 鬆開),每當按下時就將按下的鍵的state改成true,鬆開則相反,
            而後,設置了一個定時器,每必定時間會讀取這些按鍵state,根據state和速度進行頭像位置座標的調整,而且擦除頭像,從新畫上

            2.矩形下落
            與頭像移動相似,只是擦除和繪製有多個(本代碼中用設置多個定時器來維護這些矩形的繪製),而且橫座標和下落速度在必定範圍內隨機

            3.碰撞檢測
            這個直接看代碼,本代碼寫的有點囉嗦
         */
        //全局變量
        var g = {

            ctx: null,//畫布會話上下文
               itv_ids: [],//用於記錄定時器id,便於一塊兒clear掉,減小內存消耗
               game_over: false,//遊戲是否結束
               refreshNumForSec: 60,//每秒畫面刷新次數
               game_time: 0, //遊戲堅持的時間
            canvasWidth: 1000,//畫布寬度
            canvasHeight: 700,//畫布高度
            faceDataUrl: 'http://www.200ok.fun:3100/images/wudier.png',//頭像地址
            img: null,//頭像圖片引用
            faceWidth: 80,
            faceHeight: 82.5,
            faceX: 0,//頭像位置x座標(其初始值會在首次加載被initFacePos方法計算得出,故設置是無效的)
            faceY: 0,//頭像位置y座標(其初始值會在首次加載被initFacePos方法計算得出,故設置是無效的)
            faceLastX: 0,//頭像上一次位置x座標,用於擦除(其初始值會在首次加載被initFacePos方法計算得出,故設置是無效的)
            faceLastY: 0,//頭像上一次位置y座標,用於擦除(其初始值會在首次加載被initFacePos方法計算得出,故設置是無效的)
            keyRight: false,//是否按了→
            keyLeft: false,//是否按了←
            keyUp: false,//是否按了↑
            keyDown: false,//是否按了↓
            faceSpeed: 15,//頭像每次刷新畫布移動的像素
               rectW: 200,//矩形的寬度
            rectH: 20,//矩形的高度
               rectSpeedMin: 3,//方塊速度最小值
               rectSpeedMax: 15,//方塊速度最大值
               rectFlowNumForSec: 1,//每秒生成矩形個數
        };
 
        _main();//主函數
 
        //獲取畫布上下文
        function getCtx(){
            if(!g.ctx){
                //獲取dom
                var canvas = document.querySelector('#canvas_dom');
                
                //設置寬高
                canvas.width = g.canvasWidth;
                canvas.height = g.canvasHeight;
                
                //獲取會話上下文
                g.ctx = canvas.getContext('2d');
            }
            
            return g.ctx;
        }
 
        function _main(){
            getCtx();//獲取畫布上下文

            //獲取頭像圖片
            g.img = new Image();
            g.img.src = g.faceDataUrl;
            g.img.width = g.faceWidth;
            g.img.height = g.faceHeight;
            g.img.onload = function(){//圖片加載完成
                g.img.style.border = "1px solid #000";
                initFacePos();
            }
             
             //監聽keydown事件
            window.addEventListener('keydown',function(e){
                var k = e.key;
                stateJudge(k,true);//修改按鍵state
            });
         
             //監聽keyup事件
            window.addEventListener('keyup',function(e){
                var k = e.key;
                stateJudge(k,false);//修改按鍵state
            });
             
             //設置定時器,1秒重繪 g.refreshNumForSec 次頭像
            g.itv_ids.push(
                setInterval(function(){
                    if(!g.game_over){
                        moveJudge();
                        //擦除上一次畫的
                        g.ctx.clearRect(g.faceLastX,g.faceLastY,g.img.width,g.img.height);
                        //繪製以前保存一下位置,用於下次擦除
                        g.faceLastX = g.faceX;
                        g.faceLastY = g.faceY;
                        //繪製頭像
                        g.ctx.drawImage(g.img,g.faceX,g.faceY,g.img.width,g.img.height);
                    }
                },1000/g.refreshNumForSec)
            );

            //每1秒生成一個隨機位置,隨機速度的矩形往下掉
            g.itv_ids.push(
                setInterval(function(){
                    var max = g.canvasWidth - g.rectW;//畫布寬度 - 矩形寬度
                    var x = get_random_num(0,max);//獲取x座標隨機值
                    var speed = get_random_num(g.rectSpeedMin,g.rectSpeedMax);//獲取速度隨機值
                       drawTheRect(x,0,g.rectW,g.rectH,speed);
                },1000/g.rectFlowNumForSec)
            );

            //數秒,改dom
            g.itv_ids.push(
                setInterval(function(){
                    g.game_time++;
                    document.querySelector('#game_time span').innerText = g.game_time;
                },1000)
            );
        }

        //根據畫布和頭像寬高,初始化頭像位置,將其畫於底部中間位置
        function initFacePos(){
            g.faceX = g.canvasWidth/2 - g.img.width/2;
            g.faceY = g.canvasHeight - g.img.height;
            g.faceLastX = g.faceX;
            g.faceLastY = g.faceY;
            g.ctx.drawImage(g.img,g.faceX,g.faceY,g.img.width,g.img.height);//繪製頭像
        }

        /**
        * [checkRectImpact 碰撞檢測,若是所提供的座標點碰撞則返回true,不然返回false]
        * - - 沒看過別人怎麼寫的,寫的有點囉嗦
        * x1 x2 y1 y2 分別爲第一個矩形(下稱矩形A)的左邊橫座標,右邊橫座標,上邊縱座標,下邊縱座標
        * a1 a2 b1 b2 分別爲第二個矩形(下稱矩形B)的左邊橫座標,右邊橫座標,上邊縱座標,下邊縱座標
        * 座標點以下圖所示
          x1      x2 
            ————————   y1
            |       |
            |       |
            |        |
            ————————   y2

            a1      a2 
            ————————   b1
            |       |
            |       |
            |        |
            ————————   b2
        */
        function checkRectImpact(x1,x2,y1,y2,a1,a2,b1,b2){
            //分別爲 左上角碰到 || 右上角碰到 || 右下角碰到 || 左下角碰到 || 矩形B大於矩形A且矩形B從下至上包含矩形A穿過 || 矩形B大於矩形A且矩形B從上至下包含矩形A穿過 || 矩形A大於矩形B且矩形A從下至上包含矩形B穿過 || 矩形A大於矩形B且矩形A從上至下包含矩形B穿過
            return (x1 <= a1 && x2 >= a1 && y1 <= b1 && y2 >= b1) || (x1 <= a2 && x2 >= a2 && y1 <= b1 && y2 >= b1) || (x1 <= a2 && x2 >= a2 && y1 <= b2 && y2 >= b2) || (x1 <= a1 && x2 >= a1 && y1 <= b2 && y2 >= b2) || (x1 >= a1 && x2 <= a2 && y2 >= b1 && y1 <= b1) || (x1 >= a1 && x2 <= a2 && y2 >= b2 && y1 <= b2) || (a1 >= x1 && a2 <= x2 && b2 >= y1 && b1 <= y1) || (a1 >= x1 && a2 <= x2 && b2 >= y2 && b1 <= y2);
        }

        //獲取某個某個區間內的隨機整數 ,獲取到的值域爲[min,max)
        function get_random_num(min,max){
            if(/^-?\d+$/.test(min) && /^-?\d+$/.test(max) && max>min){
                return parseInt(Math.random()*(max - min) + min);
            }else{
                return false;
            }
        }

        /**
         * [drawTheRect 畫矩形]
         * @param  {[type]} x     [矩形距離左邊框的距離]
         * @param  {[type]} y     [矩形距離上邊框的距離]
         * @param  {[type]} w     [矩形的寬度]
         * @param  {[type]} h     [矩形的高度]
         * @param  {[type]} speed [每一次重繪畫面,矩形下墜的距離,即下墜速度]
         * @return {[type]}       [description]
         */
        function drawTheRect(x,y,w,h,speed){
            var itv_id = setInterval(function(){
                if(!g.game_over){
                    //繪製以前保存一下位置,用於下次擦除
                    var lastX = x;
                    var lastY = y;
                    // var speed = get_random_num(3,120); //經G民同窗提示,能夠考慮每次重繪方塊時給其變速
                    y += speed;
                    g.ctx.clearRect(lastX,lastY,w,h); //擦除
                    g.ctx.fillRect(x,y,w,h); //
                    if(checkRectImpact(g.faceX,g.faceX + g.img.width,g.faceY,g.faceY + g.img.height,x,x+w,y,y+h)){//碰撞檢測
                        game_over();
                        return;
                    }
                    if(y > g.canvasHeight){//若是已經超過了畫布的高度那麼移除這個定時器,減小內存消耗
                        clearInterval(itv_id);
                    }
                }
            },1000/g.refreshNumForSec);
            g.itv_ids.push(itv_id);//存到全局變量
        }

        //遊戲結束
        function game_over(){
            clearAllInterval();
            g.game_over = true;
            setTimeout(function(){//若是不延遲可能會致使上一次繪畫未完成就被阻塞了
                if(confirm("遊戲結束,你堅持了" + g.game_time + "S" + ",繼續努力!\n從新開始遊戲請點'是'")){
                    window.location.reload();
                }
            },50);
        }

        //clear全部定時器
        function clearAllInterval(){
            for(var i in g.itv_ids){
                r = g.itv_ids[i];
                clearInterval(r);
            }
        }
         
         //根據按鍵狀態修改頭像的座標
        function moveJudge(){
            if(g.keyRight === true){
                g.faceX += g.faceSpeed;
            }else if(g.keyLeft === true){
                g.faceX -= g.faceSpeed;
            }else if(g.keyUp === true){
                g.faceY -= g.faceSpeed;
            }else if(g.keyDown === true){
                g.faceY += g.faceSpeed;
            }

            //邊界狀況處理,不要讓頭像超出邊界
            if(g.faceX > g.canvasWidth - g.img.width){
                g.faceX = g.canvasWidth - g.img.width;
            }else if(g.faceX < 0){
                g.faceX = 0;
            }
            if(g.faceY > g.canvasHeight - g.img.height){
                g.faceY = g.canvasHeight - g.img.height;
            }else if(g.faceY < 0){
                g.faceY = 0;
            }
        }
 
        //根據按鍵修改狀態
        function stateJudge(k,v){
            if(k == 'ArrowRight'){
                g.keyRight = v;
            }else if(k == 'ArrowLeft'){
                g.keyLeft = v;
            }else if(k == "ArrowUp"){
                g.keyUp = v;
            }else if(k == "ArrowDown"){
                g.keyDown = v;
            }
        }
    </script>
</body>
</html>
相關文章
相關標籤/搜索