
用 JavaScript 從零構建太空射擊游戲Web-Dev-For-Beginners 六課實戰指南【免費下載鏈接】Web-Dev-For-Beginners24 Lessons, 12 Weeks, Get Started as a Web Developer項目地址: https://gitcode.com/GitHub_Trending/we/Web-Dev-For-Beginners本文以 Web-Dev-For-Beginners 倉庫中「太空游戲Space Game」系列課程即 translations/de/6-space-game/README.md 對應的英文原版 6-space-game/README.md為核心展開。該系列用 6 節課、12 周的課程體系中一個完整可玩的游戲項目帶你掌握Canvas 繪圖、笛卡爾坐標系、Pub/Sub 發布訂閱架構、Async/Await 異步資源加載、鍵盤事件處理等進階 JavaScript 基礎。讀完本文你不僅能理解這套太空射擊游戲玩法靈感來自經典《太空侵略者》Space Invaders駕駛飛船、射擊從上方來襲的怪物從靜態畫面到完整可玩游戲的全部實現路徑還能直接運行倉庫中的 完整解決方案 進行驗證。一、課程全景六課進階路線本系列遵循「理論先行、實踐跟進」的教學結構倉庫 6-space-game/README.md 給出了清晰的學習地圖理論使用 JavaScript 構建游戲入門繼承、組合與 Pub/Sub 架構實踐在 Canvas 上繪制圖形讓元素在屏幕上移動碰撞檢測計分系統游戲結束與重新開始六節課環環相扣先用第 1 課打牢對象組織與事件架構的地基再用第 23 課實現「畫出來 動起來」第 4 課加入戰斗與碰撞第 56 課補全計分、生命、勝負判定與重開機制最終形成完整游戲閉環。二、Canvas 與笛卡爾坐標系游戲世界的「畫布」第 2 課 2-drawing-to-canvas/README.md 講解了 HTML5 Canvas 的繪圖基礎。Canvas 是瀏覽器內實現動態圖形與動畫的核心 API通過 JavaScript 可以逐像素控制屏幕上的一切非常適合游戲、數據可視化和交互式藝術。2.1 畫布的聲明與坐標系一個畫布元素通常這樣聲明在頁面 body 中canvas idmyCanvas width200 height100/canvas關鍵在于理解 Canvas 使用的坐標系它基于數學課上學過的笛卡爾坐標系x 軸水平、y 軸垂直但原點(0,0)位于左上角x 值向右遞增、y 值向下遞增。這與數學坐標系的差異源自早期顯示器電子束自上而下的掃描方式。在完整解決方案 6-space-game/solution/index.html 中游戲畫布被設置為1024×768所有實體坐標都以該邏輯尺寸為基準。2.2 繪圖的固定三步在 Canvas 上繪制任何內容都遵循三步流程獲取畫布元素引用document.getElementById(myCanvas)獲取 2D 渲染上下文canvas.getContext(2d)它提供了全部繪圖方法開始繪制使用上下文內置方法fillRect、arc、drawImage等// 第 1 步獲取畫布元素 const canvas document.getElementById(myCanvas); // 第 2 步獲取 2D 渲染上下文 const ctx canvas.getContext(2d); // 第 3 步設置填充色并繪制一個矩形x, y, width, height ctx.fillStyle red; ctx.fillRect(0, 0, 200, 200);Canvas 支持繪制幾何形狀、任意字體與顏色的文本、以及基于.jpg/.png等圖片資源的圖像。若需要 3D 能力可進一步研究 WebGL API。在 解決方案的渲染函數 中可以看到更完整的實踐clearRect清空畫布、按星級分層繪制星空背景、用translate/rotate變換繪制旋轉的敵艦與飛船、通過shadowBlur實現發光特效并且針對高 DPI 屏幕用setTransform(dpr, 0, 0, dpr, 0, 0)做了縮放適配見 resizeCanvas 函數。三、對象組織繼承與組合第 1 課 1-introduction/README.md 指出任何游戲都包含「游戲對象」Game Objects——英雄、敵人、道具、特效等交互元素。它們雖然視覺各異卻共享四類基本行為存在于某處每個對象都有 x、y 坐標游戲據此繪制可以移動英雄奔跑、敵人追擊、子彈飛馳擁有生命周期有的常駐有的如爆炸特效短暫出現后消失對事件做出反應碰撞發生時收集道具、更新血條3.1 類與繼承Class-Based Approach以基礎類承載公共屬性再派生子類添加特定能力// 第 1 步創建基礎 GameObject 類 class GameObject { constructor(x, y, type) { this.x x; this.y y; this.type type; } } // 第 2 步通過繼承添加移動能力 class Movable extends GameObject { constructor(x, y, type) { super(x, y, type); // 調用父類構造函數 } moveTo(x, y) { this.x x; this.y y; } } // 第 3 步創建具體游戲對象類型 class Hero extends Movable { constructor(x, y) { super(x, y, Hero); // 自動設置類型 } } class Tree extends GameObject { constructor(x, y) { super(x, y, Tree); // 樹不需要移動能力 } } // 第 4 步使用游戲對象 const hero new Hero(0, 0); hero.moveTo(5, 5); // 英雄可以移動 const tree new Tree(10, 15); // tree.moveTo() 會報錯——樹不能移動繼承適合清晰的「is-a」關系Hero 是 Movable 的一種但層級僵化。第 3 課在 3-moving-elements-around/README.md 中給出了本項目的實際基類形態GameObject攜帶x/y、dead標記、type、width/height、img并提供draw(ctx)方法Hero與Enemy通過extends GameObject繼承。3.2 組合與工廠函數Composition Approach組合遵循模塊化設計哲學像工程師用可互換組件組裝航天器一樣把獨立行為對象拼裝出恰好滿足需求的對象// 第 1 步創建基礎行為對象 const gameObject { x: 0, y: 0, type: }; const movable { moveTo(x, y) { this.x x; this.y y; } }; // 第 2 步通過展開語法組合對象 const movableObject { ...gameObject, ...movable }; // 第 3 步用工廠函數創建不同類型的對象 function createHero(x, y) { return { ...movableObject, x, y, type: Hero }; } function createStatic(x, y, type) { return { ...gameObject, x, y, type }; } // 第 4 步使用組合對象 const hero createHero(10, 10); hero.moveTo(5, 5); // 正常工作 const tree createStatic(0, 0, Tree); // tree.moveTo() 為 undefined——沒有組合移動行為組合適合「has-a」關系Hero 擁有移動能力更加靈活。實戰中兩種模式可以混用第 1 課的自測題給出示例——飛行敵人用繼承寫是class FlyingEnemy extends Movable用組合寫則是{ ...movable, ...flyable, ...gameObject }。四、Pub/Sub 發布訂閱架構解耦游戲組件通信第 1 課的核心架構知識點是發布-訂閱模式Publish-Subscribe。當英雄受傷時血條要更新、音效要播放、畫面要有反饋——若讓英雄對象直接耦合這些系統代碼將難以維護。Pub/Sub 讓英雄只廣播一條「受到傷害」的消息任何需要響應的系統訂閱該消息即可原理類似無線電廣播一個發射端可覆蓋多個接收端無需知道誰在收聽。4.1 關鍵角色消息Messages如PLAYER_SCORED這樣的簡單文本標簽描述發生了什么發布者Publishers喊出「有事發生了」的對象訂閱者Subscribers聲明「我關心該事件」并在發生時響應的對象事件系統Event System確保消息送達正確監聽者的「中間人」4.2 手寫一個 EventEmitter課程給出了一個簡潔而強大的事件系統實現// 第 1 步創建 EventEmitter 類 class EventEmitter { constructor() { this.listeners {}; // 存儲所有事件監聽器 } // 為特定消息類型注冊監聽器 on(message, listener) { if (!this.listeners[message]) { this.listeners[message] []; } this.listeners[message].push(listener); } // 向所有已注冊監聽器廣播消息 emit(message, payload null) { if (this.listeners[message]) { this.listeners[message].forEach(listener { listener(message, payload); }); } } }4.3 消息常量與鍵盤事件連接用常量對象防止消息名拼寫錯誤并通過鍵盤事件驅動游戲邏輯// 第 1 步定義消息類型 const Messages { HERO_MOVE_LEFT: HERO_MOVE_LEFT, HERO_MOVE_RIGHT: HERO_MOVE_RIGHT, ENEMY_SPOTTED: ENEMY_SPOTTED }; const eventEmitter new EventEmitter(); const hero createHero(0, 0); // 第 2 步設置事件監聽器訂閱者 eventEmitter.on(Messages.HERO_MOVE_LEFT, () { hero.moveTo(hero.x - 5, hero.y); console.log(Hero moved to position: ${hero.x}, ${hero.y}); }); // 第 3 步將鍵盤輸入連接到事件發布者 window.addEventListener(keydown, (event) { switch(event.key) { case ArrowLeft: eventEmitter.emit(Messages.HERO_MOVE_LEFT); break; case ArrowRight: eventEmitter.emit(Messages.HERO_MOVE_RIGHT); break; } });該模式的收益體現在新增音效、屏幕震動、粒子特效只需「多掛一個監聽器」無需改動既有鍵盤或移動代碼測試時每個組件可獨立運行出問題時能準確定位。課程給出的最佳實踐包括盡早以寬泛類別定義消息、使用描述性命名、保持 payload 簡單聚焦、為團隊文檔化消息類型。五、Async/Await 異步加載游戲資源第 2 課指出加載圖片資源是異步的在后臺進行。需要創建Image對象、設置src觸發請求待onload觸發后再繪制到畫布。課程的進階方案是把圖片加載包裝成 Promise用 ES6 的 async/await 讓代碼更整潔并優雅處理錯誤function loadAsset(path) { return new Promise((resolve, reject) { const img new Image(); img.src path; img.onload () { resolve(img); }; img.onerror () { reject(new Error(Failed to load image: ${path})); }; }); } // 結合 async/await 的現代用法 async function initializeGame() { try { const heroImg await loadAsset(hero.png); const monsterImg await loadAsset(monster.png); // 圖片現已就緒可以繪制 } catch (error) { console.error(Failed to load game assets:, error); } }在完整解決方案中同樣采用此模式將飛船、敵艦等素材加載后才啟動游戲循環見 6-space-game/solution/app.js 中bgImage的加載邏輯L96-L105并且支持玩家通過輸入框提供自定義背景圖 URL——設置img.crossOrigin anonymous以規避跨域污染問題。六、鍵盤事件與游戲循環讓一切動起來第 3 課 3-moving-elements-around/README.md 專注于「動起來」。6.1 鍵盤事件處理鍵盤事件發生在 window 層級整個瀏覽器窗口監聽按鍵鼠標點擊則可以綁定到具體元素。使用window.addEventListener()傳入事件名如keyup和回調函數window.addEventListener(keyup, (evt) { // evt.key 按鍵的字符串表示 if (evt.key ArrowUp) { // 執行操作 } });按鍵事件的兩個關鍵屬性key字符串如ArrowUp與keyCode數字如37對應左箭頭。特別注意方向鍵會滾動頁面、空格鍵會跳頁必須用e.preventDefault()阻止瀏覽器默認行為否則會干擾游戲操控const onKeyDown function (e) { console.log(e.keyCode); switch (e.keyCode) { case 37: case 39: case 38: case 40: // 方向鍵 case 32: e.preventDefault(); break; // 空格 default: break; // 不攔截其他按鍵 } }; window.addEventListener(keydown, onKeyDown);在 解決方案的按鍵處理 中課程實現更進一步用keys集合記錄持續按下的鍵、用keyMap同時支持 WASD 與方向鍵兩套方案并通過preventDefault阻止空格滾動頁面keyup時從集合移除實現連續平滑移動。6.2 游戲循環Game Loop游戲循環是「把幀連起來」的引擎更新所有對象位置 → 清屏 → 重繪 → 循環。課程用setInterval實現const gameLoopId setInterval(() { function gameLoop() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle black; ctx.fillRect(0, 0, canvas.width, canvas.height); drawHero(); drawEnemies(); drawStaticObjects(); } gameLoop(); }, 200);幀率控制要點60 FPS 對應約 16.67ms/幀30 FPS 約 33.33ms/幀。而 解決方案的循環 采用更專業的requestAnimationFrame(loop)驅動用dt Math.min(0.033, (ts - state.lastTs) / 1000)計算每幀時間差并鉗制在 33ms 以內避免切頁/失焦導致的跳幀同時每 0.5 秒統計一次實時 FPS 顯示在 HUD 上。6.3 敵人自動移動自動移動依賴 JavaScript 定時器每 100ms 讓敵人沿 y 軸下移 10 像素const id setInterval(() { enemy.y 10; // 沿 y 軸移動敵人 }, 100);第 3 課還要求用GameObject基類重構x/y、dead、type、width/height、img、draw(ctx)Hero與Enemy繼承之Enemy構造器內用setInterval自主向下移動到達畫布底部時clearInterval停止并打印日志。七、碰撞檢測與激光武器加入戰斗系統第 4 課 4-collision-detection/README.md 引入戰斗機制。核心思路是把每個游戲對象當作矩形處理——計算效率高適合絕大多數游戲場景。7.1 矩形邊界與相交算法每個對象用位置加尺寸推導出四條邊界rectFromGameObject() { return { top: this.y, left: this.x, bottom: this.y this.height, right: this.x this.width }; }相交檢測采用分離軸否定測試——只要四個「分離條件」有一個成立r2 完全在 r1 右側/左側/下方/上方就沒有碰撞function intersectRect(r1, r2) { return !(r2.left r1.right || r2.right r1.left || r2.top r1.bottom || r2.bottom r1.top); }解決方案中的碰撞檢測 提供了真實世界的印證代碼用圓形距離檢測dist2(e.x, e.y, b.x, b.y) (e.r b.r)2判斷子彈是否命中敵艦命中后加分、生成爆炸粒子、移除敵艦敵艦與玩家碰撞則扣減護盾值護盾耗盡再扣生命。7.2 對象生命周期標記刪除而非立即刪除在循環中途刪除對象可能導致崩潰。正確做法是「標記為 dead幀間再過濾」enemy.dead true; // 標記刪除當前幀安全結束 gameObjects gameObjects.filter(go !go.dead); // 渲染前過濾7.3 激光與開火冷卻課程用Cooldown類 Weapon類實現 500ms 開火冷卻防止無限連發class Cooldown { constructor(time) { this.cool false; setTimeout(() { this.cool true; }, time); } } class Weapon { constructor() { this.cooldown null; } fire() { if (!this.cooldown || this.cooldown.cool) { this.cooldown new Cooldown(500); // 創建激光 } else { // 武器仍在冷卻中 } } }Laser類繼承GameObject尺寸9×33用setInterval每 100ms 上移 15 像素觸頂后自毀。課程要求實現四條碰撞規則激光擊中敵人雙方銷毀、激光觸頂移除、敵人與英雄碰撞雙方銷毀、敵人到達底部游戲結束。解決方案中對應的開火實現 用fireCooldown 0.15秒的幀間冷卻并借助sfxShoot?.cloneNode(true)?.play()實現低延遲重疊音效。八、計分與生命游戲反饋系統第 5 課 5-keeping-score/README.md 聚焦讓游戲「真正可玩」的反饋機制。8.1 畫布文本渲染用fillText()在畫布上繪制分數左上角與剩余生命右下角用飛船圖標直觀展示ctx.font 30px Arial; ctx.fillStyle red; ctx.textAlign right; ctx.fillText(show this on the screen, 0, 0);8.2 計分與生命的實現游戲規則每擊毀一艘敵艦 100 分取整便于玩家心算英雄初始 3 條生命與敵人碰撞扣 1 條。核心方法掛在Hero上decrementLife() { this.life--; if (this.life 0) { this.dead true; // 生命耗盡標記死亡 } } incrementPoints() { this.points 100; }再通過事件系統串聯COLLISION_ENEMY_LASER時hero.incrementPoints()COLLISION_ENEMY_HERO時hero.decrementLife()。繪制函數drawLife()按剩余生命循環繪制生命圖標drawPoints()在左下角渲染文本分數二者在游戲循環中每次更新。解決方案的 HUD 實現 走得更遠用語義化output元素與aria-livepolite實時展示分數、等級、生命、護盾、FPS 與時長updateHUD 函數 每幀同步這些 DOM 輸出其中護盾以百分比顯示、時間格式化為MM:SS。九、勝負判定與重新開始完整的游戲閉環第 6 課 6-end-condition/README.md 為游戲補上「終點」。常見的結束條件包括摧毀 N 艘敵艦、己方飛船被毀耗盡生命、收集 N 分、完成關卡目標。9.1 結束條件檢測function isHeroDead() { return hero.life 0; } function isEnemiesDead() { const enemies gameObjects.filter((go) go.type Enemy !go.dead); return enemies.length 0; }9.2 事件化判定與提示消息在碰撞事件處理器中先判定失敗再判定勝利避免「同時贏又輸」eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) { first.dead true; second.dead true; hero.incrementPoints(); if (isEnemiesDead()) { eventEmitter.emit(Messages.GAME_END_WIN); } }); eventEmitter.on(Messages.COLLISION_ENEMY_HERO, (_, { enemy }) { enemy.dead true; hero.decrementLife(); if (isHeroDead()) { eventEmitter.emit(Messages.GAME_END_LOSS); return; // 失敗優先于勝利 } if (isEnemiesDead()) { eventEmitter.emit(Messages.GAME_END_WIN); } });新增消息常量GAME_END_LOSS/GAME_END_WIN并在keydown中監聽 Enter 鍵發射KEY_EVENT_ENTER觸發重開。勝利/失敗用顏色編碼提示綠色表示勝利、紅色表示失敗function displayMessage(message, color red) { ctx.font 30px Arial; ctx.fillStyle color; ctx.textAlign center; ctx.fillText(message, canvas.width / 2, canvas.height / 2); } function endGame(win) { clearInterval(gameLoopId); setTimeout(() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle black; ctx.fillRect(0, 0, canvas.width, canvas.height); if (win) { displayMessage(Victory!!! Pew Pew... - Press [Enter] to start a new game Captain Pew Pew, green); } else { displayMessage(You died !!! Press [Enter] to start a new game Captain Pew Pew); } }, 200); }9.3 重置系統與內存清理resetGame()必須完整清理舊狀態清除游戲循環、清空事件監聽器為EventEmitter添加clear()方法重置this.listeners {}、調用initGame()重建對象、再啟動新循環。否則會產生內存泄漏與重復事件處理器導致行為不可預測。解決方案中的重置實現 更為徹底整體重建 state 對象分數、等級、生命、護盾、計時、冷卻全部歸零清空子彈/敵人/粒子數組重置玩家坐標并同步刷新 HUD。十、運行與驗證本地起服務看效果每個課程子目錄都準備了your-work練習腳手架與solution參考答案兩套工程包含assets/圖片資源、index.html、app.js與package.json內置輕量開發服務器。啟動方式統一為cd your-work npm start上述命令會在http://localhost:5000啟動本地 HTTP 服務器并提供文件監聽熱更新。以第 2 課為例your-work結構為your-work/ ├── assets/ │ ├── enemyShip.png │ └── player.png ├── index.html ├── app.js └── package.json第 2 課的動手任務可對照 參考答案 app.js① 繪制1024×768黑色背景② 用loadAsset()加載玩家與敵艦貼圖③ 把英雄畫在底部居中canvas.width / 2 - 45, canvas.height - canvas.height / 4④ 用嵌套循環排布 5×5 敵艦方陣。方陣布局常量如下const ENEMY_TOTAL 5; const ENEMY_SPACING 98; const FORMATION_WIDTH ENEMY_TOTAL * ENEMY_SPACING; const START_X (canvas.width - FORMATION_WIDTH) / 2; const STOP_X START_X FORMATION_WIDTH; for (let x START_X; x STOP_X; x ENEMY_SPACING) { for (let y 0; y 50 * 5; y 50) { ctx.drawImage(enemyImg, x, y); } }完整版游戲位于 6-space-game/solution其 app.js 匯集了全部進階特性可作為功能清單對照學習難度體系difficultyTableeasy/normal/hard/insane 四檔分別調節敵艦生成速率1.6→0.7 秒、移動速度60→160與最大敵艦數12→28畫質分級graphicsTablelow/medium/high/ultra 控制發光特效、模糊與星空圖層數多套操控方案WASD 與方向鍵二選一空格開火、Shift 釋放消耗 25 點護盾的「清屏炸彈」冷卻 6 秒、P 暫停、F 全屏等級進度每存活 30 秒自動升級1 Math.floor(state.timeSec / 30)本地排行榜游戲結束時把分數寫入localStorage鍵名spaceGameScores取前 10 名渲染到排行榜見 gameOver 與 renderScores無障礙與可訪問性index.html 采用語義化 HTML、aria-label、aria-live、鍵盤焦點樣式并支持dialog原生對話框做設置與暫停界面十一、素材來源與致謝本課程使用的全部游戲素材飛船、敵艦、激光、生命圖標、爆炸特效等來自 Kenney.nl第 4 課的挑戰題正是利用這些素材為「激光擊中外星人」添加爆炸特效。結語這套太空游戲課程的價值在于它不是孤立地講某個 API而是把**對象組織繼承/組合、事件驅動Pub/Sub、異步加載Async/Await、輸入處理鍵盤事件、實時渲染Canvas 游戲循環、物理判定碰撞檢測、反饋設計計分/生命、狀態管理結束/重開**這些軟件工程通用概念編織進一個從零到完整可玩游戲的真實項目中。完成六課學習并對照 solution 源碼你收獲的不僅是「做出一個射擊游戲」更是一套可遷移到任意 Web 應用的可擴展架構能力。【免費下載鏈接】Web-Dev-For-Beginners24 Lessons, 12 Weeks, Get Started as a Web Developer項目地址: https://gitcode.com/GitHub_Trending/we/Web-Dev-For-Beginners創作聲明:本文部分內容由AI輔助生成(AIGC),僅供參考