天天看點

遊戲程式設計模式:前言(架構,性能和遊戲)(Part I)

        譯者的序:《Game Programming Patterns》是一本不錯的書,講了設計模式在遊戲程式設計領域中的靈活運用。由于寫得實在是太好了,遂觸發了本人翻譯的熱情。是以把本人的渣翻放在這裡做成系列部落格,希望大家多多提出寶貴意見呀!下面正式開始了!

        今天講的是引言部分。引言部分有很多對此書進行說明的部分,就略過了,直接來說說Architecture, Performance and Games這一章。下面是本人的翻譯。有一些本人的注解就放在括号裡了,大家很容易找到的。

-----------------------------------------------------分割線---------------------------------------------------------------

        在我們一頭紮進成堆的模式之前,我覺得先跟各位看官說說“我是怎麼看待軟體架構的以及它是如何運用于遊戲中的”會比較有用。這也許會讓你們能夠更好地了解本書餘下的部分。至少,當你參與到一個關于設計模式和軟體架構多麼糟糕(或者給力)的辯論中時,這會給你一些武器來使用。

    注意我并沒有假設你會站在哪一邊。就像武器供應商一樣,我為交戰雙方都出售貨物。

1、什麼是軟體架構?

        如果你完完整整地讀完了此書,你并不會學會3D圖形背後的線性代數或者是遊戲實體學背後的微積分。它并不會告訴你如何修剪你的AI搜尋樹或者是在你的音頻重放中模拟一個房間的回音。

    天哪!上面這一段話将會成為本書的一個糟糕的廣告!

        實際上,此書是關于那些代碼之間的那些事的。它更多地是關于組織代碼的,而非寫代碼的。每個程式都有某種組織形式,即使它僅僅是“将所有的事情擠在main()函數中然後看看會發生什麼”,是以我覺得談談什麼可以造就好的組織形式會更加有意思。我們怎麼區分架構是好是壞呢?

        我對這個問題琢磨了差不多五年。當然,像你一樣,我對于好的設計有一種直覺。我們都承受過代碼庫的折磨,這折磨是如此之嚴重,以至于你可以希望做的最好的事情就是take them out back and put them out of their misery(譯者注:這句話不知道怎麼翻譯啊,求助!)。

    讓我們承認吧,我們大部分人對其中一些糟糕的代碼庫是負有責任的。

        一些幸運兒有相反的經曆,有機會與設計得很漂亮的代碼一起工作。這種代碼庫感覺就像是一個完美裝飾的奢華旅館,還有門房在熱切地期待着你的每一個怪念頭。這兩種代碼的差別是什麼?

什麼是好的軟體架構?

        對于我來說,好的設計意味着當我做出一個改變的時候,看起來就像是整個程式就是為了變更而制造的。我可以用僅僅幾個供選擇的、可以完美嵌入的函數調用解決一個任務,不會在平靜的代碼表面上留下任何漣漪。

        這聽上去不錯,但是不太可行。“盡管去寫你的代碼,使得變更不會擾亂其平靜的表面。”是的。

        讓我把這件事稍微細分一下。第一個關鍵部分是“架構是關于變更的”。某人不得不修改代碼庫。如果沒人去碰這代碼——要麼是因為它很完美,很完整,要麼是因為它很爛,以至于沒人會用它來弄髒自己的編輯器——那麼其設計就無關緊要了。對于一個設計的好壞的量度是它應對變更的友善程度。如果沒有變更的話,那麼它就是一個從未離開起跑線的賽跑者。

你怎麼做出一個變更?

        在你可以更改代碼來增加一個新特性、修複一個bug或者是為了其他什麼導緻你打開你的編輯器的目的之前,你得要了解現有的代碼正在做什麼。當然,你不必了解整個程式,但是你需要将相關的部分載入到你的頭腦中。

    想想有點奇怪,這是一個光學文字辨識(Optical CharacterRecognition, OCR)過程。

       我們傾向于對這一步驟避而不談,但是這經常是程式設計過程中最耗時的部分。If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves.

        一旦你将所有的正确的相關文本放到你的大腦中,你會略微思索,并找出你的解決方案。這裡可能會有很多的來回往複,但是這一般是比較直接的。一旦你了解了問題以及與之有接觸的那部分代碼,實際寫代碼的時候有時就不值得一提了。

        你用你肉乎乎的手指在鍵盤上敲打了一會兒,直到螢幕上閃爍着正确的顔色的光,然後你就完事了,對吧?還沒呢!在你寫測試代碼并将代碼發出去以供代碼評審之前,你經常有一些清理工作要做。

    我是不是說了“測試”?哦,是的,我說了。對于某些遊戲代碼來說寫單元測試很難,但是代碼庫的很大一部分完全是可以測試的。

    我不會在這裡搞長篇大論(譯者注:I won't get on a soapbox here),但是我會讓你考慮做更多的自動化測試(automated testing),如果你還沒有做的話。相比于一次又一次手動地确認事情,難道你沒有更好的事情去做嗎?

        你往你的遊戲中又塞入了一些代碼,但是你不希望下一個人被你在源代碼中留下的褶皺所絆倒。除非變更很小,否則通常你需要對代碼進行重新組織,以使你的新代碼與程式的其他部分無縫地結合成整體。如果你做得對,下一個人将沒法判斷出哪一行代碼是什麼時候寫的。

        簡而言之,程式設計的流程圖有點如下圖所示:

遊戲程式設計模式:前言(架構,性能和遊戲)(Part I)

    現在我想了想,這個循環沒有跳出這一事實是令人擔憂的。

解耦如何有幫助?

        雖然這不明顯,但是我覺得軟體架構的很大一部分是關于上面那個學習階段的。将代碼載入到神經元中的過程慢得令人痛苦,是以找到政策來減小代碼的體積是值得的。本書有一整節是關于解耦模式的,并且《設計模式》(Design Patterns)的一大塊跟這一概念有關。

        你可以用好幾種方式來定義“解耦”,但是我覺得如果兩塊代碼是耦合的,那麼這意味着你要了解其中之一,就必得了解另一個。如果你解耦了它們,那麼你可以獨立地了解其中之一了。這很棒,因為如果隻有一塊代碼與你的問題有關,那麼你就隻需要将這一塊代碼載入到你的頭腦中,而非把另一半也載入進來。

        對我來說,這是軟體架構的一個關鍵目标:減少你在能夠有進展之前需要知道的知識的量。

        後面的階段當然也有戲份了。解耦的另一個定義是對一塊代碼做出的變更未必要求另一塊的變更。我們顯然必須要變更某些東西,但是我們耦合的東西越少,該變更對遊戲的其他部分産生的漣漪就越少。

2、以什麼代價?

        這聽上去很棒,對吧?對所有東西進行解耦,然後你就可以行雲流水地寫代碼了。每一個變更意味着隻要觸碰一兩個可供選擇的函數,并且你可以在代碼的表面跳舞,不留下一絲陰影。

        這種感覺正是為什麼人們對抽象(abstraction)、子產品化(modularity)、設計模式和軟體架構感到興奮的原因。與一個良好構架的程式一起工作是一個愉快的經曆,并且每個人都喜歡變得更加多産。好的架構對于生産效率能夠産生巨大影響。再怎麼強調其效果的深遠性都不為過。

        但是,就像人生中的所有事情一樣,這并不是免費的。好的架構需要付出努力以及訓練。每當你做出一個變更或者實作一個特性的時候,你必須努力工作以使得其餘程式的其餘部分優雅地結合成一個整體。你必須在兩方面小心行事:一方面是好好地組織代碼,另一方面是在經曆過上千個構成開發周期的小小的變更後保持代碼為有組織的狀态。

    上述的第二部分——維護你的設計——需要引起特别的重視。我見過許多程式一開始是優美的,但是随着程式員們一遍又一遍地添加“僅僅一個小小的劈砍”而死于遍體鱗傷。

    就像園藝一樣,僅僅放入新的植物是不夠的。你必須除草、剪枝。

        你必須考慮程式的哪一部分應該被解耦出來,并且在這些地方做出抽象。同樣地,你得确定什麼地方需要加入擴充性(原文:you have to determine where extensibility should be enginnered in)以便可以更加容易地做出未來的變更。

        人們對此感到興奮。他們設想未來的開發人員(或者就是未來的他們自己)涉足于代碼庫中,然後發現它是可修整的(open-ended)、強大的、希望被擴充的。他們想象着統治他們所有人的一個遊戲引擎(The One Game Engine To Rule Them All)。

        但這就是事情開始變得棘手的地方。每當你增加一個抽象層級或者一個可以支援可擴充性的地方的時候,你推測你後來會需要這種靈活性。你正在往你的遊戲中增加代碼和複雜性,而這需要時間來開發、調試以及維護。

        如果你猜得對并在後來接觸到那部分代碼的話,那麼這種努力會得到補償。但是預測未來是困難的,而當該子產品性最終證明無用的話,那麼它就會很快地變得有害。畢竟,你需要處理更多的代碼了。

    有些人發明了術語YAGNI(是You aren't gonna need it的縮寫),作為用于對抗推測自己未來會需要什麼的沖動的真言。

        當人們對此變得過于狂熱的時候,你會得到一個代碼庫,其結構已經繞得脫離控制了(whose architecture has spiraled out of control)。你會在所有的地方遇到接口和抽象。插件系統(plug-in systems)、抽象基類、虛方法巨多無比,還有各種各樣的可擴充點。

        這會讓你花上一輩子在所有這樣的腳手架間穿梭以追蹤到一些真正做了實事的代碼。當你需要做出一個變更的時候,當然,很可能有一個接口在那裡可以相助,但是希望你運氣好到能夠找到它。理論上,所有的這種解耦意味着你在可以對代碼進行擴充之前,隻需要了解比較少的代碼就行了,但是抽象層本身最終會裝滿你的心理暫存磁盤(mental scratch disk)的。

        像這樣的代碼庫正是使得人們反對軟體架構、尤其是設計模式的原因。很容易深陷進代碼本身,以至于你忘卻了你正在努力地發售一個遊戲。可擴充性這首塞壬之歌吞沒了無數的開發者,他們花了數年時間來做一個“引擎”,卻并不知道這是一個用于什麼的引擎。

繼續閱讀