互動入門・第一次上手網頁遊戲
每課三段:🎮 玩玩看(真的在動)→ 📝 它是怎麼做的(20 行程式碼白話拆解)→ 🔗 在陀螺專案裡(同一個概念的真實版)。順著看完約一小時。
學會:遊戲迴圈(Game Loop)——所有遊戲的共同骨架,只有三句話。
影片是一格一格的,遊戲也是。差別是:影片的每一格是拍好的,遊戲的每一格是「當場算出來的」。負責這件事的程式叫「遊戲迴圈」,全世界的遊戲(含 Unity 的 Update)都是這個結構:
// 這就是全部了——遊戲的心臟只有三件事
function 每一格() {
更新world(); // 1. 算:球該移到哪(位置 += 速度)
清空畫面(); // 2. 擦:把上一格擦掉
畫出world(); // 3. 畫:在新位置重畫一次
requestAnimationFrame(每一格); // 請瀏覽器下一格再叫我(每秒約 60 次)
}
GameEngine.ts:「更新」是 update()(4000 行——所有玩法:移動/碰撞/技能/計分),「畫」是 _renderFrame()。你在玩的每一秒,這個循環跑了 60 次。學會:輸入處理——鍵盤按下去,只是改了一個變數。
操作的原理出乎意料地簡單:按鍵不直接移動角色。按鍵只是把「右鍵有沒有被按著」這個變數設成 true;真正移動發生在遊戲迴圈的「更新」步驟裡:
// 按下/放開 → 只記錄狀態
按下('右') → 按著右 = true
放開('右') → 按著右 = false
// 每一格的「更新」步驟才真的動
if (按著右) 角色.x += 速度;
為什麼要繞這一手?因為按鍵事件的節奏(按下才觸發一次)和畫面節奏(每秒 60 格)不同步——「狀態變數」是兩者之間的橋。
App.tsx 監聽鍵盤 → 寫進按鍵狀態 → GameEngine.update() 每格讀狀態決定陀螺的推力。四個玩家各有一組按鍵配置。之後接機台按鈕,也只是把「按鈕訊號」寫進同一組狀態變數——遊戲邏輯完全不用改。學會:碰撞檢測+反彈——「手感」就藏在這幾行數學裡。
圓形碰撞只需要一句數學:「兩個圓心的距離 < 兩個半徑相加」= 撞到了。撞到之後怎麼反應,就是「手感」的全部祕密:
// 1. 有沒有撞到?(勾股定理)
距離 = √((甲.x - 乙.x)² + (甲.y - 乙.y)²)
if (距離 < 甲.半徑 + 乙.半徑) {
// 2. 撞到了:沿「兩球連線方向」互推(動量交換)
推力方向 = (乙 - 甲) / 距離
乙.速度 += 推力方向 × 撞擊力道
甲.速度 -= 推力方向 × 撞擊力道
// 3. 手感調味料:閃白 0.15 秒+凍結一拍+畫面震動
乙.閃白計時 = 0.15
}
physics.ts 是這段數學的完整版;CollisionSystem.ts(2100 行)是「手感調味料」的大全——同樣的碰撞,撞到木乃伊、撞到 Boss、超級狀態撞、都有不同的力道/演出/獎勵規則。我們修過的 hit-stop(凍結一拍)就是第 3 步的調味料之一。學會:繪製順序 = 圖層。理解了這個,就理解了我們的「兩張紙問題」。
在同一張畫布上,後畫的蓋住先畫的——就這麼簡單。所以 2D 遊戲的「圖層」其實只是「畫的先後順序」:背景最先畫、角色其次、UI 最後畫。
// 每一格的「畫」步驟,順序就是圖層
畫背景(); // 最底
畫地面特效(); // 想被角色踩住 → 比角色先畫
畫角色();
畫空中特效(); // 想蓋住角色 → 比角色後畫
畫UI(); // 最上
學會:粒子系統——所有華麗特效的基本款,資料+更新+畫,沒有魔法。
// 「爆一團火花」= 生 30 筆資料
for (30 次) {
粒子們.push({ x: 點擊處x, y: 點擊處y,
速度: 隨機方向 × 隨機力道,
壽命: 0.6秒 })
}
// 每一格:所有粒子照物理走一步、快死的變透明
for (每顆粒子) {
粒子.位置 += 粒子.速度
粒子.速度.y += 重力
粒子.壽命 -= 時間
畫小方塊(粒子.位置, 透明度 = 剩餘壽命比例)
}
衝擊波、斬擊、光環都是同一思路的變體:一筆資料描述特效狀態,每格更新,每格畫。差別只在「畫」的方式——這正是報告裡「特效四種做法」在講的事(CPU 畫/GPU 粒子/shader 公式/貼圖)。
GameEngine 的 particles / shockwaves / slashLines 陣列就是「資料」;EffectRenderer.ts 是 2D 的「畫」;我們正在做的特效 3D 化 = 「資料完全不動、只換『畫』的方式」。學會:3D = 多一個「高度」維度+一台「相機」。用假 3D 示範最容易懂。
2D 世界每個東西只有 (x, y);3D 多了「高度」。而要把 3D 世界變成螢幕上的 2D 圖,需要一台相機做投影。兩種投影方式:
| 正交投影 | 透視投影 | |
|---|---|---|
| 比喻 | 建築平面圖:不管遠近,一樣大 | 人眼/照片:近大遠小 |
| 俯視遊戲的效果 | 完全平面感(2D 時代的樣子) | 邊緣物體露出側面、有立體感 |
| 我們的專案 | 2D 版+3D 化初期 | 現在(輕透視,強度可調) |
CameraSystem.ts 就是那台相機。3D 化的難點也在這裡具象化了:怪物有了「身高」,所以會在透視下產生偏移;貼在平面上的特效沒有身高,所以會「對不上」——第一次上手最容易暈的坑,本質只是「有沒有高度」的差別。把前六課學的概念,對到真專案的每個部分——之後想改什麼,就知道去哪裡。
| 你學到的概念 | 課 | 真專案的位置 | 規模 |
|---|---|---|---|
| 遊戲迴圈(心臟) | 1 | GameEngine.ts 的 update + _renderFrame | ~4700 行(含全部玩法) |
| 輸入處理 | 2 | App.tsx 鍵盤層 → 按鍵狀態 | 4 玩家配置 |
| 碰撞數學 | 3 | physics.ts | ~500 行 |
| 手感規則(調味料) | 3 | CollisionSystem.ts | ~2100 行特例 |
| 2D 繪製與分層 | 4 | renderers/(Arena/Entity/Effect/UI 四支) | 共 ~8000 行 |
| 3D 渲染(新路) | 6 | render-pipeline/(場景/相機/怪物/道具/特效 adapter) | ~5000 行 |
| 特效資料 | 5 | GameEngine 的 particles/shockwaves 等陣列 | — |
| 2D↔3D 的翻譯官 | — | stateMapper.ts(遊戲狀態→渲染狀態) | ~700 行 |
| 怪物 AI 與生成 | — | systems/(Spawn/AI/技能) | — |
| 機率水位(業務核心) | — | ProbabilityManager | 票數/中獎控制 |
學習路徑建議:玩完這 7 課(1 小時)→ 回去讀報告站的「前言+總藍圖」(這時每個名詞都有畫面了)→ 想深入哪塊,對照上表打開那個檔案,或直接問 AI「帶我看懂 XX」。