- 取得連結
- X
- 以電子郵件傳送
- 其他應用程式
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 中,並取得圖片連結使用:
開啟 Pen:進入你要使用圖片的 Pen(或建立新 Pen)。
進入 Settings:在右下角或左下角(依介面不同)可能會看到一個設定齒輪「Settings」,點選之後在選單中找到 Assets。
上傳圖片:在 Assets 面板中可以直接拖曳或點按「Upload」將圖片上傳。
複製圖片連結:上傳完成後,CodePen 會提供一個連結(URL)。你可以在 HTML、CSS 或 JS 中引用該 URL,即可顯示圖片。
注意:Assets 功能只有 CodePen Pro 才能使用,免費用戶的帳號中不提供圖片上傳至 CodePen 伺服器的服務。
二、使用外部圖片託管
如果你是 免費用戶,或不想依賴 CodePen 自帶空間,可以將圖片上傳至 外部圖片伺服器(如 Imgur、ImageBB、GitHub Repo 或自己架設的空間),再把該圖片的 URL 貼到 CodePen :
選擇圖床:
常見免費圖床如 Imgur、ImageBB 等,也可以把圖片放在自己的 GitHub 專案中(GitHub Pages / GitHub Repo),或使用其他雲端空間(例如 Dropbox、Google Drive,但需注意連結權限)。
上傳圖片:
按照圖床或伺服器的操作流程,上傳你的圖片,取得一個公開的圖片連結(URL)。
在 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名稱: |
程式碼 |
遊戲截圖 |