本文翻譯自 Patrick Wyatt 的博客。原文地址Tough times on the road to Starcraft - Code Of Honor,由Thinkraft翻譯並發表
作者簡介Wyatt, "Pat" Patrick,1991年加入矽與神經鍵(暴雪前身),是三位創始人以外第一個員工(本人說法是第二個員工)。自1994年起任暴雪研發副總裁,2000年從暴雪離職創辦ArenaNet公司,後推出『激戰』系列遊戲。
Pat作為元老級員工,參與了幾乎所有暴雪早期遊戲的開發——包括暴雪起家時的幾款SFC遊戲,也包括後來膾炙人口的三大系列:『魔獸爭霸』初代和二代(製作人+主程)、『星際爭霸』(程序)、『暗黑破壞神』初代和二代(程序),以及戰網。
(詳細請閱讀:About me - Code Of Honor)
關於博文和翻譯Pat原本的計劃是用三篇博文介紹如何在C++中使用侵入式鍊表來替代非侵入的std::list、提高程序的性能和可靠性。由於工作繁忙,實際只發表了兩篇就中止了。第二篇博文是專門面向程式設計師讀者的技術內容,這裡翻譯的是比較有故事的第一篇。
在中文網際網路上,此文已經有若干個版本的翻譯,我見過的至少有三版,包括各種機翻、不忍直視的錯誤。希望我這次的翻譯這樣的問題少一些(儘管我的C和C++比較水,英語也算不上大師),如果有譯錯之處,請在評論區指正,謝謝。
——@Thinkraft
—————以下是正文—————
星際爭霸的起點開發星際爭霸經過了兩年半的艱苦時光,光是發售前的壓榨期(*譯註1)就持續了一年多,當時這遊戲的bug之多簡直就像蟻巢(*譯註2)一般。儘管它的前作(魔獸爭霸1和2)跟市面上其它遊戲比起來穩定性出眾,星際爭霸卻是頻頻崩潰,乃至於測試工作直到發售都難以正常進行,發售後我們還得給它繼續打補丁。
為什麼?說來話長了。
太空獸人原計劃中的星際爭霸本來是個中型遊戲,開發周期只有一年,以便在1996年聖誕節發售。項目組原本來自『破碎國度』(Shattered Nations)——那是一個基於X-COM的回合制戰略遊戲,暴雪1995年5月公布了它的存在,但幾個月以後就給取消了。然後這個團隊就被重組來開發一個快餐作品,好讓公司的發售空白期不會太長。
Q4 1994——魔獸爭霸
Q4 1995——魔獸爭霸2
Q4 1996——星際爭霸的計劃發售時間
Q2 1998——星際爭霸的實際發售時間
這個趕工開發的決定,用現在的眼光看來確實有點奇葩,但是公司總裁Allen Adham當時正扛著業績壓力。暴雪之前的作品表現通通遠超預期,這更加拉高了老大們對未來增長的期望。
時間特別緊,人手又有限,星際爭霸團隊的目標是做一款中型遊戲——說白了就是「太空獸人」(*譯註3)。1996年第二季度,大概E3展那個時候,遊戲還是這個鬼樣子:
——1996年6月E3展(*譯註4)上公布的星際爭霸,呵呵,換我我也不玩。
但是,另一個高優先項目將星際爭霸踢到了角落裡,還奪走了一個又一個的開發人員。『暗黑破壞神』,坐落於加州Redwood市的禿鷹工作室開發的一款角色扮演遊戲,正需要援手。Dave Brevik和Max/Erich Schaefer兄弟成立的這家禿鷹公司只有區區120萬美元的預算,這點錢即使放在當年也少得可憐。(*譯註5)
要完成他們夢寐以求的遊戲,禿鷹團隊是有心無力,但他們起碼為一個好遊戲打下了創意基礎,足以令暴雪收購禿鷹,將其改名為北方暴雪,並投入真正配得上這遊戲的資金和人力。
剛開始,我和Collin Murray(星際爭霸的一個程式設計師)飛到Redwood去幫忙,而其他開發人員留在Irvine市的暴雪總部,搞戰網、數據機、區域網遊戲,還有屏幕界面(在暴雪內部俗稱「glue screens」),用於角色創建、加入遊戲等基本功能。
暗黑破壞神的項目規模越來越大,最終暴雪總部的所有人——美工、程序、設計、音效、測試——都進來了,直到星際爭霸那邊一個人也不剩。就連項目負責人也得親自上陣,把我寫了一半太忙沒時間完成的安裝程序寫完。
1996年底,暗黑破壞神發售,星際爭霸的開發被重新提上日程,大家這才有空重新審視一下這遊戲的前景,然後發現它一點也不美好。這遊戲一看就過時了,更是完全談不上震撼人心什麼的,尤其是和六個月前在E3展上demo效果出眾的『領土』這種項目相比。(*譯註6、7)
暗黑破壞神的巨大成功重塑了暴雪的奮鬥目標:星際爭霸成了第一個貫徹暴雪「不完成就不上市」戰略的遊戲,但是在證明這戰略有效性的道路上,卻是苦難重重。
證明實力所有人都用批判的眼光看待星際爭霸,很明顯,這個項目需要更大的雄心壯志,我們之前的兩款魔獸爭霸定義了即時戰略遊戲的發展方向,而這次還要更具突破性。
根據當時最大的遊戲雜誌『Computer Gaming World』的主編Johnny Wilson所言,在星際爭霸重新啟動開發時,業界同在開發中的即時戰略遊戲超過80款。身後追著這麼多競爭對手,甚至包括Westwood這個現代即時戰略遊戲的祖師爺,我們必須拿出一個屌炸天的作品。
而且,我們也不是以前的小作坊了。有了魔獸的成功,再加上暗黑,無論是玩家還是媒體都對我們充滿期待。遊戲業界的法則是逆水行舟不進則退(*譯註8)。我們必須遠遠超越之前的成就,而這需要冒一些風險。
新隊員魔獸爭霸2隻有六個核心程式設計師外加兩個輔助,對於星際爭霸這樣規模的遊戲,這幾個人太少了。所以開發團隊大大擴張,引入了一堆新的、未經測試的程式設計師,他們需要在沒有太多指導的情況下學習如何編寫遊戲代碼。(*譯註9)
我們的編程管理能力也不夠:當時我們並沒有意識到應該在項目早期為新手程式設計師提供指導、讓他們在遊戲發售前掌握必備知識,對新手來說他們基本就是被一腳踹進水裡自己要麼撲騰要麼嗆死的感覺。問題的一大原因是我們人手實在太少了,搞得所有程式設計師為了完成目標都在拼命寫代碼,卻沒時間做Review、Audit和培訓。(*譯註10)
而且不光是沒經驗的初級程式設計師,就連星際爭霸的主程,也從來沒有完成過一個成熟的遊戲引擎。Bob Fitch(*譯註11)倒是幹過幾年遊戲編程,成果也不錯,但是他之前幹的一個是跨平臺移植,引擎都是現成的;還一個是為魔獸爭霸1和2開發功能,這也不需要設計大規模的引擎。然後他倒是也當過一次技術主管——在做破碎國度的時候,項目幹一半就取消了,結果它的架構是否可行也是死無對證。
團隊對項目的投入之大前所未有,為了完成項目甚至犧牲了個人健康和家庭生活。我還沒見過哪個項目能讓每個人都如此拼命幹活。但是,項目中的一些關鍵的技術決策搞亂了之後的整個開發過程。
面目全非先是花了幾個月完成暗黑破壞神,發售後又用了幾個月清理和打補丁,然後我回來幫忙接著弄星際爭霸了。我並不願意扎進另外一個滿是bug的大坑,但事情還是這麼發生了。
當時我本以為重歸這個項目應該挺輕鬆的,因為我對魔獸爭霸的代碼了如指掌——畢竟每一塊程序我都有參與。結果我震驚地發現,引擎的很多模塊都被甩在一邊沒用,然後其中一些邏輯又在別的地方重寫了。
遊戲的「單位」類當時正處於從頭重寫的過程中,而且單位調度器被廢掉了。這個調度器是我做的機制,用來確保遊戲中每個單位都有時間來計劃自己的行動。每個單位都會周期性地問「我當前手上的事做完了,現在需要幹啥?」「我是否應該重新計算行進路線?」「我要不要換一個攻擊目標?」「玩家給我下達新指令了嗎?」「我死了,如何清理自己?」等之類的問題。
重寫代碼當然是有原因的,但是刪除舊代碼時風險也會隨之而來。Joel Spolsky(*譯註12)在Things You Should Never Do, Part I 中說過:
當你們打算從頭重寫的時候,最好記住,不要以為你們會比第一次幹得更好。首先,你們的團隊可能根本就不是原來版本1的團隊了,所以你們其實並沒有所謂的「經驗」。之前犯過的大多數錯誤,你們會通通原樣重來一遍,並且還會製造一些舊版本中沒有的新問題。魔獸爭霸引擎當初花了我們幾個月工夫才完成,而一個全新的程式設計師團隊需要為了新功能對它進行重寫時,他們要花大把的時間重新學習遊戲引擎的結構是怎麼回事、為什麼要設計成這樣。
引擎架構面向MSDOS寫魔獸爭霸的原版引擎時,我用的是C語言和Watcom編譯器。當遊戲平臺要切換到Windows時,Bob選用了Visual Studio編譯器,並用C++重寫了引擎。這兩套選擇都有道理,問題是當時團隊裡根本沒幾個人寫過C++,尤其是這語言坑還特別多。
C++有其優勢,但特別容易被用錯。Bjarne Stroustrup,發明這語言的人,有一句名言:「用C你容易不小心砸自己的腳;C++砸腳的機率低一點,但一但砸了,你整個下半身就廢了。」(*譯註13)
歷史證明,程式設計師在接觸一門新語言時,總會迫不及待地在第一個項目試遍所有功能,星際爭霸的類繼承關係就是這麼來的。看看遊戲單位的這個繼承鏈,老司機看了能氣得直哆嗦。
CUnit < CDoodad < CFlingy < CThingy
CThingy對象是精靈,可以在地圖的任何地方出現,但不能移動,也沒有行為,而CFlingy是用來創建粒子效果的,爆炸時會有幾個這玩意隨機亂濺。CDoodad——過了14年了我覺得好像是叫這個類名吧,這個類本身沒有被實例化過,卻包含了子類所需的一堆重要行為。然後就是CUnit,單位的行為分散在這麼多模塊的代碼中,不論你想幹什麼,都得先弄明白所有的類才行。
除了恐怖的繼承關係以外,CUnit這個類本身也是一坨翔,它的定義用了好幾個頭文件。
class CUnit ... { #include "header_1.h" #include "header_2.h" #include "header_3.h" #include "header_4.h"};
每個頭文件都有幾百行,整個類的代碼……往好聽了說是搞笑。
多年以後,「組合優於繼承」這個理念才成為程式設計師一族的信條,但是星際爭霸的開發團隊們早就通過血淚得到了這個教訓。
由於早期曾被中斷,項目重啟後開發團隊一直被催著交活,而且「兩個月內就能發售遊戲」的日程表已經被傳出去了。
考慮到需要增加那麼多遊戲單位和行為、把視角從鳥瞰改為等角投影(*譯註14)的工作量、全新的地圖編輯器,還有戰網對戰的新功能,遊戲根本不可能在這麼短時間內發售,就算是美工、設計、音效、平衡和測試全都按期完工也不可能。結果編程團隊在「目標是兩個月內完工」的狀態下連續工作了整整十四個月!
整個團隊都一直在加班,Bob本人則是40小時、42小時甚至48小時連續編程。雖然我印象裡拼到如此自虐程度的只有他,但是其它人的加班也很瘋狂。
我之前開發魔獸爭霸時經常整宿地寫代碼,後來做暗黑破壞神時也曾連續數周堅持14×7工作,這些經歷向我證明了熬夜沒有任何意義。過了晚上某個時間點之後寫出來提交的任何代碼,都只會讓你後悔並浪費清醒時間重寫。長時間連續幹活讓人頭昏眼花,在這種狀態下進行創新性的腦力勞動,出現再多的錯誤和明顯的bug也沒什麼可奇怪的。
順便說一下,這種瘋狂加班並不是公司要求的——我們拼命只是因為我們自己特別想做出好遊戲。現在看來當年真傻,欲速則不達,我們本來可以用更少的時間幹得更好的。
我最自豪的成就之一是,兩年內給『激戰』發布了四個篇章(*譯註15)而沒有把開發團隊拖入加班泥潭。
星際爭霸遊戲崩潰的常見原因為星際爭霸實現了一些重要功能——包括戰爭迷霧、視線檢測、飛行單位的尋路和碰撞(*譯註16)、語音聊天、AI強化……等等之後,我的主要工作就成了修bug。
啥?語音聊天?1998年?是啊,1997年12月我就搞定了。我用了一個第三方的語音壓縮算法將語音壓縮成音素,然後用代碼實現了通過網絡發送這些音素、在其它七個玩家的電腦上解壓還原並播放。
問題是我們辦公室裡所有的音效卡都需要升級驅動才能正常使用語音聊天,哪怕音效卡本身支持全雙工(錄製和播放同時進行),於是我很遺憾地建議放棄這個功能。不然我們的技術支持工作量就太大了,賣遊戲賺的錢還不夠僱客服接電話的。
總之我修了一大堆bug,其中當然有我自己造成的,但主要還是其它人在疲憊中寫出的那些詭異的bug。我得到的最好表揚之一,大概是在幾個月之前來的,Brian Fitzgerald(*譯註17),我共事過的程式設計師當中最優秀的兩位之一,提到了星際爭霸的一次code review,他們發現我對整個代碼庫修復了多少問題時驚呆了。呵呵至少還是有人承認我的功勞苦勞了,都這麼多年過去了!(*譯註18)
故事這麼多災多難,你可能會覺得很難評出一個罪魁禍首、萬bug之源,但是根據我的經驗,星際爭霸中的最嚴重的問題都和雙向鍊表的用法有關。
遊戲引擎中使用了大量的鍊表,用來追蹤擁有共同行為的單位(*譯註19)。兩倍於前作的單位數量——星際的人口上限是1600,相對於只有800人口的魔獸2,如何優化對特定單位類型的搜索、使它們互相關聯,就成了一個必需的工作。
回想當年,到處都是鍊表,每個玩家的單位和建築、每個玩家的「人口」建築、每個航母上帶的小蒼蠅……很多很多。
這些鍊表都是雙向的,這樣一來,添加和刪除元素的時間複雜度就是固定的O(1);而不用像單向鍊表那樣執行O(N)的遍歷操作。
問題在於,每個列表都是「手動維護」的——沒有公用的方法來對這些鍊表進行添加刪除元素操作,程式設計師們只好各自在需要用到的地方自己寫。比起簡單調用一個debug過的公用方法,這些手擀的代碼超級容易出錯。
有一些連結指針欄位被共享給多個鍊表,所以要想安全刪除,還得先弄清楚對象到底鏈在哪個表上。還有一些連結指針欄位,為了減少內存佔用,甚至用了C Union來存儲。
所以遊戲才會一直崩潰。一直,崩潰。
這都是何苦呢?悲劇的是,這些鍊表問題本來不應該存在。Mike O'Brien(*譯註20),跟我還有Jeff Strain(*譯註21)一起創建了ArenaNet的那個哥們,寫了一個叫做Storm.DLL的庫,暗黑破壞神就用了這個。Storm的功能眾多,其中就包含了一個用C++模版做的、非常棒的雙向鍊表實現。
在星際爭霸的開發中,一開始用到了這個庫。但是很快大家就把代碼刨了出來然後開始手工實現鍊表,主要是為了更簡單地寫入存檔文件。
嗯存檔,我這裡再多解釋一些,省得你看不明白。
保存遊戲在開發魔獸爭霸之前,我玩過的很多遊戲的存檔功能都很屎。玩過Origin(*譯註22)隨便哪款遊戲的人,應該還記得存個檔要花多長長長長時間。我的意思是,確實,當年的CPU很慢,硬碟性能也是,跟如今的標準之間的差距就如同瘸逼樂和超跑,但那也沒道理會慢成這個球樣子。於是我決定魔獸爭霸不能出這個問題。
所以在魔獸爭霸中,我用了一些竅門,讓它將大塊內存整個一次性寫入磁碟,而不是在內存中來回晃蕩、這來一點那來一點。整個單位數組(600個單位×每個單位幾百字節)是一次性寫進磁碟的。所有的「非基於指針」的全局變量也是一次性寫入,類似的還有地形、戰爭迷霧等。
說來奇怪,這種一次性寫入對存檔速度的影響沒那麼可觀,倒是大大簡化了代碼。只是,魔獸爭霸中的單位沒有「指針」類型的數據,不然這招根本不能用。
星際爭霸的單位,前面說過了,為了實現鍊表,裡面有一大堆的指針欄位,是完全不同的怪物。存檔的時候必須搞定所有的指針(嗯尤其要小心Union裡的那些),這樣1600個單位才能一次性持久化,然後繼續玩之前還得把這些指針還原。呸。
修了好多好多鍊表相關的bug之後,我強烈要求大家改回用Storm版的鍊表,哪怕這樣存檔的邏輯會更複雜一些。這裡說的「強烈要求」,基本上在暴雪我們想討論點什麼事都是這樣的——大家個個年輕氣盛、目空一切,一吵起來就會很激烈,除非話題是午飯吃什麼這種沒人願意做主的事。
結果我沒吵贏。畢竟我們離發售「只剩兩個月」,改底層引擎這種大動作沒有獲批,只能基於現成的低效方案修修補補,結果這造成了深遠長久的痛苦,痛苦到從此改變(改進)了我編程的方式,下一篇博文會細說這個。
修補:星際爭霸的尋路我還有一個治標不治本的例子:星際爭霸從鳥瞰視角改成等角投影視角時,後臺的圖塊繪製引擎還是用的舊的——我1993年還是94年寫的老代碼。
在方形圖塊引擎上繪製等角投影風格的圖像並不算難,但是這樣一來地圖編輯器之類的東西就比較難搞了。因為這些斜著的圖像其實是畫在方塊裡的,圖塊互相疊加的時候需要做很多「邊緣修正」工作。
渲染還不是最複雜的,尋路才叫一個難。顯示是斜著的、底層邏輯是正的,所以不能直接判斷整個32×32像素的斜塊能否通行,必須得拆成8×8像素的小塊——這一下就讓尋路算法的工作量乘了16倍,而且那種擠不過去的大型單位還得另行處理。
要不是有Brian Fitzgerald這位編程大牛,光是尋路問題就能讓這遊戲永無發售之日。說起來,尋路是直到項目的尾聲階段才最終解決的,其中有很多值得一提的技術和設計細節,所以我打算多寫一些關於星際爭霸中尋路計算的東西。(*譯註23)
結語以上嘮叨了這麼多,把星際爭霸這遊戲做出來真是不容易。因為無論是遊戲的方向、技術還是設計,公司上上下下每個層次充滿了各種決策錯誤。
幸運的是,我們不只是莽撞,還有英勇,而且充滿智慧。最後我們全神貫注、不再添加新功能,讓遊戲得以發售,並且玩家們也看不到底層的一團糟。這可能也是編譯語言相對JavaScript這種腳本語言的一個優點:代碼再爛用戶也看不見!(*譯註24)
在下一篇博文中,我會寫一些更技術的內容,談談為什麼很多程式設計師都沒有用對鍊表,並且提供一個已經在暗黑、戰網、激戰中實戰驗證過的替代方案。
還有,即使你不用鍊表,這個方案對於更複雜的數據結構也適用,如哈希表、B樹還有優先隊列。總之,我覺得這個基本思路對於一切編程都是通用的……還是別扯太遠,要不然又要另寫一篇東西了。
—————正文完—————
(此部分內容為譯者個人見解)
「Crunch time」,遊戲業界術語,形容發售前為了趕進度全體瘋狂加班。
「Buggy」在這裡是雙關語。蟲子多 = bug多。
原文「Orcs in space」,直譯「太空中的獸人」,可理解為「宇宙版魔獸爭霸」。
原文「June 1996」,實際上1996年的E3展是5月,細節勿糾結。
120萬美元出處不詳,個人猜測可能是項目重新評估後的最終預算。暴雪起初和禿鷹籤署的暗黑破壞神發行合同只有30萬美元,為了貼補家用維持公司生存,禿鷹當時還在給3DO公司的M2主機開發一款橄欖球遊戲『NFLPA Superstars』。雖然3DO的單子更大,價值50萬美元,但大部分開發資源還是給了親兒子暗黑,橄欖球只是出出demo交差。
後來禿鷹被暴雪收編,徹底不缺錢了,而3DO那邊連M2主機也黃了,一拍兩散,橄欖球遊戲自然也停了。
詳情見『Stay Awhile and Listen: How Two Blizzards Unleashed Diablo and Forged a Video-Game Empire - Book I』(我還沒有取得這本書的翻譯授權,無法公開翻譯,請自購原書)
遊戲原名『Dominion』,後改名『Dominion Storm』,最終發售名稱『Dominion: Storm Over Gift 3』,離子風暴公司的出道作,質量慘不忍睹。遊戲成本300萬美元,前四個月銷量只有不到3萬份。
詳情見『Masters of Doom: How Two Guys Created an Empire and Transformed Pop Culture』,中文版各大網站有售。
1996年E3展上的『領土』demo並不是實機遊戲,而是預先做好的動畫,負責演示demo的工作人員也只是裝模作樣、假裝在操作而已。諷刺的是,雖然用意不良,這個假demo反而刺激了暴雪、間接造就了一代經典。
詳情見Pat的另一篇博文,未來會譯成中文發在這裡。
原文「In the gaming world you're only ever as good as your last game」,意思大概是續作的口碑很難超越前作,除非品質有飛躍性的進步。這裡用成語意會。
原文「so the dev team grew to include a cadre of new and untested game programmers who needed to learn how to write game code without much mentoring」,語法上看有歧義:「他們需要獨立學習如何編程」或「他們需要學習如何獨立編程」,這裡按前一種意思理解。
雖然Review和Audit兩詞有若干種版本的中文翻譯,但實際軟體團隊中大多直接用英文表示這兩個工作。
Bob Fitch,星際爭霸中擔任主程,1992年加入暴雪,至今仍在暴雪工作(2016年現在:技術總監)。
Joel Spolsky,中文名周思博,Stackoverflow/StackExchange的創始人兼CEO。
原文「C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off.」
「Isometric projection」即等角投影,俗稱「斜45度」視角。這種視角下三條坐標軸比例尺相同、互成120度夾角。可以實現有限的立體感。
激戰是按「Campaign」發售的,大概可以理解為資料片。
原文「fog-of-war, line-of-sight, flying unit pathing-repulsion」。視線檢測除了普通的視野,還有平地看高地視線受阻等特性;飛行單位那個應該是指單位之間互相推擠、扎堆後會慢慢散開的特性。
Brian Fitzgerald,星際爭霸中擔任AI開發,1995年加入暴雪,至今仍在暴雪工作(2016年現在:技術總監)。
原文「At least I got some credit for the effort, if only well after the fact!」,這裡的翻譯很不確定意思。
我個人的理解是,例如多兵種組隊攻擊、蟲族單位組隊鑽地等,相同行為的單位需要一起處理。
Mike O'Brien,星際爭霸中程序。除了Storm.DLL以外,其重要發明還包括戰網,以及MPQ(Mike O'Brien Pack)壓縮格式。
2000年和Pat、Jeff一起離開暴雪,創立ArenaNet。
Jeff Strain,星際爭霸中擔任地圖編輯器(StarEdit)開發。可能有人還記得星際項目組有位猛士、老婆馬上就要生了還在寫代碼的故事,說的就是他。
「Are you working on that damn game during the birth of our daughter?」
「It's not 'that damn game' Annie, it's Starcraft!」(鄰居的博客)。
2000年和Pat、Mike一起離開暴雪,創立ArenaNet。
Origin Systems,遊戲公司,代表作『創世紀』(Ultima)系列。被業界毒瘤EA收購後解散,就像Westwood、Bullfrog……
Pat已經寫了這篇有關尋路的博文,詳情見The StarCraft path-finding hack - Code Of Honor。這篇也很有料,作者已經允許我進行翻譯,未來會譯成中文發在這裡。
原文「end users never see the train wreck!」,請結合前文理解。