亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

JavaScript躲避行星游戲?qū)崿F(xiàn)全程

 更新時(shí)間:2022年08月08日 09:36:20   作者:夏安  
本文將使用 canvas 創(chuàng)建一個(gè)躲避小行星游戲。另外將重點(diǎn)介紹的兩個(gè)方面是:如何使用 JavaScript 來(lái)檢測(cè)鍵盤(pán)輸入,以及如何在游戲中使用和處理 HTML5 音頻。希望你能夠喜歡

1. 游戲概述

顧名思義,躲避小行星游戲的目標(biāo)是非常明顯的:當(dāng)小行星向你沖來(lái)時(shí),讓火箭飛行和生存的時(shí)間盡可能長(zhǎng)一些(如圖91所示)。如果你碰上某顆小行星,游戲?qū)⒔Y(jié)束,游戲的分?jǐn)?shù)是通過(guò)火箭生存的時(shí)間來(lái)計(jì)算的。

躲避小行星游戲是一個(gè)“橫向卷軸式”游戲,或者說(shuō)它至少類似于這樣的游戲,將會(huì)側(cè)重于動(dòng)態(tài)場(chǎng)景。

2. 核心功能

在創(chuàng)建游戲之前,首先需要構(gòu)建一些基本框架。就創(chuàng)建躲避小行星游戲而言,這些框架就是基本的HTML、CSS以及JavaScript代碼(作為將來(lái)要添加的高級(jí)代碼的基礎(chǔ))。

2.1 構(gòu)建 HTML 代碼

在瀏覽器中創(chuàng)建游戲的優(yōu)點(diǎn)在于可以使用一些構(gòu)建網(wǎng)站的常用技術(shù)。也就是說(shuō),可以使用 HTML 語(yǔ)言來(lái)創(chuàng)建游戲的用戶界面(UI)?,F(xiàn)在的界面看上去不太美觀,這是因?yàn)槲覀冞€沒(méi)有使用 CSS 來(lái)設(shè)計(jì)用戶界面的樣式,但目前內(nèi)容的原始結(jié)構(gòu)是最重要的。

在你的計(jì)算機(jī)上為該游戲創(chuàng)建一個(gè)新目錄,新建一個(gè)index.html文件,在其中加入以下代碼:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Asteroid avoidance</title>
  <link rel="stylesheet" href="style.css" rel="external nofollow"  rel="external nofollow" >
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
  <script src="./main.js"></script>
</head>
<body>
  <div id="game">
    <div id="game-ui">
      <div id="game-intro">
        <h1>Asteroid avoidance</h1>
        <p>Click play and then press any key to start.</p>
        <p>
          <a id="game-play" class="button" href="#" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow" >Play</a>
        </p>
      </div>
      <div id="game-stats">
        <p>Time: <span class="game-score"></span> </p>
        <p> <a class="game-reset" href="#" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow" >Reset</a> </p>
      </div>
      <div id="game-complete">
        <h1>Game over!</h1>
        <p>You survived for <span class="game-score"></span> seconds. </p>
        <p><a class="game-reset buyyon" href="#" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow" >Play</a></p>
      </div>
    </div>
    <canvas id="game-canvas" width="800" height="600">
      <!-- 在此處插入后備代碼 -->
    </canvas>
  </div>
</body>
</html>

我不打算過(guò)多解釋這些 HTML 代碼,因?yàn)樗鼈儽容^簡(jiǎn)單,但你只要知道這就是游戲所需的所有標(biāo)記即可。

2.2 美化界面

創(chuàng)建一個(gè)名為 style.css 的新文件,并把它和 HTML 文件放在相同的目錄下。在該 CSS 文件中插入以下代碼:

* {
  margin: 0;
  padding: 0;
}
html, body {
  height: 100%;
  width: 100%;
}
canvas {
  display: block;
}
body {
  background-color: #000;
  color: #fff;
  font-family: Verdana, Arial, sans-serif;
  font-size: 18px;
  height: 100%;
}
h1 {
  font-size: 30px;
}
p {
  margin: 0 20px;
}
a {
  color: #fff;
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
}
a.button {
  background-color: #185da8;
  border-radius: 5px;
  display: block;
  font-size: 30px;
  margin: 40px 0 0 270px;
  padding: 10px;
  width: 200px;
}
a.button:hover {
  background-color: #2488f5;
  color: #fff;
  text-decoration: none;
}
#game {
  height: 600px;
  left: 50%;
  margin: -300px 0 0 -400px;
  position: relative;
  top: 50%;
  width: 800px;
}
#game-canvas {
  background-color: #001022;
}
#game-ui {
  height: 600px;
  position: absolute;
  width: 800px;
}
#game-intro, #game-complete {
  background-color: rgba(0, 0, 0, .5);
  margin-top: 100px;
  padding: 40px 0;
  text-align: center;
}
#game-stats {
  font-size: 14px;
  margin: 20px 0;
}
#game-stats, .game-reset {
  margin: 20px 20px 0 0;
  position: absolute;
  right: 0;
  top: 0;
}

2.3 編寫(xiě) JavaScript 代碼

在添加一些有趣的游戲邏輯之前,首先需要用JavaScript實(shí)現(xiàn)核心功能。創(chuàng)建一個(gè)名為 main.js 的新文件,并把它和 HTML 文件放在相同的目錄下。在該 js 文件中插入以下代碼:

$(document).ready(function () {
  const canvas = $('#game-canvas');
  const context = canvas.get(0).getContext("2d");
  // 畫(huà)布尺寸
  const canvasWidth = canvas.width();
  const canvasHeight = canvas.height();
  // 游戲設(shè)置
  let playGame;
  // 游戲UI
  const ui = $("#game-ui");
  const uiIntro = $("#game-intro");
  const uiStats = $("#game-stats");
  const uiComplete = $("#game-complete");
  const uiPlay = $("#game-play");
  const uiReset = $(".game-reset");
  const uiScore = $(".game-score");
  // 重至和啟動(dòng)游戲
  function startGame() {
    // 重置游戲狀態(tài)
    uiScore.html("0");
    uiStats.show();
    // 初始游戲設(shè)置
    playGame = false;
    // 開(kāi)始動(dòng)畫(huà)糖環(huán)
    animate();
  }
  //初始化游戲環(huán)境
  function init() {
    uiStats.hide();
    uiComplete.hide();
    uiPlay.click(function (e) {
      e.preventDefault();
      uiIntro.hide();
      startGame();
    });
    uiReset.click(function (e) {
      e.preventDefault();
      uiComplete.hide();
      startGame();
    });
  }
  // 動(dòng)畫(huà)循環(huán),游戲的嫌味性就在這里
  function animate() {
    // 清除
    context.clearRect(0, 0, canvasWidth, canvasHeight);
    if (playGame) {
      setTimeout(animate, 33);
    }
  }
  init();
});

在你最喜歡的瀏覽器中運(yùn)行該游戲,應(yīng)該會(huì)看到一個(gè)更加美觀的 UI。另外,你還可以單擊 Play 按鈕來(lái)顯示游戲的主窗口,盡管它看上去也許還有些單調(diào)。

3. 創(chuàng)建游戲?qū)ο?/h2>

躲避小行星游戲使用兩個(gè)主要對(duì)象:小行星和玩家使用的火箭。我們將使用 JavaScript 類來(lái)創(chuàng)建這些對(duì)象。你也許會(huì)覺(jué)得奇怪,既然玩家只有一個(gè),為什么還要通過(guò)一個(gè)類來(lái)定義它呢?簡(jiǎn)而言之,如果你以后需要在游戲中添加多個(gè)玩家,通過(guò)類創(chuàng)建玩家就會(huì)更容易一些。

3.1 創(chuàng)建小行星

通過(guò)類創(chuàng)建游戲?qū)ο笠馕吨憧梢栽谄渌螒蛑蟹浅7奖愕刂赜煤透淖兯鼈兊挠猛尽?/p>

第一步是聲明主要變量,我們將使用這些變量來(lái)存儲(chǔ)所有的小行星。同時(shí),還需要聲明另外一個(gè)變量,用于計(jì)算游戲中應(yīng)該存在的小行星數(shù)目。在 JavaScript 代碼頂部的 playGame 變量下面添加以下代碼:

let asteroids;
let numAsteroids;

稍后你將會(huì)為這些變量賦值,但現(xiàn)在我們只建立小行星類。在startGame函數(shù)上面添加以下代碼:

function Asteroid(x, y, radius, vX) {
  this.x = x;
  this.y = y;
  this.radius = radius;
  this.vX = vX;
}

這里存在一個(gè)速度屬性,這是因?yàn)樾⌒行侵恍枰獜挠蚁蜃筮\(yùn)動(dòng),即只需要 x 速度。這里不需要 y 速度,所以就省略了。

在開(kāi)始創(chuàng)建所有小行星之前,需要建立數(shù)組來(lái)存儲(chǔ)這些小行星,并聲明實(shí)際需要使用的小行星數(shù)目。在startGame函數(shù)中的PlayGame變量下面添加以下代碼:

asteroids = new Array();
numAsteroids = 10;

你也許認(rèn)為 10 個(gè)小行星是一個(gè)很小的數(shù)目,但是當(dāng)這些小行星在屏幕上消失時(shí),你將重復(fù)使用它們,所以在游戲中你實(shí)際看到的小行星數(shù)目可以有無(wú)窮多個(gè)。你可以把這里的小行星數(shù)目看做屏幕上某一時(shí)刻可能出現(xiàn)的小行星總數(shù)。

創(chuàng)建小行星的過(guò)程其實(shí)就是一個(gè)創(chuàng)建循環(huán)的過(guò)程,循環(huán)的次數(shù)就是你剛才聲明的小行星數(shù)目。在你剛才賦值的numAsteroids變量下面添加以下代碼:

for (let i = 0; i < numAsteroids; i++) {
  const radius = 5 + (Math.random() * 10);
  const x = canvasWidth + radius + Math.floor(Math.random() * canvasWidth);
  const y = Math.floor(Math.random() * canvasHeight);
  const vx = -5 - (Math.random() * 5);
  asteroids.push(new Asteroid(x, y, radius, vX));
}

為了讓每顆小行星的外觀都與眾不同,并且使游戲看上去更有趣一些,可以把小行星的半徑設(shè)為一個(gè)介于 5 到 15 像素之間的隨機(jī)數(shù)( 5 加上一個(gè)介于 0 到 10 之間的隨機(jī)數(shù))。雖然 x 速度的值介于 -5 到 -10 之間,但你也可以采用同樣的方法來(lái)設(shè)置它( -5 減去一個(gè) 0 到 5 之間的數(shù))。因?yàn)槟阈枰屝⌒行前磸挠蚁蜃蟮姆较蜻\(yùn)動(dòng),所以使用的是一個(gè)負(fù)的速度值,這說(shuō)明 x 的位置將隨著時(shí)間的推移而減小。

計(jì)算每顆小行星的 x 位置看上去有些復(fù)雜,但其實(shí)非常簡(jiǎn)單。在開(kāi)始啟動(dòng)游戲的時(shí)候,如果讓所有的小行星全部顯示在屏幕上,就讓人覺(jué)得有些太奇怪了。因此在游戲開(kāi)始之前,最好把小行星放在屏幕的右側(cè),當(dāng)游戲開(kāi)始時(shí)才讓它們按從右向左的順序穿過(guò)屏幕。

為此,首先需要把 x 位置設(shè)為 canvas 元素的寬度,然后加上小行星的半徑。這意味著如果你現(xiàn)在畫(huà)出小行星,那么它應(yīng)該位于屏幕的右側(cè)。如果僅僅這樣做,那么所有的小行星將會(huì)排成一行,所以下一步我們需要把 x 位置加上一個(gè)介于 0 到畫(huà)布寬度之間的隨機(jī)值。與 x 位置相比,y 位置簡(jiǎn)單一些,它只是一個(gè)介于 0 到畫(huà)布高度之間的隨機(jī)值。

這樣可以產(chǎn)生一個(gè)與畫(huà)布尺寸相同的方框,方框中隨機(jī)分布著一些小行星。當(dāng)游戲開(kāi)始時(shí),這些小行星將穿過(guò)可見(jiàn)的畫(huà)布。

最后一步是把一個(gè)新的小行星推送到數(shù)組中,做好移動(dòng)和繪制小行星的準(zhǔn)備。

3.2 創(chuàng)建玩家使用的火箭

首先聲明用于建立玩家的初始化變量。在 JavaScript 頂部的 numAsteroids 變量下面添加以下代碼:

let player;

該變量將用于存儲(chǔ)玩家對(duì)象的引用,但現(xiàn)在我們還沒(méi)有定義玩家對(duì)象。在Asteroid 類下面添加以下代碼:

function Player(x, y) {
  this.x = x;
  this.y = y;
  this.width = 24;
  this.height = 24;
  this.halfWidth = this.width / 2;
  this.halfHeight = this.height / 2;
  this.vX = 0;
  this.vY = 0;
}

你應(yīng)該熟悉以上代碼的某些部分,例如位置和速度屬性。其余屬性用于描述玩家使用的火箭的尺寸,包括整個(gè)尺寸和一半的尺寸。繪制火箭和執(zhí)行碰撞檢測(cè)時(shí),你需要使用這些尺寸。

最后一步是創(chuàng)建一個(gè)新的玩家對(duì)象。為此,在 startGame 函數(shù)中的numAsteroids變量下面添加以下對(duì)象:

player = new Player(150, canvasHeight / 2);

通過(guò)以上代碼,玩家的位置將垂直居中,并且距離畫(huà)布左邊界 150 像素。

現(xiàn)在還不能看到任何效果,稍后當(dāng)你開(kāi)始著手移動(dòng)所有的游戲?qū)ο髸r(shí),將會(huì)從視覺(jué)上看到這種效果。

4. 檢測(cè)鍵盤(pán)輸入

本游戲?qū)⑹褂面I盤(pán)來(lái)控制游戲。更確切地說(shuō),你將使用方向鍵來(lái)四處移動(dòng)玩家使用的火箭。如何才能實(shí)現(xiàn)這種控制呢?這比控制鼠標(biāo)輸入更難嗎?不,其實(shí)非常簡(jiǎn)單。下面我來(lái)教你怎么做。

4.1 鍵值

在處理鍵盤(pán)輸人時(shí),首先需要知道哪一個(gè)按鍵被按下了。在 JavaScript 中,普通鍵盤(pán)上的每一個(gè)按鍵都分配了一個(gè)特定的鍵值(key code)。通過(guò)這些鍵值,可以唯一確定按下或釋放了哪個(gè)鍵。稍后你將學(xué)習(xí)如何使用鍵值,現(xiàn)在我們首先需要了解每個(gè)按鍵所對(duì)應(yīng)的數(shù)值。

例如,鍵 az (無(wú)論在什么情況下)對(duì)應(yīng)的鍵值分別是從 65 到 90 。箭頭鍵對(duì)應(yīng)的鍵值是從 37 到 40,其中左箭頭的鍵值是 37、上箭頭的鍵值是 38、右箭頭的鍵值是 39、下箭頭的鍵值是 40。空格鍵的鍵值是 32。

在躲避小行星游戲中,你需要重點(diǎn)關(guān)注的是箭頭鍵,因此在 JavaScript 代碼頂部的 player 變量下面添加以下代碼:

const arrowUp = 38;
const arrowRight = 39;
const arrowDown = 40;

以上代碼為每個(gè)箭頭對(duì)應(yīng)的鍵值分別分配了一個(gè)變量。這種方法稱作枚舉(enumeration),它是對(duì)值進(jìn)行命名的過(guò)程。這主要是為后面的工作提供方便,因?yàn)橥ㄟ^(guò)這些名稱你能很容易確定變量引用的是哪個(gè)箭頭鍵。

請(qǐng)注意,為什么沒(méi)有為左箭頭聲明一個(gè)變量呢?因?yàn)槟悴粫?huì)手動(dòng)地讓玩家向后移動(dòng)。相反,當(dāng)玩家沒(méi)有按任何按鍵時(shí),就會(huì)表現(xiàn)為向后移動(dòng)的狀態(tài)。稍后你就會(huì)明白其中的道理。

4.2 鍵盤(pán)事件

在向游戲中添加鍵盤(pán)交互效果之前,首先需要確定玩家在何時(shí)按下或釋放某個(gè)按鍵。為此,需要使用 keydownkeyup 事件監(jiān)聽(tīng)器。

startGame 函數(shù)中的 animate 函數(shù)調(diào)用上面(在創(chuàng)建所有小行星的循環(huán)下面)添加以下代碼:

$(window).keydown(e => {
});
$(window).keyup(e => {
});

按下某個(gè)按鍵時(shí)將觸發(fā)第一個(gè)監(jiān)聽(tīng)器,釋放某個(gè)按鍵時(shí)將觸發(fā)第二個(gè)監(jiān)聽(tīng)器。非常簡(jiǎn)單。稍后我們將在這些事件監(jiān)聽(tīng)器中添加一些有用的代碼,但首先需要在重新設(shè)置游戲時(shí)刪除這些監(jiān)聽(tīng)器,這能防止玩家由于無(wú)意按下某個(gè)按鍵而啟動(dòng)游戲。在 uiReset.click 事件監(jiān)聽(tīng)器中的 startGame 調(diào)用上面添加以下代碼:

$(window).unbind('keyup');
$(window).unbind('keydown');

接下來(lái),還需要添加一些在激活玩家后用到的屬性。在 Player 類的末尾添加以下代碼:

this.moveRight = false;
this.moveUp = false;
this.moveDown = false;

通過(guò)這些屬性,你可以知道玩家的移動(dòng)方向,這些屬性值的設(shè)置取決于玩家按下了哪個(gè)按鍵?,F(xiàn)在你是不是已經(jīng)理解了其中的所有道理呢?

最后,需要向鍵盤(pán)事件監(jiān)聽(tīng)器中添加一些邏輯。首先,在 keydown 事件監(jiān)聽(tīng)器中添加以下代碼:

const keyCode = e.keyCode;
if (!playGame) {
  playGame = true;
  animate();
}
if (keyCode == arrowRight) {
  player.moveRight = true;
} else if (keyCode == arrowUp) {
  player.moveUp = true;
} else if (keyCode == arrowDown) {
  player.moveDown = true;
}

并在 keyup 事件監(jiān)聽(tīng)器中添加以下代碼:

const keyCode = e.keyCode;
if (keyCode == arrowRight) {
  player.moveRight = false;
} else if (keyCode == arrowUp) {
  player.moveUp = false;
} else if (keyCode == arrowDown) {
  player.moveDown = false;
}

以上代碼的作用非常明顯,但我還需要作一些說(shuō)明。在兩個(gè)監(jiān)聽(tīng)器中,第一行的作用都是把按鍵的鍵值賦給一個(gè)變量。然后在一組檢查語(yǔ)句中使用該鍵值來(lái)判斷是否按下了某個(gè)箭頭鍵,如果按下了箭頭鍵,判斷是哪個(gè)箭頭鍵。這樣,我們就可以啟動(dòng)(如果按下了該鍵)或禁用(如果釋放了該鍵)玩家對(duì)象的對(duì)應(yīng)屬性。

例如,如果按下了向右的箭頭鍵,那么玩家對(duì)象的 moveRight 屬性將被設(shè)為 true。如果釋放了該方向鍵,則 moveRight 屬性將被設(shè)為false。

**注意:**如果玩家一直按住某個(gè)按鍵,那么將觸發(fā)多個(gè) keydown 事件。因此,代碼要具備處理多個(gè)被觸發(fā)的 keydown 事件的能力,這一,點(diǎn)非常重要。在每個(gè) keydown 事件之后不一定總是一個(gè) keyup 事件,另外還要注意的是,在 keydown 事件監(jiān)聽(tīng)器中是如何通過(guò)一個(gè)條件語(yǔ)句來(lái)查看游戲當(dāng)前是否正在進(jìn)行的。如果玩家沒(méi)有做好游戲準(zhǔn)備,該語(yǔ)句將阻止游戲運(yùn)行。只有玩家按下鍵盤(pán)上的某個(gè)鍵時(shí),才會(huì)啟動(dòng)游戲。方法很簡(jiǎn)單,但卻非常有效。

游戲中的鍵盤(pán)輸入非常多,我們不可能一一列舉。在下一節(jié)中,我們將通過(guò)這些輸入來(lái)控制玩家沿著正確的方向運(yùn)動(dòng)。

5. 讓對(duì)象運(yùn)動(dòng)起來(lái)

現(xiàn)在你已經(jīng)做好了實(shí)現(xiàn)游戲?qū)ο髣?dòng)畫(huà)的所有準(zhǔn)備。當(dāng)你實(shí)際看到游戲效果時(shí),這一切會(huì)變得更有趣。

第一步是更新所有游戲?qū)ο蟮奈恢谩N覀儚母滦⌒行菍?duì)象的位置開(kāi)始,在 animate 函數(shù)中

畫(huà)布的 clearRect 方法下面添加以下代碼:

const asteroidsLength = asteroids.length;
for (let i = 0; i < asteroidsLength; i++) {
  const tmpAsteroid = asteroids[i];
  tmpAsteroid.x += tmpAsteroid.vX;
  context.fillStyle = "rgb(255, 255, 255)";
  context.beginPath();
  context.arc(tmpAsteroid.x, tmpAsteroid.y, tmpAsteroid.radius, 0, Math.PI * 2, true);
  context.closePath();
  context.fill();
}

這些代碼非常簡(jiǎn)單。主要是遍歷每一顆小行星,并根據(jù)速度來(lái)更新它的位置,然后在畫(huà)布上繪制小行星。

刷新瀏覽器查看效果(記住按下某個(gè)按鍵啟動(dòng)游戲)。應(yīng)該能夠看到某顆小行星帶穿越屏幕的場(chǎng)景。

注意它們是如何消失在屏幕左側(cè)的。下一節(jié)將學(xué)習(xí)如何在橫向滾動(dòng)的屏幕上阻止它們的運(yùn)動(dòng)。

迄今為止,假設(shè)這些小行星都實(shí)現(xiàn)了我們的預(yù)期效果。接下來(lái)還需要更新并顯示玩家!

animate 函數(shù)中剛才添加小行星代碼的下面再添加以下代碼:

player.vX = 0;
player.vY = 0;
if (player.moveRight) {
  player.vX = 3;
}
if (player.moveUp) {
  player.vY = -3;
}
if (player.moveDown) {
  player.vY = 3;
}
player.x += player.vX;
player.y += player.vY;

以上代碼將更新玩家的速度,并將速度設(shè)置為一個(gè)特定的值,該值由玩家移動(dòng)的方向來(lái)確定。如果玩家需要向右移動(dòng),那么速度值為 x 軸上的 3像素。如果玩家需要向上移動(dòng),那么速度值為 y 軸上的 -3 像素。同樣,如果玩家需要向下移動(dòng),那么速度值即為y軸上的3像素。這非常簡(jiǎn)單。另外,還需要注意如何在代碼的開(kāi)始處重置速度值。如果玩家沒(méi)有按下任何按鍵,該語(yǔ)句將阻止玩家移動(dòng)。

最后,還需要根據(jù)速度來(lái)更新玩家的 xy 位置?,F(xiàn)在你還看不到任何效果,但你已經(jīng)做好了在屏幕上繪制火箭的所有準(zhǔn)備工作。

在剛才添加的代碼下面直接添加以下代碼:

context.fillStyle = 'rgb(255, 0, 0)';
context.beginPath();
context.moveTo(player.x + player.halfWidth, player.y);
context.lineTo(player.x - player.halfWidth, player.y - player.halfHeight);
context.lineTo(player.x - player.halfWidth, player.y + player.halfHeight)
context.closePath();
context.fill();

你知道以上代碼的作用嗎?很明顯,你正在繪制一條填充路徑,但你能告訴我繪制的路徑是什么形狀嗎?是的,它只是一個(gè)三角形而已。

如果你仔細(xì)查看,會(huì)發(fā)現(xiàn)玩家對(duì)象的尺寸屬性的作用。知道了玩家對(duì)象的寬度值和高度值的一半,就可以構(gòu)建一個(gè)動(dòng)態(tài)三角形,它能隨著尺寸值的變化變大或縮小。方法很簡(jiǎn)單,但效果卻很好。

在瀏覽器中查看游戲的效果,應(yīng)該能夠看到玩家使用的火箭。

試著按下箭頭鍵??吹交鸺苿?dòng)了嗎?現(xiàn)在的游戲效果已經(jīng)非常棒了。

這里可以只使用運(yùn)動(dòng)邏輯,但游戲會(huì)顯得有些單調(diào)。我們不妨在火箭上再添加一團(tuán)閃動(dòng)的火焰!在 Player 類的末尾添加以下代碼:

this.flameLength = 20;

以上代碼用于確定火焰的持續(xù)時(shí)間,稍后我們還需要添加更多代碼?,F(xiàn)在先在 animate 函數(shù)中繪制火箭的代碼前面添加以下代碼:

if (player.moveRight) {
  context.save();
  context.translate(player.x - player.halfWidth, player.y);
  if (player.flameLength == 20) {
    player.flameLength = 15;
  } else {
    player.flameLength = 20;
  }
  context.fillStyle = "orange";
  context.beginPath();
  context.moveTo(0, -5);
  context.lineTo(-player.flameLength, 0);
  context.lineTo(0, 5);
  context.closePath();
  context.fill();
  context.restore();
}

條件語(yǔ)句用于確保只有當(dāng)玩家向右運(yùn)動(dòng)時(shí)才繪制火焰,因?yàn)槿绻谄渌麜r(shí)間也能看到火焰,看上去就不符合常理了。

我們使用畫(huà)布的 translate 方法來(lái)繪制火焰,因?yàn)樵诤竺嬲{(diào)用 save 方法來(lái)保存畫(huà)布的繪圖狀態(tài)時(shí),translate 方法可以節(jié)約一些時(shí)間?,F(xiàn)在已經(jīng)存儲(chǔ)了繪圖上下文的原始狀態(tài),接下來(lái)就可以調(diào)用 translate 方法,并把 2D 繪圖上下文的原點(diǎn)移到玩家使用的火箭的左側(cè)。

現(xiàn)在已經(jīng)移動(dòng)了畫(huà)布的原點(diǎn),接下來(lái)的任務(wù)就非常簡(jiǎn)單了。只需要對(duì)存儲(chǔ)在玩家對(duì)象的 flameLength 屬性中的值執(zhí)行循環(huán)(使火箭呈現(xiàn)閃爍效果),并把填充顏色改為橙色,然后從新的起點(diǎn)繪制一個(gè)長(zhǎng)度與flameLength屬性相同的三角形。最后還需要調(diào)用 restore 方法,將原始繪圖狀態(tài)恢復(fù)到畫(huà)布上。

刷新瀏覽器看看剛才的勞動(dòng)成果。當(dāng)按下向右的箭頭鍵時(shí),火箭上應(yīng)該出現(xiàn)了一團(tuán)閃爍的火焰。

接下來(lái)需要做好準(zhǔn)備,我們將使游戲產(chǎn)生一種逼真的橫向卷軸效果。

6. 假造橫向卷軸效果

雖然這個(gè)游戲看上去好像是橫向卷動(dòng)的,但實(shí)際上你并沒(méi)有穿越在游戲世界中。相反,你將循環(huán)利用所有在屏幕上消失的對(duì)象,并讓它們重新顯示在屏幕的另一側(cè)。這樣就會(huì)產(chǎn)生一種始終穿越在永無(wú)止境的游戲世界中的效果。聽(tīng)起來(lái)好像有些奇特,其實(shí)它只是一種橫向卷動(dòng)效果而已。

6.1 循環(huán)利用小行星

讓游戲產(chǎn)生一種永無(wú)止境的穿越效果其實(shí)并不難。實(shí)際上非常簡(jiǎn)單!在animate 函數(shù)中剛才繪制每顆小行星的代碼上面添加以下代碼:

if (tmpAsteroid.x + tmpAsteroid.radius < 0) {
  tmpAsteroid.radius = 5 + (Math.random() * 10);
  tmpAsteroid.x = canvasWidth + tmpAsteroid.radius;
  tmpAsteroid.y = Math.floor(Math.random() * canvasHeight);
  tmpAsteroid.vX = -5 - (Math.random() * 5);
}

這點(diǎn)代碼就夠了。這段代碼的作用是檢查小行星是否移動(dòng)到畫(huà)布的左邊界之外,如果是,則重置該小行星,并將它重新移回到畫(huà)布的右側(cè)。你已經(jīng)重新利用了該小行星,但它看上去卻像是一顆全新的小行星。

6.2 添加邊界

現(xiàn)在,玩家火箭可能會(huì)自由地在游戲中飛越,也可能會(huì)停止不動(dòng)(試圖飛越畫(huà)布的右側(cè)時(shí))。為了解決這個(gè)問(wèn)題,需要在適當(dāng)?shù)奈恢迷O(shè)置一些邊界。在繪制火箭火焰的代碼上面(正好在設(shè)置新的玩家位置的代碼下面)添加以下代碼:

if (player.x - player.halfWidth < 20) {
  player.x = 20 + player.halfWidth;
} else if (player.x + player.halfWidth > canvasWidth - 20) {
  player.x = canvasWidth - 20 - player.halfWidth;
}
if (player.y - player.halfHeight < 20) {
  player.y = 20 + player.halfHeight;
} else if (player.y + player.halfHeight > canvasHeight - 20) {
  player.y = canvasHeight - 20 - player.halfHeight;
}

你也許能猜出以上代碼的作用。它主要執(zhí)行一些標(biāo)準(zhǔn)的邊界檢查。這些檢查查看玩家是否位于畫(huà)布邊界 20 像素之內(nèi),如果是,則阻止它們沿著該方向進(jìn)一步移動(dòng)。我認(rèn)為在畫(huà)布的邊界處預(yù)留 20 像素的空隙視覺(jué)效果更佳,但也可以把這個(gè)值再改小一點(diǎn),以便玩家能夠向右移動(dòng)到畫(huà)布的邊緣處。

6.3 讓玩家保持連續(xù)移動(dòng)

目前,如果玩家沒(méi)有按下任何按鍵,火箭將停止移動(dòng)。當(dāng)所有的小行星正在飄蕩時(shí),火箭突然停止移動(dòng)不太符合常理。因此可以在游戲中添加一些額外的運(yùn)動(dòng),當(dāng)玩家不再向前移動(dòng)時(shí),可以讓它們繼續(xù)向后移動(dòng)。

animate 函數(shù)中把改變玩家 vX 屬性的代碼段更換為以下代碼:

if (player.moveRight) {
  player.vX = 3;
} else {
  player.vX = -3;
}

這段代碼只是在條件語(yǔ)句中添加了一段額外代碼,即當(dāng)玩家不需要向右移動(dòng)時(shí),把玩家的 vX 屬性設(shè)為-3。你總結(jié)一下就會(huì)發(fā)現(xiàn),這與大部分游戲邏輯都是相同的。在瀏覽器中運(yùn)行該游戲,現(xiàn)在的游戲看上去更逼真了!

7. 添加聲音

這也許是游戲中最酷的一部分。在游戲中添加一些簡(jiǎn)單的聲音非常有趣,游戲也會(huì)變得更加引人入勝。你也許覺(jué)得在游戲中添加音頻是非常困難的,但使用 HTML5 音頻來(lái)實(shí)現(xiàn)卻是一件輕而易舉的事!下面我們來(lái)看看。

首先需要在游戲的 HTML 代碼中聲明所有的 HTML5 音頻元素。直接在index.html文件中的 canvas 元素下面添加以下代碼:

<audio id="game-sound-background" loop>
  <source src="sounds/background.ogg">
  <source src="sounds/background.mp3">
</audio>
<audio id="game-sound-thrust" loop>
  <source src="sounds/thrust.ogg">
  <source src="sounds/thrust.mp3">
</audio>
<audio id="game-sound-death">
  <source src="sounds/death.ogg">
  <source src="sounds/death.mp3">
</audio>

如果你掌握了 HTML5 音頻部分的內(nèi)容,那么應(yīng)該對(duì)以上代碼非常熟悉了。如果你還沒(méi)有掌握該內(nèi)容,也不用著急,因?yàn)樗浅:?jiǎn)單。這里聲明了 3 個(gè)獨(dú)立的 HTML5 audio 元素,并且為每個(gè) audio 元素定義了一個(gè)唯一的 id 屬性,后面將用到這些 id 屬性。循環(huán)播放的聲音還需要定義一個(gè) loop 屬性。

注意:并非所有的瀏覽器都支持 loop 屬性。由于它是規(guī)范的部分,因此越來(lái)越多的瀏覽器將會(huì)全面支持該屬性。如果需要采用一種變通的方案,可以在音頻播放結(jié)束時(shí)添加一個(gè)事件監(jiān)聽(tīng)器,并再次播放。

這 3 種聲音都是背景音樂(lè),火箭開(kāi)始移動(dòng)時(shí)使用推進(jìn)器的聲音,最后玩家死亡時(shí)使用深沉的轟鳴聲。為了與大多數(shù)瀏覽器兼容,每種聲音都需要兩個(gè)版本的文件,因此也需要包含兩個(gè) source 元素:一個(gè)是 mp3 版本,另一個(gè)是 ogg 版本。

在 HTML 文件中只需要完成這些任務(wù)就可以了,接下來(lái)我們回 JavaScript文件中,并在 JavaScript文件頂部的 uiScore 變量下面添加以下代碼:

const soundBackground = $("#game-sound-background").get(0);
const soundThrust = $("#game-sound-thrust").get(0);
const soundDeath = $("#game-sound-death").get(0);

這些變量使用 HTML 文件中聲明的 id 屬性來(lái)獲取每個(gè)audio元素,這與在游戲中獲取 canvas 元素非常相似。接下來(lái)將使用這些變量訪問(wèn)HTML5 音頻 API 并控制聲音。

這些內(nèi)容無(wú)需過(guò)多解釋,緊接著我們轉(zhuǎn)入 keydown 事件監(jiān)聽(tīng)器中,在把playGame設(shè)置為 true 的代碼后面添加以下代碼:

soundBackground.currentTime = 0;
soundBackground.play();

現(xiàn)在,你已經(jīng)在游戲中添加了HTML5音頻,并且可以非常方便地控制它。很酷吧?以上代碼的作用是訪問(wèn)與背景音樂(lè)相關(guān)的HTML5 audio元素,并且可以通過(guò)HTML5音頻 API 直接控制它。因此,通過(guò)更改 currentTime 屬性,可以重置音頻文件播放的起始位置,另外,通過(guò)調(diào)用 play 方法,可以播放該音頻文件。真的很簡(jiǎn)單!

載入并運(yùn)行游戲,現(xiàn)在當(dāng)你開(kāi)始移動(dòng)火箭時(shí),應(yīng)該能聽(tīng)到一些美妙的背景音樂(lè)。

下一步是控制推進(jìn)器的聲音(當(dāng)玩家移動(dòng)火箭時(shí))。我希望你已經(jīng)猜到了如何去實(shí)現(xiàn),其實(shí)這與實(shí)現(xiàn)背景音樂(lè)一樣簡(jiǎn)單。

keydown 事件監(jiān)聽(tīng)器中 player 對(duì)象的 moveRight 屬性設(shè)置代碼下面添加以下代碼:

if (soundThrust.paused) {
  soundThrust.currentTime = 0;
  soundThrust.play();
}

第一行代碼用于檢查是否正在播放推進(jìn)器聲音,如果是,則禁止在游戲中再次播放它。這可以防止該聲音在播放的過(guò)程中被中途切斷,因?yàn)槊棵腌娍赡軙?huì)觸發(fā)多次 keydown 事件,而你當(dāng)然也不希望每次觸發(fā) keydown 事件時(shí)都再次播放推進(jìn)器聲音。

當(dāng)玩家停止移動(dòng)時(shí),你也許不希望推進(jìn)器聲音繼續(xù)播放,為此,在 keyup 事件監(jiān)聽(tīng)器中 player 對(duì)象的 moveRight 屬性設(shè)置代碼下面添加以下代碼:

soundThrust.pause();

就這么簡(jiǎn)單,音頻 API 太方便了,通過(guò)它訪問(wèn)和操縱音頻非常簡(jiǎn)單。

在繼續(xù)下一步之前(下一節(jié)將添加死亡的聲音),我們還需要考慮一個(gè)問(wèn)題:如果玩家重置游戲,我們需要如何確保停止播放聲音。為此,在 init 函數(shù)的 uiReset,click 事件處理程序中的 startGame 調(diào)用上面添加以下代碼:

soundThrust.pause();
soundBackground.pause();

當(dāng)游戲重置時(shí),以上兩行代碼可以確保停止播放推進(jìn)器聲音和背景音樂(lè)。因?yàn)樗劳龅穆曇舨恍枰M(jìn)行循環(huán),并且你希望在游戲結(jié)束時(shí)才播放它,所以暫時(shí)不需要考慮死亡的聲音。

8. 結(jié)束游戲

現(xiàn)在的游戲已經(jīng)逐漸成型了。實(shí)際上,它就快完成了。接下來(lái)唯一要做的就是實(shí)現(xiàn)某種計(jì)分系統(tǒng),并通過(guò)某種方法來(lái)結(jié)束游戲。首先解決計(jì)分系統(tǒng)問(wèn)題,稍后介紹如何結(jié)束游戲。

8.1 計(jì)分系統(tǒng)

在游戲中,鑒于玩家試圖生存盡可能長(zhǎng)的時(shí)間,所以把存活時(shí)間作為計(jì)分標(biāo)準(zhǔn)顯然是一個(gè)不錯(cuò)想法。不是嗎?

我們需要通過(guò)某種方法來(lái)計(jì)算游戲從開(kāi)始到現(xiàn)在所持續(xù)的時(shí)間。這正好是JavaScript 計(jì)時(shí)器的強(qiáng)項(xiàng),但在構(gòu)建計(jì)時(shí)器之前需要聲明一些變量。在JavaScript 代碼頂部的 player 變量下面添加以下代碼:

let score;
let scoreTimeout;

這些變量將用于存儲(chǔ)分?jǐn)?shù)(已經(jīng)過(guò)去的秒數(shù))和對(duì)計(jì)時(shí)器操作的引用,以便根據(jù)需要來(lái)開(kāi)始或停止計(jì)時(shí)器。

另外,在游戲開(kāi)始或重置時(shí)也需要重新設(shè)置分?jǐn)?shù)。為此,在 startGame 函數(shù)頂部的 numAsteroids 變量下面添加以下代碼:

score = 0;

為了便于管理得分計(jì)時(shí)器,我們創(chuàng)建一個(gè)名為 timer 的專用函數(shù)。在 animate 函數(shù)上面添加以下代碼:

function timer() {
  if (playGame) {
    scoreTimeout = setTimeout(() => {
      uiScore.html(++score);
      timer();
    }, 1000);
  }
}

以上代碼現(xiàn)在還不會(huì)起作用,但它會(huì)檢查游戲是否開(kāi)始,如果游戲已經(jīng)開(kāi)始,它就把計(jì)時(shí)器的時(shí)間間隔設(shè)置為 1 秒,并把該計(jì)時(shí)器賦給 scoreTimeout 變量。在計(jì)時(shí)器中,score 變量的值在增加,同時(shí)計(jì)分 UI 也在更新。然后,計(jì)時(shí)器自身將調(diào)用 timeout 函數(shù)來(lái)重復(fù)整個(gè)過(guò)程,這意味著游戲結(jié)束時(shí)計(jì)時(shí)器才會(huì)停止計(jì)時(shí)。

現(xiàn)在還沒(méi)有調(diào)用 timer 函數(shù),所以它還不會(huì)發(fā)揮作用。當(dāng)游戲開(kāi)始時(shí),需要調(diào)用該函數(shù),因此在 keydown 事件監(jiān)聽(tīng)器中的 animate 函數(shù)調(diào)用下面添加以下代碼:

timer();

只要玩家開(kāi)始游戲,以上代碼就會(huì)觸發(fā)計(jì)時(shí)器。在瀏覽器中查看效果,在游戲界面的左上角可以看到分?jǐn)?shù)在不斷增加。

但遺憾的是,這里還存在一個(gè)問(wèn)題——如果你重置游戲,分?jǐn)?shù)有時(shí)候會(huì)顯示為 1 秒鐘。這是因?yàn)楫?dāng)你重置游戲時(shí),分?jǐn)?shù)計(jì)時(shí)器仍然在運(yùn)行,但它實(shí)際在你重置游戲之后才運(yùn)行(將重置分?jǐn)?shù)由 0 更改為 1 )。為了解決這個(gè)問(wèn)題,需要在重置游戲時(shí)先清除計(jì)時(shí)器。幸運(yùn)的是,JavaScript 有特定的函數(shù)可以實(shí)現(xiàn)該操作。

init 函數(shù)的 uiReset.click 事件監(jiān)聽(tīng)器的 startGame 調(diào)用上面添加以下代碼:

clearTimeout(scoreTimeout);

顧名思義,以上代碼的作用顯而易見(jiàn)。通過(guò)這個(gè)獨(dú)立的函數(shù)可以獲取 scoreTime 變量中的分?jǐn)?shù)計(jì)時(shí)器,并且阻止計(jì)時(shí)器的運(yùn)行。再次運(yùn)行游戲,你可以發(fā)現(xiàn)通過(guò)這行簡(jiǎn)單的 JavaScript 代碼已經(jīng)成功解決了上面遇到的問(wèn)題。

8.2 殺死玩家

如果小行星無(wú)法傷害你,那么躲避小行星就沒(méi)有任何意義了,因此我們需要添加一些功能來(lái)殺死玩家(當(dāng)玩家碰到小行星時(shí))。

在這里發(fā)現(xiàn)明顯的問(wèn)題了嗎?在火箭是三角形的情況下,你能執(zhí)行圓周碰撞檢測(cè)嗎?簡(jiǎn)單地說(shuō),你不能執(zhí)行圓周碰撞檢測(cè),或者說(shuō)至少?zèng)]那么容易。但這里將忽略一些細(xì)節(jié)問(wèn)題,也就是說(shuō),把玩家火箭的一小部分區(qū)域作為碰撞檢測(cè)區(qū)域。在實(shí)際中如果你幸運(yùn)一些,這種檢測(cè)有助于躲避小行星。

為了簡(jiǎn)化代碼,我認(rèn)為這樣做是值得的。畢竟,這只是一個(gè)供娛樂(lè)的小游戲而已,因此不需要追求絕對(duì)的真實(shí)。

因此,只需在 animate 函數(shù)中繪制每顆小行星的代碼上面添加以下代碼即可:

const dx = player.x - tmpAsteroid.x;
const dy = player.y - tmpAsteroid.y;
const distance = Math.sqrt((dx * dx) + (dy * dy));
if (distance < player.halfWidth + tmpAsteroid.radius) {
  soundThrust.pause();
  soundDeath.currentTime = 0;
  soundDeath.play();
  // 游戲結(jié)束
  playGame = false;
  clearTimeout(scoreTimeout);
  uiStats.hide();
  uiComplete.show()
  soundBackground.pause();
  $(window).unbind("keyup");
  $(window).unbind("keydown");
}

你應(yīng)該能很快明白以上代碼中的距離計(jì)算方法。你將通過(guò)它們來(lái)計(jì)算玩家火箭與當(dāng)前循環(huán)中的小行星之間的像素距離。

下一步是判斷火箭是否與小行星發(fā)生碰撞,可以通過(guò)查看上面計(jì)算的像素距離是否小于小行星半徑加上火箭碰撞圓周的半徑之和。這里使用的火箭碰撞圓周的半徑是火箭寬度的一半,你也可以隨意改變它。

如果火箭與小行星發(fā)生碰撞,就要?dú)⑺劳婕?。殺死玩家并結(jié)束游戲的過(guò)程非常簡(jiǎn)單,但我還需要逐行對(duì)它進(jìn)行解釋。

前三行代碼停止播放推進(jìn)器聲音,并重置和播放死亡的聲音。開(kāi)始播放死亡的聲音時(shí),需要把 playGame 設(shè)置為 false 來(lái)結(jié)束整個(gè)游戲,并通過(guò)前面已經(jīng)使用過(guò)的 clearTimeout 函數(shù)來(lái)停止計(jì)分計(jì)時(shí)器。

此時(shí),所有的游戲邏輯都已經(jīng)停止,因此可以隱藏統(tǒng)計(jì)界面,并顯示游戲結(jié)束界面。

顯示游戲結(jié)束界面時(shí),需要停止播放背景音樂(lè),并最終釋放鍵盤(pán)事件處理程序,從而防止玩家由于無(wú)意按下某個(gè)按鍵而啟動(dòng)游戲。

9. 增加游戲難度

好的,其實(shí)我們要?jiǎng)?chuàng)建的游戲還沒(méi)有完成。讓我們?cè)谟螒蛑性偬砑右恍┕δ?,即增加游戲的難度,玩家要想存活更長(zhǎng)的時(shí)間就變得更加困難了。

我們還是直入主題吧,在 timer 函數(shù)中的 uiScore.html下面添加以下代碼:

if (score % 5 === 0) {
  numAsteroids += 5;
}

以上代碼看上去是否像一個(gè)普通的條件語(yǔ)句?其實(shí)不是。注意其中的百分比符號(hào)。它是求模運(yùn)算符。求??梢杂?jì)算一個(gè)數(shù)是否能被另一個(gè)數(shù)完全整除,它將返回兩個(gè)數(shù)相除所得的余數(shù)。

你可以通過(guò)求模計(jì)算來(lái)執(zhí)行周期性的操作,例如,每隔 5 秒發(fā)生一次。因?yàn)槟憧梢园涯?5 運(yùn)算運(yùn)用于某個(gè)數(shù),如果它返回的結(jié)果為 0,那么該數(shù)一定能夠被 5 整除。

在本游戲中,我們使用模 5 運(yùn)算來(lái)確保某個(gè)代碼段每隔 5 秒鐘執(zhí)行一次。這個(gè)代碼段的作用是,每隔 5 秒鐘就向游戲中添加 5 顆小行星。實(shí)際上,這里并沒(méi)有增加小行星,增加的只是小行星的數(shù)目。

添加小行星很簡(jiǎn)單,在 animate 函數(shù)中繪制玩家火箭的代碼下面添加以下代碼:

while (asteroids.length < numAsteroids) {
  const radius = 5 + (Math.random() * 10);
  const x = Math.floor(Math.random() * canvasWidth) + canvasWidth + radius;
  const y = Math.floor(Math.random() * canvasHeight);
  const vX = -5 - (Math.random() * 5);
  asteroids.push(new Asteroid(x, y, radius, vX));
}

以上代碼檢查每個(gè)循環(huán)中的小行星數(shù)目,如果數(shù)目沒(méi)有達(dá)到要求,它將繼續(xù)向游戲中添加新的小行星,直到小行星的數(shù)目達(dá)到要求為止。

再次在瀏覽器中啟動(dòng)游戲,你會(huì)發(fā)現(xiàn),當(dāng)存活的時(shí)間越來(lái)越長(zhǎng)時(shí),游戲中的小行星就會(huì)越來(lái)越多?,F(xiàn)在,你已經(jīng)真正完成了游戲。

10. 完整源碼

下面給出該游戲的完整源碼:

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Asteroid avoidance</title>
  <link rel="stylesheet" href="style.css" rel="external nofollow"  rel="external nofollow" >
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
  <script src="./main.js"></script>
</head>
<body>
  <div id="game">
    <div id="game-ui">
      <div id="game-intro">
        <h1>Asteroid avoidance</h1>
        <p>Click play and then press any key to start.</p>
        <p>
          <a id="game-play" class="button" href="#" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow" >Play</a>
        </p>
      </div>
      <div id="game-stats">
        <p>Time:<span class="game-score"></span> </p>
        <p> <a class="game-reset" href="#" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow" >Reset</a> </p>
      </div>
      <div id="game-complete">
        <h1>Game over!</h1>
        <p>You survived for <span class="game-score"></span> seconds. </p>
        <p><a class="game-reset button" href="#" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow" >Play</a></p>
      </div>
    </div>
    <canvas id="game-canvas" width="800" height="600">
      <!-- 在此處插入后備代碼 -->
    </canvas>
    <audio id="game-sound-background" loop>
      <source src="sounds/background.ogg">
      <source src="sounds/background.mp3">
    </audio>
    <audio id="game-sound-thrust" loop>
      <source src="sounds/thrust.ogg">
      <source src="sounds/thrust.mp3">
    </audio>
    <audio id="game-sound-death">
      <source src="sounds/death.ogg">
      <source src="sounds/death.mp3">
    </audio>
  </div>
</body>
</html>

style.css

* {
  margin: 0;
  padding: 0;
}
html, body {
  height: 100%;
  width: 100%;
}
canvas {
  display: block;
}
body {
  background-color: #000;
  color: #fff;
  font-family: Verdana, Arial, sans-serif;
  font-size: 18px;
  height: 100%;
}
h1 {
  font-size: 30px;
}
p {
  margin: 0 20px;
}
a {
  color: #fff;
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
}
a.button {
  background-color: #185da8;
  border-radius: 5px;
  display: block;
  font-size: 30px;
  margin: 40px 0 0 270px;
  padding: 10px;
  width: 200px;
}
a.button:hover {
  background-color: #2488f5;
  color: #fff;
  text-decoration: none;
}
#game {
  height: 600px;
  left: 50%;
  margin: -300px 0 0 -400px;
  position: relative;
  top: 50%;
  width: 800px;
}
#game-canvas {
  background-color: #001022;
}
#game-ui {
  height: 600px;
  position: absolute;
  width: 800px;
}
#game-intro, #game-complete {
  background-color: rgba(0, 0, 0, .5);
  margin-top: 100px;
  padding: 40px 0;
  text-align: center;
}
#game-stats {
  font-size: 14px;
  margin: 20px 0;
}
#game-stats, .game-reset {
  margin: 20px 20px 0 0;
  position: absolute;
  right: 0;
  top: 0;
}

main.js

$(document).ready(function () {
  const canvas = $('#game-canvas');
  const context = canvas.get(0).getContext("2d");
  // 畫(huà)布尺寸
  const canvasWidth = canvas.width();
  const canvasHeight = canvas.height();
  // 游戲設(shè)置
  let playGame;
  let asteroids;
  let numAsteroids;
  let player;
  let score;
  let scoreTimeout;
  const arrowUp = 38;
  const arrowRight = 39;
  const arrowDown = 40;
  // 游戲UI
  const ui = $("#game-ui");
  const uiIntro = $("#game-intro");
  const uiStats = $("#game-stats");
  const uiComplete = $("#game-complete");
  const uiPlay = $("#game-play");
  const uiReset = $(".game-reset");
  const uiScore = $(".game-score");
  const soundBackground = $("#game-sound-background").get(0);
  const soundThrust = $("#game-sound-thrust").get(0);
  const soundDeath = $("#game-sound-death").get(0);
  function Asteroid(x, y, radius, vX) {
    this.x = x;
    this.y = y;
    this.radius = radius;
    this.vX = vX;
  }
  function Player(x, y) {
    this.x = x;
    this.y = y;
    this.width = 24;
    this.height = 24;
    this.halfWidth = this.width / 2;
    this.halfHeight = this.height / 2;
    this.vX = 0;
    this.vY = 0;
    this.moveRight = false;
    this.moveUp = false;
    this.moveDown = false;
    this.flameLength = 20;
  }
  // 重至和啟動(dòng)游戲
  function startGame() {
    // 重置游戲狀態(tài)
    uiScore.html("0");
    uiStats.show();
    // 初始游戲設(shè)置
    playGame = false;
    asteroids = new Array();
    numAsteroids = 10;
    score = 0;
    player = new Player(150, canvasHeight / 2);
    for (let i = 0; i < numAsteroids; i++) {
      const radius = 5 + (Math.random() * 10);
      const x = canvasWidth + radius + Math.floor(Math.random() * canvasWidth);
      const y = Math.floor(Math.random() * canvasHeight);
      const vX = -5 - (Math.random() * 5);
      asteroids.push(new Asteroid(x, y, radius, vX));
    }
    $(window).keydown(e => {
      const keyCode = e.keyCode;
      if (!playGame) {
        playGame = true;
        soundBackground.currentTime = 0;
        soundBackground.play();
        animate();
        timer();
      }
      if (keyCode == arrowRight) {
        player.moveRight = true;
        if (soundThrust.paused) {
          soundThrust.currentTime = 0;
          soundThrust.play();
        }
      } else if (keyCode == arrowUp) {
        player.moveUp = true;
      } else if (keyCode == arrowDown) {
        player.moveDown = true;
      }
    });
    $(window).keyup(e => {
      const keyCode = e.keyCode;
      if (keyCode == arrowRight) {
        player.moveRight = false;
        soundThrust.pause();
      } else if (keyCode == arrowUp) {
        player.moveUp = false;
      } else if (keyCode == arrowDown) {
        player.moveDown = false;
      }
    });
    // 開(kāi)始動(dòng)畫(huà)糖環(huán)
    animate();
  }
  //初始化游戲環(huán)境
  function init() {
    uiStats.hide();
    uiComplete.hide();
    uiPlay.click(function (e) {
      e.preventDefault();
      uiIntro.hide();
      startGame();
    });
    uiReset.click(function (e) {
      e.preventDefault();
      uiComplete.hide();
      $(window).unbind('keyup');
      $(window).unbind('keydown');
      soundThrust.pause();
      soundBackground.pause();
      clearTimeout(scoreTimeout);
      startGame();
    });
  }
  function timer() {
    if (playGame) {
      scoreTimeout = setTimeout(() => {
        uiScore.html(++score);
        if (score % 5 === 0) {
          numAsteroids += 5;
        }
        timer();
      }, 1000);
    }
  }
  // 動(dòng)畫(huà)循環(huán),游戲的嫌味性就在這里
  function animate() {
    // 清除
    context.clearRect(0, 0, canvasWidth, canvasHeight);
    const asteroidsLength = asteroids.length;
    for (let i = 0; i < asteroidsLength; i++) {
      const tmpAsteroid = asteroids[i];
      tmpAsteroid.x += tmpAsteroid.vX;
      if (tmpAsteroid.x + tmpAsteroid.radius < 0) {
        tmpAsteroid.radius = 5 + (Math.random() * 10);
        tmpAsteroid.x = canvasWidth + tmpAsteroid.radius;
        tmpAsteroid.y = Math.floor(Math.random() * canvasHeight);
        tmpAsteroid.vX = -5 - (Math.random() * 5);
      }
      const dx = player.x - tmpAsteroid.x;
      const dy = player.y - tmpAsteroid.y;
      const distance = Math.sqrt((dx * dx) + (dy * dy));
      if (distance < player.halfWidth + tmpAsteroid.radius) {
        soundThrust.pause();
        soundDeath.currentTime = 0;
        soundDeath.play();
        // 游戲結(jié)束
        playGame = false;
        clearTimeout(scoreTimeout);
        uiStats.hide();
        uiComplete.show()
        soundBackground.pause();
        $(window).unbind("keyup");
        $(window).unbind("keydown");
      }
      context.fillStyle = "rgb(255, 255, 255)";
      context.beginPath();
      context.arc(tmpAsteroid.x, tmpAsteroid.y, tmpAsteroid.radius, 0, Math.PI * 2, true);
      context.closePath();
      context.fill();
    }
    while (asteroids.length < numAsteroids) {
      const radius = 5 + (Math.random() * 10);
      const x = Math.floor(Math.random() * canvasWidth) + canvasWidth + radius;
      const y = Math.floor(Math.random() * canvasHeight);
      const vX = -5 - (Math.random() * 5);
      asteroids.push(new Asteroid(x, y, radius, vX));
    }
    if (player.moveRight) {
      player.vX = 3;
    } else {
      player.vX = -3;
    }
    player.vY = 0;
    if (player.moveRight) {
      player.vX = 3;
      context.save();
      context.translate(player.x - player.halfWidth, player.y);
      if (player.flameLength == 20) {
        player.flameLength = 15;
      } else {
        player.flameLength = 20;
      }
      context.fillStyle = "orange";
      context.beginPath();
      context.moveTo(0, -5);
      context.lineTo(-player.flameLength, 0);
      context.lineTo(0, 5);
      context.closePath();
      context.fill();
      context.restore();
    }
    if (player.moveUp) {
      player.vY = -3;
    }
    if (player.moveDown) {
      player.vY = 3;
    }
    player.x += player.vX;
    player.y += player.vY;
    if (player.x - player.halfWidth < 20) {
      player.x = 20 + player.halfWidth;
    } else if (player.x + player.halfWidth > canvasWidth - 20) {
      player.x = canvasWidth - 20 - player.halfWidth;
    }
    if (player.y - player.halfHeight < 20) {
      player.y = 20 + player.halfHeight;
    } else if (player.y + player.halfHeight > canvasHeight - 20) {
      player.y = canvasHeight - 20 - player.halfHeight;
    }
    context.fillStyle = 'rgb(255, 0, 0)';
    context.beginPath();
    context.moveTo(player.x + player.halfWidth, player.y);
    context.lineTo(player.x - player.halfWidth, player.y - player.halfHeight);
    context.lineTo(player.x - player.halfWidth, player.y + player.halfHeight)
    context.closePath();
    context.fill();
    if (playGame) {
      setTimeout(animate, 33);
    }
  }
  init();
});

到此這篇關(guān)于JavaScript躲避行星游戲?qū)崿F(xiàn)全程的文章就介紹到這了,更多相關(guān)JS躲避行星內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 微信小程序?qū)崿F(xiàn)簡(jiǎn)單文字跑馬燈

    微信小程序?qū)崿F(xiàn)簡(jiǎn)單文字跑馬燈

    這篇文章主要介紹了微信小程序?qū)崿F(xiàn)簡(jiǎn)單文字跑馬燈,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-05-05
  • 自己寫(xiě)一個(gè)uniapp全局彈窗(APP端)

    自己寫(xiě)一個(gè)uniapp全局彈窗(APP端)

    應(yīng)用uni-app跨平臺(tái)框架進(jìn)行項(xiàng)目開(kāi)發(fā)過(guò)程中,需要實(shí)現(xiàn)版本更新時(shí)全頁(yè)面彈窗,底部導(dǎo)航欄無(wú)法點(diǎn)擊的效果,下面這篇文章主要給大家介紹了關(guān)于uniapp全局彈窗(APP端)的相關(guān)資料,需要的朋友可以參考下
    2022-10-10
  • 最新評(píng)論