H5坦克大戰之【玩家控制坦克移動2】

来源:http://www.cnblogs.com/zhouhuan/archive/2016/12/27/H5_tankgame3.html
-Advertisement-
Play Games

周一沒有看聖誕大戰,這幾天比較忙也沒有看賽後的報道,今天就先不扯NBA,隨便扯扯自己。昨天在電腦里找東西的時候翻到以前兼職健身教練時的照片,思緒一下子回到學生時代,腦子久久換不過來。現在深深覺得健身和寫代碼真的是兩個極端,一個往死里做(做動作),一個往死里坐(坐椅子),現在的體重和巔峰時期足足差了6 ...


  周一沒有看聖誕大戰,這幾天比較忙也沒有看賽後的報道,今天就先不扯NBA,隨便扯扯自己。昨天在電腦里找東西的時候翻到以前兼職健身教練時的照片,思緒一下子回到學生時代,腦子久久換不過來。現在深深覺得健身和寫代碼真的是兩個極端,一個往死里做(做動作),一個往死里坐(坐椅子),現在的體重和巔峰時期足足差了6公斤,以前80公斤5*10的卧推變成了現在5*20的下斜俯卧撐,唉...掉了的塊和體重里可都藏著我寫過的代碼啊!這裡給大家送點小福利,珍藏版的衛平-布萊恩特奉上:  

  好了不扯,接著上一篇H5坦克大戰之【玩家控制坦克移動】(http://www.cnblogs.com/zhouhuan/p/H5_tankgame2.html),今天我們來繼續看玩家怎麼控制坦克,今天主要修複兩處bug,第一個bug,玩家按下方向鍵時,坦克的炮筒應該指向相應的方向,並向該方向移動,第二,坦克不能開出邊界,上一節的代碼坦克是可以開出邊界的,這樣顯然不行,會讓坦克沒有安全感,我們造出了它,就要給它安全感。     1. 修複第一個bug   我們的思路是,給造坦克的函數里再傳一個方向的參數,我們讓"u", "d", "l", "r"分別表示上下左右,封裝這樣一個可以傳方向的函數之後,我們在用戶按下不同的鍵時傳不同的參數進去,由於整個地板每隔100毫秒會刷新一次,那麼這個函數就能以肉眼分辨不出來的速度,在用戶按下鍵的一瞬間相應地生產出不同方向的坦克了。   如下:
//封裝一個畫坦克的函數,傳兩個參數x,y,分別代表左上角的橫縱坐標
//再增加一個參數dir來表示方向 上下左右分別傳"u" "d" "l" "r"
function drawTank(x,y,dir){
    var cxt = getCxt();
    switch(dir){
        case "u":                             //此時造一個向上的坦克
            cxt.fillStyle = "#542174";
            cxt.fillRect(x,y,20,65);                
            cxt.fillRect(x+70,y,20,65);                
            cxt.fillRect(x+23,y+10,44,50);                
            cxt.fillStyle = "#FCB827";
            cxt.beginPath();
            cxt.arc(x+45,y+35,16,0,2*Math.PI,false);        
            cxt.closePath();
            cxt.fill();
            cxt.strokeStyle = "#FCB827";
            cxt.lineWidth = "8.0";
            cxt.moveTo(x+45,y+35);                        
            cxt.lineTo(x+45,y-25);                        
            cxt.stroke();
            break;
        case "d":                             //此時造向下的坦克
            cxt.fillStyle = "#542174";
            cxt.fillRect(x,y,20,65);                
            cxt.fillRect(x+70,y,20,65);                
            cxt.fillRect(x+23,y+10,44,50);
            cxt.fillStyle = "#FCB827";
            cxt.beginPath();
            cxt.arc(x+45,y+35,16,0,2*Math.PI,false);        
            cxt.closePath();
            cxt.fill();
            cxt.strokeStyle = "#FCB827";
            cxt.lineWidth = "8.0";
            cxt.moveTo(x+45,y+35);
            cxt.lineTo(x+45,y+95);        //和向上造相比,只有炮筒需要改變        
            cxt.stroke();
            break;
        case "l":                             //此時造向左的坦克
            cxt.fillStyle = "#542174";
            cxt.fillRect(x,y,65,20);        //和向上造坦克相比,畫第一個矩形時長寬互換即可
            cxt.fillRect(x,y+70,65,20);        //向左的坦克,註意坐標之間的轉換即可,以下類似不再一一解釋
            cxt.fillRect(x+10,y+23,50,44);
            cxt.fillStyle = "#FCB827";
            cxt.beginPath();
            cxt.arc(x+35,y+45,16,0,2*Math.PI,false);
            cxt.closePath();
            cxt.fill();
            cxt.strokeStyle = "#FCB827";
            cxt.lineWidth = "8.0";
            cxt.moveTo(x+35,y+45);
            cxt.lineTo(x-25,y+45);
            cxt.stroke();
            break;
        case "r":
            cxt.fillStyle = "#542174";
            cxt.fillRect(x,y,65,20);        //和造向左的坦克類似,只要改動炮筒即可向右
            cxt.fillRect(x,y+70,65,20);        
            cxt.fillRect(x+10,y+23,50,44);
            cxt.fillStyle = "#FCB827";
            cxt.beginPath();
            cxt.arc(x+35,y+45,16,0,2*Math.PI,false);
            cxt.closePath();
            cxt.fill();
            cxt.strokeStyle = "#FCB827";
            cxt.lineWidth = "8.0";
            cxt.moveTo(x+35,y+45);
            cxt.lineTo(x+95,y+45);
            cxt.stroke();
    }
}
  和之前不同的是,我們增加了一個switch語句,用來判斷傳進來的方向,然後將不同的畫坦克的動作放進不同的case分支里。看起來代碼量很大,但其實都是從向上畫的代碼改過來的,難度不大,具體一些的說明都寫在了註釋里。   接下來我們給myTank這個對象增加一個屬性direction,用來確定坦克的方向,初始的時候我們讓它等於"u",畫一個向上的坦克。   相應地,造坦克的時候多傳一個參數進去就可以了:
drawTank(myTank.x,myTank.y,myTank.direction);
  再接著我們要做的就是在玩家按下不同的鍵時響應的改變方向就可以了:
window.onkeydown = function(eve){                                                
    switch(eve.keyCode){
        case 38:
        case 87:
            myTank.y -= myTank.step;    //Y坐標減小向上移動
            myTank.direction = "u";        //改變成向上的方向
            break;
        case 40:
        case 83:
            myTank.y += myTank.step;    //Y坐標增加向下移動
            myTank.direction = "d";        //改變為向下的方向
            break;
        case 37:
        case 65:
            myTank.x -= myTank.step;    //X坐標減小向左移動
            myTank.direction = "l";        //改變為向左的方向
            break;
        case 39:
        case 68:
            myTank.x += myTank.step;    //X坐標增加向右移動
            myTank.direction = "r";        //改變為向右的方向
    }
};
  不過,我們最好再做進一步的封裝,首先給myTank對象增加一些方法:
myTank.turnUp = function(){
    myTank.y -= myTank.step;    
    myTank.direction = "u";        
};
myTank.turnDown = function(){
    myTank.y += myTank.step;    
    myTank.direction = "d";        
};
myTank.turnLeft = function(){
    myTank.x -= myTank.step;    
    myTank.direction = "l";        
};
myTank.turnRight = function(){
    myTank.x += myTank.step;    
    myTank.direction = "r";        
};
  再根據玩家的操作進行相應地調用:
window.onkeydown = function(eve){                                                
    switch(eve.keyCode){
        case 38:
        case 87:
            myTank.turnUp();
            break;
        case 40:
        case 83:
            myTank.turnDown();
            break;
        case 37:
        case 65:
            myTank.turnLeft();
            break;
        case 39:
        case 68:
            myTank.turnRight();
    }
};
  給myTank對象添加的屬性和方法多了之後這樣看著很煩,可讀性也比較差,我們有必要對它進行改動:
var myTank = {
    x : 350,
    y : 400,
    step : 3,
    direction : "u",
    turnUp : function(){
        myTank.y -= myTank.step;    
        myTank.direction = "u";    
    },
    turnDown : function(){
        myTank.y += myTank.step;    
        myTank.direction = "d";    
    },
    turnLeft : function(){
        myTank.x -= myTank.step;    
        myTank.direction = "l";    
    },
    turnRight : function(){
        myTank.x += myTank.step;    
        myTank.direction = "r";        
    }
};
  嗯,這樣看著舒服多了。     2.解決第二個bug   我們的思路是,重新封裝一下turnUp turnDown turnLeft turnRight這幾個方法,給裡面加上判斷條件,如果判斷為將要出界,那麼不再執行改變坐標的代碼,這樣,坦克就只能在可視區內運動了。具體判斷方法如下:   坦克如果將要開出上面的邊界,那麼開出去之前它一定是向上的,此時(myTank.x, myTank.y)點是坦克左邊履帶左上角的點,我們暫且將這個點稱為原點,再回頭看一下向上畫坦克時的代碼:cxt.arc(x+45,y+35,16,0,2*Math.PI,false); 可知圓蓋中心點(也就是炮筒的起點)和原點之間的縱向距離為35,又易知炮筒的總長度為60(cxt.moveTo(x+45,y+35); cxt.lineTo(x+45,y-25);),那麼顯然炮筒的起點與原點的縱向距離就是25,所以我們就可以這樣判斷,坦克的y坐標減去25大於等於0的時候我們再讓坦克向上動起來,轉化成代碼就是:
var myTank = {
    turnUp : function(){
        if((myTank.y-25) >= 0){
            myTank.y -= myTank.step;    
            myTank.direction = "u";    
        }
    }
};
  這時候,坦克就不會再超出上面的邊界了。   坦克向下移動的時候,我們把坦克履帶的長度考慮進去就可以了,如下:
var myTank = {
    turnDown : function(){
        if((myTank.y+90) <= 500){
            myTank.y += myTank.step;    
            myTank.direction = "d";
        }    
    }
};
  同理坦克向左和向右移動時如下:
var myTank = {
    turnLeft : function(){
        if((myTank.x-25) >= 0){
            myTank.x -= myTank.step;    
            myTank.direction = "l";    
        }
    },
    turnRight : function(){
        if((myTank.x+90) <= 800){
            myTank.x += myTank.step;    
            myTank.direction = "r";
        }        
    }
};

 

  3. 補充說明   上一節的代碼其實存在一定的問題,就是每一次更新戰場的時候都會去getCxt()一下,清理了戰場之後後面又會drawTank(), drawTank()裡面又有getCxt(),這樣就會重覆不斷地獲取同一個節點,而且更新戰場的函數每100毫秒執行一次,雖然不會影響功能,但是會影響到游戲的性能,我們可以定義一個變數專門用來存放獲取到的繪圖環境,後面需要的時候直接用就好了。     4. 最終代碼
//封裝一個獲取繪圖環境的函數
function getCxt(){
    var myCanvas = document.getElementById('floor'),
       myContext = myCanvas.getContext('2d');
    return myContext;
}
//為了防止重覆地獲取節點影響性能,我們將獲取到的繪圖環境(也就是畫筆對象)存起來
var oCxt = getCxt();

//封裝一個畫坦克的函數,傳兩個參數x,y,分別代表左上角的橫縱坐標
//再增加一個參數dir來表示方向 上下左右分別傳"u" "d" "l" "r"
function drawTank(x,y,dir){
    switch(dir){
        case "u":                             //此時造一個向上的坦克
            oCxt.fillStyle = "#542174";
            oCxt.fillRect(x,y,20,65);                
            oCxt.fillRect(x+70,y,20,65);                
            oCxt.fillRect(x+23,y+10,44,50);                
            oCxt.fillStyle = "#FCB827";
            oCxt.beginPath();
            oCxt.arc(x+45,y+35,16,0,2*Math.PI,false);        
            oCxt.closePath();
            oCxt.fill();
            oCxt.strokeStyle = "#FCB827";
            oCxt.lineWidth = "8.0";
            oCxt.moveTo(x+45,y+35);                        
            oCxt.lineTo(x+45,y-25);                        
            oCxt.stroke();
            break;
        case "d":                             //此時造向下的坦克
            oCxt.fillStyle = "#542174";
            oCxt.fillRect(x,y,20,65);                
            oCxt.fillRect(x+70,y,20,65);                
            oCxt.fillRect(x+23,y+10,44,50);
            oCxt.fillStyle = "#FCB827";
            oCxt.beginPath();
            oCxt.arc(x+45,y+35,16,0,2*Math.PI,false);        
            oCxt.closePath();
            oCxt.fill();
            oCxt.strokeStyle = "#FCB827";
            oCxt.lineWidth = "8.0";
            oCxt.moveTo(x+45,y+35);
            oCxt.lineTo(x+45,y+95);        //和向上造相比,只有炮筒需要改變        
            oCxt.stroke();
            break;
        case "l":                             //此時造向左的坦克
            oCxt.fillStyle = "#542174";
            oCxt.fillRect(x,y,65,20);        //和向上造坦克相比,畫第一個矩形時長寬互換即可
            oCxt.fillRect(x,y+70,65,20);        //向左的坦克,註意坐標之間的轉換即可,以下類似不再一一解釋
            oCxt.fillRect(x+10,y+23,50,44);
            oCxt.fillStyle = "#FCB827";
            oCxt.beginPath();
            oCxt.arc(x+35,y+45,16,0,2*Math.PI,false);
            oCxt.closePath();
            oCxt.fill();
            oCxt.strokeStyle = "#FCB827";
            oCxt.lineWidth = "8.0";
            oCxt.moveTo(x+35,y+45);
            oCxt.lineTo(x-25,y+45);
            oCxt.stroke();
            break;
        case "r":
            oCxt.fillStyle = "#542174";
            oCxt.fillRect(x,y,65,20);        //和造向左的坦克類似,只要改動炮筒即可向右
            oCxt.fillRect(x,y+70,65,20);        
            oCxt.fillRect(x+10,y+23,50,44);
            oCxt.fillStyle = "#FCB827";
            oCxt.beginPath();
            oCxt.arc(x+35,y+45,16,0,2*Math.PI,false);
            oCxt.closePath();
            oCxt.fill();
            oCxt.strokeStyle = "#FCB827";
            oCxt.lineWidth = "8.0";
            oCxt.moveTo(x+35,y+45);
            oCxt.lineTo(x+95,y+45);
            oCxt.stroke();
    }
}

//初始化一個對象myTank,用來存儲一些屬性和方法
var myTank = {
    x : 350,
    y : 400,
    step : 3,
    direction : "u",
    turnUp : function(){
        if((myTank.y-25) >= 0){                //加判斷條件防止開出邊界
            myTank.y -= myTank.step;    
            myTank.direction = "u";    
        }
    },
    turnDown : function(){
        if((myTank.y+90) <= 500){
            myTank.y += myTank.step;    
            myTank.direction = "d";
        }    
    },
    turnLeft : function(){
        if((myTank.x-25) >= 0){
            myTank.x -= myTank.step;    
            myTank.direction = "l";    
        }
    },
    turnRight : function(){
        if((myTank.x+90) <= 800){
            myTank.x += myTank.step;    
            myTank.direction = "r";
        }        
    }
};

//先畫一個坦克出來
drawTank(myTank.x,myTank.y,myTank.direction);        //一開始先造一個向上的出來

//封裝一個更新戰場的函數
function updateFloor(){
    oCxt.clearRect(0,0,800,500);            //更新之前先清除畫布
    drawTank(myTank.x,myTank.y,myTank.direction);        //清除完之後重新造坦克,坦克要移動就必須實時地根據坐標重新來造
}

//設置一個間歇調用的函數,每隔100ms更新一下戰場
setInterval(function(){
    updateFloor();
},100);

//響應玩家的操作指令
window.onkeydown = function(eve){                                                
    switch(eve.keyCode){
        case 38:
        case 87:
            myTank.turnUp();
            break;
        case 40:
        case 83:
            myTank.turnDown();
            break;
        case 37:
        case 65:
            myTank.turnLeft();
            break;
        case 39:
        case 68:
            myTank.turnRight();
    }
};    

(PS:一個人的力量畢竟有限,如在閱讀的過程中發現有描述不當或者錯誤的地方歡迎隨時指正,筆者不勝感激!)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • FunDA的特點之一是以數據流方式提供逐行數據操作支持。這項功能解決了FRM如Slick數據操作以SQL批次模式為主所產生的問題。為了實現安全高效的數據行操作,我們必須把FRM產生的Query結果集轉變成一種強類型的結果集,也就是可以欄位名稱進行操作的數據行類型結果集。在前面的一篇討論中我們介紹了通 ...
  • 一個已有的Struts+Spring+Hibernate項目,以前使用MySQL資料庫,現在想把Redis也整合進去。 1. 相關Jar文件 下載並導入以下3個Jar文件: commons-pool2-2.4.2.jar、jedis-2.3.1.jar、spring-data-redis-1.3.4 ...
  • 總的來說,要使在thinkphp框架下麵HTML導入的圖片、css文件和js文件有效,只有兩種方法:(1)使用絕對路徑;(2)在項目目錄下創建新目錄Public,把所有的img文件夾、js文件夾和css文件夾放在裡面,同時把原有的HTML中的導入路徑前面統一加上“lxx_public/子目錄/”,比 ...
  • Annotations ___ They provide information that you need to fully describe your program, but that cannot be expressed in Java. In general the kind of an ...
  • 已知:public class ExceptionText{ public static void main(String[] args){ try{ int i=1; System.out.println(i++); }finally{ System.out.println(++i); } } } ...
  • A 調用攝像頭拍照,自定義裁剪編輯頭像 【新錄針對本系統的視頻教程,手把手教開發一個模塊,快速掌握本系統】B 集成代碼生成器 [正反雙向](單表、主表、明細表、樹形表,開發利器)+快速構建表單; 技術:313596790freemaker模版技術 ,0個代碼不用寫,生成完整的一個模塊,帶頁面、建表s ...
  • 在前一篇文中,我們對一個聚合SDK服務端所需要實現的功能作了簡單的分析。通過兩個主要場景的功能流程圖,我們可以看到,作為多款游戲要適配多個渠道的統一請求轉發中心,TYPESDK服務端主要需要實現的功能有以下幾個要點: l 接收請求和返迴響應,通常是HTTP的請求響應。 l 獲取配置信息。 n 識別游 ...
  • 記得第一次接觸HTML5還是在《聯信永益》實習那會兒(2011),當時一個項目技術選型的時候面臨兩種選擇,分別是Silverlight和HTML5,記得當年用的最新的IE瀏覽器版本還是IE9,才剛剛開始支持HTML5。後來考慮到當時各個版本的瀏覽器對HTML5的支持都不怎麼好,最終公司還是選擇了Si ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...