codepen 1945 Air Forces 射擊遊戲製作 Step by Step

 1945 Air Forces Online

  • 與《1943》極為相似的二戰題材飛行射擊遊戲,操控戰鬥機,體驗懷舊的垂直卷軸射擊樂趣。

Air Strike HTML5

  • 穿梭在天空中的經典捲軸射擊,簡單好上手,提供多種戰機與武器升級選項。

Air War 1941

  • 同樣採用二戰空戰題材,以關卡推進方式展開,遊戲節奏輕快,適合短時間體驗。

Strikers 1945 Mini(HTML5移植)

  • 由街機射擊名作改編的簡易版本,有多種經典戰機可選,畫面復古且操作直覺。

Galactic War

  • 將戰場搬到宇宙的經典風格射擊遊戲,子彈風格與戰機升級充滿科幻感。

Sky Knight

  • 扮演空戰騎士對抗無盡的敵軍飛行部隊,支援多人競技或單人闖關模式。

Bomber at War Online

  • 偏向橫向卷軸射擊,結合空投與炸彈攻擊任務,過程需要一定操作技巧。

Fractal Combat X Web

  • 以 3D 方式呈現科幻空戰,玩家要不斷閃躲敵彈並完成戰鬥任務,關卡逐漸提升難度。

Galaxy Defense

  • 敵機種類繁多、彈幕華麗,玩家可以不斷升級武器並解鎖新戰機,偏向彈幕射擊。

Spaceship Survival

  • 單純的太空射擊遊戲,玩家需對抗一波又一波的敵軍艦隊,考驗反應力與走位。

Air Wars 2

  • 結合多人對戰與空戰要素,玩家可即時與線上其他人競技,看誰能主宰天空。

Galactic Warrior

  • 在場景佈滿機器人與外星船艦的世界裡展開射擊大戰,操作類似經典射擊遊戲。

Plane Master

  • 以闖關推進為主,每關敵人與頭目(Boss)特色不同,關卡設計豐富。

Squadron Angels

  • 玩家可指揮一支空戰小隊,除了自己的戰機外,也能分配隊友空戰位置。

Strike Galaxy Attack

  • 高速節奏、子彈華麗,合成武器與升級戰機收集要素豐富,難度可自行調整。

Jets of War

  • 相較傳統 2D 捲軸,加入較多武器切換與空中特技,較考驗玩家操作。

Retro Shooting

  • 像素風復古射擊,操作方式簡單,彈幕效果華麗,適合懷舊氛圍。

Pixel Plane

  • 走像素可愛風,但保留經典飛機射擊與 Boss 攻擊機制,關卡不長但具挑戰性。

Alien Sky Force

  • 舞台在外星領域,玩家需迎戰成群結隊的外星艦隊,攻擊方式多樣化。

Galactic Guardian

  • 半塔防、半射擊的結合,玩家在射擊之餘,還要保護基地不被敵機破壞。

Sky Hero

  • 以空戰英雄為主角闖關,過程中可收集強力道具,地面與空中的敵人同時出沒。

Gunbird Mini(HTML5 版)

  • 改編自著名街機作品《Gunbird》,角色多樣,彈幕較為華麗,適合喜歡日系風的玩家。

Space Blaze

  • 擁有多艘可操作的戰鬥機,從一般雷射槍到範圍攻擊無不包辦,搭配多段關卡主線。

Retro Striker

  • 黑白像素風,主打快速移動閃避與射擊,搭配懷舊音效,帶來街機體驗。

Thunder Force Web Tribute

  • 向經典射擊遊戲《Thunder Force》致敬的網頁版,關卡 BGM 熱血沸騰。

Astro Fang

  • 向隕石、太空軍艦射擊並收集資源的遊戲,支援雙人合作或單人模式。

Bullet Heaven

  • 以「彈幕」為主打的射擊模式,敵機會釋放大量子彈,考驗玩家走位。

Comet Clash

  • 以彗星與小行星為主要障礙物,穿插敵人雜兵與關卡頭目,節奏緊湊。

Helicopter Rescue Operation

  • 雖然是直升機操作,但基本概念和戰鬥機類似,需在敵火力中拯救人質。

Sky Force Reloaded(HTML5 簡化版)

  • 改編自知名手機射擊遊戲簡易版本,保留部分經典關卡與多樣 Boss 戰。

Xenoraid Web

  • 多艘戰機可隨時切換,每艘戰機血量與武器不同,需策略配合闖關。

Jetpack Fight

  • 結合平台跳躍與射擊,角色背著噴射包在空中邊閃邊射,帶有搞怪風。

Space Armor Online

  • 俯視視角的迷宮式射擊,玩家飛船在各個區域清敵,需留意陷阱與補給。

Neon Blaster

  • 色彩繽紛、操作流暢,關卡短小卻密集,屬於「放鬆小品」的射擊遊戲。

Metal Wings

  • 偏橫向射擊捲軸,操控戰士進行空中與地面敵人的對戰,也有武器升級要素。

Space Mission Survival

  • 以生存為核心,敵人源源不絕,收集補給與升級戰艦是活下去的關鍵。

Plane Clash

  • 多人對戰版本的飛行射擊,玩家可挑戰線上其他玩家,在地圖內蒐集道具或武器。

Thunderbolt 2D

  • 雷電風格的捲軸射擊,以超多升級裝備和火力強大的敵機著稱。

Arcadia Fighters

  • 體驗懷舊街機,支援雙人同時遊戲,適合同學間一起對抗 Boss 挑戰。

Jetpack Galactic Shooter

  • 角色移動自由度高,可在空中飛行和地面射擊之間切換,節奏偏快。

Mecha Wars

  • 操作機器人也能享受彈幕射擊感,換裝要素豐富,深具可玩性。

Rocket Blitz

  • 以火箭為主題的射擊遊戲,玩家需不斷升級火箭引擎並躲避敵機攻擊。

Air Guardian

  • 偏模擬飛行類型,操作感更接近真實飛機,但仍包含射擊闖關要素。

Operation Thunder

  • 遊戲本身畫風簡潔,玩家透過不斷蒐集金幣來強化武器與防禦力。

Planet Invasion

  • 整個關卡以星球為單位,玩家需要在星球上空消滅敵人飛船、建造防禦設施。

Alien Shooter Defense

  • 結合塔防與飛行射擊模式,外星大軍來襲時同時需要射擊與布陣,增添策略性。

Asteroid Storm

  • 靠近經典街機《Asteroids》的玩法,玩家主要射擊並閃躲小行星;也有敵艦出現。

Cyber Copter

  • 在科幻城市中操控直升機,空中攻擊敵人並解救城市住民,具任務導向。

Space Runners

  • 融合了橫向奔跑與射擊元素,玩家在逃脫關卡的同時必須消滅阻擋的敵方戰機。

Epic Air Battle

  • 主打 Boss 關卡巨型戰艦、戰機,操作簡單但敵方火力頗強,適合想挑戰高難度的玩家。


利用emoji角色寫一個 HTML5遊戲 1945 Air Forces Online 與《1943》極為相似的二戰題材飛行射擊遊戲,操控戰鬥機,體驗懷舊的垂直卷軸射擊樂趣。



第 1 章:簡介與學習目標 


1.1 學習目標 HTML5 Canvas:如何在網頁上使用 Canvas 進行繪圖與遊戲開發。 JavaScript:建立並操作遊戲物件、事件偵聽與遊戲邏輯。 程式結構:如何維護程式的可讀性,如變數宣告、函式拆分及程式迴圈。 


<!DOCTYPE html>

<html lang="zh-Hant">

<head>

  <meta charset="UTF-8" />

  <title>1.3 學習目標</title>

  <style>

    body {

      font-family: sans-serif;

      background-color: white;

      margin: 0;

      padding: 20px;

    }

    h1 {

      color: red;

    }

    h2 {

      margin-top: 1.5em;

      color: blue;

    }

    p {

      color: black;

      line-height: 1.6;

    }

  </style>

</head>

<body>


  <h1>第 1 章:簡介與學習目標(1.3 學習目標)</h1>


  <h2>HTML5 Canvas</h2>

  <p>

    了解如何在網頁上使用 Canvas 進行繪圖與遊戲開發,包含

    <strong>2D 繪圖介面</strong>的基礎操作。

  </p>


  <h2>JavaScript</h2>

  <p>

    學習如何建立並操作遊戲物件,例如飛機、子彈與敵機,

    同時掌握事件偵聽與遊戲邏輯的撰寫。

  </p>


  <h2>程式結構</h2>

  <p>

    熟悉程式的可讀性與維護性,例如變數宣告、函式拆分、

    程式迴圈以及基本的程式設計思維。

  </p>


  <p>

    只要掌握以上三項,就能為後續更進階的遊戲功能開發奠定穩固基礎!

  </p>


</body>

</html>











1.2 遊戲簡介 這款遊戲是以經典飛行射擊遊戲《1943》或「1945 Air Forces Online」為靈感,使用 HTML5 Canvas 與 JavaScript 製作簡易版。 透過 emoji 角色展現敵機、飛彈與爆炸效果,簡易又有趣。 


<!DOCTYPE html>

<html lang="zh-Hant">

<head>

  <meta charset="UTF-8" />

  <title>1.2遊戲簡介</title>

  <style>

    body {

      font-family: sans-serif;

      background-color: #f5f5f5;

      margin: 0;

      padding: 20px;

    }

    h1 {

      color: #333;

    }

    p {

      line-height: 1.6;

      color: #555;

    }

  </style>

</head>

<body>


  <h1>第 1 章:簡介與學習目標(1.2 遊戲簡介)</h1>

  <p>

    這款遊戲是以經典飛行射擊遊戲《1943》或「1945 Air Forces Online」為靈感,

    使用 <strong>HTML5 Canvas</strong> 與 <strong>JavaScript</strong> 製作簡易版。

    透過 <em>emoji</em> 角色展現敵機、飛彈與爆炸效果,簡易又有趣。

  </p>


  <p>

    我們將在之後的章節,逐步學習如何用程式碼實現飛機移動、子彈發射、敵機生成、

    碰撞判定以及遊戲結束等功能。

  </p>


</body>

</html>




貼上你的答案:






1.3 主要功能 玩家操控飛機(✈),左右移動並發射子彈(🚀)。 敵機(🛩️)從螢幕頂端依序生成,玩家需盡可能擊落以獲得分數。 當玩家與敵機發生碰撞,或玩家未能閃避敵機,即判定遊戲結束(Game Over)。 



<!DOCTYPE html>

<html lang="zh-Hant">

<head>

  <meta charset="UTF-8" />

  <title>1.3 主要功能</title>

  <style>

    body {

      font-family: sans-serif;

      background-color: #eef;

      margin: 0;

      padding: 20px;

    }

    h1 {

      color: #333;

    }

    ul {

      color: #555;

      line-height: 1.8;

    }

  </style>

</head>

<body>


  <h1>第 1 章:簡介與學習目標(1.3 主要功能)</h1>

  <ul>

    <li>

      <strong>玩家操控飛機(✈)</strong>:可以左右移動並發射子彈(🚀)。

    </li>

    <li>

      <strong>敵機(🛩️)自螢幕頂端依序生成</strong>,玩家需盡可能擊落它們以獲得分數。

    </li>

    <li>

      <strong>Game Over 條件</strong>:當玩家與敵機發生碰撞,

      或玩家未能閃避敵機時,即判定遊戲結束。

    </li>

  </ul>


  <p>

    以上是遊戲的核心功能,在後續章節將一步一步教你如何使用

    <em>Canvas</em> 和 <em>JavaScript</em> 來實作這些機制。

  </p>


</body>

</html>



註解:

使用 <ul><li> 列出「遊戲主要功能」,讓內容條列化更清晰


貼上你的答案:



2.1 開發環境

目標:

  • 了解在本機端進行 HTML5 + JavaScript + Canvas 的最基本工具需求。

  • 認識常見的瀏覽器與文字編輯器。

範例檔案: setup_2_1.html


<!DOCTYPE html>

<html lang="zh-Hant">

<head>

  <meta charset="UTF-8" />

  <title>2.1 開發環境</title>

  <style>

    body {

      background-color: #fafafa;

      font-family: sans-serif;

      margin: 0;

      padding: 20px;

    }

    h1 {

      color: #333;

    }

    p {

      line-height: 1.6;

      color: #555;

    }

    ul {

      margin: 0;

      padding-left: 20px;

    }

  </style>

</head>

<body>


  <h1>第二單元:2.1 開發環境</h1>


  <p>在進行 HTML5 Canvas 與 JavaScript 遊戲開發前,您需要準備以下環境:</p>


  <ul>

    <li>

      <strong>文字編輯器</strong>:如 VS Code、Sublime Text、Atom 或任何能編輯 .html/.js 的工具。

    </li>

    <li>

      <strong>現代瀏覽器</strong>:如 Chrome、Firefox、Edge 或 Safari,都支援 Canvas 與大部分 JavaScript ES6 語法。

    </li>

    <li>

      <strong>可選的本機伺服器</strong>:雖然直接開啟 .html 檔在瀏覽器也可執行,但若要使用一些進階功能(如 AJAX、模組化),則建議使用本機伺服器(例如 Live Server、npm http-server、XAMPP)。不過本教學大多內容只要「檔案直接雙擊開啟」即可。

    </li>

  </ul>


  <p>

    只要能確保您能使用文字編輯器編寫程式碼並在瀏覽器中開啟網頁,就可以開始本單元後續的練習。

  </p>


</body>

</html>





2.2 檔案結構

目標:

  • 說明本教學所建議的檔案與資料夾配置。

  • 讓未來的程式碼能夠有序管理(HTML、CSS、JS 分離或整合都可)。

範例檔案: setup_2_2.html


<!DOCTYPE html>

<html lang="zh-Hant">

<head>

  <meta charset="UTF-8" />

  <title>2.2 檔案結構</title>

  <style>

    body {

      background-color: #eef;

      font-family: sans-serif;

      margin: 0;

      padding: 20px;

    }

    h1 {

      color: #333;

    }

    p {

      line-height: 1.6;

      color: #555;

    }

    pre {

      background-color: #fff;

      padding: 10px;

      border-left: 4px solid #ccc;

    }

  </style>

</head>

<body>


  <h1>第二單元:2.2 檔案結構</h1>


  <p>下方是一個可能的檔案結構範例:</p>


  <pre>

  my-canvas-game/

  ├─ index.html          (主要的網頁檔)

  ├─ style.css           (可選:放置自訂的 CSS)

  ├─ script.js           (可選:放置主程式邏輯)

  └─ assets/             (可選:放置圖檔、音效等資源)

  </pre>


  <p>

    在本教學中,為了方便初學者直接複製範例,

    我們通常先將 HTML、CSS、JavaScript 整合在同一個 .html 中。

    但若專案日後擴大,可以考慮將各部分拆分到不同檔案,以利維護。

  </p>


</body>

</html>





2.3 Hello Canvas

目標:

  • 建立一個最簡單的 Canvas 頁面,並嘗試在上面用 JavaScript 繪製一點內容。

  • 讓初學者熟悉 <canvas>getContext('2d') 的基礎。

範例檔案: setup_2_3.html


<!DOCTYPE html>

<html lang="zh-Hant">

<head>

  <meta charset="UTF-8" />

  <title>2.3 Hello Canvas</title>

  <style>

    body {

      background-color: #fff;

      font-family: sans-serif;

      margin: 0;

      padding: 20px;

    }

    #myCanvas {

      border: 1px solid #ccc;

      display: block;

      margin: 0 auto;

      background: #f9f9f9;

    }

    p {

      text-align: center;

      color: #555;

    }

  </style>

</head>

<body>


  <h1 style="text-align: center;">第二單元:2.3 Hello Canvas</h1>


  <!-- 建立一個 Canvas -->

  <canvas id="myCanvas" width="400" height="300"></canvas>


  <p>請在瀏覽器中開啟此檔案,即可看到簡易的 Canvas 繪製效果!</p>


  <script>

    // 1. 取得 Canvas 元素 & 2D 繪圖環境

    const canvas = document.getElementById('myCanvas');

    const ctx = canvas.getContext('2d');


    // 2. 設定繪圖樣式

    ctx.fillStyle = 'blue';

    ctx.font = '20px Arial';


    // 3. 在 (x=50, y=50) 的位置繪製文字

    ctx.fillText('Hello Canvas!', 50, 50);


    // 4. 繪製一個矩形

    ctx.fillStyle = 'red';

    ctx.fillRect(50, 80, 100, 50); // (x, y, width, height)

  </script>

</body>

</html>





第 3 章:Canvas 與基礎事件處理

3.1 Canvas 與座標系

目標:

  • 認識 Canvas 的座標系原點(左上角)以及 x、y 軸的方向。

  • 簡單演示在 Canvas 不同座標點繪製方塊與文字。

範例檔案: chapter3_3_1.html


<!DOCTYPE html>

<html lang="zh-Hant">

<head>

  <meta charset="UTF-8" />

  <title>3.1 Canvas 與座標系</title>

  <style>

    body {

      margin: 0;

      font-family: sans-serif;

      background-color: #fafafa;

    }

    h1 {

      text-align: center;

      margin-top: 20px;

      color: #333;

    }

    #myCanvas {

      display: block;

      margin: 10px auto;

      border: 1px solid #ccc;

      background: #fff;

    }

  </style>

</head>

<body>


<h1>第 3 章:3.1 Canvas 與座標系</h1>

<canvas id="myCanvas" width="400" height="300"></canvas>


<script>

  // 1. 取得 Canvas & 2D 上下文

  const canvas = document.getElementById('myCanvas');

  const ctx = canvas.getContext('2d');


  // 2. 繪製左上角座標系的說明

  ctx.fillStyle = 'blue';

  ctx.fillRect(0, 0, 50, 50); 

  // 在 (0,0) 繪製 50x50 的藍色方塊


  // 3. 繪製在 (200,100) 處的紅色方塊

  ctx.fillStyle = 'red';

  ctx.fillRect(200, 100, 50, 50);


  // 4. 顯示文字座標說明

  ctx.fillStyle = 'black';

  ctx.font = '16px Arial';

  ctx.fillText('(0,0)', 5, 15);        // 靠近左上角的文字

  ctx.fillText('(200,100)', 205, 115); // 靠近紅色方塊

</script>


</body>

</html>





3.2 監聽鍵盤事件並控制物件

目標:

  • 學習如何在網頁上監聽鍵盤事件(keydown / keyup),並根據按鍵控制 Canvas 裡的圖案。

  • 讓使用者可透過 ← / → / ↑ / ↓ 鍵移動一個矩形。

範例檔案: chapter3_3_2.html


<!DOCTYPE html>

<html lang="zh-Hant">

<head>

  <meta charset="UTF-8" />

  <title>3.2 鍵盤事件處理</title>

  <style>

    body {

      margin: 0;

      font-family: sans-serif;

      background-color: #f0f8ff;

    }

    h1 {

      text-align: center;

      margin-top: 20px;

      color: #333;

    }

    #gameCanvas {

      display: block;

      margin: 10px auto;

      border: 1px solid #ccc;

      background: #fff;

    }

  </style>

</head>

<body>


<h1>第 3 章:3.2 監聽鍵盤事件並控制物件</h1>

<canvas id="gameCanvas" width="400" height="300"></canvas>


<script>

  const canvas = document.getElementById('gameCanvas');

  const ctx = canvas.getContext('2d');


  // 矩形的初始位置與大小

  let rectX = 180;

  let rectY = 130;

  const rectSize = 40;

  const speed = 5;


  // 用來偵測按鍵是否按下

  let leftPressed = false;

  let rightPressed = false;

  let upPressed = false;

  let downPressed = false;


  // 監聽鍵盤事件

  window.addEventListener('keydown', (e) => {

    if (e.code === 'ArrowLeft') leftPressed = true;

    if (e.code === 'ArrowRight') rightPressed = true;

    if (e.code === 'ArrowUp') upPressed = true;

    if (e.code === 'ArrowDown') downPressed = true;

  });

  window.addEventListener('keyup', (e) => {

    if (e.code === 'ArrowLeft') leftPressed = false;

    if (e.code === 'ArrowRight') rightPressed = false;

    if (e.code === 'ArrowUp') upPressed = false;

    if (e.code === 'ArrowDown') downPressed = false;

  });


  function draw() {

    // 1. 清空畫面

    ctx.clearRect(0, 0, canvas.width, canvas.height);


    // 2. 根據按鍵改變矩形位置

    if (leftPressed && rectX > 0) {

      rectX -= speed;

    }

    if (rightPressed && rectX < canvas.width - rectSize) {

      rectX += speed;

    }

    if (upPressed && rectY > 0) {

      rectY -= speed;

    }

    if (downPressed && rectY < canvas.height - rectSize) {

      rectY += speed;

    }


    // 3. 繪製矩形

    ctx.fillStyle = 'orange';

    ctx.fillRect(rectX, rectY, rectSize, rectSize);


    // 4. 顯示提示文字

    ctx.font = '14px Arial';

    ctx.fillStyle = 'black';

    ctx.fillText('用方向鍵移動方塊', 10, 20);


    // 5. 不斷循環呼叫

    requestAnimationFrame(draw);

  }


  // 啟動

  draw();

</script>


</body>

</html>




3.3 初探遊戲迴圈:計時與移動

目標:

  • 介紹以「遊戲迴圈」方式更新畫面的基礎概念(requestAnimationFrame)。

  • 在畫面中顯示一個會自動水平移動、並在碰到邊緣後反彈的簡單物件。

範例檔案: chapter3_3_3.html


<!DOCTYPE html>

<html lang="zh-Hant">

<head>

  <meta charset="UTF-8" />

  <title>3.3 初探遊戲迴圈</title>

  <style>

    body {

      margin: 0;

      font-family: sans-serif;

      background-color: #fff;

    }

    h1 {

      text-align: center;

      margin-top: 20px;

      color: #333;

    }

    #stage {

      display: block;

      margin: 10px auto;

      border: 1px solid #ccc;

      background: #fafafa;

    }

  </style>

</head>

<body>


<h1>第 3 章:3.3 初探遊戲迴圈</h1>

<canvas id="stage" width="400" height="300"></canvas>


<script>

  const canvas = document.getElementById('stage');

  const ctx = canvas.getContext('2d');


  let x = 50;

  let y = 150;

  const radius = 20;

  let speedX = 3;  // 水平速度


  function update() {

    // 移動

    x += speedX;


    // 若碰到邊界,反轉方向

    if (x - radius < 0 || x + radius > canvas.width) {

      speedX = -speedX;

    }

  }


  function draw() {

    // 清除畫布

    ctx.clearRect(0, 0, canvas.width, canvas.height);


    // 繪製圓

    ctx.beginPath();

    ctx.fillStyle = 'green';

    ctx.arc(x, y, radius, 0, Math.PI * 2);

    ctx.fill();


    // 繪製提示文字

    ctx.font = '14px Arial';

    ctx.fillStyle = 'black';

    ctx.fillText('球在左右來回移動 (遊戲迴圈機制)', 10, 20);

  }


  function gameLoop() {

    update();

    draw();

    requestAnimationFrame(gameLoop);

  }


  gameLoop();

</script>


</body>

</html>







選作五檔遊戲


遊戲1名稱:


程式碼


遊戲截圖


第 4 章:簡易飛行射擊遊戲雛形

4.1 玩家飛機移動

目標:

  • 建立一個「玩家飛機」形象(此處可先用簡單矩形或 emoji 代替)。

  • 使用鍵盤方向鍵,讓飛機能在 Canvas 中左右移動(或上下也可)。

範例檔案: chapter4_4_1.html


<!DOCTYPE html>

<html lang="zh-Hant">

<head>

  <meta charset="UTF-8" />

  <title>4.1 玩家飛機移動</title>

  <style>

    body {

      margin: 0;

      font-family: sans-serif;

      background-color: #f0f0f0;

    }

    h1 {

      text-align: center;

      margin-top: 20px;

      color: #333;

    }

    #gameCanvas {

      display: block;

      margin: 10px auto;

      border: 1px solid #ccc;

      background: #fff;

    }

    .info {

      text-align: center;

      color: #666;

    }

  </style>

</head>

<body>


<h1>第 4 章:4.1 玩家飛機移動</h1>

<canvas id="gameCanvas" width="400" height="300"></canvas>

<p class="info">使用左右方向鍵移動「飛機」</p>


<script>

  const canvas = document.getElementById('gameCanvas');

  const ctx = canvas.getContext('2d');


  // 飛機相關設定

  let planeX = canvas.width / 2;    // 飛機的 x 座標(置中)

  let planeY = canvas.height - 50; // 飛機的 y 座標(靠近底部)

  const planeSpeed = 5;

  const planeWidth = 30;

  const planeHeight = 30;


  // 鍵盤偵測

  let leftPressed = false;

  let rightPressed = false;


  window.addEventListener('keydown', (e) => {

    if (e.code === 'ArrowLeft') leftPressed = true;

    if (e.code === 'ArrowRight') rightPressed = true;

  });

  window.addEventListener('keyup', (e) => {

    if (e.code === 'ArrowLeft') leftPressed = false;

    if (e.code === 'ArrowRight') rightPressed = false;

  });


  function drawPlane() {

    // 先用簡單矩形代表飛機

    ctx.fillStyle = 'blue';

    ctx.fillRect(planeX, planeY, planeWidth, planeHeight);


    // 可改成繪製文字 (emoji) 例如:

    // ctx.font = '24px Arial';

    // ctx.fillText('✈', planeX, planeY + 24);

  }


  function update() {

    // 左右移動

    if (leftPressed && planeX > 0) {

      planeX -= planeSpeed;

    }

    if (rightPressed && planeX < canvas.width - planeWidth) {

      planeX += planeSpeed;

    }

  }


  function draw() {

    ctx.clearRect(0, 0, canvas.width, canvas.height);

    drawPlane();

  }


  function gameLoop() {

    update();

    draw();

    requestAnimationFrame(gameLoop);

  }


  gameLoop();

</script>


</body>

</html>





4.2 子彈發射

目標:

  • 在上一個範例的基礎上,加入「子彈」陣列。

  • 使用 空白鍵(Space)控制發射子彈,並讓子彈往上飛行。

範例檔案: chapter4_4_2.html


<!DOCTYPE html>

<html lang="zh-Hant">

<head>

  <meta charset="UTF-8" />

  <title>4.2 子彈發射</title>

  <style>

    body {

      margin: 0;

      font-family: sans-serif;

      background-color: #f0f0f0;

    }

    h1 {

      text-align: center;

      margin-top: 20px;

      color: #333;

    }

    #gameCanvas {

      display: block;

      margin: 10px auto;

      border: 1px solid #ccc;

      background: #fff;

    }

    .info {

      text-align: center;

      color: #666;

    }

  </style>

</head>

<body>


<h1>第 4 章:4.2 子彈發射</h1>

<canvas id="gameCanvas" width="400" height="300"></canvas>

<p class="info">左右鍵移動飛機,空白鍵 (Space) 發射子彈</p>


<script>

  const canvas = document.getElementById('gameCanvas');

  const ctx = canvas.getContext('2d');


  // 飛機相關

  let planeX = canvas.width / 2;

  let planeY = canvas.height - 50;

  const planeW = 30;

  const planeH = 30;

  const planeSpeed = 5;


  // 子彈

  let bullets = [];

  const bulletSpeed = 7;


  // 鍵盤偵測

  let leftPressed = false;

  let rightPressed = false;

  let spacePressed = false;


  window.addEventListener('keydown', (e) => {

    if (e.code === 'ArrowLeft') leftPressed = true;

    if (e.code === 'ArrowRight') rightPressed = true;

    if (e.code === 'Space') spacePressed = true;

  });

  window.addEventListener('keyup', (e) => {

    if (e.code === 'ArrowLeft') leftPressed = false;

    if (e.code === 'ArrowRight') rightPressed = false;

    if (e.code === 'Space') spacePressed = false;

  });


  function update() {

    // 飛機左右移動

    if (leftPressed && planeX > 0) {

      planeX -= planeSpeed;

    }

    if (rightPressed && planeX < canvas.width - planeW) {

      planeX += planeSpeed;

    }


    // 若按下空白鍵,產生一顆子彈

    if (spacePressed) {

      // 子彈初始位置在飛機正上方

      bullets.push({ x: planeX + planeW / 2 - 2, y: planeY - 5 });

    }


    // 更新子彈位置

    for (let i = 0; i < bullets.length; i++) {

      bullets[i].y -= bulletSpeed;


      // 若子彈超出畫面上方,就移除

      if (bullets[i].y < 0) {

        bullets.splice(i, 1);

        i--;

      }

    }

  }


  function draw() {

    ctx.clearRect(0, 0, canvas.width, canvas.height);


    // 繪製飛機 (矩形)

    ctx.fillStyle = 'blue';

    ctx.fillRect(planeX, planeY, planeW, planeH);


    // 繪製子彈 (小矩形或其他形狀)

    ctx.fillStyle = 'red';

    for (let b of bullets) {

      ctx.fillRect(b.x, b.y, 4, 10);

    }

  }


  function gameLoop() {

    update();

    draw();

    requestAnimationFrame(gameLoop);

  }


  gameLoop();

</script>

</body>

</html>






4.3 敵機生成與簡易碰撞

目標:

  • 定時在畫面頂端隨機位置出現「敵機」。

  • 敵機向下移動,若與子彈發生碰撞,則移除敵機並移除子彈。

  • 尚未實作玩家碰撞;此階段先練習多物件碰撞概念。

範例檔案: chapter4_4_3.html


<!DOCTYPE html>

<html lang="zh-Hant">

<head>

  <meta charset="UTF-8" />

  <title>4.3 敵機與碰撞</title>

  <style>

    body {

      margin: 0;

      font-family: sans-serif;

      background-color: #f9f9f9;

    }

    h1 {

      text-align: center;

      margin-top: 20px;

      color: #333;

    }

    #gameCanvas {

      display: block;

      margin: 10px auto;

      border: 1px solid #ccc;

      background: #fff;

    }

    .info {

      text-align: center;

      color: #666;

    }

  </style>

</head>

<body>


<h1>第 4 章:4.3 敵機與碰撞</h1>

<canvas id="gameCanvas" width="400" height="300"></canvas>

<p class="info">飛機 (←/→) + 發射子彈 (Space) + 敵機下落 + 碰撞</p>


<script>

  const canvas = document.getElementById('gameCanvas');

  const ctx = canvas.getContext('2d');


  // 飛機設定

  let planeX = canvas.width / 2;

  let planeY = canvas.height - 50;

  const planeW = 30, planeH = 30;

  const planeSpeed = 5;


  // 子彈

  let bullets = [];

  const bulletSpeed = 7;


  // 敵機

  let enemies = [];

  const enemySpeed = 2;  // 敵機下落速度

  const enemyW = 30, enemyH = 30;


  // 鍵盤輸入

  let leftPressed = false;

  let rightPressed = false;

  let spacePressed = false;


  // 敵機生成 (每 1 秒一次)

  setInterval(() => {

    // 隨機 x 位置

    let randomX = Math.random() * (canvas.width - enemyW);

    enemies.push({ x: randomX, y: -enemyH });

  }, 1000);


  window.addEventListener('keydown', (e) => {

    if (e.code === 'ArrowLeft') leftPressed = true;

    if (e.code === 'ArrowRight') rightPressed = true;

    if (e.code === 'Space') spacePressed = true;

  });

  window.addEventListener('keyup', (e) => {

    if (e.code === 'ArrowLeft') leftPressed = false;

    if (e.code === 'ArrowRight') rightPressed = false;

    if (e.code === 'Space') spacePressed = false;

  });


  function update() {

    // 飛機移動

    if (leftPressed && planeX > 0) {

      planeX -= planeSpeed;

    }

    if (rightPressed && planeX < canvas.width - planeW) {

      planeX += planeSpeed;

    }


    // 發射子彈

    if (spacePressed) {

      bullets.push({ x: planeX + planeW / 2 - 2, y: planeY - 5 });

    }


    // 更新子彈

    for (let i = 0; i < bullets.length; i++) {

      bullets[i].y -= bulletSpeed;


      // 飛出畫面上方就移除

      if (bullets[i].y < 0) {

        bullets.splice(i, 1);

        i--;

        continue;

      }

    }


    // 更新敵機位置

    for (let j = 0; j < enemies.length; j++) {

      enemies[j].y += enemySpeed;


      // 超出螢幕底部就移除

      if (enemies[j].y > canvas.height) {

        enemies.splice(j, 1);

        j--;

        continue;

      }

    }


    // 碰撞偵測 (子彈 vs 敵機)

    for (let b = 0; b < bullets.length; b++) {

      for (let e = 0; e < enemies.length; e++) {

        // 判斷矩形碰撞 (簡易作法)

        let bullet = bullets[b];

        let enemy = enemies[e];

        if (

          bullet.x < enemy.x + enemyW &&

          bullet.x + 4 > enemy.x &&  // 子彈寬度假設為 4

          bullet.y < enemy.y + enemyH &&

          bullet.y + 10 > enemy.y    // 子彈高度假設為 10

        ) {

          // 碰撞發生,移除子彈與敵機

          bullets.splice(b, 1);

          enemies.splice(e, 1);

          b--;

          break; // 跳出內層迴圈

        }

      }

    }

  }


  function draw() {

    ctx.clearRect(0, 0, canvas.width, canvas.height);


    // 繪製飛機

    ctx.fillStyle = 'blue';

    ctx.fillRect(planeX, planeY, planeW, planeH);


    // 繪製子彈

    ctx.fillStyle = 'red';

    for (let b of bullets) {

      ctx.fillRect(b.x, b.y, 4, 10);

    }


    // 繪製敵機

    ctx.fillStyle = 'green';

    for (let en of enemies) {

      ctx.fillRect(en.x, en.y, enemyW, enemyH);

    }


    // 提示文字

    ctx.font = '14px Arial';

    ctx.fillStyle = '#444';

    ctx.fillText('子彈與敵機碰撞即移除敵機', 10, 20);

  }


  function gameLoop() {

    update();

    draw();

    requestAnimationFrame(gameLoop);

  }


  gameLoop();

</script>

</body>

</html>






第 5 章:分數、生命值與遊戲結束

5.1 分數與計分板

目標:

  • 當玩家的子彈擊落敵機時,增加分數。

  • 在畫面上即時顯示分數(score)。

範例檔案: chapter5_5_1.html


<!DOCTYPE html>

<html lang="zh-Hant">

<head>

  <meta charset="UTF-8" />

  <title>5.1 分數與計分板</title>

  <style>

    body {

      margin: 0;

      font-family: sans-serif;

      background-color: #fafafa;

    }

    h1 {

      text-align: center;

      margin-top: 20px;

      color: #333;

    }

    #gameCanvas {

      display: block;

      margin: 10px auto;

      border: 1px solid #ccc;

      background: #fff;

    }

    .info {

      text-align: center;

      color: #666;

    }

  </style>

</head>

<body>


<h1>第 5 章:5.1 分數與計分板</h1>

<canvas id="gameCanvas" width="400" height="300"></canvas>

<p class="info">延續第 4 章的雛形,加入「分數」顯示</p>


<script>

  const canvas = document.getElementById('gameCanvas');

  const ctx = canvas.getContext('2d');


  // 飛機設定

  let planeX = canvas.width / 2;

  let planeY = canvas.height - 50;

  const planeW = 30, planeH = 30;

  const planeSpeed = 5;


  // 子彈

  let bullets = [];

  const bulletSpeed = 7;


  // 敵機

  let enemies = [];

  const enemySpeed = 2;

  const enemyW = 30, enemyH = 30;


  // 鍵盤輸入

  let leftPressed = false;

  let rightPressed = false;

  let spacePressed = false;


  // 分數

  let score = 0;


  // 週期生成敵機

  setInterval(() => {

    let randomX = Math.random() * (canvas.width - enemyW);

    enemies.push({ x: randomX, y: -enemyH });

  }, 1000);


  window.addEventListener('keydown', (e) => {

    if (e.code === 'ArrowLeft') leftPressed = true;

    if (e.code === 'ArrowRight') rightPressed = true;

    if (e.code === 'Space') spacePressed = true;

  });

  window.addEventListener('keyup', (e) => {

    if (e.code === 'ArrowLeft') leftPressed = false;

    if (e.code === 'ArrowRight') rightPressed = false;

    if (e.code === 'Space') spacePressed = false;

  });


  function update() {

    // 飛機移動

    if (leftPressed && planeX > 0) {

      planeX -= planeSpeed;

    }

    if (rightPressed && planeX < canvas.width - planeW) {

      planeX += planeSpeed;

    }


    // 發射子彈

    if (spacePressed) {

      bullets.push({ x: planeX + planeW / 2 - 2, y: planeY - 5 });

    }


    // 子彈位置更新

    for (let i = 0; i < bullets.length; i++) {

      bullets[i].y -= bulletSpeed;

      if (bullets[i].y < 0) {

        bullets.splice(i, 1);

        i--;

        continue;

      }

    }


    // 敵機位置更新

    for (let j = 0; j < enemies.length; j++) {

      enemies[j].y += enemySpeed;

      if (enemies[j].y > canvas.height) {

        enemies.splice(j, 1);

        j--;

        continue;

      }

    }


    // 碰撞偵測 (子彈 vs 敵機)

    for (let b = 0; b < bullets.length; b++) {

      for (let e = 0; e < enemies.length; e++) {

        let bullet = bullets[b];

        let enemy = enemies[e];

        if (

          bullet.x < enemy.x + enemyW &&

          bullet.x + 4 > enemy.x &&

          bullet.y < enemy.y + enemyH &&

          bullet.y + 10 > enemy.y

        ) {

          // 碰撞:移除子彈、敵機,並加分

          bullets.splice(b, 1);

          enemies.splice(e, 1);

          score += 10;

          b--;

          break; 

        }

      }

    }

  }


  function draw() {

    ctx.clearRect(0, 0, canvas.width, canvas.height);


    // 繪製飛機

    ctx.fillStyle = 'blue';

    ctx.fillRect(planeX, planeY, planeW, planeH);


    // 繪製子彈

    ctx.fillStyle = 'red';

    for (let b of bullets) {

      ctx.fillRect(b.x, b.y, 4, 10);

    }


    // 繪製敵機

    ctx.fillStyle = 'green';

    for (let en of enemies) {

      ctx.fillRect(en.x, en.y, enemyW, enemyH);

    }


    // 顯示分數

    ctx.font = '16px Arial';

    ctx.fillStyle = '#333';

    ctx.fillText(`Score: ${score}`, 10, 20);

  }


  function gameLoop() {

    update();

    draw();

    requestAnimationFrame(gameLoop);

  }


  gameLoop();

</script>

</body>

</html>





5.2 玩家生命值與碰撞

目標:

  • 讓玩家也有「生命值(lives)」或「HP」。

  • 敵機若與玩家飛機碰撞,扣除玩家生命值,若減至 0 則進入遊戲結束狀態。

範例檔案: chapter5_5_2.html


<!DOCTYPE html>

<html lang="zh-Hant">

<head>

  <meta charset="UTF-8" />

  <title>5.2 玩家生命值與碰撞</title>

  <style>

    body {

      margin: 0;

      font-family: sans-serif;

      background-color: #fafafa;

    }

    h1 {

      text-align: center;

      margin-top: 20px;

      color: #333;

    }

    #gameCanvas {

      display: block;

      margin: 10px auto;

      border: 1px solid #ccc;

      background: #fff;

    }

    .info {

      text-align: center;

      color: #666;

    }

  </style>

</head>

<body>


<h1>第 5 章:5.2 玩家生命值與碰撞</h1>

<canvas id="gameCanvas" width="400" height="300"></canvas>

<p class="info">玩家與敵機碰撞會扣血,血量歸零則遊戲結束</p>


<script>

  const canvas = document.getElementById('gameCanvas');

  const ctx = canvas.getContext('2d');


  // 遊戲狀態

  let gameRunning = true;


  // 飛機設定

  let planeX = canvas.width / 2;

  let planeY = canvas.height - 50;

  const planeW = 30, planeH = 30;

  const planeSpeed = 5;


  // 子彈

  let bullets = [];

  const bulletSpeed = 7;


  // 敵機

  let enemies = [];

  const enemySpeed = 2;

  const enemyW = 30, enemyH = 30;


  // 玩家血量

  let lives = 3;


  // 分數

  let score = 0;


  // 鍵盤輸入

  let leftPressed = false;

  let rightPressed = false;

  let spacePressed = false;


  // 生成敵機

  setInterval(() => {

    if (gameRunning) {

      let randomX = Math.random() * (canvas.width - enemyW);

      enemies.push({ x: randomX, y: -enemyH });

    }

  }, 1000);


  window.addEventListener('keydown', (e) => {

    if (e.code === 'ArrowLeft') leftPressed = true;

    if (e.code === 'ArrowRight') rightPressed = true;

    if (e.code === 'Space') spacePressed = true;

  });

  window.addEventListener('keyup', (e) => {

    if (e.code === 'ArrowLeft') leftPressed = false;

    if (e.code === 'ArrowRight') rightPressed = false;

    if (e.code === 'Space') spacePressed = false;

  });


  function update() {

    if (!gameRunning) return;


    // 飛機移動

    if (leftPressed && planeX > 0) {

      planeX -= planeSpeed;

    }

    if (rightPressed && planeX < canvas.width - planeW) {

      planeX += planeSpeed;

    }


    // 發射子彈

    if (spacePressed) {

      bullets.push({ x: planeX + planeW / 2 - 2, y: planeY - 5 });

    }


    // 更新子彈

    for (let i = 0; i < bullets.length; i++) {

      bullets[i].y -= bulletSpeed;

      if (bullets[i].y < 0) {

        bullets.splice(i, 1);

        i--;

        continue;

      }

    }


    // 更新敵機

    for (let j = 0; j < enemies.length; j++) {

      enemies[j].y += enemySpeed;

      if (enemies[j].y > canvas.height) {

        enemies.splice(j, 1);

        j--;

        continue;

      }

    }


    // 子彈 vs 敵機

    for (let b = 0; b < bullets.length; b++) {

      for (let e = 0; e < enemies.length; e++) {

        let bullet = bullets[b];

        let enemy = enemies[e];

        if (

          bullet.x < enemy.x + enemyW &&

          bullet.x + 4 > enemy.x &&

          bullet.y < enemy.y + enemyH &&

          bullet.y + 10 > enemy.y

        ) {

          // 撞到 -> 移除 + 加分

          bullets.splice(b, 1);

          enemies.splice(e, 1);

          score += 10;

          b--;

          break;

        }

      }

    }


    // 敵機 vs 玩家 (簡易矩形碰撞)

    for (let k = 0; k < enemies.length; k++) {

      let enemy = enemies[k];

      if (

        planeX < enemy.x + enemyW &&

        planeX + planeW > enemy.x &&

        planeY < enemy.y + enemyH &&

        planeH + planeY > enemy.y

      ) {

        // 撞到玩家 -> 扣血

        lives--;

        // 也把敵機移除

        enemies.splice(k, 1);

        k--;

        

        // 若血量 <= 0 -> 遊戲結束

        if (lives <= 0) {

          gameRunning = false;

        }

      }

    }

  }


  function draw() {

    ctx.clearRect(0, 0, canvas.width, canvas.height);


    // 繪製飛機

    ctx.fillStyle = 'blue';

    ctx.fillRect(planeX, planeY, planeW, planeH);


    // 繪製子彈

    ctx.fillStyle = 'red';

    for (let b of bullets) {

      ctx.fillRect(b.x, b.y, 4, 10);

    }


    // 繪製敵機

    ctx.fillStyle = 'green';

    for (let en of enemies) {

      ctx.fillRect(en.x, en.y, enemyW, enemyH);

    }


    // 分數 & 血量

    ctx.font = '16px Arial';

    ctx.fillStyle = '#333';

    ctx.fillText(`Score: ${score}`, 10, 20);

    ctx.fillText(`Lives: ${lives}`, 10, 40);


    // 若遊戲結束,顯示結束畫面

    if (!gameRunning) {

      ctx.fillStyle = 'rgba(0,0,0,0.5)';

      ctx.fillRect(0, 0, canvas.width, canvas.height);


      ctx.fillStyle = '#fff';

      ctx.font = '28px Arial';

      ctx.fillText('Game Over', canvas.width / 2 - 60, canvas.height / 2);

      ctx.fillText(`Score: ${score}`, canvas.width / 2 - 50, canvas.height / 2 + 40);

    }

  }


  function gameLoop() {

    update();

    draw();

    requestAnimationFrame(gameLoop);

  }


  gameLoop();

</script>

</body>

</html>




5.3 遊戲重新開始

目標:

  • 在玩家死亡(或遊戲結束)後,提供「重新開始」的功能。

  • 重新開始時,重置分數、生命值、飛機位置等變數。

範例檔案: chapter5_5_3.html


<!DOCTYPE html>

<html lang="zh-Hant">

<head>

  <meta charset="UTF-8" />

  <title>5.3 遊戲重新開始</title>

  <style>

    body {

      margin: 0;

      font-family: sans-serif;

      background-color: #fafafa;

    }

    h1 {

      text-align: center;

      margin-top: 20px;

      color: #333;

    }

    #gameCanvas {

      display: block;

      margin: 10px auto;

      border: 1px solid #ccc;

      background: #fff;

    }

    .info {

      text-align: center;

      color: #666;

    }

  </style>

</head>

<body>


<h1>第 5 章:5.3 遊戲重新開始</h1>

<canvas id="gameCanvas" width="400" height="300"></canvas>

<p class="info">按 R 鍵重新開始遊戲</p>


<script>

  const canvas = document.getElementById('gameCanvas');

  const ctx = canvas.getContext('2d');


  let gameRunning = true;

  let planeX, planeY;

  const planeW = 30, planeH = 30;

  const planeSpeed = 5;


  let bullets = [];

  const bulletSpeed = 7;


  let enemies = [];

  const enemySpeed = 2;

  const enemyW = 30, enemyH = 30;


  let leftPressed = false;

  let rightPressed = false;

  let spacePressed = false;


  let lives, score;


  // 生成敵機的計時器 (每秒)

  let enemyInterval;


  function initGame() {

    // 初始化變數

    planeX = canvas.width / 2;

    planeY = canvas.height - 50;

    bullets = [];

    enemies = [];

    lives = 3;

    score = 0;

    gameRunning = true;


    // 啟動敵機生成

    if (enemyInterval) clearInterval(enemyInterval);

    enemyInterval = setInterval(() => {

      if (gameRunning) {

        let randomX = Math.random() * (canvas.width - enemyW);

        enemies.push({ x: randomX, y: -enemyH });

      }

    }, 1000);

  }


  // 初始化一次

  initGame();


  window.addEventListener('keydown', (e) => {

    if (e.code === 'ArrowLeft') leftPressed = true;

    if (e.code === 'ArrowRight') rightPressed = true;

    if (e.code === 'Space') spacePressed = true;


    // 按 R 重新開始

    if (!gameRunning && e.code === 'KeyR') {

      initGame();

    }

  });

  window.addEventListener('keyup', (e) => {

    if (e.code === 'ArrowLeft') leftPressed = false;

    if (e.code === 'ArrowRight') rightPressed = false;

    if (e.code === 'Space') spacePressed = false;

  });


  function update() {

    if (!gameRunning) return;


    // 飛機移動

    if (leftPressed && planeX > 0) {

      planeX -= planeSpeed;

    }

    if (rightPressed && planeX < canvas.width - planeW) {

      planeX += planeSpeed;

    }


    // 發射子彈

    if (spacePressed) {

      bullets.push({ x: planeX + planeW / 2 - 2, y: planeY - 5 });

    }


    // 更新子彈

    for (let i = 0; i < bullets.length; i++) {

      bullets[i].y -= bulletSpeed;

      if (bullets[i].y < 0) {

        bullets.splice(i, 1);

        i--;

        continue;

      }

    }


    // 更新敵機

    for (let j = 0; j < enemies.length; j++) {

      enemies[j].y += enemySpeed;

      if (enemies[j].y > canvas.height) {

        enemies.splice(j, 1);

        j--;

        continue;

      }

    }


    // 子彈 vs 敵機

    for (let b = 0; b < bullets.length; b++) {

      for (let e = 0; e < enemies.length; e++) {

        let bullet = bullets[b];

        let enemy = enemies[e];

        if (

          bullet.x < enemy.x + enemyW &&

          bullet.x + 4 > enemy.x &&

          bullet.y < enemy.y + enemyH &&

          bullet.y + 10 > enemy.y

        ) {

          // 撞到

          bullets.splice(b, 1);

          enemies.splice(e, 1);

          score += 10;

          b--;

          break;

        }

      }

    }


    // 敵機 vs 玩家

    for (let k = 0; k < enemies.length; k++) {

      let enemy = enemies[k];

      if (

        planeX < enemy.x + enemyW &&

        planeX + planeW > enemy.x &&

        planeY < enemy.y + enemyH &&

        planeH + planeY > enemy.y

      ) {

        lives--;

        enemies.splice(k, 1);

        k--;

        if (lives <= 0) {

          gameRunning = false;

        }

      }

    }

  }


  function draw() {

    ctx.clearRect(0, 0, canvas.width, canvas.height);


    // 繪製飛機

    ctx.fillStyle = 'blue';

    ctx.fillRect(planeX, planeY, planeW, planeH);


    // 繪製子彈

    ctx.fillStyle = 'red';

    for (let b of bullets) {

      ctx.fillRect(b.x, b.y, 4, 10);

    }


    // 繪製敵機

    ctx.fillStyle = 'green';

    for (let en of enemies) {

      ctx.fillRect(en.x, en.y, enemyW, enemyH);

    }


    // 顯示分數與血量

    ctx.font = '16px Arial';

    ctx.fillStyle = '#333';

    ctx.fillText(`Score: ${score}`, 10, 20);

    ctx.fillText(`Lives: ${lives}`, 10, 40);


    // 若遊戲結束

    if (!gameRunning) {

      ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';

      ctx.fillRect(0, 0, canvas.width, canvas.height);

      ctx.fillStyle = '#fff';

      ctx.font = '28px Arial';

      ctx.fillText('Game Over', canvas.width / 2 - 60, canvas.height / 2);

      ctx.fillText(`Score: ${score}`, canvas.width / 2 - 50, canvas.height / 2 + 40);


      ctx.font = '16px Arial';

      ctx.fillText('按 R 重新開始', canvas.width / 2 - 60, canvas.height / 2 + 70);

    }

  }


  function gameLoop() {

    update();

    draw();

    requestAnimationFrame(gameLoop);

  }


  gameLoop();

</script>

</body>

</html>






第 6 章:視覺與音效增強

6.1 以圖片取代簡單圖形

目標:

  • 使用圖片(<img>new Image())取代先前的方塊/emoji 來顯示飛機、敵機、子彈。

  • 初步示範如何在 Canvas 上繪製圖像 ctx.drawImage(...)

CodePen 上使用圖片(Images)有幾種方式,以下介紹常見的做法:


一、透過 CodePen 自帶的「Assets」功能(需付費帳戶)

若你是 CodePen Pro 付費用戶,可以直接把圖片上傳到 CodePen 的 Assets 中,並取得圖片連結使用:

  1. 開啟 Pen:進入你要使用圖片的 Pen(或建立新 Pen)。

  2. 進入 Settings:在右下角或左下角(依介面不同)可能會看到一個設定齒輪「Settings」,點選之後在選單中找到 Assets

  3. 上傳圖片:在 Assets 面板中可以直接拖曳或點按「Upload」將圖片上傳。

  4. 複製圖片連結:上傳完成後,CodePen 會提供一個連結(URL)。你可以在 HTML、CSS 或 JS 中引用該 URL,即可顯示圖片。

注意:Assets 功能只有 CodePen Pro 才能使用,免費用戶的帳號中不提供圖片上傳至 CodePen 伺服器的服務。


二、使用外部圖片託管

如果你是 免費用戶,或不想依賴 CodePen 自帶空間,可以將圖片上傳至 外部圖片伺服器(如 Imgur、ImageBB、GitHub Repo 或自己架設的空間),再把該圖片的 URL 貼到 CodePen :

  1. 選擇圖床

    • 常見免費圖床如 Imgur、ImageBB 等,也可以把圖片放在自己的 GitHub 專案中(GitHub Pages / GitHub Repo),或使用其他雲端空間(例如 Dropbox、Google Drive,但需注意連結權限)。

  2. 上傳圖片

    • 按照圖床或伺服器的操作流程,上傳你的圖片,取得一個公開的圖片連結(URL)。

  3. 在 CodePen 引用

    • 在 HTML、CSS 或 JS 中,使用該連結即可載入圖片。例如:


範例檔案: chapter6_6_1.html



<!-- 在 HTML 中 --> <img src="https://image.shutterstock.com/image-vector/2025-chinese-new-year-vector-260nw-2539690263.jpg" > 



<!-- 在 CSS 中 --> .banner { background-image: url('https://i.imgur.com/XXXXXXX.jpg'); }




6.2 加入背景音樂與音效

目標:

  • 於遊戲開始時播放背景音樂(BGM)。

  • 在發射子彈或擊中敵機時播放特定音效(SFX)。

範例檔案: chapter6_6_2.html


<!DOCTYPE html>

<html lang="zh-Hant">

<head>

  <meta charset="UTF-8" />

  <title>6.2 加入音樂與音效</title>

  <style>

    body {

      margin: 0;

      font-family: sans-serif;

      background: #fafafa;

    }

    h1 {

      text-align: center;

      margin-top: 20px;

      color: #333;

    }

    #gameCanvas {

      display: block;

      margin: 10px auto;

      border: 1px solid #ccc;

      background: #fff;

    }

    .info {

      text-align: center;

      color: #666;

    }

  </style>

</head>

<body>


<h1>第 6 章:6.2 加入背景音樂與音效</h1>

<canvas id="gameCanvas" width="400" height="300"></canvas>

<p class="info">

  本範例示範如何在遊戲中加入背景音樂 (BGM) 與擊發/爆炸音效 (SFX)<br>

  (需確保音檔存在,如 .mp3/.wav)

</p>


<script>

  const canvas = document.getElementById('gameCanvas');

  const ctx = canvas.getContext('2d');


  // BGM 與 SFX

  const bgm = new Audio('bgm.mp3');           // 背景音樂

  const shootSfx = new Audio('shoot.wav');    // 發射音效

  const explodeSfx = new Audio('explode.wav'); // 爆炸音效


  // 遊戲變數 (僅示範音效流程)

  let planeX = 170;

  let planeY = 250;

  const planeSpeed = 5;


  let bullets = [];

  const bulletSpeed = 7;

  

  let leftPressed = false;

  let rightPressed = false;

  let spacePressed = false;


  // 監聽鍵盤

  window.addEventListener('keydown', (e) => {

    if (e.code === 'ArrowLeft') leftPressed = true;

    if (e.code === 'ArrowRight') rightPressed = true;

    if (e.code === 'Space') {

      spacePressed = true;

      // 發射子彈音效 (可在這裡或 update() 內呼叫)

      shootSfx.currentTime = 0; // 重置播放頭,確保連續按還能播放

      shootSfx.play();

    }

  });

  window.addEventListener('keyup', (e) => {

    if (e.code === 'ArrowLeft') leftPressed = false;

    if (e.code === 'ArrowRight') rightPressed = false;

    if (e.code === 'Space') spacePressed = false;

  });


  function startBGM() {

    // 背景音樂可以在玩家互動後開始播放,以避免某些瀏覽器自動封鎖

    bgm.loop = true;     // 設為循環播放

    bgm.volume = 0.5;    // 調整音量

    bgm.play();

  }


  // Demo: 當玩家點擊畫面任意處,即開始 BGM (或可在其他事件觸發)

  canvas.addEventListener('click', () => {

    startBGM();

  });


  function update() {

    if (leftPressed && planeX > 0) planeX -= planeSpeed;

    if (rightPressed && planeX < canvas.width - 30) planeX += planeSpeed;


    if (spacePressed) {

      bullets.push({ x: planeX + 10, y: planeY - 5 });

    }

    // 更新子彈

    for (let i = 0; i < bullets.length; i++) {

      bullets[i].y -= bulletSpeed;

      if (bullets[i].y < 0) {

        bullets.splice(i, 1);

        i--;

      }

    }

    // (略) 可能在子彈與敵機碰撞時呼叫 explodeSfx.play();

  }


  function draw() {

    ctx.clearRect(0, 0, canvas.width, canvas.height);


    // 飛機 (簡單方塊)

    ctx.fillStyle = 'blue';

    ctx.fillRect(planeX, planeY, 30, 30);


    // 子彈

    ctx.fillStyle = 'red';

    for (let b of bullets) {

      ctx.fillRect(b.x, b.y, 5, 10);

    }


    ctx.font = '14px Arial';

    ctx.fillStyle = '#333';

    ctx.fillText('點擊遊戲畫面開始播放 BGM', 10, 20);

  }


  function gameLoop() {

    update();

    draw();

    requestAnimationFrame(gameLoop);

  }


  gameLoop();

</script>


</body>

</html>





6.3 初步動畫優化:爆炸特效

目標:

  • 當子彈擊中敵機時,繪製一個爆炸動畫,並在短時間內結束。

  • 此處僅示範最簡單的「爆炸影格」切換方式,可供延伸參考。

範例檔案: chapter6_6_3.html


<!DOCTYPE html>

<html lang="zh-Hant">

<head>

  <meta charset="UTF-8" />

  <title>6.3 初步爆炸動畫</title>

  <style>

    body {

      margin: 0;

      font-family: sans-serif;

      background: #fafafa;

    }

    h1 {

      text-align: center;

      margin-top: 20px;

      color: #333;

    }

    #gameCanvas {

      display: block;

      margin: 10px auto;

      border: 1px solid #ccc;

      background: #fff;

    }

    .info {

      text-align: center;

      color: #666;

    }

  </style>

</head>

<body>


<h1>第 6 章:6.3 初步爆炸動畫</h1>

<canvas id="gameCanvas" width="400" height="300"></canvas>

<p class="info">子彈擊中敵機時,播放簡易爆炸影格</p>


<script>

  const canvas = document.getElementById('gameCanvas');

  const ctx = canvas.getContext('2d');


  // 爆炸影格 (示範用 4 張圖,可用更完整的 sprite sheet)

  const explosionFrames = [

    'explosion1.png',

    'explosion2.png',

    'explosion3.png',

    'explosion4.png'

  ];

  // 預載影格圖

  const explosionImgs = explosionFrames.map(src => {

    const img = new Image();

    img.src = src;

    return img;

  });


  // 定義 "爆炸" 類型物件

  // explosion = { x, y, frameIndex, frameCount }

  // frameCount: 每幾幀換下一張

  let explosions = [];


  // 飛機 / 子彈 / 敵機 (簡化)

  let plane = { x: 180, y: 250, w: 40, h: 40 };

  let bullets = [];

  let enemies = [];


  // 產生敵機

  setInterval(() => {

    let x = Math.random() * (canvas.width - 40);

    enemies.push({ x, y: -40, w: 40, h: 40 });

  }, 1000);


  window.addEventListener('keydown', (e) => {

    if (e.code === 'Space') {

      bullets.push({ x: plane.x + plane.w / 2 - 2, y: plane.y });

    }

  });


  function update() {

    // 更新子彈

    for (let i = 0; i < bullets.length; i++) {

      bullets[i].y -= 6;

      if (bullets[i].y < -10) {

        bullets.splice(i, 1);

        i--;

      }

    }

    // 更新敵機

    for (let j = 0; j < enemies.length; j++) {

      enemies[j].y += 2;

      if (enemies[j].y > canvas.height + 10) {

        enemies.splice(j, 1);

        j--;

      }

    }

    // 子彈 vs 敵機

    for (let b = 0; b < bullets.length; b++) {

      for (let e = 0; e < enemies.length; e++) {

        let bullet = bullets[b];

        let enemy = enemies[e];

        if (

          bullet.x < enemy.x + enemy.w &&

          bullet.x + 4 > enemy.x &&

          bullet.y < enemy.y + enemy.h &&

          bullet.y + 10 > enemy.y

        ) {

          // 產生爆炸

          explosions.push({

            x: enemy.x,

            y: enemy.y,

            frameIndex: 0,

            frameTick: 0  // 用來計算每幾幀切下一張

          });

          // 移除子彈 & 敵機

          bullets.splice(b, 1);

          enemies.splice(e, 1);

          b--;

          break;

        }

      }

    }

    // 更新爆炸動畫

    for (let ex = 0; ex < explosions.length; ex++) {

      let explosion = explosions[ex];

      // 每幾幀換下一張

      explosion.frameTick++;

      if (explosion.frameTick > 5) {

        explosion.frameIndex++;

        explosion.frameTick = 0;

      }

      // 若已超過最後一張影格,移除爆炸物件

      if (explosion.frameIndex >= explosionFrames.length) {

        explosions.splice(ex, 1);

        ex--;

      }

    }

  }


  function draw() {

    ctx.clearRect(0, 0, canvas.width, canvas.height);


    // 繪製飛機 (簡化用方塊)

    ctx.fillStyle = 'blue';

    ctx.fillRect(plane.x, plane.y, plane.w, plane.h);


    // 繪製子彈

    ctx.fillStyle = 'red';

    for (let b of bullets) {

      ctx.fillRect(b.x, b.y, 4, 10);

    }


    // 繪製敵機

    ctx.fillStyle = 'green';

    for (let en of enemies) {

      ctx.fillRect(en.x, en.y, en.w, en.h);

    }


    // 繪製爆炸

    for (let explosion of explosions) {

      let frameIdx = explosion.frameIndex;  // 0 ~ 3

      let img = explosionImgs[frameIdx];

      // 為讓爆炸中心大致落在敵機中心,可做些微校正

      ctx.drawImage(img, explosion.x - 5, explosion.y - 5, 50, 50);

    }


    // 提示文字

    ctx.font = '14px Arial';

    ctx.fillStyle = '#333';

    ctx.fillText('子彈擊中敵機 -> 產生簡易爆炸動畫', 10, 20);

  }


  function gameLoop() {

    update();

    draw();

    requestAnimationFrame(gameLoop);

  }


  // 等爆炸圖都載入後再開始 (簡化處理,實務上可再加載入檢查)

  explosionImgs[explosionImgs.length - 1].onload = () => {

    gameLoop();

  };

</script>

</body>

</html>





第 7 章:進階關卡與難度設計

7.1 關卡結構與難度調整

目標:

  • 以「Wave」或「Stage」的概念,規劃遊戲中不同波次的敵機或難度。

  • 透過程式控制敵機數量、速度、出現頻率等,讓遊戲逐漸變難。

範例檔案: chapter7_7_1.html


<!DOCTYPE html>

<html lang="zh-Hant">

<head>

  <meta charset="UTF-8">

  <title>7.1 關卡結構與難度調整</title>

  <style>

    body {

      margin: 0;

      font-family: sans-serif;

      background: #fafafa;

    }

    h1 {

      text-align: center;

      margin-top: 20px;

      color: #333;

    }

    #gameCanvas {

      display: block;

      margin: 10px auto;

      border: 1px solid #ccc;

      background: #fff;

    }

    .info {

      text-align: center;

      color: #666;

    }

  </style>

</head>

<body>


<h1>第 7 章:7.1 關卡結構與難度調整</h1>

<canvas id="gameCanvas" width="400" height="300"></canvas>

<p class="info">

  以「Wave」概念分段,逐漸加大敵機數量或速度,讓關卡越來越具有挑戰性。

</p>


<script>

  const canvas = document.getElementById('gameCanvas');

  const ctx = canvas.getContext('2d');


  // 飛機與遊戲狀態

  let plane = { x: 180, y: 250, w: 40, h: 40 };

  let bullets = [];

  let enemies = [];

  let wave = 1;           // 目前關卡(Wave)

  let waveMax = 5;        // 設定大約 5 Wave 後進入下一章節

  let waveEnemyCount = 0; // 當前 Wave 已生成的敵機數量

  let waveCleared = false; // 判斷是否所有敵機都被清除


  // 玩家操控

  let leftPressed = false;

  let rightPressed = false;

  let spacePressed = false;


  // 敵機參數(會隨 wave 成長)

  let enemyBaseSpeed = 2;

  let enemyPerWave = 10;    // 每個 Wave 生成的敵機數

  let spawnInterval = null; // 敵機生成計時器


  // 建立鍵盤監聽

  window.addEventListener('keydown', (e) => {

    if (e.code === 'ArrowLeft') leftPressed = true;

    if (e.code === 'ArrowRight') rightPressed = true;

    if (e.code === 'Space') spacePressed = true;

  });

  window.addEventListener('keyup', (e) => {

    if (e.code === 'ArrowLeft') leftPressed = false;

    if (e.code === 'ArrowRight') rightPressed = false;

    if (e.code === 'Space') spacePressed = false;

  });


  // 初始化第一波

  startWave(wave);


  function startWave(w) {

    waveEnemyCount = 0;

    waveCleared = false;


    // 敵機生成邏輯(約每秒產生一次)

    spawnInterval = setInterval(() => {

      if (waveEnemyCount < enemyPerWave) {

        spawnEnemy(w);

        waveEnemyCount++;

      } else {

        // 已生成足夠的敵機,停止計時器

        clearInterval(spawnInterval);

      }

    }, 1000);

  }


  function spawnEnemy(w) {

    // 隨 Wave 提升速度或其他屬性

    const speed = enemyBaseSpeed + (w - 1) * 0.5;

    // 敵機起始位置

    let xPos = Math.random() * (canvas.width - 40);

    enemies.push({ x: xPos, y: -40, w: 40, h: 40, speed });

  }


  function update() {

    // 玩家移動

    if (leftPressed && plane.x > 0) plane.x -= 5;

    if (rightPressed && plane.x < canvas.width - plane.w) plane.x += 5;

    if (spacePressed) {

      bullets.push({ x: plane.x + plane.w / 2 - 2, y: plane.y, w: 4, h: 10 });

    }


    // 子彈更新

    for (let i = 0; i < bullets.length; i++) {

      bullets[i].y -= 7;

      if (bullets[i].y < -bullets[i].h) {

        bullets.splice(i, 1);

        i--;

      }

    }


    // 敵機更新

    for (let j = 0; j < enemies.length; j++) {

      enemies[j].y += enemies[j].speed;

      if (enemies[j].y > canvas.height + enemies[j].h) {

        enemies.splice(j, 1);

        j--;

        continue;

      }

    }


    // 子彈 vs 敵機 碰撞

    for (let b = 0; b < bullets.length; b++) {

      for (let e = 0; e < enemies.length; e++) {

        let bullet = bullets[b];

        let enemy = enemies[e];

        if (

          bullet.x < enemy.x + enemy.w &&

          bullet.x + bullet.w > enemy.x &&

          bullet.y < enemy.y + enemy.h &&

          bullet.y + bullet.h > enemy.y

        ) {

          // 碰撞發生

          bullets.splice(b, 1);

          enemies.splice(e, 1);

          b--;

          break;

        }

      }

    }


    // 若本 Wave 的敵機已全部生成,且現在敵機清單也空了 => Wave 結束

    if (waveEnemyCount === enemyPerWave && enemies.length === 0 && !waveCleared) {

      waveCleared = true;

      // 進入下一 Wave

      wave++;

      // 若還沒達到 waveMax,開始下一波

      if (wave <= waveMax) {

        startWave(wave);

      } else {

        // 所有 Wave 都完成了,可以視情況做其他處理 (如 Game Clear)

      }

    }

  }


  function draw() {

    ctx.clearRect(0, 0, canvas.width, canvas.height);


    // 繪製玩家

    ctx.fillStyle = 'blue';

    ctx.fillRect(plane.x, plane.y, plane.w, plane.h);


    // 繪製子彈

    ctx.fillStyle = 'red';

    for (let b of bullets) {

      ctx.fillRect(b.x, b.y, b.w, b.h);

    }


    // 繪製敵機

    ctx.fillStyle = 'green';

    for (let en of enemies) {

      ctx.fillRect(en.x, en.y, en.w, en.h);

    }


    // 顯示目前 Wave

    ctx.font = '16px Arial';

    ctx.fillStyle = '#333';

    ctx.fillText(`Wave: ${wave}/${waveMax}`, 10, 20);


    // 簡易提示

    if (wave > waveMax) {

      ctx.fillStyle = 'rgba(0,0,0,0.5)';

      ctx.fillRect(0, 0, canvas.width, canvas.height);

      ctx.fillStyle = '#fff';

      ctx.font = '24px Arial';

      ctx.fillText('所有關卡完成!', 100, 150);

    }

  }


  function gameLoop() {

    update();

    draw();

    requestAnimationFrame(gameLoop);

  }

  gameLoop();

</script>


</body>

</html>





7.2 BOSS 戰機制

目標:

  • 在指定 Wave(或條件)後,出現「Boss 敵機」,血量較高、攻擊模式更複雜。

  • 以簡易示範:Boss 有多段血量,可顯示血條;若被擊中多次才會被消滅。

範例檔案: chapter7_7_2.html


<!DOCTYPE html>

<html lang="zh-Hant">

<head>

  <meta charset="UTF-8" />

  <title>7.2 BOSS 戰機制</title>

  <style>

    body {

      margin: 0;

      font-family: sans-serif;

      background: #fafafa;

    }

    h1 {

      text-align: center;

      margin-top: 20px;

      color: #333;

    }

    #gameCanvas {

      display: block;

      margin: 10px auto;

      border: 1px solid #ccc;

      background: #fff;

    }

    .info {

      text-align: center;

      color: #666;

    }

  </style>

</head>

<body>


<h1>第 7 章:7.2 BOSS 戰機制</h1>

<canvas id="gameCanvas" width="400" height="300"></canvas>

<p class="info">

  當達到最後 Wave 時,生成 BOSS;BOSS 擁有較高血量與血條,需要多次攻擊才能消滅。

</p>


<script>

  const canvas = document.getElementById('gameCanvas');

  const ctx = canvas.getContext('2d');


  // 玩家 / 子彈

  let plane = { x: 180, y: 250, w: 40, h: 40 };

  let bullets = [];

  let leftPressed = false, rightPressed = false, spacePressed = false;


  // BOSS 狀態(假設只有在最後一關出現)

  let boss = null;

  const bossMaxHp = 50;   // BOSS 血量

  const bossSpeed = 1.5;


  // 監聽鍵盤

  window.addEventListener('keydown', (e) => {

    if (e.code === 'ArrowLeft') leftPressed = true;

    if (e.code === 'ArrowRight') rightPressed = true;

    if (e.code === 'Space') spacePressed = true;

  });

  window.addEventListener('keyup', (e) => {

    if (e.code === 'ArrowLeft') leftPressed = false;

    if (e.code === 'ArrowRight') rightPressed = false;

    if (e.code === 'Space') spacePressed = false;

  });


  // 簡單示範:直接生成 BOSS

  spawnBoss();


  function spawnBoss() {

    boss = {

      x: 100,

      y: -100,      // 從畫面上方進場

      w: 200,

      h: 80,

      hp: bossMaxHp,

      direction: 1  // 用來左右移動

    };

  }


  function update() {

    // 飛機移動

    if (leftPressed && plane.x > 0) plane.x -= 5;

    if (rightPressed && plane.x < canvas.width - plane.w) plane.x += 5;

    if (spacePressed) {

      bullets.push({ x: plane.x + plane.w / 2 - 2, y: plane.y, w: 4, h: 10 });

    }


    // 子彈移動

    for (let i = 0; i < bullets.length; i++) {

      bullets[i].y -= 7;

      if (bullets[i].y < -10) {

        bullets.splice(i, 1);

        i--;

      }

    }


    // BOSS 行動

    if (boss) {

      // 若 BOSS 還在進場

      if (boss.y < 30) {

        boss.y += bossSpeed;

      } else {

        // 到達目標位置後左右來回移動

        boss.x += bossSpeed * boss.direction;

        // 碰到左右邊界就反轉

        if (boss.x < 0 || boss.x + boss.w > canvas.width) {

          boss.direction *= -1;

        }

      }


      // 子彈 vs BOSS

      for (let b = 0; b < bullets.length; b++) {

        let bullet = bullets[b];

        if (

          bullet.x < boss.x + boss.w &&

          bullet.x + bullet.w > boss.x &&

          bullet.y < boss.y + boss.h &&

          bullet.y + bullet.h > boss.y

        ) {

          // 擊中 BOSS

          boss.hp--;

          // 移除子彈

          bullets.splice(b, 1);

          b--;

          // 若 hp <= 0,BOSS 消失

          if (boss.hp <= 0) {

            boss = null;

          }

        }

      }

    }

  }


  function draw() {

    ctx.clearRect(0, 0, canvas.width, canvas.height);


    // 畫玩家

    ctx.fillStyle = 'blue';

    ctx.fillRect(plane.x, plane.y, plane.w, plane.h);


    // 畫子彈

    ctx.fillStyle = 'red';

    for (let b of bullets) {

      ctx.fillRect(b.x, b.y, b.w, b.h);

    }


    // 畫 BOSS

    if (boss) {

      ctx.fillStyle = 'purple';

      ctx.fillRect(boss.x, boss.y, boss.w, boss.h);


      // BOSS 血條

      ctx.fillStyle = 'black';

      ctx.fillRect(boss.x, boss.y - 10, boss.w, 5);

      ctx.fillStyle = 'lime';

      let hpWidth = (boss.hp / bossMaxHp) * boss.w;

      ctx.fillRect(boss.x, boss.y - 10, hpWidth, 5);

    } else {

      // BOSS 終結後顯示訊息

      ctx.font = '20px Arial';

      ctx.fillStyle = '#333';

      ctx.fillText('BOSS 已被擊敗!', 100, 150);

    }

  }


  function gameLoop() {

    update();

    draw();

    requestAnimationFrame(gameLoop);

  }

  gameLoop();

</script>


</body>

</html>




7.3 高分紀錄(Local Storage)

目標:

  • 使用瀏覽器的 Local Storage(或 Session Storage)來儲存玩家最高得分。

  • 每次結束遊戲後,若分數超過最高分,則更新紀錄並顯示「新紀錄!」。

範例檔案: chapter7_7_3.html


<!DOCTYPE html>

<html lang="zh-Hant">

<head>

  <meta charset="UTF-8">

  <title>7.3 高分紀錄(LocalStorage)</title>

  <style>

    body {

      margin: 0;

      font-family: sans-serif;

      background: #fafafa;

    }

    h1 {

      text-align: center;

      margin-top: 20px;

      color: #333;

    }

    #gameCanvas {

      display: block;

      margin: 10px auto;

      border: 1px solid #ccc;

      background: #fff;

    }

    .info {

      text-align: center;

      color: #666;

    }

  </style>

</head>

<body>


<h1>第 7 章:7.3 高分紀錄(Local Storage)</h1>

<canvas id="gameCanvas" width="400" height="300"></canvas>

<p class="info">

  遊戲結束後,若當前分數超越最高分,則更新並顯示「新紀錄!」<br>

  分數存於瀏覽器 Local Storage,重新整理或下次進入仍可保留。

</p>


<script>

  const canvas = document.getElementById('gameCanvas');

  const ctx = canvas.getContext('2d');


  // 先嘗試從 localStorage 取得最高分

  let bestScore = localStorage.getItem('bestScore');

  if (!bestScore) {

    bestScore = 0;

  } else {

    bestScore = parseInt(bestScore, 10);

  }


  let score = 0;            // 本局分數

  let gameRunning = true;   // 控制遊戲是否持續


  // 模擬:按空白鍵 +10 分,若按 R 結束遊戲

  window.addEventListener('keydown', (e) => {

    if (e.code === 'Space' && gameRunning) {

      score += 10;

    }

    if (e.code === 'KeyR') {

      endGame();

    }

  });


  function endGame() {

    gameRunning = false;

    // 若本局分數超過最高分 -> 更新

    if (score > bestScore) {

      bestScore = score;

      localStorage.setItem('bestScore', bestScore.toString());

    }

  }


  function update() {

    if (!gameRunning) return;

    // 此處省略遊戲邏輯,專注示範高分紀錄

  }


  function draw() {

    ctx.clearRect(0, 0, canvas.width, canvas.height);


    ctx.font = '16px Arial';

    ctx.fillStyle = '#333';

    ctx.fillText(`Score: ${score}`, 10, 20);

    ctx.fillText(`Best: ${bestScore}`, 10, 40);


    if (!gameRunning) {

      ctx.fillStyle = 'rgba(0,0,0,0.5)';

      ctx.fillRect(0, 0, canvas.width, canvas.height);


      ctx.fillStyle = '#fff';

      ctx.font = '24px Arial';

      ctx.fillText('Game Over', 120, 140);

      if (score >= bestScore) {

        ctx.fillText('新紀錄!', 140, 180);

      }

      ctx.font = '16px Arial';

      ctx.fillText('按 F5 或重整來重玩,或在真正遊戲邏輯下做重置', 20, 220);

    }

  }


  function gameLoop() {

    update();

    draw();

    requestAnimationFrame(gameLoop);

  }

  gameLoop();

</script>


</body>

</html>








遊戲2名稱:


程式碼


遊戲截圖





遊戲3名稱:


程式碼


遊戲截圖





遊戲4名稱:


程式碼


遊戲截圖





遊戲5名稱:


程式碼


遊戲截圖