2012年12月13日 星期四

StarCraft 的開發歷史 [從PPT轉載, zabiak翻譯] (Tough times on the road to Starcraft)



首先感謝LCamel分享這篇有趣的文章,我嘗試翻譯了這包含星海爭霸的由來、開發 過程、軟體工程及程式開發等面向的故事,與大家分享,其中的翻譯錯誤,也請大家不吝 指正,謝謝 原文連結:


那段邁向"星海爭霸之道"的艱苦時光

我已經寫了一些關於魔獸爭霸早期開發的相關文章,但是最近我讀到一篇部落格文章,促 使我振筆疾書,結果就是這分成三部分、二十多頁關於開發星海爭霸的文章,並包含一些 我對於撰寫可靠遊戲程式碼的想法。我將在接下來的幾天發表其餘的部分 

此篇文章:為什麼星海爭霸在開發階段頻繁的死當 
第二部分:我們如何修正時常造成死當的原因 
第三部分:詳述關於修正的實作細節




星海爭霸之始

在星海爭霸推出前的開發階段(包含兩年半長時間的艱苦工作以及超過一年的收尾),遊戲 中的臭蟲多到像是白蟻巢穴一樣。相比於運作穩定度剩過其他同業的前作(魔獸爭霸及魔獸 爭霸二),星海爭霸實在太常死當,甚至直到釋出前的試玩測試都難以進行,在推出後更需 要持續不斷的補丁修正。 為什麼?有太太太太太多原因了。

太空獸族 

星海爭霸的初始預想是相當簡單的,符合一年的開發週期,並可在1996年的聖誕檔期推出 。 專案負責人是由"破碎的國度"團隊成員組成,破碎的國度是暴雪在1995年5月發表的回合制戰略遊戲,類似X-COM,此專案在幾個月之後被腰斬。 團隊成員則進行重組,預計可在短時間內作出些什麼並推出,使得暴雪不會在各遊戲的上 市日期之間有太大的空窗期。 
  • 1994年第4季 - 魔獸爭霸 
  • 1995年第4季 - 魔獸爭霸2 
  • 1996年第4季 - 預計的星海爭霸出貨日期 
  • 1998年第2季 - 實際的星海爭霸出貨日期 
現在回想起來,加快遊戲的開發腳步似乎荒唐可笑,但是公司總裁Allen adham承受著增 加公司營收的壓力,尤其是當暴雪早先推出的遊戲出乎預期的成功,更加深了對於公司未 來成長的期待。 在緊湊的時間表以及有限的人力下,星海爭霸團隊的目標是實作出一個簡單的遊戲 - 一 個稱之為"太空獸族"的遊戲。一張於1996年第2季左右的E3展覽會發布圖片可以看出遊戲 團隊最初決定的走向。 

1996六月首次在E3大展上出現的星海爭霸
沒錯!我才不會想要玩這種東西咧

但是一個更高優先權的專案凌駕於星海爭霸之上,並把開發成員一個接一個"偷"走了。暗 黑破壞神,一個由位於加州Redwood市Condor Studio所開發的角色扮演遊戲,正處於需要 額外幫助的狀態。Condor,一家由Dave BrevikMax Shaefer以及Erich Schaefer兄弟創 立的公司,僅編列120萬美元的預算 - 即使在那時候,也可以說是少得可憐。 Condor團隊並沒有辦法作出他們想要作出的遊戲,但是他們仍作出一些具開創性且有趣的 東西,促使暴雪收購它,更名為Blizzard North,並開始投入開發此遊戲實際上需要的資 金及人力。 最初,我跟Collin Murray(星海爭霸的程式設計師)飛到Redwood市去幫忙,後來,包括 加州Irvine暴雪總部的battle.net網路服務提供人員,數據機及區域網路遊戲人員,以及 圖型使用者介面(在暴雪稱之為"glue-screen")人員(包括創造角色,加入遊戲以及其他 meta-game的功能),都投入到這個遊戲的開發行列。 暗黑破壞神的開發規模越來越大,最後暴雪總部的每一個人 - 美術人員、程式設計師、 設計師、音效工程師、測試人員,都陸續投入暗黑破壞神的開發工作,最後,沒有任何人 在進行星海爭霸專案的開發工作。甚至連專案負責人都被指派來完成我已經寫到一半,但 忙到無法完成的遊戲安裝程式。 在暗黑破壞神於1996年末推出後,星海爭霸的開發工作才重啟,每一個人得到機會檢視遊 戲的走向,結論是相當糟糕。這遊戲已經過時,甚至無法留下深刻印象,尤其是與像是 Dominion Storm等專案進行比較,其於六個月前的E3進行展示,看起來相當棒。 因為暗黑破壞神的巨大成功,暴雪重新訂定他們所追求的目標:暴雪的策略是,星海爭霸 不應該在沒有準備好的情況下發行。為了達到這個目標,我們可是吃足了苦頭。


需要證明的東西 

在每一個人嚴苛地檢視下,很明顯星海爭霸專案需要有更大的雄心壯志,即超過我們之前 兩款魔獸爭霸遊戲(其定義了即時戰略遊戲未來)的開創性成就。 在星海爭霸專案重啟的當下,根據Computer Gaming World(當時發行量最大的遊戲雜誌) 總編Johnny Wilson所言,當時有超過80款即時戰略遊戲正在進行開發,有這麼多的競爭 者,包括Westwood Studios(首創即時戰略遊戲類型的公司),我們需要作出些什麼對對手 迎頭痛擊。 我們已非昔日的無名小卒;我們確信我們並未因報章雜誌充斥著魔獸爭霸及暗黑破壞神的 成功,而怠慢了玩家及遊戲媒體。在遊戲產業,你的評價只來自於你上一個推出的遊戲, 我們需要遠遠超過之前所作,而這代表我們需要冒險。


新面孔 

魔獸爭霸2只有6個核心程式設計師以及2個支援程式設計師;對於星海爭霸較大的規模來 說,實在是太少了,所以開發團隊增加了新的且未經試用的遊戲程式設計師,這些程式設 計師需要在得不到太多指導的情況下,學習如何寫出遊戲程式碼。 我們的程式設計領導環節相當薄弱:在專案早期,我們還不知道提供缺乏經驗的開發者開 發指南是必需的,導致他們在遊戲推出前的這段時間,學到大量的教訓,就像是對於新的 帕達瓦接受溺水或學會游泳的課題一樣。很大一部分的問題出在時程緊迫 - 導致每一個 程式設計師瘋狂地撰寫程式碼好達到目標,根本沒時間進行檢視、程式碼稽核或是教育訓 練。 不只是團隊中具有沒有經驗的新成員,星海爭霸專案的負責人也沒有商用遊戲引擎架構設 計的經驗。Bob Fitch有多年遊戲程式設計經驗,並伴隨著成功的結果,但是他之前的經 驗為遊戲移植,使用已存在的遊戲引擎,進行魔獸爭霸及魔獸爭霸2的功能程式撰寫,這些 都不需要大型的引擎設計經驗。當他作為破碎的國度主程式設計師時,這個專案後來被腰 斬了,因此並沒有機會驗證他對於架構的決策是否正確。 團隊為這個專案投入令人難以置信,甚至為了完成專案犧牲了個人健康及家庭生活。我從 來沒經歷過一個專案,其中的成員如此的鞠躬盡瘁。但是專案中的幾個主要的程式設計決 策,卻在專案的餘下部分,困擾著程式設計團隊。

那些改變的事情 

在為了暗黑破壞神上市工作了幾個月,以及之後幾個月的收尾及後來的補丁,我重返崗位 回到星海爭霸重啟的計畫。我沒有預料到我會掉入另一個臭蟲宴會,但它就是發生了。 我以為回到專案工作是容易的,因為我對於魔獸爭霸的程式碼的理解相當透徹 - 事實上 ,我可以對每一個元件進行開發工作。取而代之的是,我嚇了一大跳,因為許多遊戲引擎 的元件已經被棄之如屣或經過部分改寫。 遊戲的單位類別被重新改寫,而單位調度機制則已被丟棄。調度機制是由我創造,使得每 一個遊戲單位取得時間並計畫它想要作什麼。每一單位週期性地詢問:"我已經完成我現在 的行為,現在該作些什麼?","我需要重新評估路徑以到達我要去的地方嗎?","有比目 前我瞄準的單位更好的攻擊目標嗎?","玩家有給我新的命令嗎?""我已經死了,我該怎 麼把自己清除呢?" 等等 有合理的理由說明程式碼需要被改寫,但是除去舊的程式碼同樣具有風險。Joel Spolsky 在"你絕對不應該作的事之一"文章中大聲疾呼: 
"你一定要記住,想要從頭開始時,請不要抱著這次會做得比第一次好的想法。首先,你 的程式團隊根本不可能和當初相同,所以並不會真的有"更多的經驗"。其實只會把大部分 的舊錯誤重新再犯一次,並且再多增加一些舊版本所沒有的新問題。" (註) 註:摘錄自"約爾趣談軟體(Joel On Software) Joel Spolsky 著 梅普華 譯 悅之文化 出版
魔獸爭霸引擎花了幾個月的工作量才使得它正常運作,並需要額外的工作增加嶄新的遊戲 功能。現在,新的程式設計團隊需要先花好大一番功夫重新瞭解,遊戲引擎如何及為什麼 如此的建構。


遊戲引擎架構 

我使用微軟DOS平台、C語言及Watcom編譯器來撰寫原本的魔獸世界引擎。為了釋出到到微 軟視窗作業系統的轉變,Bob選擇使用Visual Studio編憶器,並使用C++重新架構遊戲引擎 。這兩者皆是合理的選擇,但在那個時候,事實上,團隊中只有少數的開發者具有使用此 種語言進行開發的經驗,更遑論此語言中蘊含的許多陷阱。 C++有其長處,但也容易被誤用。就像C++之父Bjarne Stroustrup的名言: "使用C語言,很容易就搬起石頭砸自己的腳;使用C++,變得沒這麼容易,但是一旦你這 麼作,你整條腿都要報銷" 歷史告訴我們,程式設計師在使用新的語言進行第一個專案開發時,覺得有必要嘗試新語 言的每一個特徵,這體現在星海爭霸的類別繼承。有經驗的程式設計師在看到為了遊戲單 位設計的繼承鍊時,會感到不寒而慄。

CUnit < CDoodad < CFlingy < CThingy

CThingy物件在遊戲地圖中無所不在,但並不移動或具有行為。CFlingy用在創造微粒,當 爆炸發生時,許多CFlingy以隨機方向分散開來。CDoodad - 在過了14年之後,我想這是 一個類別名稱 - 是一個未具現化類別。CUnit則是最高層級的類別。單位的行為被分散在 這些各種不同的模祖中,這需要對每一個類別都有深刻的瞭解,才能夠完成任何事情。 除了這恐怖的類別階層,CUnit類別本身簡直是亂七八糟,搞得天怒人怨,其定義包含了 多個標頭檔: 

class CUnit ... {
    #include "header_1.h"
    #include "header_2.h"
    #include "header_3.h"
    #include "header_4.h" 
}; 

其中的每一個標頭檔都有好幾百行,導致整個類別定義就像是整人大爆笑。 在多年之後,有一句在程式設計師間口耳相傳的口訣如是說"優先使用組合而非繼承",但 對於在暴雪工作的程式設計師來說,早就已經體驗過"震撼教育"的洗禮了。

離推出只有兩個月 

其多災多難的早期歷史,以及自計畫重啟後開發團隊就面臨早日完工的壓力,所以時程規 劃也是急就章,要在兩個月內讓遊戲上市。 增加大量的遊戲單位及其行為,需要從鳥瞰圖變成立體投影圖,全新的地圖編輯器,可以 透過battle.net進行網路對戰,在在都凸顯準時上市是不可能的任務,這還是在沒有把跟 美術團隊、設計師、音效工程師、遊戲平衡制定人員以及測試人員的"吵架大會"列入考慮 的情況下。但是程式設計團隊仍持續不斷地工作,想要在短短的兩個月內推出這款遊戲, 但最終我們花了14個月才達到這個目標。 整個團隊工時過長,Bob不間斷地撰寫程式整整40小時、40小時,甚至48小時。在我的回 憶裡,沒有其他人像他如此地燃燒小宇宙,但是每一個人的工時都長得離譜。 在我開發魔獸爭霸以及暗黑破壞神的經驗裡(頻繁的通宵達旦地撰寫程式,每天工作超過 14小時,每週工作7天),讓我知道徹夜工作是毫無意義的。所有在深夜完成的"鬼斧神工" ,在接下來的日子裡都會現出原形,而需要砍掉重練。 長時間的工作會使人們腦筋不清楚,這對於需仰賴大量創造力的腦力密集型工作來說,簡 直是糟糕透頂,所以產生大量錯誤,功能缺失,以及顯而易見的臭蟲,也就不令人意外了 。 順帶一提,這種瘋狂的工時是不必要的,之所以這麼作的原因,是因為我們想要作出好遊 戲。回顧過去,我們當時真是犯傻,只有在合理的工時下,才可以完成品質更好的作品。 我一生最驕傲的事,就是激戰在推出遊戲及三款資料片這為期兩年的時間中,我沒有引領 開發團隊再次走上那充滿荊棘的道路。

造成星海爭霸死當的原兇 

我實作星海爭霸中一些重要的功能,包括視野範圍限制、視野、飛行單位的路徑互斥、語 音聊天、人工智能集節點,以及其他功能,但我最主要的工作其實是修正臭蟲。 等一下,在1988年實作語音聊天功能?沒錯,一切都在1997年12月大功告成。我使用第三 方開發的聲音-音素轉換壓縮工具,並撰寫了在網路上傳輸音素的程式碼,以及解 壓縮功能,接著在其他七位玩家的電腦播放。 但是我們辦公室的每一張支援全雙工(同時錄音並播放聲音)的音效卡,都需要更新驅動程 式才可以使用這項功能,只能說遺憾,這點子只得被束之高閣。因為提供技術支援的金錢 負擔將會相當巨大,超過我們賣出遊戲的營收所得。 我修正了相當多的臭蟲,一些當然是我造成的,但是大部分難以發現的臭蟲是由其他疲憊 不堪的程式設計師所撰寫的。幾個月前,Brian Fitzgerald(他是我合作過的程式設計師中 最好的兩位其中之一)對星海爭霸進行程式碼檢視後,給予我極高的讚譽,可以說是我這輩 子所得到最好的。我對整個程式碼經過許多變更及修正,臭蟲被一網打盡。至少我的努力 得到肯定,而事實也是如此! 由於橫亙在開發團隊面前的所有問題,你可能認為在龐大的程式碼中很難找出臭蟲,但是 根據我的經驗,星海爭霸最大的問題其實來自於使用了雙向鏈結串列。 鏈結串列在引擎中被廣泛地始用,用來記錄單位及其共享行為。魔獸爭霸2的單位數目有 800個,但星海爭霸最多有1600個,整整增加了一倍。這使得我們必需對尋找特定類型的單 位這件事進行優化,方法是將他們鏈結在一起存放於串列中。 回顧遙遠的記憶,有很多用來儲存玩家的單位及建築的串列,很多用來儲存每個玩家"發 電"建築的串列,一個儲存每一架航空母艦無人戰鬥機的串列,以及其他很多很多其他的串 列。 所有的串列都是雙向鏈結的,使得我們可以在常數時間(O(1))裡新增及移除節點,而不需 要搜尋整個串列找到欲移除的節點(O(N))。 不幸的是,每個串列都是"手工維護"的 - 沒有共用的函式去鏈結及取消鏈結這些串列,程 式設計師全憑手動在需要鏈結及取消鏈結的地方加入行內函式。手工程式碼遠比使用已經 過除錯的常式來的容易出錯。 有些鏈結欄位是由多個串列共享,所以必須要確切知道這個物件被哪些串列鏈結,好安全 地移除鏈結。一些鏈結欄位使用C語言的union型別儲存,可以最小化記憶體的使用。 總的來說,這個遊戲總是死當,履試不爽。

但是為什麼你們要這麼作呢?

一切都是悲劇阿!其實這些鏈結串列問題根本就不該存在。我跟Mike O’BrienJeffStrain(後來共同成立ArenaNet公司)撰寫了一個叫作Storm.DLL的函式庫,與暗黑破壞神一 起出貨。在Storm眾多的功能中,有一個卓越的雙向鏈結串列的實作,是我們使用C++中的 範本功能撰寫出來的。 在星海爭霸最初的開發階段,我們使用這個函式庫。但是在早期開發階段,開發團隊將這 程式碼刪去,並手工處理鏈結串列,為的是讓遊戲存檔時的寫檔比較容易。 讓我們接著談談遊戲存檔好讓一切明朗。

儲存遊戲 

在我開發魔獸爭霸之前,我玩過許多遊戲,他們的遊戲存檔功能都很糟糕。玩家玩任何由 Origin出品的遊戲,都會記得他們需要花費多久的時間存檔。我的意思是,他們使用速度 慢的微處理器對硬碟進行寫入的動作,以現在的標準來看,就像是拿三輪車與賽車作比較 。但是,仍然沒有理由會爛成這樣,所以我暗自決定,這些問題絕不會出現在魔獸爭霸裡 。 魔獸爭霸使用了一些小技巧,讓他可以一次將一大塊記憶體區塊寫入硬碟,而不是緩慢地 透過記憶體一個bit一個bit的進行寫入。整個單位陣列(600個單位乘上幾百個byte),可以 一次寫入硬碟。所有非指標的全域變數,像是每一個遊戲地形及視野範圍限制地圖,可以 用類似的方法一次寫入。 奇怪的是,這個將單位一次寫入硬碟的能力,並不是提升遊戲存檔寫檔速度的決定性因素 ,但仍顯著地簡化了程式碼。而魔獸爭霸單位並不包含指標資料,透過一次寫入,效果則 相當明顯。 如前所述,星海爭霸單位儲存於具有指標的鏈結串列欄位中,完全是不同的挑戰。必須要 修復所有的鏈結指標(使用特別手法處理union類型指標欄位),使得所有1600單位可以一次 寫入。接著反修復這些鏈結指標,使得可以繼續進行遊戲。啐!

給我改回來! 

在修正了相當多的鏈結串列臭蟲後,我激烈地爭論我們應該回頭使用Storm的鏈結串列, 縱使這會讓遊戲儲存的程式碼更加複雜。當我說"激烈地爭論",我應該或多或少提一下, 在暴雪裡進行爭論的唯一已知方法,就是憑藉著年輕人的自以為是以及傲慢自大。唯一不 激烈的爭論是,討論當天午餐該吃些什麼,因為沒人想作決定。 我並沒有贏得這場爭論。因為我們距離出貨只剩"兩個月",對引擎進行變更比較好的方法 是,經常對現有但並不完美的解決方案貼OK蹦,這導致多個月的水深火熱,並造就我使用 更好的方法來撰寫程式,我將在本文的第二部分進行討論。


更多的OK蹦:星海爭霸的路徑搜尋 

我想再舉一個例子關於寧可使用補丁修正臭蟲,也不要嘗試去修正底下的問題。當星海爭 霸從鳥瞰圖喘變成立體投影圖,背景的砌塊式圖像繪圖引擎可追溯至我在1993/1994年撰 寫的程式碼,分毫未改。 使用方型砌塊繪出立體投影的砌塊並不困難,但要讓地圖編輯器正常運作仍有相當的難度 ,因為將一地圖砌塊放在另一個砌塊上面,需要大量的"邊緣修復",因為地圖編輯器嘗試 將對角線形狀的圖像繪於方型砌塊上。 繪圖沒那麼糟糕,在方型砌塊上的立體投影路徑搜尋非常困難。不使用分成可通過及不可 通過大的(32x32像素)對角線砌塊,地圖是以小的8x8像素組成 - 使得路徑搜尋以16倍的 數量級成長,而且造成較大的單位沒有辦法擠進狹窄的路徑。 要不是Brian Fitzgerald是一位一流的程式設計師,本來路徑搜尋的問題將使得這款遊戲 永遠無法上市,路徑問題直到專案收尾階段才解決。我計畫寫更多有關星海爭霸的路徑搜 尋的內容,因為在技術上或設計上都相當有趣。

第一部分的結尾

關於製作星海爭霸有多困難這件事,你已經聽我大吐苦水,很大一部分是因為公司的每一 個層級對於遊戲方向、科技以及設計,作了錯誤的選擇。 我們很幸運我們是一支鐵頭又強悍的團隊,且我們的洞察力讓我們贏得最後的勝利。在收 尾階段,我們繼續埋頭苦幹,並停止增加新功能,確定遊戲時間夠長,可以推出了。玩家 絕不會發現底下發生的恐怖事情,或許這是編譯式語言另一個勝過像是Javascript這種腳 本語言的地方 - 終端使用者絕不會看見火車失事! 在這篇文章的第二部分,我將會談論更多技術性的話題,並講述為什麼大部份的程式設計 師總是把鏈結串列搞砸了,接著提出成功使用於暗黑破壞神、battle.net以及激戰的另一 種解決方案。 即使你不使用鏈結串列,相同的解決方案甚至會使用更複雜的資料結構,像是雜湊表、 B樹以及優先佇列。我相信基本思想足以概括所有的程式設計面向。但讓我們慢慢來,那屬 於另一篇文章的內容。 謝謝你讀到這裡,很抱歉我還不知道如何寫得簡單扼要。


感謝zabiak同意轉載!

2 則留言 :