|
剛拿到前導的《G檔案》,發(fā)現(xiàn)了主程劉剛的文章,是目前我所見 到的關(guān)于游戲編程的最好的一篇,與大家共享。
PC游戲編程
目錄
1 游戲程序理論 1.1 技術(shù)基礎 1.2 游戲底層 1.3 編寫規(guī)則 1.4 程序設計 1.5 制作流程 1.6 程序調(diào)式 1.7 代碼優(yōu)化
2 游戲?qū)嵺`討論 2.1 制作概況 2.2 模塊劃分 2.3 游戲引擎 2.4 關(guān)鍵討論 2.5 希望
1 游戲程序理論 我做游戲的歷史只有三年,我所寫的內(nèi)容都只是我在此期間的感覺和經(jīng)驗,還遠遠談 不上完整和正確,甚至有些內(nèi)容我們自己也沒有完全達到,我只是試圖說明我們曾經(jīng)是怎 樣做的和在最近的將來打算怎樣做。 我讀過一些關(guān)于如何制作游戲的文章和書籍,有外國的,也有國內(nèi)的。有的過于難懂 (那些專業(yè)技術(shù)有的我根本看不懂),有的過于簡單,而且大都著重于程序編寫技術(shù)。我在 這里希望能夠提供其它方面的幫助,比如游戲中的程序設計,項目管理和與我們中國人編 寫游戲相關(guān)聯(lián)的一些問題。 我希望能夠給那些想編寫商業(yè)游戲和正在編寫商業(yè)游戲的人們提供一個樣本,這不是 最好的樣本,而是在中國內(nèi)陸游戲制作業(yè)的初級階段中一個普通產(chǎn)品的制作方法和經(jīng)驗。 如果是一個初學者,我希望給他一個編寫游戲程序的完整印象,讓他至少了解編游戲和玩 游戲是有區(qū)別的。 我希望閱讀本文的人應該是一個程序員,熟練使用C語言和C++語言,曾經(jīng)編寫的單個 程序長度應該在5000行(150k)以上。因為我在這里不會介紹任何有關(guān)編程語言的語法,編 譯等內(nèi)容,如果你連這些內(nèi)容都還沒有弄清楚,請先學習一下,否則將很難與本文引起共 鳴。假如你是一位美術(shù)設計,游戲策劃人員或游戲項目管理人員,雖然你無法看懂里面的 大部分內(nèi)容,但是我也希望你能夠了解里面的有關(guān)內(nèi)容,因為如果你能夠了解程序員的工 作,我們就能夠更好地配合。 我更希望那些不了解游戲的人們能夠看到本文,我希望他們能夠了解編寫游戲程序與 編寫其它程序是一樣艱巨和復雜的,制作游戲并不等同于玩游戲。
1.1 技術(shù)基礎 1.1.1 人員 作一個商業(yè)游戲所需要的程序員的水平,可以分成三個級別:系統(tǒng)分析員、主模塊程 序員和外圍程序員。 ·系統(tǒng)分析員: 這里的系統(tǒng)分析員也許比不上程序員資格考試中的系統(tǒng)分析員,因為我們制作的游戲 程序到目前為止都屬于中小型程序,長度一般在50000到200000行之間(1.5M到6M),經(jīng)過 壓縮之后源代碼可以輕松放在一張軟盤上。工作量一般在20到60人月之間。所以游戲程序 的復雜程度比一些大型應用程序要小。但是,游戲系統(tǒng)分析員的工作也一樣是復雜繁重的 。他的主要工作是需求分析,程序設計和進度管理。 在需求分析階段,主要是根據(jù)游戲設計制定的游戲方案確定那些內(nèi)容是可以實現(xiàn)的, 那些是不能實現(xiàn)的;可以實現(xiàn)的部分如何實現(xiàn),不能實現(xiàn)的部分該如何修改。最后制定一 個可行的游戲程序制作方案。這個方案最后被系統(tǒng)分析員寫在程序設計文檔中。當設計定 稿后,制定開發(fā)計劃,組織人力開發(fā),監(jiān)督制作成果。 ·主模塊程序員: 過去我們的系統(tǒng)分析員和主模塊程序員由同一人擔任,但是到后來,我們發(fā)現(xiàn),因為 程序的管理設計人員也要擔任繁重的程序制作任務,很難面面俱到。所以我建議把主模塊 程序員獨立出來。由他來負責對游戲主要運行部分進行編程。這需要主模塊程序員具有非 常豐富的編程經(jīng)驗,至少應該具有完成一個游戲的經(jīng)驗。但是其實每個人都是從不會到會 ,從沒有經(jīng)驗到經(jīng)驗豐富的,不能滿足條件也沒有關(guān)系,只要程序難度小一些就可以了。
·外圍程序員: 游戲的程序一般都會被分割程序若干個模塊,如果該模塊獨立性比較大就可以交給其 他人員完成。這些人只要有一定編程經(jīng)驗就可以勝任了。
1.1.2 編程平臺 任何的計算機語言都可以被用來寫游戲。游戲可以被運行在任何可能的計算機硬件設 備上。但是,因為我的制作經(jīng)驗的局限,我無法向你們介紹用Cobal語言編寫AppleII上運 行的游戲。同樣,我也不會教各位如何使用Visual Basic,Pascal或Java,任何形式的 DOS程序也是我不會花時間講解的。在我后面的討論中將只涉及在windows95/98下的 Visual C++編程。 現(xiàn)在,編寫游戲時不能不提到Microsoft公司的DirectX。它是一套基于Windows95/NT 的游戲底層接口,在我們的游戲中使用了它作為底層接口。關(guān)于它的使用想必大家已經(jīng)很 熟悉了,現(xiàn)在已經(jīng)有中文的書籍出版,我不必贅述。 有的書上講在編譯和調(diào)試程序時最好使用Windows NT或雙顯示器,尤其因為 DirectDraw中獨占顯示器。可是我試過之后發(fā)現(xiàn)都不實用。對于WindowsNT,它的4.0版只 支持DirectX 3.0, 聽說5.0版才支持DirectX 6.0,但我總不能等到NT5.0出了之后才開 始編吧。對于雙顯示器,我一直耿耿于懷,外國人都很闊,每個人可以配備兩臺機器,可 是,就算我找到兩臺計算機,程序裝入的速度實在讓我不耐煩,調(diào)試一遍時間太長,等不 起。 我們應該重視的倒應該是計算機的兼容性問題。為我們提供測試的計算機是很少的, 如何找到更多的條件才測試我們的程序是后期應該注意的問題,千萬不要以為在 Windows95時代計算機硬件的差別已經(jīng)沒有了,它們出起錯來更危險。
1.1.3 語言 在任何一本寫游戲編程的外文書籍里都會提到編程語言主要有兩種,匯編和C。有的 書里甚至不提倡使用C++。不使用C++的原因我猜有兩個:第一,速度。據(jù)說C++代碼比C代 碼慢10%。這個數(shù)字我沒有測試過,但C++比C慢大概應該是對的。因為用C++比直接用C編 寫代碼要方便和簡單,所以速度應該會慢。第二,便于移植,移植這個詞可很時髦,什么 PC到PS啦,PS到Saturn啦,Saturn到N64啦,N64到Mac啦,Mac到PC啦之類,一個游戲必須 要在所有地方出現(xiàn)才過癮。如果使用C++就難了,因為象次世代游戲機上的開發(fā)環(huán)境,一 般不支持C++,N64雖然說是支持但仍然建議使用C。 使用匯編的理由也是很簡單的,那就是快!因為游戲編程中表現(xiàn)最明顯的地方就是游 戲的速度。匯編比C快的道理也是很明顯的,那就是匯編要比C難寫多了,速度自然快些。
那么速度就那么重要么?過去上學的時候,用匯編寫出來的程序編譯出來的一般十幾k ,后來用C寫的小程序大的有幾十k,而現(xiàn)在編譯windows程序,一上來就有一百多k。過去 的游戲就是綠色屏幕上幾個字符,而現(xiàn)在都是真彩色的圖象和實時三維。過去的游戲一個 人就可以“搞定”,而現(xiàn)在必須分工合作。這些給我的感覺就是游戲越做越大,程序越寫 越長。是不是有一些方面開始變得與游戲速度一樣甚至更加重要了呢? 有!這其實就是大家經(jīng)常談論到的一個問題:程序的Bug。一個程序如果越來越復雜 ,越來越長,而且多個人共同編寫,這時候就越容易出現(xiàn)程序中的臭蟲。我認為,以我們 目前的程序編寫水平而言,程序的正確性要遠遠高于它的效率。實際上,我在后面的主要 篇幅都在講如何提高程序的正確性上,而只有很少的篇幅介紹程序的優(yōu)化。 所以我選擇編程語言主要是從其正確性和效率兩個方面來考慮的。目前我選擇C++。
這不僅僅因為我對這個語言最熟悉,更因為它的特點,對兩個方面都符合得很好。程 序是人寫出來的,如果程序越容易寫,就越容易正確,這方面匯編語言顯然不是候選者, Pascal雖然好,但是那些Begin和End寫起來也太累。 C語言要省事得多,C++更進一步, 而且有了封裝,繼承,多態(tài)的概念??墒蔷帉懽罘奖愕氖走x應該是Visual Basic,Delphi 和Java了。程序的框架都已經(jīng)被準備好,只要在里面填東西就可以了。只是因為它們的效 率比較低,而不能成為游戲編寫的最佳語言。 現(xiàn)在很難想象一個游戲整個都是用匯編寫的,因為一個10000行的C程序改用匯編寫至 少需要50000行,那其中出錯的機會至少要多出5倍,而且匯編代碼太過抽象,很難讀懂。 所以應該只在非常需要效率的地方才需要使用匯編語言。 C語言與C++的差別不大,而且我認為C++所帶來的封裝的概念足以彌補它們的速度差 異。關(guān)于移植,有消息說Sega最新一代的游戲機DreamCast要使用Windows CE作為操作系 統(tǒng),那么C++應該也會支持的了。大融合的時代總會到來,不同擔心。我的C語言代碼也主 要用于程序的底層。 那么,現(xiàn)在大家對我所使用的語言應該有所了解了。使用C++作為主要語言,同時輔 助以C和匯編。
1.1.4 編譯環(huán)境 Microsoft Visual C++2.0/4.0/5.0/6.0是我一直使用的編譯器。雖然Borland C++和 Watcom C++也可以編譯Windows程序,我仍然認為Visual C++是最好的。沒有別的理由, 就是因為習慣了。如果你已經(jīng)習慣使用Watcom C或Free C什么的,也沒必要改。如果從來 沒有編過Windows程序,倒是可以先從這里開始。 我聽說誰如果不會使用SoftICE調(diào)試程序就沒有達到編程的頂峰,我一直以此為遺憾 。我不會使用SoftICE。我也遠遠沒有達到編程的頂峰。“竟然這樣的人也編起游戲來了 ”,也許有的人會這樣想,我想這沒關(guān)系,因為本來誰都可以編游戲的嘛。
1.1.5 背景知識 編程序到底需不需要是專業(yè)人才,這也是個大家爭論的話題。我還是那個觀點,這只 是一個起點的問題,它只表明入門時候的速度而已。但這并不是說你什么都不需要學。有 許多東西需要我們學,甚至需要我們不停地學。 ·硬件知識: 你總不能連自己的計算機是什么CPU都不知道吧。雖然我們不需要知道每個芯片的內(nèi) 部構(gòu)造,但如果我們知道了Pentium CPU有兩條并行流水線同時處理指令的話,我們就會 特別注意這方面的優(yōu)化了。 ·操作系統(tǒng): 我們的程序不一定需要多任務,多線程,但是了解我們的程序何時被調(diào)用是很重要的 。 ·數(shù)學: 數(shù)學是我最頭痛的科目了。好在我們在這里所接觸的大多是離散數(shù)學(我也很頭痛!) 。最基本的要算是數(shù)理邏輯,就是程序中最普通的判斷語句中的那些“與”,“或”,“ 非”。其次是那些圖論,概率。知道這些編寫二維游戲就足夠了。如果編寫三維游戲,那 就趕快復習一下線性代數(shù)吧。 ·專業(yè)課: 這是計算機系中學到的有關(guān)課程。雖然數(shù)據(jù)庫,網(wǎng)絡的概念在游戲編寫中都可能會用 到,但是因為都是間接的,就不在這里說了。數(shù)據(jù)結(jié)構(gòu)大概是最重要的課程了,這里所提 到的數(shù)組,隊列,堆棧,鏈表,表,樹,圖等概念在我們的程序中幾乎都會或多或少地用 到。另外,對于三維編程,還有一門叫計算機圖形學,也是很重要的。 ·文件: 與游戲密切相關(guān)的文件有圖象文件,聲音文件,視頻文件等。這些知識越豐富對我們 編寫游戲就越有用??墒?,這也并不是絕對的,我在剛開始寫游戲時連BMP圖的格式都不 知道。 ·美術(shù): 大概每個游戲程序員都有過當“助美”(助理美工)的體驗吧。當我們做程序測試,或 美術(shù)師們沒有時間完成裁圖,切圖等“體力”勞動時,自然由我們來做這些工作。但是, 請記住,程序員永遠是程序員,就算我們畫得再好,也比不過他們的。
1.2 游戲底層 在我們編寫每一個游戲時,都希望能有一部分代碼可以重用,這樣我們才能有更多的 時間做更多的事情。這些重用的代碼就慢慢形成了游戲底層。游戲底層多了,自然就形成 了一些類別。 顯示底層: 用于基本顯示的函數(shù)。把常用的功能制作成函數(shù),統(tǒng)一出錯管理。對于Windows95/98 下的游戲編程,我們一般使用DirectX作為大部分底層的底層。因為這部分是我們可以用 于編程的最底層函數(shù)。對于二維顯示,我們使用的是DirectDraw部分。為什么還需要在 DirectDraw外面再包一層函數(shù)呢?原因有三:第一,DirectDraw提供的函數(shù)擴充性很強, 函數(shù)參數(shù)較多,而我們使用時參數(shù)比較固定,包裹成類庫后,參數(shù)更加可以封裝起來;第 二,DirectDraw提供的函數(shù)比較分散,而有許多操作是需要多個函數(shù)按照一定順序執(zhí)行的 ,包裝起來后編寫程序比較簡單;第三,DirectDraw函數(shù)的返回值比較復雜,而且沒有對 錯誤進行系統(tǒng)的處理,我們需要自己制作一套錯誤處理函數(shù),對錯誤代碼進行解釋。 顯示底層有許多種做法,可以完全自己開發(fā),也可以直接使用別人完成的函數(shù)庫,比 如Internet上比較流行的CDX類庫就是對DirectDraw進行了很好封裝的底層。 顯示底層主要分成下面幾個部分: 窗口處理:窗口的創(chuàng)建、管理、刪除。 圖形接口處理:圖形設備的初始化、退出、顯示模式的設定、主顯示面、后緩沖區(qū)區(qū)的?立。 二維圖形處理:游戲使用的二維圖象的裝入、顯示、剪裁、邏輯操作。 外圍工具:游戲使用的鼠標,刷新率計算和顯示,顯示內(nèi)存斟測,面丟失(Sur faceLost)處理(在DirectX 6.0后據(jù)說可以不用),錯誤處理,調(diào)試函數(shù)。 關(guān)于顯示底層還有兩個問題需要說明: 第一,顯示部分的調(diào)試。因為我們所做的游戲一般為獨占模式,在游戲運行時不能 跟蹤(除非使用雙顯示器),所以我們只能另想辦法,一個比較常用的辦法是使用窗口模式 和全屏模式切換。不一定在游戲運行中隨時切換顯示,在調(diào)試時事先指定也可以實現(xiàn)該功 能。 第二,多媒體播放底層: 播放多種聲音,音樂,視頻等多媒體效果。比較古老的一種辦法是使用Windows 3.1 就開始使用的MCI調(diào)用。它是一種通用接口,使用不同的參數(shù)調(diào)用同一個函數(shù)就可以實現(xiàn) 基本相同的功能。用MCI可以實現(xiàn)的主要媒體類型有MIDI,WAVE,CDAUDIO,和VIDEO。它 的優(yōu)點是使用簡單。缺點則是缺乏交互性,而且同種類型的媒體不能同時播放。這樣就很 難實現(xiàn)多種音效的混合。 一個基于DirectX的調(diào)用是使用DirectSound。它是基于WAVE文件的一套播放函數(shù)。 非常適合混音。缺點就是使用較復雜,而且如果想實現(xiàn)分段讀取較長的WAVE文件,編程有 些難度。 另外一個針對MIDI的DirectX是DirectMusic。它是DirectX系列中的一個新成員。 對于播放高質(zhì)量的VIDEO(視頻)文件,則有另外一套底層。比如Microsoft的 ActiveMovie和Intel的Indeo Video。有了它們我們就可以放心地全屏播放AVI文件了。
對于這些底層,我們所做的只是簡單地封裝上一層函數(shù),使我們在使用該功能時達 到最簡便。 但是假如你對這些現(xiàn)成的播放程序不滿意,也可以完全自己開發(fā)。只要你知道這些 文件的格式,再把它們存儲成你需要的格式,用自己更加優(yōu)化的算法將它顯示出來就可以 了。用這種方式你可以自己播放AVI VIDEO, MPEG, MPEG2或FLI/FLC。 文件封裝底層: 對使用的文本,圖象等數(shù)據(jù)進行封裝。我們也常用這種方式把多媒體文件封裝起來, 防止別人事先查看。這種工作往往是循序漸進的,我們會根據(jù)自己的時間和實力,封裝 相應的部分。比如先封裝比較簡單的文本,圖象。然后是較為復雜的聲音文件,最后是最 復雜的視頻和動畫文件。 有的時候封裝也是為了壓縮。不僅再安裝時少占用硬盤,裝入時也可以節(jié)省時間, 有時在運行時也會減少內(nèi)存占用而不會對速度有太大的影響。 根據(jù)文件類型的不同,我們可以這樣分類:文本、圖象、聲音、視頻、動畫等。對 于每種類型,我們還必須有打包函數(shù)和解包函數(shù)之分。 界面底層: 自己編制的類Windows風格的界面類庫。我總有這樣的習慣,希望在我的游戲里沒有 一點Windows的風格,于是我不會使用任何一點的有Windows風格的界面(游戲工具除外)。 所以游戲的界面必須我們自己做。然而我又使用過MFC那一套界面類庫函數(shù),很佩服 Microsoft的本事。也希望我們的游戲中有一套自己的界面系統(tǒng)。這套系統(tǒng)將會包含 Windows用到的主要界面元素:位圖(Bitmap)、按鈕(Button)、檢查框(Checkbox)、滾動 條(Scrollbar)、滑塊(Slider)等等。 雖然Windows允許我們在使用標準界面時自己畫圖,但是因為它只支持256色,是基 于消息響應機制的顯示,而且無法支持位圖的封裝,所以我們使用一套完全由自己制作的 界面底層。這套底層建立在顯示底層和文件封裝底層之上,形成了一套比較完整的系統(tǒng)。
上面這些只是一般游戲所需要的底層,對于某些特殊類型的游戲,可能還會有戰(zhàn)略 游戲的顯示,操作底層,RPG游戲的事件處理底層,戰(zhàn)棋游戲的顯示,智能底層,動作游戲 的場景底層等。如果編寫三維程序,則還會有三維顯示和操作底層。這些底層都不需要也 不可能在一開始就全部完成,而必然是在不斷的修改和檢驗中完善的。它們也不可能一成 不變,必然隨著我們編程經(jīng)驗的積累和開發(fā)環(huán)境的改變而改變和完善。 每個編寫游戲的程序員都會慢慢積累出符合自己游戲特點的底層函數(shù)庫,這些函數(shù)庫 如果得到不斷的發(fā)展和擴展將會越來越大,越來越好,這對于每個游戲制作公司和集體來 講都是一筆不可忽視的財富。 而這套函數(shù)庫越來越復雜,再加上輔助的一整套工具,就可以被稱之為游戲的引擎。 有了引擎在手之后,不僅意味著我們有了一套低Bug的現(xiàn)成代碼,程序員的工作量將會大 幅度下降,而且他們可以把更多的精力放在游戲的優(yōu)化、游戲的人工智能、游戲的操作、 以及游戲性上面了。到了這個層次,我才可以想象一個好的游戲?qū)鞘裁礃幼印? 1.3 編寫規(guī)則 現(xiàn)在,只需要一個程序員編寫的程序已經(jīng)很少了,大部分的程序都需要多個程序員 的協(xié)作才能完成。這期間必然需要程序員之間互相閱讀代碼。而代碼的編寫規(guī)則就是非常 重要的環(huán)節(jié)。一段代碼如果寫的淺顯易懂,不但在程序交流時非常方便,而且對于尋找程 序錯誤也時很有幫助的。程序員也是人,是人就會犯錯誤,不管他多么地不愿意犯這個錯 誤。所以我們也希望能制定下一個規(guī)則,使他們在一開始想犯錯誤也不行。于是就出現(xiàn)了 程序的編寫規(guī)則。 如果不按照這些規(guī)則編寫代碼編譯程序是不會報出錯誤的。但是如果按照規(guī)則編寫, 則對我們的思路的整理、筆誤的減少、錯誤的查找和代碼的閱讀都是極有好處的。 下面我只把規(guī)則中幾個重要的部分加以簡單的描述,在后面附錄中會有一份我們已 經(jīng)使用了一段時間的完整規(guī)則。 命名規(guī)則: 程序中對變量和函數(shù)的命名看似簡單,可是名字起得好與不好有很大的不同。對于 變量我們一般采用匈牙利命名法和微軟命名法的結(jié)合。對于函數(shù)我們一般要把模塊名寫在 函數(shù)名的開始。對于類庫我們一般要求類的成員和成員函數(shù)要緊密對應。 變量使用: 對于程序中所使用的所有變量,我們都需要其有明顯的定義域。對于全局的變量要 盡量封裝。對于類庫,所有成員必須封裝。 函數(shù)使用: 我們對函數(shù)的參數(shù)和返回值做了要求。對于函數(shù)的使用范圍也做了規(guī)定,要求范圍 越小越好。 注釋: 對于程序注釋,我們雖然不需要很多,但是在函數(shù)聲明、變量聲明、主要算法部分、 源程序文件的開始都需要有注釋。 調(diào)試: 我們的底層和Visual C++都提供了一些供調(diào)試用的代碼,在我們的代碼中需要適時 地使用這些代碼,以提高程序的糾錯機會。 這里有幾個問題需要特別說明: 第一,關(guān)于鏈表的使用。 我在編程中所遇到的所有死機錯誤中,有90%來自于指針,9%來自于字符串(其實也 是指針的一種),1%來自死循環(huán)。而鏈表這種把指針的優(yōu)勢發(fā)揮到極限的方式我是不提倡使 用的。有人講如果不會使用鏈表就不是C程序員,那么現(xiàn)在我連個合格的程序員的資格都 夠不上了。的確,對許多人來講,編寫沒有Bug的鏈表是很容易的事情,甚至對某些人而 言,一個沒有鏈表存在的程序幾乎不能稱得上是商業(yè)程序。我承認有許多天才的程序員 在自己的鏈表程序中表現(xiàn)得十分出色,他的程序效率既高又沒有Bug,但是我也知道還有更 多的程序員并不是天才。就算是天才的程序員如果在他的十萬行代碼中有五萬行于某些鏈 表有關(guān),這其中的某個指針出了一個錯誤,而這個錯誤又不是每運行一次都出現(xiàn)的時候, 他又有多大的把握很迅速地找到這個錯誤呢?鏈表的復雜性在于它的地址的隨機性,這使 我們在調(diào)試跟蹤時非常困難,我們很難隨時觀察每一個節(jié)點的信息,尤其當節(jié)點多的時候。所以我把鏈表的使用完全限制在算法的需要和該代碼被某個獨立模塊完全封裝的情況下。 實際上我在設計程序時,要求盡量減少指針在主要模塊之間的傳遞,而使用效率較 低但是不易出錯的句柄ID的方式。 第二,關(guān)于類庫的成員。 既然我們使用了類庫,其中一個主要目的就是要把其中的成員完全封裝起來。首先 把它設置為私有,然后定義標準的一套Set和Get函數(shù)。這雖然使我們在寫代碼時比較煩瑣,但是只有這樣我們才可以嚴格規(guī)定該成員的只讀屬性和可寫屬性,減少對某些重要變量 的無意修改。 第三,關(guān)于變量和操作符之間的間隔 許多人對于變量和操作符之間的距離并不在意,認為多個空格少個空格只是看起來 好不好看而已。而實際上,這對我們是很重要的。因為如果我們把間隔固定下來,在查找 某項操作時就可以使用帶空格的字符串,而大大增加找到的機會。 1.4 程序設計 我們在編寫自己第一個游戲時,往往是憑借自己的興趣,想到哪就編到哪,有時連自 己都沒想到會編出這么大的程序來。那時侯我們對代碼的修改往往是很隨意和徹底的,因 為本來都是自己的東西,而且更希望它更好??墒?,程序越寫越大,有一個問題就會越來 越明顯,那就是程序的錯誤。為了減少錯誤的產(chǎn)生,我們必須采取一些辦法,所以也就產(chǎn) 生了程序設計。程序設計的必要性還表現(xiàn)在另一個方面,那就是程序員間的合作。為了讓 每個程序員對我們所要做的事和我們將如何做這件事有個明確的了解,也為了讓管理人員 能夠從量的角度了解任務的完成狀況和進度,我們也必須有程序設計。否則我們就只能象 一群烏合之眾一般,鬼才知道這游戲要做到什么時侯。 一般,我個人認為,當一個程序需要寫到10000行以上時就應該有比較獨立的程序設 計文檔了??墒浅绦蛟O計文檔應該是什么樣子呢?很抱歉,我也一直在尋找這個問題的答 案。在我剛開始做游戲的時侯,沒有人教,于是我翻出上學用的課本,打算來個生搬硬套 ,寫來寫去,發(fā)現(xiàn),不行。這樣的寫法太累了。比如在軟件工程課上所講的數(shù)據(jù)流圖,我 好不容易寫滿了一張八開的紙,馬上就發(fā)現(xiàn)自己已經(jīng)糊涂了。就算不馬上糊涂,過一段時 間我也會認不出那些條條框框是什么了。就算我能記得這些圖表的意義,我能夠保證所寫 的代碼和圖表上的一樣么?我寫完后,還需要和這些表對一下。就算程序和文檔中的內(nèi)容 一樣,那如果我的設計方案改了呢?又需要先改文檔,再改程序,再對程序。這樣對我來 講可太累了。 后來,我才發(fā)現(xiàn),原來書上所寫確實有道理,可是它并不是面向我們這些人的,它所 面向的是寫大型程序的人。這些程序大到需要專門有這樣的人來寫文檔。這些人只要把文 檔寫好再確認完成的程序是否符合要求就可以了,根本不用去寫一句的代碼。是啊,如果 編游戲也有這樣的工作就好了??墒呛苓z憾,目前我尚未遇到可以聘有專職程序設計人員 的公司。所以程序設計者就是程序編寫者的這個事實起碼在我們的目前階段是很難避免的 了。 那么怎么設計文檔對我們用處又大又比較節(jié)省時間呢?每個公司和每位程序設計人員 的習慣都不一樣,我只介紹一下我們曾經(jīng)使用過的一些方法和文檔的類型。
程序設計總綱: 無論我們做什么,最先要清楚的一個問題就是:我們做什么?很弱智是不是?但是我發(fā) 現(xiàn)有些人在很多時侯都“不知道”自己在做什么。程序設計總綱就是要告訴自己和自己的 伙伴們,我們要做的是什么樣的游戲程序。它包括: 游戲概述:游戲的背景、類型、操作方法、特點等; 程序概述:游戲程序的編譯平臺、硬件運行需求、語言、編程重點和難點、技術(shù)能力 分析等; 程序員概述:參與編程的人的能力和在整個程序制做中的作用和地位; 程序模塊劃分概述:有本游戲所需要的所有程序,和程序內(nèi)部的功能模塊的劃分。這 部分只要有比較概括的說明就可以了。 這篇文檔的作用就是讓所有人都能夠了解本游戲的程序?qū)鞘裁礃幼?,它的特點是 什么,完成它的難點又是什么。以及我們?yōu)槭裁匆@樣做,有誰來做等這樣基本的問題。 它所面向的對象除了有自己和本組內(nèi)的所有程序員以外,還應該有本游戲的游戲策劃,美 術(shù)設計、項目負責人、項目經(jīng)理、制片人等。如果有可能也應該和本公司的市場、銷售、 廣告公關(guān)、出版、甚至公司總經(jīng)理充分接觸,聽取各個方面的意見,也充分表達自己的意 見。如果能做到制做本游戲項目的所有人員從上到下對本游戲的程序制做都能有一個基本 的了解,理解和信心那是最好的。
程序模塊劃分: 這是對程序設計總綱中最后一個部分的詳細說明。說明的內(nèi)容主要有:該模塊的功能 、接口、技術(shù)要點、所用到的軟件底層等。它所面向的對象一般是所有的程序員,但是如 果讓與程序合作的人員(美術(shù)、策劃、經(jīng)理)也能了解則更好,這樣可以加強我們之間的勾 通,讓不懂編程的人也能了解編程人員的苦衷。 一般而言,一個游戲按功能劃分(我們很少用其它的分類方法)可以有下面幾個部分:
工具部分:在游戲的制做過程之中,從游戲策劃的文檔,游戲美工的圖片到游戲完成 ,中間必須經(jīng)過游戲程序的轉(zhuǎn)換和加工。這些數(shù)據(jù)在進入程序之前必須經(jīng)過規(guī)整,排序等 操作。這部分工作過去一般都是由程序員來做的。在人手緊張和工作量比較小的時侯這樣 做還是可行的。但是如果這樣的工作過于復雜,工作量太大,就不行了,我們需要一種方 法來提高工作效率。這種方法就是工具。其實我們在計算機所做的任何工作都是在使用工 具,唯一的區(qū)別只是這些工具都不是我們自己做的。因為游戲本身的特殊性,我們不可能 總能找到我們所需要的工具,自己制做就是十分必要的工作。 這些工具也可以分成三個級別:別人的工具,我們自己的可以重用的工具,我們自己 的只在本游戲使用的工具。別人的工具是最好的工具。原因很簡單,因為它不用我們再去 編了,不僅程序Bug較少,操作界面友好和簡單,而且開發(fā)時間是零。所以如果可能一定 要使用別人做好的工具。如果實在沒有滿足要求的工具,或者我們還需要該工具所沒有的 功能,我們只能自己編。那么如果這個工具可以被以后用到也不錯。所以我們在編寫任何 工具的時侯,其通用性是我們考慮的首要因素。 工具與游戲其它部分的程序有一個最大的區(qū)別,那就是工具程序是獨立的可執(zhí)行文件 ,它與游戲的唯一交互方式就是存在磁盤上的文件。所以它比較適合交給其他的程序員制 做。另外一個特點就是它一般都是針對非程序員的,所以如果工具做好了,也可以很大程 度上幫助他們提高工作效率。
底層部分:這部分程序代碼的最大特點是可重用性。它是游戲本身與計算機通用系統(tǒng) 之間的橋梁,它與游戲的具體細節(jié)關(guān)系并不大,可以被使用到多種游戲類型和多個游戲之 上。這對我們是非常重要的。實際上,我們編寫程序所積累的最直接的財富就是可重用的 工具和游戲底層。 游戲底層從與游戲的相關(guān)性可以有大概三個層次: 最底層:它們向下直接調(diào)用系統(tǒng)函數(shù)和功能,向上與游戲幾乎完全無關(guān),是游戲程序 與計算機系統(tǒng)的直接橋梁。它包括: ·顯示底層:提供游戲顯示所需要的所有函數(shù); ·文件底層:提供對游戲所需的各種文件處理的接口; ·多媒體底層:提供游戲所需的多媒體效果,如聲音,視頻等; ·對于三維游戲有模型管理底層:專門保存,處理和顯示三維數(shù)據(jù); 中間層:它們向下調(diào)用最底層的游戲底層,向上為游戲提供函數(shù)。與游戲無關(guān)。它可 能: ·界面底層:使用顯示和文件底層,提供界面類庫和操作函數(shù); 最高層:它們向下調(diào)用中間層和最底層,向上為某種類型的游戲提供特殊的邏輯支持 。它可能有: ·戰(zhàn)略游戲的命令執(zhí)行底層,顯示底層,地圖底層。 ·RPG游戲的事件處理底層,場景底層。
這三個層次的制做難度是越來越難的。它往往需要我們在制作過幾個游戲之后提煉和 總結(jié)出來(實際上我們到現(xiàn)在也還沒有發(fā)展出第三個層次的底層)。但同時,這三個層次是 越來越重要的,因為它距離一個成品游戲更進一步。我們在寫游戲時可重用的部分越多, 重用的層次越高,我們的工作量就越少。我們也就越可能有時間研究和開發(fā)新的技術(shù),好 的算法,我們的游戲也就會越做越精。 相比之下,前面的兩個底層都與計算機系統(tǒng)有關(guān),與游戲本身無關(guān);第三個底層與計 算機系統(tǒng)無關(guān),與某種類型的游戲有關(guān)。 底層部分與工具部分也是密切相關(guān)的。一般每一套底層都需要一些工具來與之對應。 比如,我的顯示底層需要某種格式的圖片,我們就要制做一個格式轉(zhuǎn)換和圖片觀察的工具 。把工具和底層結(jié)合起來分類,然后把它較給獨立的程序員去完成,是一種比較常見的模 塊劃分方法。 當然,我們在做游戲以前也不一定非要什么工具和底層不可,這些東西完全都可以“ 自然”產(chǎn)生。假如你能夠堅持制做幾個游戲,把可以反復使用的程序歸納起來,修改一下 ,就是一套底層了。如果我們在編寫程序時,每時每刻都在想:這段代碼是否可以在以后 使用,怎樣的寫法可以使我在以后使用,那么我們在后來的修改工作中就會輕松很多。而 假如我們在編寫程序以前多花一些時間,把想要做的東西設計清楚,在一開始就可以把它 寫成獨立的底層代碼。這樣積少成多,我們的財富就會越來越豐富。
游戲本身:這才是我們每次都要做的游戲。所有體現(xiàn)游戲特色的內(nèi)容全在這里。當然 也有的人認為如果我們的經(jīng)驗足夠豐富,想得足夠全面,連這部分的程序也可以重用起來 。以后我們編游戲只要改一下圖片,換幾個名字,編一段故事,一個新游戲就產(chǎn)生了。實 事上,也有公司制做了一些類似游戲工具的東西,其中做得最好的應該是象Director, Autherware那樣的多媒體編輯工具吧。但是,它們都有其局限。最明顯的一點就是:它們 必須針對某一個類型的游戲或只能是多媒體程序,不同類型游戲的制做是有很大區(qū)別的, 幾乎很難用一個固定的模式將其完全函蓋。其次,它們創(chuàng)新困難,現(xiàn)在游戲幾乎成了創(chuàng)新 的代名詞,哪一個游戲做出來沒有它自己的特點呢?而使用一個固定的工具是很難做到這 一點的。第三,難以對它們進行優(yōu)化。程序的優(yōu)化往往和程序本身的特點有關(guān),優(yōu)化的程 度越高,它的特殊性也會越大,所以,通用工具只能犧牲一些性能。我到目前還沒有看到 一個游戲制做工具可以達到商業(yè)效果,而多媒體工具之所以流行正因為制作多媒體產(chǎn)品所 需的模式比較單一,對速度要求也不高。 所以,制作一個完全可以通用的游戲工具到目前仍然還只是一個夢想。但是,我想這 是一個很美的夢,如果真的到那個時侯,我們制做游戲?qū)团碾娪耙粯雍唵?我指的只 是制做的過程),只需要一個程序員根據(jù)策劃的意思把圖片和聲音放在一起就可以了。 每個游戲都是不一樣的,但它們都有一些相似的地方,比如: 游戲的操作:游戲者如何操作和控制游戲中的一個或多個主角; 游戲的顯示:不是游戲的底層,而是與具體游戲有關(guān)的顯示部分; 游戲的運動:游戲中有的內(nèi)容是要不斷變化的,包括位置變化,圖片變化和形式變化 ; 游戲的智能;能夠與人對抗的思考; 游戲的存儲;把所有當前游戲需要的數(shù)據(jù)保存在磁盤上,然后再重新裝入游戲; 游戲狀態(tài)的轉(zhuǎn)換:從界面到游戲,從游戲到動畫,在不同的時侯有不同的操作和顯示 管理。 我們通常也會按照這樣的規(guī)律對游戲進行模塊劃分
游戲數(shù)據(jù)結(jié)構(gòu)和算法: 有人講程序就等于數(shù)據(jù)結(jié)構(gòu)加算法。這句話很有道理。我們所編的程序其實就是把某 種格式的數(shù)據(jù)(圖片,主角的參數(shù))經(jīng)過一系列的轉(zhuǎn)換,成為計算機屏幕上的數(shù)據(jù)(游戲本 身)。所有的數(shù)據(jù)都需要以某種方式存儲,這就是數(shù)據(jù)結(jié)構(gòu)。而算法因為是與數(shù)據(jù)結(jié)構(gòu)密 切相關(guān)的,所以雖然它們不屬于同一個概念,我把它們放在一起設計。 我們通常所使用的數(shù)據(jù)一般可以分成兩個部分:數(shù)據(jù)庫結(jié)構(gòu)和當前結(jié)構(gòu)。 這里的數(shù)據(jù)庫不是什么商業(yè)數(shù)據(jù)庫軟件,而是游戲中所使用到的所有數(shù)據(jù)的集合。我 還沒有見到有哪個游戲在存儲這些數(shù)據(jù)時使用商業(yè)數(shù)據(jù)庫軟件(比如FoxBase)。也許它們 都被隱藏起來了吧,我猜想。但不管怎樣,其意義是一樣的,我們需要把所有在游戲中用 到的數(shù)據(jù)都以一定的形式儲存起來。同商業(yè)數(shù)據(jù)庫一樣,數(shù)據(jù)庫的組成也是由字段組成, 即數(shù)據(jù)元素。每個數(shù)據(jù)元素一般是一個類庫或結(jié)構(gòu),有若干的成員。數(shù)據(jù)元素所組成的數(shù) 組就是數(shù)據(jù)庫結(jié)構(gòu)中最主要的組成部分。 數(shù)據(jù)庫的內(nèi)容很多,而且按照一定的規(guī)則排列,而真正需要顯示和當前需要計算的數(shù) 據(jù)卻很少,有些數(shù)據(jù)還是計算的中間結(jié)果,比如主角的位置,主角的動作等,這時侯就需 要我們另外存儲一些數(shù)據(jù)。它往往是數(shù)據(jù)庫中的某個字段(數(shù)據(jù)元素),或一些數(shù)據(jù)的組合 。 如果把數(shù)據(jù)結(jié)構(gòu)按照功能分類,我們還可以把它分成主數(shù)據(jù)結(jié)構(gòu),模塊數(shù)據(jù)結(jié)構(gòu)。它 們的定義域是不一樣的。 在我們的游戲中,會有許多數(shù)據(jù)結(jié)構(gòu)的類庫,它們之間也必然有數(shù)據(jù)結(jié)構(gòu)之間的數(shù)據(jù) 傳遞和保存。如何處理好這些數(shù)據(jù)之間的關(guān)系是編寫這部分內(nèi)容最重要的考慮。 首先是數(shù)據(jù)結(jié)構(gòu)定義域的問題。在無數(shù)的教學課本上都在不斷提醒大家,要盡量避免 使用全局量。為什么要這樣做呢?第一個原因顯然是因為這樣做的人太多了,我們使用全 局量直接,方便,快捷,有什么理由不用呢?而答案也很簡單,因為它暗含了錯誤,因為 它不易于閱讀和修改。于是我們用兩個規(guī)則來限止全局量的使用:第一,盡量使用局部全 局量,使該全局量的定義域最小,比如基于源程序文件的全局量,只有整個游戲的核心結(jié) 構(gòu)是全局范圍內(nèi)有效。第二,對于所有的全局量都對其內(nèi)容進行全面的封裝,如果是類庫 則使用成員函數(shù),如果是結(jié)構(gòu),則定義專門的函數(shù)對其內(nèi)容進行處理。 其次是數(shù)據(jù)的傳遞。我們經(jīng)常遇到數(shù)據(jù)結(jié)構(gòu)之間的互相調(diào)用和嵌套,很多人都喜歡使 用指針(又是指針)做這項工作,因為它象全局量一樣:直接、方便、快捷。但是我因為個 人水平的問題在使用指針時總遇到一些不便,比如,易于出錯,而且一旦出錯就是很嚴重 的當機,程序非法退出。要知道現(xiàn)在每啟動一次計算機仍然是很耗費時間的。還有另外一 個問題,那就是指針不能存盤,我必須另外制作一套程序?qū)Y(jié)構(gòu)的存盤做特殊處理。所以 我一般使用索引ID號來代替指針。具體的使用是這樣的:數(shù)據(jù)庫結(jié)構(gòu)是以數(shù)據(jù)元素為單位 的數(shù)組,數(shù)據(jù)元素的索引(ID)就是它在數(shù)據(jù)庫數(shù)組中的下標。所有其它結(jié)構(gòu)對該數(shù)據(jù)元素 的保存都只保存了該元素的ID號。指針只在從數(shù)據(jù)庫根據(jù)索引得到元素時使用。這樣的好 處很明顯,ID號是整型數(shù)據(jù),可以通過該值的范圍判斷出是否有效,只要在其有效范圍內(nèi) ,再通過指針取值肯定是有效的,幾乎跟本不會出現(xiàn)非法指針的現(xiàn)象。
程序開發(fā)計劃 有了要做什么和怎么做,下面也就是需要什么時侯做和由誰來做了。 一般我們都會制定一系列的里程碑。比如演示版,原型版(體驗版),測試版和正式版 等。在這之間也可能會有其它什么版本。然后制定完成每一個版本所需要的時間,主要內(nèi) 容和驗收標準。除此之外,我們還有: 程序中每個模塊的制作時間的表格, 每個程序員分工的說明,工作量說明。 編程過程中可能出現(xiàn)的風險和問題的說明。
在寫完總計劃以后,我們還會有更加詳細的月計劃,階段性計劃,我們還會寫程序進 度表。我們所做的一切都是為了游戲程序可以按時完成。 影響開發(fā)時間的主要因素有很多: ·資金因素:這些錢至少應該支持到我們可以把游戲做完,而且能夠把我們的游戲向 市場推出; ·市場因素:是否是銷售旺季,是否有同類產(chǎn)品產(chǎn)生競爭,本類型,題材的游戲是否 還受歡迎; ·策劃因素:游戲的復雜性有多大,內(nèi)容有多少,策劃的進度是怎樣的; ·美術(shù)因素:美術(shù)的制作進度是怎樣的,何時可以提供需要的內(nèi)容; ·技術(shù)因素:用戶的計算機硬件是否已經(jīng)準備好。我們的技術(shù)是否會過時,開發(fā)新技 術(shù)的風險會有多大。還需要制作哪些外圍工具; ·人員因素:程序員水平如何,配合狀況如何。
程序設計人員需要仔細考慮這些因素,才能根據(jù)具體情況制定比較完善的程序制作計 劃。 但是,很不幸,我從沒有按時完成一個游戲,所以我目前尚不知道妨礙我們按時完成 進度的到底是計劃不夠好還是執(zhí)行的人不夠努力??傊谧鲇媱潟r一定要留有余地,而 且我的經(jīng)驗是我們完成游戲的時間一般比計劃的要多20%到30%??墒侨绻凑者@樣的計劃 去做游戲的話,一定會把投資人嚇跑的。于是在現(xiàn)實生活中這種狀況就變成了“先斬后奏 ”,寫報告的時侯說時間很短,而到時侯我們就因為某些原因不得不拖期,反正錢也花了 這么多了,必須再增加投資而把它做完。所以無論是直接制作游戲的人或者投資制作游戲 的人,我都希望他們可以對這件事有個比較客觀的了解。也許在將來制作游戲可以不必再 加班,制作計劃也可以按時完成,但是至少在我們這里,還必須隨時面對它們,依靠我們 頑強的毅力和堅忍不拔的精神把游戲做出來。
1.5 制作流程 等到游戲計劃寫完,并得到通過之后,我們便開始進入游戲制作的時間。其實游戲的制作可能從很早就開始了,比如早期的技術(shù)探討和準備。而我在這里想再說得稍微遠一些,遠離現(xiàn)在的題目:程序,而轉(zhuǎn)向整個游戲的制作。 整個游戲的制作過程一般可以分成三個階段。策劃階段,制作階段和調(diào)試階段。它們之間的關(guān)系一般應該是在策劃階段后期,開始程序和美術(shù)的制作,在制作后期開始游戲的調(diào)試。它們?nèi)叩臅r間比應該是3:4:3。我認為這是一個理想狀態(tài),但是我們往往達不到理想狀態(tài),典型的情況是游戲策劃遲遲不能定稿,我們不得不在一邊設計游戲時一邊制作,而到了后期,制作又很難按時完成,必然壓縮調(diào)試時間,最后只好倉促地發(fā)售游戲。 它們?nèi)叩臅r間變成了4:6:0。我所說的這種狀態(tài)當然只是一個極端,但是我想在我們 目前所開發(fā)的游戲中或多或少都有這方面的問題。 造成這個問題的原因也有很多: ·游戲立項不規(guī)范:人們不知道為什么要做這個游戲; ·策劃設計工作準備不足:人們不清楚該做什么、什么時侯做、誰來做; ·制作人員水平有限,缺乏經(jīng)驗:自己做不好,或做不快,甚至都不知道該怎么做做? ·資金:沒有錢誰會做呢? ·項目缺乏管理:各部門缺乏協(xié)調(diào),大家群龍無首,缺乏溝通,最后大大降低士氣和工作熱情。 但是無論這些因素都是些什么,以及它將如何嚴重地影響我們,我都覺得只要有一樣東西就足以支持我們把這個游戲做完。這就是我們的堅持不懈的努力。現(xiàn)在,我們在市場上的游戲質(zhì)量都不算很好,但是我要說,只要它被做出來了,就是一個成功的作品。我不管游戲玩家們對我這句話有何看法,我只是認為,在那些作品的背后一定有一個或一些人在努力著。也只有他們才能真正體會制作游戲的艱辛。而且,只要他們還能繼續(xù)做下去,經(jīng)驗會逐漸增加,配合會更加默契,游戲也一定會越做越好。 游戲程序的制作過程與游戲的模塊劃分有關(guān),大概可以分成三個階段:工具制作階段,底層制作階段,游戲制作階段。 底層制作可能是最開始進行的階段,有可能與策劃階段同時,甚至更早。因為這個部分與具體游戲無關(guān),而可能只與游戲所要運行的平臺和我們所使用的開發(fā)工具有關(guān)。我認為,開始的時間應該越早越好。游戲程序員在技術(shù)上的準備如果越充分,制作起游戲來就會越順利。 在策劃大綱基本上完成后,也就是游戲的類型,模式基本上固定之后,就可以開始游 戲工具的制作了。游戲工具一般是提供給美術(shù)人員,策劃人員使用的(當然也有自己使用的),所以在用戶界面上應該多多聽取他們的意見。這些天天與我們在一起的用戶,如果我們都不能好好對待,那就更別提我們游戲的用戶了。 游戲本身程序的制作是最耗費時間和精力的地方,如果說底層程序和工具都可以把重點在結(jié)構(gòu)化和優(yōu)化上,這部分程序正好相反,我們通常做不到這些。因為我們在寫這部分程序的時侯,總是已經(jīng)精疲力盡了,而且我們的代碼已經(jīng)很長很長,一旦發(fā)現(xiàn)了程序錯誤,能找到就很不錯了,更別提把它改得漂亮一點。再加上游戲策劃隨時提出的一點小改動,就可能要改變我們整體的數(shù)據(jù)結(jié)構(gòu),做大的改動幾乎是不可能的。所以到了游戲制作后期,把程序員叫作鐵匠更合適一些,他們在到處打補丁(注意,千萬別在寫游戲底層的時侯也干這種事)。 在游戲制作的中后期,每當程序員一睜開眼,進入眼簾的就是滿是Bug和缺乏功能的游戲,這樣的一個程序如何才能變成游戲中真正需要得程序呢?無論當初程序計劃制定得多么好,在這時候都顯得有些不中用。但這并不說明計劃沒用,而是需要我們把計劃變成我們真正需要的東西,這就是每日工作計劃。名字聽起來很正式,其實并沒有什么特殊的格式,僅僅是把我還沒有做出來的游戲的內(nèi)容一條條地寫出來,不必區(qū)分什么模塊和內(nèi)容的多少,只要手寫而且自己看得懂,然后貼在機箱上,要保證隨時看到。這些內(nèi)容我可能在一個星期后也沒做,但是它會時時提醒我還有什么沒有做。隨著時間的推移,有些項目已經(jīng)完成了,又會有新的內(nèi)容寫進來,而等到你最后積累出一大疊這種計劃單時,你會發(fā)現(xiàn)原來游戲已經(jīng)做完了。 這種辦法我一直在使用,有時侯真覺得自己沒有長進,做事一點計劃也沒有,可是它就是這么有效,你不必有什么經(jīng)驗,也不必整天面對著程序發(fā)呆,每天只要考慮如何把下面的內(nèi)容做好就可以了。 不過我還是希望以后的程序員不必這樣寫程序,如果所有的人都能夠每天按照預先制定好計劃完成工作,也不需要加班,最終做出的游戲又很不錯就好了。希望這不是永遠的夢想。 1.6 程序調(diào)式 我在前面曾經(jīng)說過,我不會使用SoftICE來調(diào)試程序。這證明我并不是一個很聰明的程序員。這也證明我在調(diào)試程序時會遇到更多的困難。讓我每天在匯編代碼和死機中漫游是件很痛苦的事,所以我總在想如何才能擺脫它們。 如果我不能很快地找到這些難纏的Bug,我能否在一開始就避免它們呢,甚至減少它們出現(xiàn)的機會呢?答案是肯定的。 死機(在Windows系統(tǒng)中有時是"非法指令"錯)錯誤很主要的來源來自指針,但這并不是說我們就不能使用指針。恰恰相反,我們在很多地方都使用指針,而且出錯的機會很少。原因很簡單,那是因為我們在使用被保護的指針。如果你能證明你所使用的指針永遠都指在合法的位置上,它還會出錯么?所以我們在分配空間時大多使用靜態(tài)空間和連續(xù)空間。所謂靜態(tài)空間就是數(shù)組,連續(xù)空間就是指針數(shù)組。我們通過數(shù)組的下標來限制指針的讀取,這樣非常方便也有效。 封裝也是減少錯誤的很好辦法。我們把對內(nèi)存的分配和釋放封裝起來,把對全局量的讀取封裝起來。在查找問題時就可以很快地縮小可疑對象。 對函數(shù)的返回值和錯誤代碼的認真對待也可以讓我們飛快地找到問題所在。很多人剛開始編程時都認為程序很短小,沒必要寫那么復雜的錯誤處理信息,也沒必要判定函數(shù)的返回值。而我則恰恰相反,無論對待多么簡單的程序我都會做非常詳盡的錯誤處理。我甚至把顯示錯誤的對話框?qū)懺诘讓映绦蛑校怨╇S時調(diào)用。假如出現(xiàn)了錯誤,我就可以迅速知道錯誤發(fā)生的位置以及發(fā)生錯誤的大概原因,我的程序還可以正常地退出,不是繼續(xù)執(zhí)行而造成死機。 使用調(diào)試信息可以幫助我在Debug版中獲得更多的有用信息。比較常用的有Assert()和OutputDebugString()。在錯誤剛剛發(fā)生時就攔截它要比它造成了更嚴重的影響后要好。 代碼寫得好看一些也有助于查錯。假如你在一行中寫進好幾句代碼,你將如何逐行跟蹤呢?我們?nèi)绻押瘮?shù),成員函數(shù)寫得規(guī)范對稱,雖然麻煩一些,但別人在使用時就會輕松多了。 這些辦法都是在我們編寫代碼時需要注意的內(nèi)容,我想如果你在準備開始編程時就對此有所準備,那么到你編程結(jié)束時應該能夠節(jié)省不少調(diào)試錯誤的時間。但是Bug是一定會有的,無論你在當初如何注意,因為我們畢竟是人,會犯錯誤。我想任何人都有過把恒等號(雙等于號)寫成賦值號(單等于號)的經(jīng)歷吧(好在在Visual C++ 6.0中有這方面的警告了)。沒有別的辦法,我們?nèi)匀恍枰鎸λ罊C。一般的指針錯誤只會導致"非法指令"或"未知的意外"錯誤,除非你向一個不知名的地方寫了大量的內(nèi)容(比如幾百K以上),一般還是可以安全地退到系統(tǒng)中的。這得非常感謝微軟,Windows95/98還是要比DOS好??墒侨绻闼L問的是系統(tǒng)區(qū)域,或申請了與硬件有關(guān)的調(diào)用,比如在DirectDraw的GetDC()中設置了斷點,程序就會直接退回系統(tǒng)或干脆死機。 這個時侯就全憑我們自己了。但是也不要害怕,因為,我們所面對的僅僅是一些代碼而已,我們有一個最為嚴格和偉大的力量:邏輯。這可能是程序員唯一能夠全心全意依靠的力量了。所以請大膽地使用它。 如果程序出錯,一定有它的道理。哪怕運行一千次只出現(xiàn)一次也是如此。所以我們改正錯誤的過程實際上也就是找到錯誤的過程。如何找到錯誤呢?可以試著用下面的方法: 屏蔽法:把一些代碼去掉,再看錯誤是否消失了,如果已經(jīng)消失,那么錯誤可能在剛才屏蔽掉的代碼中,再屏蔽掉另外的代碼,如果錯誤又出現(xiàn)了,則錯誤肯定在這段代碼中。要注意的是,這樣做不一定能夠找到錯誤,因為,有時侯錯誤是由兩段代碼或多段代碼相互影響造成的。而且有的時侯我們找到的地方可能并不是真正出錯的地方,你只找到了錯誤被引發(fā)的地方,而引發(fā)錯誤的地方還需要我們再仔細找。比如,一個指針指錯了地方,那么我們會發(fā)現(xiàn)出錯的地方是引用指針的地方而不是寫入指針的地方。這時侯就體現(xiàn)出封裝變量的用處來了,我們可以很快查出這個指針到底在哪被修改了。 如果上面的方法不能奏效,而且錯誤是在我們剛剛修改了代碼造成的,那么可以使用比較法,找到原來版本的文件進行比較。所以我們需要經(jīng)常保存?zhèn)浞莩绦?,一方面是為了保證程序不丟失,另一方面也會增加找到錯誤的機會。尤其是當我們到了編程的后期,我們的程序大都編完,正處于修改的狀態(tài),而時間又特別緊迫。如果一旦出現(xiàn)了錯誤,我們只要比較一下新舊版本,就很容易找到錯誤。 如果你跟蹤了半天也沒有結(jié)果,幾網(wǎng)下去一條魚也沒撈上來,而且必須在全屏狀態(tài)運行,不能單步跟蹤,每執(zhí)行一遍程序就死機,又需要重新啟動,這又該怎么辦呢?那也不要擔心,我們還有最后一招:輸出Log文件。在每一個需要監(jiān)視的地方,增加一段程序,向磁盤文件輸出一段文字,如果程序運行到這里,該文件就會多出一段文字,如果程序運行到這里之前就死機或退出了,那么出錯的地方就在這段文字和上段文字之間。雖然這樣做比較耗費時間和精力,但是基本上能夠找到出錯的地方。找到之后,我們再去找出錯的原因吧。 但是萬一你現(xiàn)在還是沒有找到錯誤該怎么辦呢?那也不要著急,我所遇到的找到錯誤的最長時間是整整一天,有的人會長達3天,但是我目前尚未遇到查找單個錯誤的時間超過一個星期的。如果你尋找錯誤的時間還沒有達到這個長度,就不要急于認為這個錯誤你是永遠找不到的。 其實最難找的錯誤并不是這些錯誤,因為這些錯誤是確實存在的,所以我們改正它只是時間問題。而比較難以調(diào)試的錯誤并不是每次運行都出現(xiàn)的,有的錯誤需要特定的環(huán)境和條件,比如只在某臺計算機上出現(xiàn),有的錯誤則需要特定的操作,比如打開某文件,再關(guān)上,有的錯誤則干脆需要運行一定的時間才可能出現(xiàn),比如運行一個小時。這時侯我們的工作才真正艱巨起來。 這時侯,也往往是開發(fā)的最后關(guān)頭,最緊張的時侯,如果出現(xiàn)了錯誤,將使我們直接面臨一個最嚴峻的問題,到底是發(fā)行時間重要還是修改程序錯誤重要。我姑且不談到底誰重要。關(guān)鍵是如何解決這些錯誤。這里你可以看到,把錯誤消滅于未然是多么重要。我們工作的重點現(xiàn)在轉(zhuǎn)向了重現(xiàn)錯誤。使用出錯的計算機,頻繁重復執(zhí)行某些有關(guān)的操作,隨時存盤以保存最近時的信息,都是我們常用的辦法。 有的時侯重新啟動一次計算機,重新編譯所有代碼,更換一些驅(qū)動程序,都可以解決問題。但是有些問題我必須說明: 第一,要以負責任的態(tài)度來解決問題。如果你發(fā)現(xiàn)有一個地方很明顯不會造成錯誤,但是修改一下之后錯誤確實消失了,這時侯千萬要小心,因為這個錯誤可能這樣被你又隱 藏起來了,在以后的某個時侯(可能是最后的調(diào)試期)再次出現(xiàn),這時侯它將會更加隱蔽。 第二,不要動不動就認為是編譯器,驅(qū)動程序,操作系統(tǒng)或計算機硬件有問題,而不 下功夫去尋找錯誤。這些東西確實會有問題,但它們出錯的機會比起我們自己要小得多, 我覺得初級程序員還很難發(fā)現(xiàn)它們的錯誤。有問題還是先從自己那里著手吧。
1.7 代碼優(yōu)化 我對代碼優(yōu)化的研究并不深,歸根結(jié)底是因為我并不是個很聰明的程序員。我曾經(jīng)見 到過一本非常好的書,名叫<圖形程序開發(fā)人員指南>,英文名是Michael Abrash‘sGraphics Programming Black Book Special Edition。說它好有兩個原因:第一,這本 書從頭到尾講得都是程序的優(yōu)化;第二,我看不懂。我想對于那些高級程序員來講,他們 可能就根本寫不出有錯誤的代碼,或者至少他們對于防止程序錯誤很有一套,總之,程序 的調(diào)試對他們來講根本就不是問題,所以他們有的是時間來研究程序的優(yōu)化。而我所能講 的顯然不能與他們相比,僅僅是非常初步的內(nèi)容而已。 最優(yōu)化的代碼就是沒有代碼。 我忘了這是誰曾經(jīng)說過的話。但是我覺得很有道理。有時侯我們把那些外國人想象得 多么了不得,其實他們大多只是把這句話應用了一下而已。想一個巧妙的辦法有時要比節(jié) 約幾個指令周期有效得多。但是這與我們具體所寫的代碼和人的經(jīng)驗有關(guān),我們很難只用 幾句話就把規(guī)則說清楚。要知道,游戲的速度有時比游戲的效果重要,如果你對當前我們 最需要什么樣的游戲有所了解,做起決定就會容易得多。 如果我們真的需要對某些代碼進行實質(zhì)性的優(yōu)化,那么首先我們要搞清楚哪里最浪費 時間。我們常用Profile,VTune或自制的時間計數(shù)器來測量時間。而往往最浪費時間的代 碼大都很少,多是大量的循環(huán)最占用時間。 優(yōu)化的級別也有區(qū)別:算法級,語言級和指令級。 體現(xiàn)一個程序員水平最重要的地方就是算法。一個好的算法可以使用非常少的代碼就 實現(xiàn)原來很復雜的操作。而它也是很難做到的。尤其這些算法經(jīng)常與負載的狀況有關(guān),所 以需要比較和測試才能有好的效果。 語言級優(yōu)化就是我們采用較少的C語言代碼來代替冗長的。比如使用臨時的指針來代 替多級的成員讀取。把某些賦值語句放到多重循環(huán)的外面,使用inline函數(shù),使用指針或 引用代替結(jié)構(gòu)賦值,使用指針的移動代替內(nèi)存拷貝,把初始化操作放在一開始而不是循環(huán) 之中。它所遵循的原則就是“無代碼”原則,減少需要執(zhí)行的語句是提高速度的最直接的 做法。一般的程序員只要做到這一層就應該可以實現(xiàn)比較明顯的優(yōu)化效果了,這樣的程序 比較簡捷,運行效果也比較穩(wěn)定。 指令級優(yōu)化則要深入得多,我對這項工作也并不十分擅長。這里所要用的語言一般是 匯編語言,調(diào)試和測試也比較復雜,程序不太容易看懂,也更容易出錯,有時與硬件有關(guān) 。但是它所能實現(xiàn)的效果可能是一般人所不能實現(xiàn)的。所以這種方法一般被高級程序員所 使用,所針對的代碼數(shù)量應該比較少,正是刀刃上的部分。這樣的優(yōu)化是以指令周期做為 單位的,所以千萬要注意,不要讓我們費盡心機所做的優(yōu)化效果,被另外一些很低效的C 代碼給抵銷了。 優(yōu)化的內(nèi)容一般有: 代碼替換:使用周期少的指令代替周期長的指令。比如使用左移指令代替乘數(shù) 是2檔倍數(shù)的乘法。使用倒數(shù)指令(如果有的話)代替除法指令。這要求我們對80x86的每 一條指令都有很熟悉了解; 減少分支預測:這是pentium 以上CPU特有的功能,它會在執(zhí)行該指令前預讀一 些種 令,但是如果有分支就會造成預讀的失效; 并行指令:這是pentium 以上CPU特有的多流水線的優(yōu)勢。兩條(或多條,在pentiumpro以上)參數(shù)無關(guān)的指令可以被并行執(zhí)行; MMX指令:在處理大量字節(jié)型數(shù)據(jù)時我們可以用到它,一次可以處理8個字節(jié)的數(shù)據(jù); 指令的預讀:在讀取大量數(shù)據(jù)時,如果該數(shù)據(jù)不在緩存里,將會浪費很多時間, 我要提前把數(shù)據(jù)放在緩存中。這個功能大概要在pentium II的下一代CPU Katmai中才會出 現(xiàn); Katmai指令:想一次處理4個單精讀浮點數(shù)么?那就使用Katmai CPU 中的有關(guān)指令吧 在優(yōu)化時要注意的問題有: 第一,不要本末倒置。先優(yōu)化大的內(nèi)容再優(yōu)化小的部分。這樣我們才總能找到最耗 費時間的地方而優(yōu)化它; 第二,要經(jīng)常比較。需要對每一種可能的方法進行比較,而不能只聽信書上寫的。 奇跡經(jīng)常出現(xiàn)在這里; 第三,要在效率和可讀性上掌握好平衡,不要光要求速度而不管結(jié)構(gòu)如何,最后造成 隱藏的錯誤; 2 游戲?qū)嵺`討論 現(xiàn)在有很多人對國產(chǎn)游戲事業(yè)是又愛又恨的。愛的是希望能夠出現(xiàn)一批真正屬于我們 自己的好游戲,恨的是這些游戲制作者們太不爭氣,到現(xiàn)在也沒有做出來。我非常感謝他 們,因為假如沒有他們,也就沒有了我們。我也感到非常抱歉,因為我們的作品目前還不 能讓人們滿意。我想多說什么都是無用的,只有實事才能說話。前面我所說的大多是理論 上的內(nèi)容,是我們在幾年的游戲制作過程中總結(jié)出的經(jīng)驗。這些內(nèi)容有些可能是錯誤的, 因為我們還沒有從正面證明它,有些對我們至今仍然只是個美好的愿望,我們自己還沒有 真正做到。所以請大家在閱讀時針對自己的情況進行取舍。 下面我利用我們曾經(jīng)做過的一個游戲,具體分析它的制作過程和制作方法。希望借此 為那些關(guān)心游戲制作的人提供盡可能多的材料,讓他們了解得多一些。也為那些有志于游 戲制作的人提供一些經(jīng)驗和教訓,讓他們少走一些彎路。要說明的是,我們在處理某些問 題的時侯,所使用的方法很可能是非常普通的,甚至是笨拙的,別人看來可能有更好的辦 法。但是我不想與各位爭論,假如您有什么更好的想法和辦法,就把它用到你的游戲中去 吧,我希望每一個喜愛游戲制作的人都能夠制作出更好的游戲來。 2.1 制作概況 <赤壁>作為瞬間工作室成立以來的第一個作品,是1997年7月上市的,這個版本稱作 標準版。其后增加了網(wǎng)絡功能,修改了一些Bug,增加了一些游戲事件,被稱作增強版, 增強版于1997年底上市。在此期間,我們又制作了日文版,韓文版和繁體版,又為國內(nèi)的 OEM廠商制作了相應的版本。據(jù)說<赤壁>全部的銷售量超過了十萬份,其中零售量超過一 萬五千份。這是與前導公司所有員工的努力分不開的。 <赤壁>的策劃工作開始于<官渡>制作的后期,1996年6月。而程序開始的時間要晚一 些,在1996年9月。那時侯我們碰巧見到了另一部國產(chǎn)游戲<生死之間>的早期版本。這給 我們的震動非常大,因為這兩個游戲的類型比較接近,而我們才剛剛開始這個游戲的制作 而已。從程序開始動工,到第一個版本發(fā)行,一共9個月,先后參與程序編寫的程序員有5 人,總的工作量大約40個人月。程序代碼的總量約為90000行,2.6MB。大部分用C++編寫, 少部分由C和匯編編寫。我們使用Microsoft Visual C++ 4.0編譯<赤壁>標準版, Microsoft Visual C++ 5.0編譯<赤壁>增強版??蓤?zhí)行文件大小約為500多KB。 <赤壁>的工作進度如下: 程序設計期:1996年9月初至1996年11月底。 底層制作期:1996年12月初至1997年2月底。 游戲編寫期:1997年3月初至1997年5月底。 游戲測試期:1997年6月初至1997年6月底。 日文版: 1997年8月。 OEM版: 1997年9月。 韓文版: 1997年11月。 繁體版: 1997年12月。 增強版: 1997年12月。 需要說明一點:那就是我們的所有程序全部都是自己完成的,沒有使用任何其他人其 他公司提供給我們的代碼。要知道,我們公司當時還沒有能力去購買國外的游戲引擎,而 我個人連SoftICE都不會使用,更不要說跟蹤研究別人的代碼了。我們對別人的學習方式 非常簡單和直接,就是觀察。通過觀察猜測它所使用的方法,然后考慮自己如何把它實現(xiàn) 。這可能是我想到的最笨的一種方法,如果一個程序員能力強,在制作游戲以前,詳細分 析了解別的游戲的算法我想一定是非常有用的,他在制作游戲時一定可以節(jié)約不少走彎路 的時間。 另外,我們對<赤壁>的測試時間也是很短的,在程序基本穩(wěn)定之后,我們大概只剩 下兩個星期左右的時間。所以有很多Bug。 2.2 模塊劃分 赤壁的程序分成五個大的部分和19個模塊: 顯示模塊 戰(zhàn)場顯示模塊 分為通用顯示底層和游戲戰(zhàn)場顯示。 通用顯示底層是基于DirectDraw的一套函數(shù)。 有關(guān)內(nèi)容請詳見DDApi.h, DDCompo.h。 游戲戰(zhàn)場顯示是根據(jù)游戲單元的類型,位置,狀態(tài),動動畫幀等數(shù)據(jù)將單元位圖以適 形式顯示在戰(zhàn)場的適當?shù)奈恢蒙?。要顯示的內(nèi)容有:地形,單元(士兵/建筑/將領(lǐng)),攻擊 效果,魔法效果,遠程武器物體,陰影等。 主要功能有:對圖素的壓縮和讀取,圖像的顯示,單元元歸屬顏色的轉(zhuǎn)換,邊界 舨茫 遮擋關(guān)系,縮略圖顯示和響應,屏幕移動,陰影遮擋判斷,攻擊效果,魔法效果,遠程 武器物體的顯示和移動。 有關(guān)內(nèi)容請詳見CBDraw.h, 顯示單元的位圖 CBDrawM.h, 顯示特殊效果,比如水水? CBDrawOT.h, 顯 示特殊物體。 CBMini.h, 顯示縮略圖。 CBMap.h, 圖素的操作。 CBShadow.h, 陰影的計算和顯示。 CBOther.h, 特殊效果的顯示和計算算? 界面顯示模塊 根據(jù)游戲設計需要,在顯示器相應的位置上顯示游戲的的各層界面?
它分為兩個部分:界面底層部分和游戲界面部分。 界面底層是屬于底層部分的通用函數(shù)庫。它包括按鈕,,對話框,滑塊,檢查
框等界面元素的實現(xiàn)。 有關(guān)內(nèi)容請詳見DDBitmap.h, 顯示位圖的基覽唷? DDButton.h, 按鈕。 DDCheck.h, 檢查框。 DDList.h, 列表框。 DDMenu.h, 菜單。 DDScroll.h, 滾動條。 DDText.h, 文字。 游戲界面主要有:游戲主菜單,新游戲菜單,讀取進度度菜單,保存進度菜單,網(wǎng)絡?
擇菜單,系統(tǒng)設置菜單,任務提示菜單,結(jié)束菜單,錯誤處理對話框等。 有關(guān)內(nèi)容請詳見Interfac.h, 提供基本的游舷方 面接口函數(shù)。 Interfa1.h, 處理所有的按鈕Buttoon信息。 Interfa2.h, 處理所有鼠標操作發(fā)吵齙 消息。 net_face.h, 網(wǎng)絡部分界面。 Marco.h, 所有按鈕的消息ID。 CBprompt.h, 游戲戰(zhàn)場中屏幕右方行畔⒌南允盡? CBAarray. h, 對游戲元素的查詢。 DDComUn.h, 針對游戲中下達命令時時的命令組? 過場動畫模塊 顯示公司標志,制作群,歷時回顧,片頭,片尾和過場場動畫? 它有兩個部分,第一 是播放視頻圖像,第二是調(diào)用其它它進程? 有關(guān)內(nèi)容請詳見CBAvi.h, 視頻圖像的 播放。 Mciapi.h, 播放AVI文件的底層函數(shù)數(shù)? VCMApi.h, 高效率的播 放AVI的底層層 CBGame.h, 播放結(jié)尾。 Interface.h, 程序狀態(tài)的轉(zhuǎn)換。 單挑顯示模塊 武將單挑時出現(xiàn)的專門畫面。本部分與原始設計有出入入,原始設計中可以對單挑進?
操鰨罄瓷炯醭莆徊シ乓歡蜼ideo。有關(guān)內(nèi)容請詳見CBAvi.h。 控制模塊 鼠標控制模塊 根據(jù)鼠標的位置設置鼠標的形狀,對鼠標的操作對響應應單元發(fā)出命令? 主要內(nèi)容有 :鼠標點擊檢測,目的地模式,命令構(gòu)造,,可建造區(qū)域判定,鼠標形狀變換。 鼠標點擊檢測,主要判斷鼠標點擊的位置是否在某個單單元上或地形上。詳見CBMouse.h。 目的地模式,主要控制鼠標選擇了單元(命令主體)后,,可能對單元下達的命令的模?
,根據(jù)不同的命令可能需要不同的目的地類型和命令參數(shù)。詳見CBCtrl.h。 命令構(gòu)造,通過鼠標的點擊選擇或按下某個,命令按鈕鈕,構(gòu)造出具有命令主體,命?
ID和命令客體的命令,放到命令隊列中,供執(zhí)行。詳見CBCtrl.h。 可建造區(qū)域判定,在建造建筑時需要判斷哪里可以建造造哪里不可以, 并且顯示出? 。 詳見CBBuild.h。 鼠標形狀轉(zhuǎn)換,鼠標移動到某個界面或某個單元上時,,或處于命令構(gòu)造階段時,需?
對鼠標的形狀做一定的改變,以顯示當前的操作狀態(tài)。詳見CBMouse.h。 命令處理模塊 根據(jù)鼠標和人工智能發(fā)出的命令,傳送給對象單元,并并將其轉(zhuǎn)化成為單元的相應狀?
序列。 根據(jù)單元狀態(tài),判斷單元狀況,更改單元的狀態(tài)。 詳見CBCtrl.h, 命令的構(gòu)造保存和傳遞。 CBRun.h, 命令的執(zhí)行。 CBRDelay.h, 命令執(zhí)行時需要的一行 變量。 攻擊計算模塊 根據(jù)敵我雙方的攻防力量,計謀的實施和陣型計算每一一次打擊敵人生命的損失?
詳見CBRun.h, 攻擊計算。 CBZhenFa.h, 陣法計算。 行軍控制模塊 根據(jù)單元的位置,速度,目的地和地形數(shù)據(jù),計算行軍軍路線,設置單元的下一步。 見March_n.h。 網(wǎng)絡控制模塊 游戲數(shù)據(jù)在網(wǎng)絡上的傳遞,糾錯。模擬機的建立和管理理。詳見Network.h? 策略模 塊 君主策略模塊 計算機一方根據(jù)戰(zhàn)場雙方的力量對比和戰(zhàn)斗模式計算對對單元的生產(chǎn),對敵人的攻擊?
產(chǎn)生命令。 詳見TEmperor.h。 將領(lǐng)策略模塊 每一支部隊根據(jù)將領(lǐng)的屬性,士兵的狀況調(diào)整戰(zhàn)斗的方方式或判斷撤退? 詳見TGene ral.h。 本能策略模塊 士兵單元面對周圍的情況產(chǎn)生固定的基本反應。 詳見Tai.h。 Tbnbase.h, 人工智能中需要的數(shù)據(jù)據(jù)結(jié)構(gòu)? CBEyes.h, 人工智能與游戲主體結(jié)構(gòu)怪 間的接口。 文件模塊 資料數(shù)據(jù)文件模塊 地形和單元圖素的圖像文件,相應控制數(shù)據(jù)文件。單元元的各項屬性數(shù)據(jù)文件,操作?
數(shù)據(jù)文件,界面位置數(shù)據(jù)文件。 詳見CBData.h, 游戲中使用的單元屬性全局 數(shù)據(jù)結(jié)構(gòu)。 CBMap.h, 游戲中使用的地圖圖素,,單元圖素全局數(shù)據(jù)結(jié)構(gòu)? CBGame.h, 游戲中使 用的單元全局 數(shù)據(jù)結(jié)構(gòu)。 存儲數(shù)據(jù)文件模塊 存盤用數(shù)據(jù),記錄戰(zhàn)場上的所有單元的狀態(tài)和思考數(shù)據(jù)據(jù)? 詳見CBGame.h, 存盤。
其它模塊 文件封裝模塊 為減少程序使用的文件的數(shù)量,增加程序的安全性,將將大量的圖像文件和數(shù)據(jù)文件?
裝起來,供程序調(diào)用。 詳見L_Allbmp.h, L_Image.h, L_Save.h, L_Scan.hh, L_Text.h, Tools.h。 聲音模塊 背景音樂和音效。有混音和音量控制。 詳見DsWave.h, 播放WAV文件。 文字模塊 在非中文系統(tǒng)下顯示漢字,日文,繁體漢字和韓文。詳詳見puthz.h? 地圖編輯器 為使美術(shù)人員方便快捷規(guī)范地制做戰(zhàn)場地圖,提供專門門的地圖編輯器。同時為整個?
戲的文件系統(tǒng), 顯示系統(tǒng)做技術(shù)上的準備。 這是一個單獨運行的程序,詳見Mapedit。exe。 安裝程序 將游戲安裝和卸載。詳見Setup。exe。 2.3 游戲引擎 說到游戲的引擎,很多人都不知道它是什么,以為制作它有多么困難。引擎的概念 也很混亂,至少現(xiàn)在我還不知道它的確切定義。但我想如果一個東西要被稱作引擎,它應 該具有這樣一些特點: 它應該是由函數(shù)組成的。 它應該實現(xiàn)某項具體功能。 它應該是完整的。 它應該可以被重新使用。 從上面的要求可以看出,其實這就是作為底層程序的要求。我想沒有必要把引擎認 為是游戲的現(xiàn)成編寫工具,只要2改一下美術(shù)就是另一個游戲了。只要這些程序代碼將會被 我們應用在以后的游戲中,我們就已經(jīng)節(jié)約了很多的時間和精力。 下面我會說一下在<赤壁>的代碼里,哪些將被看作我們的引擎。實際上,這些部分 經(jīng)過一些修改后正在被我們應用到新的產(chǎn)品中。 顯示底層: 這是一套包裹在DirectDraw外面的函數(shù)。為了簡化在調(diào)用DirectDraw函數(shù)時的 復雜雜度? 們使用了一些缺省參數(shù)和內(nèi)部錯誤處理函數(shù)。建立了一個CDDSurface類庫, 使得對位圖的 使用更加簡單。詳見DDApi.h 在DDCompo.h中我們有關(guān)于游戲鼠標的一套操作。在屏幕獨占模式中,Windows 標準準鼠? 有時顯示會不正常。于是我們自己制作了鼠標的顯示方法。方法很簡單,在 每幀讀取鼠標 的位置,然后在該位置上顯示一張位圖。 在赤壁里面,我們沒有使用雙緩沖區(qū)的模式,而是只更新某個特定的區(qū)域。它 的優(yōu)優(yōu)點? 當需要更新哪里的時侯就更新哪里,對于哪些在每幀中都只有小面積圖像需 要更新時是非 常高效的。比如在486上,<赤壁>的主游戲界面里的鼠標移動仍然是十分流暢的??上У?
是,在<赤壁>的戰(zhàn)場部分,它并沒有優(yōu)勢,因為基本上是需要全屏刷新的。 在未來的游戲制作中,因為計算機的速度越來越快,所以我們當時所使用的模 式恐恐怕? 得不太適用,雙緩沖區(qū)模式應該是主流方向。 多媒體底層: 主要包括聲音和視頻。我們使用了MCI設備來播放AVI,WAV,MIDI,CD AUDIO等 內(nèi)熱 。那 曾經(jīng)是我們在做上一個游戲的時侯完成的部分。但是它有很多缺點,比如不能同時播放 多個WAV文件,這對于我們制作游戲音效是很重要的內(nèi)容。 所以我們又使用DirectSound來播放聲音。這里的難點在于當我們需要播放很長 的撾 件時, 不能一次讀入,而需要建立新的線程按時裝入聲音。好在現(xiàn)在大部分游戲都使用CD Audio作為背景音樂,不需要WAV。 界面底層: 基于顯示底層之上的界面元素其實并不好做。因為我們總希望它的響應方式與 Winddows95 中相同。而大家在<赤壁>里看到的內(nèi)容就與Windows95有些不一樣。比如滾動條 (ScrollBar)對鼠標的響應就非常簡單,按鈕(Button)的反應也有所不同。但是好在它比 較簡單,易于使用。 在每做完一個游戲之后,我們都習慣要把某些東西整理一下,看看它是否可以在以 后被使用起來。而往往這些東西也都是需要不斷修改的。因為程序運行的平臺不一樣了, 它的用途也不一樣了,而我們的編程水平也不一樣了。但總之這些代碼被較為完整地保留 了下來,它必將是我們今后編程的基石。 2.4 關(guān)鍵討論 我剛開始編寫游戲的時侯總有一個想法,只要游戲的主要部分寫完了,游戲也就差 不多了。我也遇到過一些游戲制作組的成員,他們也大都是這樣的想法,認為只要把游戲 的演示版拿給別人一看,然后只要再投資讓美工畫一些畫,游戲就可以做完了。其實事情 并不想想象中那樣簡單。 在我看來,把游戲的大概樣子做完了,頂多占整個游戲的三分之一。另外的兩個三 分之一分別是整個游戲的制作和測試。 舉個簡單的例子,比如我們在演示版中通常只有一個兵種和一個戰(zhàn)場。游戲的顯示 效果可能很不錯。但是,真正在游戲中不會只有一個兵種的,每方都會有大概十種兵,又 會有三四方的敵人,這時侯你的顯示底層是否能夠勝任呢?內(nèi)存是否會占用太多呢?這時侯 還需要我們對其進行優(yōu)化和修改。連游戲的底層顯示部分都可能需要修改,更何況游戲中 還有更多的內(nèi)容呢? 下面我舉一些<赤壁>中的例子,這些都可能是極小的問題,但都是我們需要仔細考慮 的問題。在你準備開始制作一個即時戰(zhàn)略游戲之前,你是否曾經(jīng)考慮過這些問題呢?假如 你對這些問題有所了解,那么你就應該可以非常有把握地馬上開始制作游戲。如果沒有, 也沒有關(guān)系,因為這些問題我也沒有全都事先考慮過。 假如你有時間,可以對你自己的游戲多多考慮一下,這個游戲距離一個真正的產(chǎn)品, 到底還缺什么?還有哪些模塊和部分沒有做完?當你對兩者之間的差距有了一個明確的認 識后,也就不會擔心了。任何東西都是一點一點做出來的,只要按照你想做的內(nèi)容去做就 可以了。 程序狀態(tài)的轉(zhuǎn)換 我們在寫DirectX程序的時侯,總有一種偏見,那就是不希望Windows界面出現(xiàn)在我們的游戲里。于是什么都需要我們自己做。比如說窗口。因為窗口的刷新需要我們自己管理,就覺得沒有必要生成多個窗口了。這樣所有的窗口消息就必須在唯一的一個函數(shù)里實現(xiàn)。可是我們的游戲里有很多種不同的操作,比如界面,系統(tǒng)菜單,播放視頻等,這些內(nèi)容就都必須在這個地方處理。所以我們就引入了程序狀態(tài)這個概念。我們定義了一系列的狀態(tài),在每個狀態(tài)里,有固定的操作和響應,狀態(tài)之間的轉(zhuǎn)換也在特定的時侯進行。這樣我們就很容易把一些關(guān)系不大的內(nèi)容獨立開來,降低程序的復雜性。 其實我們在實現(xiàn)這一部分的時侯是很混亂的。你很難在代碼中找到所有狀態(tài)轉(zhuǎn)換的地方。但是它的實現(xiàn)很簡單,一般的規(guī)則就是程序的對稱性。有專門的裝入函數(shù)和釋放函數(shù),然后有顯示函數(shù),計算函數(shù),鼠標消息響應函數(shù),熱鍵響應函數(shù)。在內(nèi)部,需要結(jié)束本狀態(tài)時就發(fā)出一個狀態(tài)轉(zhuǎn)換的消息。在外部,只要在主程序的主循環(huán)和消息響應函數(shù)處針對不同的狀態(tài)執(zhí)行它們不同的函數(shù)就可以了。 這里的關(guān)鍵在于狀態(tài)的轉(zhuǎn)換。因為狀態(tài)在轉(zhuǎn)換中一定會釋放和申請大量內(nèi)存,如果有的內(nèi)存沒有釋放,轉(zhuǎn)換次數(shù)一多就會出現(xiàn)問題。如果我們把狀態(tài)轉(zhuǎn)換的地方寫好了,程序看起來也非常干凈整齊,Bug也會比較少。 現(xiàn)在編寫游戲,需要裝入的圖量非常大,很有必要制作一個裝入時的畫面,并且顯示百分比。這是我們可能需要一個裝入中的狀態(tài)。遺憾的是<赤壁>并沒有實現(xiàn)這個,它的淡入淡出效果僅僅是效果,程序在執(zhí)行這部分時停止在這里。 有關(guān)內(nèi)容請詳見CBGame.cpp和Interfac.cpp。
執(zhí)行任務 一個士兵在接到我的命令之后,便開始了它的行動。它察看了一下它的命令,這是由兩個部分組成的。它先取出第一個部分,是行走。于是根據(jù)自己現(xiàn)在的位置和目的地位置先計算了一下路線,把自己當前的狀態(tài)設置為正在移動,然后根據(jù)計算好的方向和自己的速度把自己移動到一個位置。如果這個位置已經(jīng)到達目的地了,它就停止行走。又察看了一下剩余命令的部分,是攻擊。于是它拔出刀砍向附近的一個敵人。敵人死了么?它不停地問自己。如果敵人真的"哇"的一聲倒下了,它就得意地站在一旁,微笑著。 這就是我們在戰(zhàn)場的一個角落所看到的一個士兵的表現(xiàn)。而實事上,在程序里我們也是這樣做的。 我們把每個士兵做為一個單元,獨立地處理自己的事物。我們把每個命令劃分得更加細致,而稱其為狀態(tài),執(zhí)行每個狀態(tài)時所需要的參數(shù)的執(zhí)行的步驟是最簡化的。每個命令都是由一個或多個狀態(tài)組成的。一個狀態(tài)滿足后就自動轉(zhuǎn)向下一個狀態(tài)。我們?yōu)槊總€狀態(tài)都編寫專門的開始,運行中,和結(jié)束代碼。這樣每個士兵都在獨立地按照順序處理自己的事務,一個個復雜的單元行動就變得有條有理了。 有關(guān)內(nèi)容請詳見CBRun.cpp。
陰影 有人說Warcraft做得好,有人說C&C做得好。我屬于前者一派,這不僅僅因為我對Warcraft的觀察多一些,而且有一個理由足以說明Warcraft在程序上比C&C更高一籌。那就是陰影。大家都知道Warcraft是雙層陰影,而C&C是單層。雙層陰影的好處是更加真實。在我們?nèi)ミ^一個地方之后,雖然我已經(jīng)看清楚了地形,但是這里敵人的活動,我們不應該永遠知道??墒且褑螌雨幱白兂呻p層,并不是只要加上一層顯示就可以解決問題的。 首先,增加一層陰影就增加了需要顯示的時間。時間對于即時戰(zhàn)略來講是至關(guān)重要的。它直接影響到游戲的表現(xiàn)效果和操作速度。多加一層陰影就意味著減少了我們增加效果的機會。在我們這里,雙層陰影占用了大約5-10%的顯示時間。 其次,增加一層陰影就增加了特殊的計算。一層陰影只需要一個二進制數(shù)組記錄哪里被打開,哪里被隱藏就可以了。而現(xiàn)在則不同,我們需要把那些已經(jīng)打開的陰影再關(guān)上。我們的做法是生成一個二維數(shù)組,每位表示一個圖素格子。當有一個士兵的視野可以打開這個格子上的陰影時,就把那里的計數(shù)器加一,離開時減一。當為0時則這塊地形被重新隱藏。 第三,陰影下的單元需要特殊的處理。當我的士兵離開敵人陣地的時侯,敵人附近被半透明的陰影重新覆蓋了,這時我們需要把正在那里活動的敵人士兵隱藏起來,而建筑不動。 這就是雙層陰影所帶來的。可是為了效果,我們不得不如此??雌饋磉€是達到了效果。 除了雙層陰影以外,陰影的邊界也是很重要的問題。我們不可能把陰影做得和刀切的一樣,而讓它必須和打開的地面相結(jié)合。于是我們必須要有一套貼圖,用于陰影邊界的各個方向。好在我們在這里利用了一個偷懶的辦法(當然是很巧的辦法,是Onefish想出來的)。我們采用了一個橢圓的貼圖,讓相鄰的橢圓相切,從而造成邊界。因為橢圓是沒有方向之分的,我們也就節(jié)省了一些貼圖的內(nèi)存和對使用哪張貼圖的復雜計算。 有關(guān)內(nèi)容請詳見CBShadow.cpp。
選中誰了 <赤壁>戰(zhàn)場里的格子是菱形的。這一點有多少人發(fā)現(xiàn)了?又有多少人知道它的代價?或許有人認為菱形的格子并不是個很好的注意,但我們恰巧在這里使用了它。當鼠標按下時我們需要知道它放在哪個格子里了。過去的經(jīng)驗是只要知道該點的位置和每個格子的寬度我們就可以很快地算出格子的序號。但是在這里不行,因為格子是菱形的,在一個矩形區(qū)域里的點不能被確定它到底屬于哪個格子。 怎么辦呢?我制作了一個二進制二維數(shù)組,大小是格子的寬和高。在這個區(qū)域里,如果是屬于矩形內(nèi)接菱形區(qū)域內(nèi)的點設置為1,否則為0。當鼠標在某個矩形區(qū)域里時,我就用這個柵格去判斷該點的位置上的數(shù)值是1還是0。是1則這個點在這個菱形格子里,否則不在。這個辦法是我遇到的比較迅速的一個辦法。
命令的產(chǎn)生 一個由游戲者發(fā)出的命令,一般有三個部分:命令主體,命令本身和命令客體。命令主體就是執(zhí)行命令的人。我想對這支部隊下達命令,這支部隊就是命令的主體,在游戲里就是我們用鼠標選中的一群士兵。命令本身是命令的標識。我想下達的是什么樣的命令,在游戲里,可能是我按下了某個命令按鈕。命令客體是命令結(jié)果的接受者,如果我選擇了攻擊命令之后,再選擇了一群敵人,那敵人就是命令客體。 如果所有的命令都這樣下達,那編起程序就簡單了,玩游戲的人也會累死了。我們常用的玩法是選擇了命令的主體后直接用鼠標右鍵點擊命令客體。而命令的產(chǎn)生就由命令主體和命令客體的類別來確定了。 這才是需要我們考慮的東西。 首先是選擇命令主體。當我們在戰(zhàn)場上鼠標一點,或圈了一個框,然后一松手,這時侯程序就開始算了。這里我要介紹一下<赤壁>的單元類型。我們把單元分成士兵,將領(lǐng)和建筑。士兵又有已經(jīng)組建成部隊的和尚未組建成部隊的。士兵的種類可以分成一般士兵,工人和船。我們的選擇規(guī)則是:組隊的士兵優(yōu)先于建筑。將領(lǐng)優(yōu)先于組隊的士兵。未組隊的一般士兵優(yōu)先于將領(lǐng),未組隊的船優(yōu)先于一般士兵,未組隊的工人優(yōu)先于船。另外還有特殊處理。按下Ctrl鍵則選中該組隊士兵的將領(lǐng),按下Shift鍵則把剛選中的士兵添加到已選中的士兵中。 其次,是選擇命令的客體。命令客體可分為地點和單元。地點有普通地點和資源地點。單元又分為敵人的和我方的。單元的種類有士兵,將領(lǐng)和建筑。士兵分為未組隊的和組隊的。士兵的種類有工人和一般士兵。建筑又分為一般建筑和資源建筑。我們需要根據(jù)命令主體和客體共同來判斷命令是什么。不同的組合所產(chǎn)生的命令可能可能是不同的。比如,如果我選擇了組隊的工人作為命令主體,又選擇了一個建筑作為命令客體。如果這個建筑是鐵礦,那么命令就是采礦。如果這個建筑是我方的建筑,則這個命令是修理。如果這個建筑是敵人的建筑,那這個命令就是攻擊了。 有關(guān)內(nèi)容請詳見CBCtrl.cpp。
眼睛函數(shù) 在游戲策劃編寫人工智能的時侯,總喜歡寫成這樣:“當匪兵甲發(fā)現(xiàn)敵人就在不遠處時就去攻擊。”而要把這句話變成程序,還需要不少的路要走。比如匪兵甲是如何發(fā)現(xiàn)敵人就在附近呢?首先必須要知道匪兵甲自己在什么地方,其次匪兵甲的視野有多大,也就是說多遠算作附近,第三敵人在哪里。這三樣東西匪兵甲是如何知道的呢?依靠的就是眼睛函數(shù)。 一般我們在編寫人工智能時都會把它作為一個比較獨立的模塊。因為它的主要功能就是判斷。它的出口同游戲者的鼠標操作一樣,發(fā)布命令。如果它的入口也很明確和簡單則這部分內(nèi)容的難度就非常小了??墒侨斯ぶ悄苄枰玫酱罅康男畔⒆鳛榕袛嗟囊罁?jù),而我們的主體數(shù)據(jù)結(jié)構(gòu)又可能寫得不很好,這時候就需要我們另外制作一套程序把游戲內(nèi)核與人工智能分開。這就是眼睛函數(shù)的用處。 編寫人工智能的程序員只需要寫出他想知道什么,然后交給游戲核心程序員,由游戲核心程序員從游戲核心數(shù)據(jù)中找到相應的信息,傳遞給人工智能。這樣編寫人工智能的程序員就可以根本不知道游戲核心數(shù)據(jù)是什么樣子就可以開始工作了。但是這也是臨時的辦法,如果我們可以把核心數(shù)據(jù)結(jié)構(gòu)寫得風雨不透那也不必如此勞神了。 有關(guān)內(nèi)容請詳見CBEyes.cpp。
命令隊列與網(wǎng)絡 本以為網(wǎng)絡編程很簡單,一寫下來才知道頭大。雖然這部分代碼不是我寫的,但是我也知道跟蹤一個10兆大的Log文件是什么滋味。網(wǎng)絡上的游戲通訊一般有兩個辦法:交換所有信息和交換重要信息。第一種方法是隨時把自己的所有數(shù)據(jù)都傳遞到另一端,另一端根據(jù)這些數(shù)據(jù)進行顯示和操作。它的優(yōu)點是不需要同步,對網(wǎng)絡狀況要求低,計算也非常簡單。缺點則是數(shù)據(jù)量太大,只傳遞少量數(shù)據(jù)還可以接受。所以這種方法多用于RPG游戲并 多在Internet上使用。第二種方法則只傳遞關(guān)鍵的數(shù)據(jù),計算機接到這些數(shù)據(jù)后對其進行計算,因為計算的結(jié)果是唯一的,所以可以保證網(wǎng)絡兩端一致。這種方法適用于那些數(shù)據(jù)量大的游戲,但它的編寫和調(diào)試就比較復雜,而且對同步的要求較高。我們的<赤壁>就是采用的這種方法(<西游記>用的是第一種方法)。 在這種網(wǎng)絡的聯(lián)接方式中,最重要的問題一個是同步,一個是結(jié)果唯一。只有同步我們才能保證在每一輪的計算中所有計算機上的初始狀況是統(tǒng)一的,只有結(jié)果唯一我們才能保證在執(zhí)行了一段時間后所有計算機上的狀態(tài)是統(tǒng)一的。這樣才能保證網(wǎng)絡游戲的正確性。 除了初始化等必要得操作以外,我們把游戲中的命令作為關(guān)鍵信息在網(wǎng)絡上傳遞。游戲者對戰(zhàn)場上部隊的任何操作,下達的任何命令都會被傳遞給其它的計算機。如果游戲者沒有操作,則我們制3在每一輪循環(huán)中傳遞同步信號。在這里我們遇到了一個DirectPlay中的問題,那就是信號并不是每次都會正確傳遞到對方那里的,盡管我們在DirectPlay中使用的參數(shù)要求系統(tǒng)必須在確認對方收到后才返回,可是它返回后對方依然沒有收到。關(guān)于這個問題我沒有查看有關(guān)文檔,也許有更好的解決辦法。所以我們自己制作了一套校驗確認的方法。 但是我們在本機上發(fā)布的命令傳遞到另一臺計算機上時一定會有延遲,這就不能保證初始狀態(tài)的一致。所以我們采用了輪回制。任何人發(fā)布的命令并不會馬上被執(zhí)行,而是先存儲到一個命令隊列中。在每次循環(huán)中對曾經(jīng)下達的所有命令統(tǒng)一進行發(fā)送,偵聽。當所有計算機都收到了所有計算機本輪要執(zhí)行的命令后,所有的命令開始被順序執(zhí)行。這樣在任何一臺計算機上本輪中所有命令的內(nèi)容和順序都是相同的,執(zhí)行時才能保證其結(jié)果的一致。 有關(guān)內(nèi)容請詳見CBCtrl.cpp, CBRun.cpp。
兵種的顏色 這大概是即時戰(zhàn)略游戲里最隱蔽的一個問題了。因為不做的人不知道,而做的人都心照不宣。在我們的游戲里,不同的君主用不同的顏色代表,屬于該君主的士兵則在它的衣服上賦予相應的顏色。這樣雖然兵種相同但是他們的顏色不同,這樣就可以區(qū)分敵我了。但是如果我為每一個兵種都制作一套圖素,那么就顯得有些浪費了,這會占用過多的內(nèi)存(我們這里所有兵種的圖素量約有10MB,如果有四種顏色就需要40MB的圖素)。誰都希望找到一個好的辦法節(jié)省這些內(nèi)容。 我們的方法很簡單,抽色。我們在調(diào)色板里的留出固定的位置,需要改變的顏色就按照順序存儲在這里。在顯示時,我們會根據(jù)傳遞進來的君主號為每張圖素的顏色進行判斷,如果顏色在這些區(qū)域里就根據(jù)規(guī)則偏移。這樣各種顏色的士兵就會顯示出來,而只需要一份圖素。 這種方法的缺點就是速度慢,畢竟需要對每個點進行判斷和處理。所以這部分也是我們針對MMX優(yōu)化的主要部分之一。 怎么樣?奇怪么?有關(guān)內(nèi)容請詳見CBDraw.cpp。
遮擋 我在設計游戲開始時就考慮過單元的遮擋問題,因為這曾經(jīng)是<官渡>遇到的問題之一。游戲單元在屏幕上的位置會經(jīng)常有所變動,但我們必須保證一點,即下面的物體總要把上面的物體遮住。但是我發(fā)現(xiàn)這個問題實際上在<赤壁>里幾乎不存在。因為在每一幀都是重新顯示一屏內(nèi)的所有信息,只要我每次從上向下畫圖就可以了。當然這是個極為極端的例子。我們?nèi)匀恍枰紤]遮擋的問題。比如,人被山擋住的問題。我們通常為了在顯示函數(shù)里顯得結(jié)構(gòu)化一些,都是先顯示地形,再顯示人物。那么當人物走到了山的上部時,就需 要山峰擋住人,可是這時侯山都已經(jīng)顯示完了,我們必須再次顯示山。而顯示山的時侯又不能夠顯示整個的山,我們必須劃分出顯示的區(qū)域,讓它可以顯示又不會遮擋別的地形。 調(diào)整這部分內(nèi)容可能占據(jù)了我制作顯示部分的絕大部分的時間。好在最后實現(xiàn)了,但我仍然在想是否有更好的辦法解決這個問題。 有關(guān)內(nèi)容請詳見CBDraw.cpp。 行軍算法 我們在制作<官渡>時沒有給士兵以一套完整的行軍算法,讓它去尋找一條到達目的地最近的路線,而在<赤壁>里想做一下嘗試。就自制了這樣一套算法。首先找到一條直線,然后圍繞擋住這條直線的障礙物搜索一條可以行走的路線。說起來簡單,做起來可很困難。這部分代碼寫了大約有四五個月。 當然它也仍然有缺點,比如如果在路上的障礙物內(nèi)部有空洞,那么就會卡住。但好在它的速度還算可以,一百個人同時走路時才會感覺到一點停頓。 本來我們打算在<荊州>里有所改變的,我們采用了世界上比較流行的行軍算法:A*算法。并且為它專門制作了一套底層,使它可以掛在任何一個地圖結(jié)構(gòu)里。 有關(guān)內(nèi)容請詳見March_n.cpp。
為什么有數(shù)量限制 又回到了老生常談的話題。編程序的人一看就可以知道,對士兵數(shù)量進行限止的唯一理由就是沒有使用鏈表。的確是我膽小的緣故。我沒有在游戲的核心數(shù)據(jù)結(jié)構(gòu)中使用鏈表,原因很簡單,怕出錯。我為每個游戲者開了200個單元的空間,也就是說每方游戲者都可以制造200個士兵和建筑。我想這個數(shù)量應該是夠了,因為如果真的每個游戲者都生產(chǎn)出這么多數(shù)量的單元,游戲也跑不動了。 使用數(shù)組不僅出錯的機會少一些,而且它對于查錯也很有幫助。在我們的早期測試版中,我讓游戲每隔10輪就自動存一次盤。存盤的內(nèi)容就是簡單的把這些數(shù)組內(nèi)的內(nèi)容寫到文件中。如果程序跳出,馬上調(diào)出存盤很快就會跟蹤出錯誤的位置。后來,在<赤壁>標準版發(fā)行后,也有玩家反映有異常退出的錯誤,我就讓他們把存盤文件寄給我,我就能很快發(fā)現(xiàn)問題。如果使用指針鏈表恐怕就沒那么容易了,但也有一個好處。如果玩家玩出一個錯誤,只要他再重新玩很可能這個錯誤就不出現(xiàn)了。 有關(guān)內(nèi)容請詳見CBCtrl.cpp。
鏤空貼圖 我估計有不少初學編游戲的人都會有這個疑問,如何讓一個人出現(xiàn)在地形上而身上沒有黑邊。要知道計算機的顯示一般都是以矩形為單位的,一個邊界不規(guī)則的圖形該如何顯示呢? 假如我們按點去操作,那么就可以判斷如果人的圖像上是黑色則跳過去,否則顯示到背景上。但是誰都知道這樣是很慢的。于是我們想到了使用邏輯操作來貼圖。我們有一個真人的圖像,邊界是黑色的,我們叫源碼。然后我們制作一個掩碼,人的內(nèi)部是黑色的實心,外部是白色。然后我們做兩次邏輯操作,先把人的掩碼與背景做“與”操作,這樣背景就被扣出了一個黑色的輪廓,然后把人的源碼與背景作“或”操作,人就呆在背景里了。 如果你看到我們的地圖編輯器,就會發(fā)現(xiàn)我們就是這樣做的。這是<官渡>的做法,在編寫編輯器時我還不會使用DirectDraw呢。但是在DirectDraw環(huán)境下,我想大家就可以省事很多了,只要一個ColorKey的變量設置,DirectDraw就可以替你完成鏤空貼圖的操作。 從這里你也可以看到我們也許不會一開始就把游戲做好,但只要堅持下去,我們一定會越做越好。 有關(guān)內(nèi)容請詳見DDApi.cpp。
2.5 希望
有人說即時戰(zhàn)略游戲已經(jīng)過時了,或者已經(jīng)做到頭了。我的觀點截然相反。 我認為它是目前唯一能夠勝任大規(guī)模集團作戰(zhàn)的游戲類型,它所能夠帶給人的東西我們還遠遠沒有做到。我是很喜歡看電影的,尤其是戰(zhàn)爭電影,那些電影里的場面和效果在游戲里出現(xiàn)了么?有,但是還遠遠不夠。 我承認我們的制作能力與國外的水平還有很大的差距,但是即時戰(zhàn)略依然會有它的發(fā)展。 比如,即時戰(zhàn)略已經(jīng)開始慢慢向戰(zhàn)棋式的戰(zhàn)略游戲靠攏,采用多層戰(zhàn)略地圖,多級控制的方式。這就是一個可能的發(fā)展方向。即時戰(zhàn)略也可以不采用采礦的方式,而利用其它(比如經(jīng)營)方式來獲得資源,這也是一個可能的發(fā)展方向。 我總是覺得,制作游戲就是在創(chuàng)造世界,雖然我們的這個世界還無法與真實的世界相比。但是,只要我們做了,就會有進步,就會離夢想近一點,也許我等不到那一天,可是也許某個看到我的文章的人等得到那一天,這我已經(jīng)很高興了。制作游戲的路剛剛開始,對于我們而言,我們希望能夠繼續(xù)做下去,可是上帝畢竟是廣大的玩家,如果大家不認可,那我們還是趁早改行為好。但我總是有些不死心,總希望能夠有人堅持下來,如果這個人不是我。我真心地希望能有更多的人了解游戲的制作,了解制作游戲的人,也希望有更多的人加入到這個充滿了艱辛的事業(yè)來。因為美好的明天需要我們?nèi)?chuàng)造,我們?yōu)橛螒蚋冻隽撕芏?,它也會同樣地回報我們的?
以上內(nèi)容僅是我個人的觀點,如果有任何異議請與我聯(lián)系,但是我不會與各位爭論什么,因為只有做才是最重要的。如果有什么問題,也可以來信,但是我可能不會很快或很詳細地回答,因為我現(xiàn)在正忙于制作新的游戲
我的E-mail地址是liu-gang@263.net
假如你已經(jīng)整裝待發(fā),那么就開始吧,不要猶豫,人的一生只有一次,只要去生活就是了。
|