Note

目前還算是比較粗糙的想法展示。到現在大概寫了 4 次左右,近期的新實作有些心得了,還沒理解到可以完美表達的程度,先把我的想法寫下來。

為什麼不使用原生的 Rigidbody?

Rigidbody 的功能,是用來模擬物體在真實世界的移動、摩擦與碰撞反應。實際上是轉接了 PhysX 與 Box2D 這種傳統物理引擎。但遊戲並不是要模擬真實世界,特別是平台類型有各種完全不符合內建物理系統設計的需求,例如:斜坡移動、碰撞忽略、碰撞扭曲、邊緣跳躍……等等。另外因為內建物裡的時序和 API 的設計,必須依賴在引擎,也不符合通用需求。

有時候也還是可以硬著頭用內建物理系統來製作,但如果要講求遊戲邏輯正確性的時候,寫起來就需要幹一些像是在 hack 物理參數的事情,我覺得只是用不適合的工具在找自己麻煩。

不錯的入門參考資料

物理計算

Game Feel

基本前提

使用 Collider 與 Raycast

我們只是不想使用內建物理引擎的計算邏輯,但 Collider 和 Raycast 還是可以用,因為它們只是資料和探針。我們的作法只是把 Unity 當作物理空間的資料,然後轉給我們自己的物理計算(反正 Rigidbody 內部也是這樣做的),同時關卡設計師也可以保留對 Unity 引擎的使用認知。

可測試性

畢竟是數學計算,還有浮點數誤差問題,會出現太多沒注意到的盲點,寫測試比較容易追蹤。也因此需要特別把移動計算的部分拆出來變成可獨立測試的區塊。

浮點數誤差問題

數學計算上常常會碰到浮點數誤差問題,所以我們需要在方塊外圍設定一層安全區域,通常會用 skin 這個詞來代表這個外圍空間。

不適合套件化

每個遊戲所需要的功能不同,細節也不同,所以很難真的有絕對泛用的套件,所以必要的時候還是得自己寫適合的控制器。

整體架構概念

玩家輸入與狀態控制層

  • 玩家輸入指的是,呃……玩家輸入。
  • 狀態控制指的是,根據上一幀的角色狀態與當前玩家輸入,計算出這一幀角色狀態的邏輯。通常就是所謂的商業邏輯。例如:速度與加速度、碰撞塊尺寸、其它參數。

Note

玩家輸入和狀態控制,在比較簡單的情況可以寫在同一個腳本。但碰到這些狀況就會想拆開:

  • AI 輸入:玩家輸入用的是 InputSystem,AI 輸入用的是程式邏輯。然後你一定會希望這些輸入可以共用同一個狀態控制邏輯。
  • 控制幀率:特別是網路遊戲,由於輸入幀率會與邏輯幀率不一致,會需要特別緩存輸入。

速度說穿了只是一個參數,數值怎麼變化是由商業邏輯決定的,不是物理計算器。物理計算器只負責由使用者給定的位置、速度與其它參數,透過 raycast 探測物理空間,來計算位移向量。

例如:

  • 單純的角色奔跑,只是加減速的做法差異。
  • 在冰面地板滑冰,只是加速度和轉向做法差異。
  • 跳躍的上升和下降的重力,可能也有做法差異。
  • 二段跳甚至根本跟物理系統沒有關係。
  • 爬牆或滑牆,只是重力參數不同。

但這些通常都不會改變內部物理碰到障礙物要位移時的計算邏輯。

世界探針層

這個層級負責處理實際的 raycast。

把 raycast 和物理計算拆開的好處是:

  • 解耦與可測試性:物理計算可以與 Physics/Physics2D 解耦,也可以在測試碼偽造物理世界,方便檢查數學計算結果。
  • 空間修改性:物理計算實際上並不知道世界長什麼樣子,只負責根據 raycast 的結果來計算,這樣角色、空間或重力的旋轉,就可以與物理計算脫鉤。世界探針也可以自己決定要忽略那些資訊。例如:剛從單向平台 (one-way platform) 落下時,世界就不應該偵測到那個碰撞塊。

物理計算層

核心程式碼。根據位置、位移與其它計算參數,透過封裝的世界探針,獲取世界資訊,來計算實際的位移向量。

修改商業邏輯時,物理計算其實很少出現改動需求,通常都是去改動狀態控制邏輯,然後其次是世界探針。所以把比較複雜的物理計算邏輯單獨拆出來,其實也符合單一職責原則 (SRP)。

下一篇

Note

TODO