← 開發日誌

M0–M1:引擎骨架、Rapier 物理與玩家手感

動作遊戲的成敗,八成在「手感」。而手感的地基,是引擎迴圈與玩家控制器。這一篇講怎麼把它們搭起來。

引擎迴圈:可變渲染、穩定物理

最核心的問題:高刷新率螢幕(120Hz、144Hz)不能讓物理跑得比 60Hz 螢幕快。解法是把渲染與物理解耦。

const loop = (now) => {
  requestAnimationFrame(loop);
  // 鉗制 dt:分頁切回或卡頓時,最多推進 1/30 秒,避免穿牆與邏輯跳躍
  const rawDt = Math.min((now - lastTime) / 1000, 1 / 30);
  const dt = rawDt * timeScale;   // timeScale 讓 hit-stop / 慢動作成為可能
  physics.step(dt);
  scene.tick(dt);
  renderer.render(scene, camera);
};

這裡有兩個關鍵設計:

  • dt 鉗制:切到別的分頁再回來,now - lastTime 會是好幾秒。不鉗制的話角色會瞬移穿牆。鉗到 1/30 秒,最壞情況就是慢一點,不會爆。
  • timeScale 全域時間倍率:把它乘進 dt,之後做命中時停(timeScale = 0.05)、升級暫停(timeScale = 0)就只是改一個數字。這個小決定後面省了非常多事。

Rogue Engine 風格的 Component 系統

遊戲邏輯不寫死在引擎裡,而是掛在 GameObject 上的 Component,生命週期是 awake → start → update(dt) → onDestroy。這借鏡 Unity / Rogue Engine 的設計,好處是每個行為(控制器、血量、AI)都是獨立可組合的小元件,日後要遷移到別的引擎也容易。

玩家控制器:用官方角色控制器,別自己造輪子

移動用 Rapier 的 KinematicCharacterController。它幫你處理了牆壁滑動、台階、斜坡——這些自己寫會痛不欲生。角色是 kinematic body(不被力推動,由程式控制位移),每幀把「想移動的向量」交給控制器,它回傳「修正後實際能移動的向量」。

移動方向與面向分離

這是動作遊戲的靈魂細節:你移動的方向,和你面對的方向,是兩回事。

  • 移動方向 = WASD
  • 面向 = 滑鼠位置

要拿到滑鼠在地面的位置,不用對整個場景做 raycast(貴又不穩),而是對一個 y = 0 的數學平面求交點:

raycaster.setFromCamera(mouseNDC, camera);
raycaster.ray.intersectPlane(groundPlane, aimPoint);

於是你可以一邊往左跑、一邊面向右邊的敵人揮砍——這就是 Hades 那種流暢感的來源。

翻滾與無敵幀

空白鍵朝移動方向衝刺約 0.3 秒,期間附帶 i-frames(無敵幀):受傷判定直接略過。這是 Roguelike 的核心生存技巧,也讓戰鬥從「站樁互砍」變成「進退走位」。

第三人稱鏡頭

鏡頭在玩家上方後傾(類 Hades 的俯角),用幀率無關的阻尼跟隨:

const t = 1 - Math.exp(-8 * dt);   // 不管幾 fps,跟隨速度一致
focus.lerp(playerPos, t);

再加一點「朝滑鼠方向的預視偏移」——鏡頭微微往你要打的方向挪,讓你看得到目標。這個偏移很小,但少了它就會覺得「視野很憋」。


到這裡,已經有一個能跑、能翻滾、鏡頭跟得很順的騎士站在地板上了。下一篇進入真正的核心:戰鬥的打擊感

← 回開發日誌立即遊玩 →

看更多開發日誌 →