因為一些幸運的機會,我最近有機會出現在德國最大的遊戲播客之一 Stay Forever 中,談論1999年《過山車大亨》的技術。這是一場很棒的訪談,我強烈建議如果你會說德語,可以完整聽聽這集內容。如果不懂德語也別擔心,本文將涵蓋訪談內容並補充更多細節。

《過山車大亨》及其續作常被譽為最佳優化遊戲之一,幾乎完全由創作者Chris Sawyer以組合語言編寫。這款遊戲能在1999年的硬體上模擬包含數千個角色的完整主題樂園,且運行流暢,令人印象深刻。即使是現今,許多類似的建造遊戲仍難以維持穩定幀率。

那麼,Chris Sawyer是如何達成這樣的成就?

這個問題有許多答案,有些是細節上的,有些則是廣泛且深遠的。大多數文章首先提到的是遊戲使用低階的組合語言編寫,這在當時讓他能寫出比使用C或C++等高階語言更高效的程式。

長期以來,組合語言一直是遊戲開發的標準,但到了當時已幾乎被放棄。即使是六年前發行的《毀滅戰士》(Doom)也主要用C語言編寫,只有少部分用組合語言,且沒有人會說《毀滅戰士》是個未優化的遊戲。

雖然難以確定,但《過山車大亨》很可能是最後一款以此方式開發的大型遊戲。當時的效能提升難以量化,但可以肯定的是,當時的效益比現在更明顯。現代編譯器在優化高階語言代碼方面已大幅進步,許多過去必須手動優化的部分現在可由編譯器自動處理。

除了使用組合語言外,《過山車大亨》的程式碼也進行了積極的優化。雖然原始碼從未公開,但我們有一個幾乎同等的替代品:OpenRCT2,一個100%相容的重製版本。

OpenRCT2由熱情的粉絲開發,重現了《過山車大亨》1與2的全部功能,並使用原始資產。雖然這不是原始碼,尤其早期版本,但經過多年逆向工程,兩者極為接近。值得注意的是,OpenRCT2持續加入許多超越原作的改進,本文會提及部分變化。

我不會列出所有優化細節,但會挑選幾個例子,說明遊戲的每個部分都被優化到極致。

例如,遊戲中如何儲存金錢數值?大多數人會先考慮遊戲中可能出現的最大金額,再選擇適合的資料型態。Chris Sawyer也採取類似方法,但更細緻。

程式中不同的金錢變數使用不同的資料型態,根據該數值可能達到的最大範圍。例如,存放整個樂園價值的變數使用4字節,因為數字可能很大;而商店商品的可調價格則只用1字節,因為數值範圍較小。值得一提的是,OpenRCT2已取消這項優化,將所有相關變數改為8字節,因為在現代CPU上這不再影響效能。

在閱讀OpenRCT2原始碼時,會看到現代程式碼少見的語法,例如:

透過運算子重載,C++中的「<<」運算子有多種意義。這行程式碼實際上等同於大多數程式設計師會寫的:

「<<」在此代表位元左移,將變數的二進位值向左移動兩位,右側補零。由於數字以二進位存儲,每向左移一位,數字即乘以2。

這聽起來像是技術細節,但在十進位乘法中,我們也做類似的事。當你乘以10時,實際上是將數字後面加個0,原理相同,只是數字系統不同。

同理,位元右移可用於除法,節省計算資源。

《過山車大亨》經常使用這種技巧,OpenRCT2也保留了這種寫法,因為編譯器不一定會自動優化。

(註:有人質疑這種優化是否多餘,因為現代編譯器通常會自動處理。但實際上,這取決於編譯器設定。MSVC若關閉優化就不會自動處理,稍舊版本的clang也可能不會。因此OpenRCT2仍手動使用位移運算。)

更有趣的是,這種計算頻繁出現。位移只能用於乘除2的冪次方(2、4、8、16等),頻繁使用表示遊戲內的計算公式特意設計成使用這些數字。這在現代開發流程中幾乎不可能,因為遊戲設計師不會為了CPU效能去調整公式。

幸運的是,《過山車大亨》的遊戲設計師與程式設計師是同一人Chris Sawyer,這也引出第三項重大優化:

雖然《過山車大亨》常被視為單人作品,但實際上並非純粹一人完成。遊戲及擴充包的所有圖像由Simon Foster製作,音效則由Allister Brimble負責。

不過稱它為Chris Sawyer的遊戲也沒錯,因為他是主要程式設計師與唯一遊戲設計師。

這種角色重疊帶來深遠優化,不僅依據遊戲體驗設計,也根據效能特性調整設計決策。

一個很好的例子是遊戲中的路徑尋找系統。若以遊戲設計文件角度設計,玩家角色會先決定想去的遊樂設施(根據個人喜好),再尋找路徑前往。

但從技術角度看,這是最壞的設計。路徑尋找計算昂貴,若同時為數千個角色執行,對硬體是巨大負擔。

因此《過山車大亨》的遊客行為根本不同。遊客不會先選擇遊樂設施再找路徑,而是在園區內盲目漫遊,等待偶然遇見感興趣的設施。他們沿著路徑走,完全不考慮遊樂設施或需求。到達路口時,隨機選擇新方向,僅用少量規則避免死路等。

這種「缺陷」在遊戲中很容易觀察。跟隨一名遊客走一會兒,你會發現他們沒有目的地,即使抱怨餓或渴,也不會主動找食物攤,只是繼續走,直到偶然經過食物攤。

這不代表《過山車大亨》完全沒有路徑尋找。某些情況仍使用傳統路徑尋找,例如維修工前往故障設施,或遊客想離開樂園。

但即使是這些情況,遊戲也設有安全機制避免幀數大幅波動。最重要的是,路徑尋找器對單次搜尋的路徑長度有限制。若在達到限制前找不到路徑,搜尋會被取消並回傳失敗。玩家可透過遊客思緒看到這些失敗訊息。

每當遊客抱怨找不到出口時,實際上是路徑尋找器告訴遊戲,可能有路徑,但為了效能不繼續搜尋。

這點對我特別有趣,因為它將技術上的必要優化轉化為遊戲特色。這在現代遊戲開發中幾乎不可能,因為程式設計師與遊戲設計師角色分離。路徑尋找限制還連結到其他遊戲系統。例如,預設路徑尋找深度限制為5個路口,但維修工因重要性較高,限制放寬到8個路口。

購買樂園地圖的遊客,路徑尋找限制也會從5增加到7,讓他們更容易找到出口。

改變遊戲設計以提升效能看似激進,但若執行得當,帶來的效益遠超過微觀優化。

另一個例子是《過山車大亨》如何處理擁擠的樂園。擁擠的路徑在主題樂園中常見,遊戲必須考慮這點。但若實作角色碰撞或避讓系統,將嚴重拖累幀率。

解決方案是直接跳過技術挑戰。遊客不會互相碰撞,也不會避開彼此。實際上,即使數千人同時站在同一格路徑上也沒問題。

不過這不代表玩家不需考慮擁擠。遊客會記錄附近人數,若周圍人太多,會影響心情並向玩家抱怨。玩家仍需規劃避免過度擁擠,但計算量大幅減少。

《過山車大亨》或許是這種優化方法的「完美風暴」,但這不代表現代無法做到。只需加強程式設計師與遊戲設計師間的溝通,並勇於對技術挑戰說「不」,不論多想解決它們。

這是複雜軟體工程中非常智慧的見解。