使用腳本語言可以更加快速地開發(fā)游戲邏輯,而不必?fù)?dān)心由于 C++ 程序員的粗心大意所造成的后果。使用已有的腳本語言可以節(jié)省開發(fā)新型自定義語言的時(shí)間和開銷,并且這些語言通常要比自己創(chuàng)造的語言更加的強(qiáng)大。Python 對(duì)于游戲腳本語言來說是一種不錯(cuò)的選擇,它很強(qiáng)大,容易嵌入使用,能夠無縫地使用 C/C++ 進(jìn)行擴(kuò)展,包含很多腳本語言所具有的高級(jí)特性,并且它可以用來實(shí)現(xiàn)自動(dòng)化過程[TR1: automating production]。另外,關(guān)于 Python 的書籍、開發(fā)工具 和 庫(kù) 很豐富,使得我們很容易從其他開發(fā)者那里受益。下來就談一談我們?cè)?Humongous 娛樂公司將 Python 集成進(jìn)新游戲引擎的一些經(jīng)驗(yàn)。說明我們選擇 Python 的原因、獲得的收益、遇到的問題,以及我們是怎樣解決它們的。
為什么要使用腳本語言
C++ 是一種強(qiáng)大的語言,并且是 C 語言的巨大改進(jìn),但它并不是完成所有任務(wù)的最佳選擇。C++ 非常強(qiáng)調(diào)運(yùn)行時(shí)性能 [Stroustrup94],譬如,假如一個(gè)語言特性使得程序跑起來變慢,那么這個(gè)特性便不會(huì)加入 C++ 語言中。C++ 程序員也因此背負(fù)了很多的限制和煩惱。這里列出一些限制,C++ 程序員經(jīng)常遭遇這些事情但很少注意它們的存在:手工管理內(nèi)存:C++ 程序員的大量時(shí)間都花在考慮調(diào)用 delete 的適當(dāng)時(shí)機(jī)。鏈接過程:C++ 模塊(在編譯時(shí)或加載時(shí))鏈接在一起,因此在運(yùn)行時(shí),無需進(jìn)行的函數(shù)地址的解析。這提高了運(yùn)行時(shí)的性能,但是卻使 編輯/測(cè)試 周期變長(zhǎng)了。缺乏自省能力 [TR2: introspection]:C++ 有自己的方式知道一個(gè)類中包含哪些成員,但是這種方式需要編寫過多的加載和存儲(chǔ)對(duì)象的代碼,而在一些腳本語言中這只需調(diào)用一個(gè)內(nèi)建函數(shù)就可以完成。C++ 是靜態(tài)的,而腳本語言是動(dòng)態(tài)的。簡(jiǎn)單地說,C++ 的程序運(yùn)行地很快,但是腳本語言能讓你編碼更快。所以,C++ 應(yīng)該只用在你希望優(yōu)化運(yùn)行時(shí)性能的地方。現(xiàn)在計(jì)算機(jī)的運(yùn)行速度都足夠快,對(duì)于大多數(shù)代碼來說性能都不是問題。如果你用 C++ 開發(fā)那些用腳本語言也能實(shí)現(xiàn)的程序,那么你是在錯(cuò)誤的事情上進(jìn)行優(yōu)化。
SCUMM 的問題
Humongous 公司已經(jīng)使用 SCUMM (Script Creation Utility for Maniac Mansion) 創(chuàng)造了 50 多個(gè)游戲。SCUMM 是一個(gè)強(qiáng)大的 冒險(xiǎn)游戲 開發(fā)語言,但是它有一些局限性。SCUMM 是十多年前寫的,它缺少一些現(xiàn)代語言的特性。盡管 SCUMM 有持續(xù)的補(bǔ)丁和維護(hù),它也沒有辦法像其它語言一樣健壯和有完備的功能了。
為什么選擇 Python
我們有過創(chuàng)造一種新型的、現(xiàn)代的 私有語言的想法,但最終明智地放棄了這種想法。我們的職責(zé)是在做游戲,而不語言。我們?cè)诿磕昊ㄙM(fèi)大量開銷維護(hù)一套私有工具的情況下,確實(shí)希望使用一種已有的腳本語言而不是重新創(chuàng)造一種。使用已有語言更快地投入工作,花費(fèi)更少的開銷,并且通常情況下要比我們創(chuàng)造的好,并且以后會(huì)發(fā)展地更好,即使我們不用它工作。一旦我們決定要使用已有的腳本語言,就需要從中選擇一種。我們需要一種支持 面向?qū)ο缶幊蹋⑶夷芮度氲轿覀冇螒蛑械恼Z言,而且它不存在任何技術(shù)和許可授權(quán)上的問題。我們考慮了 Lua [Lua01] 和 Python [Python02],這兩種語言已經(jīng)被應(yīng)用在某些游戲中了。Lua 較小,更加容易嵌入到應(yīng)用程序中,并且有一些很棒的語言結(jié)構(gòu)。但是,那時(shí)我們發(fā)覺 Lua 的文檔有些粗略,這大概是因?yàn)?Lua 是比 Python 更新的語言。Python 比 Lua 有更多的擴(kuò)展模塊,更多的參考書籍,并且 stackless Python [Tismer01] 很適合為對(duì)象 AI 創(chuàng)建微線程[TR3: micro-threads]。最后我們沒有選擇 Python 的 stackless 版本,但開始用 Python 寫自動(dòng)生成腳本,這給了我們繼續(xù)使用 Python 的動(dòng)力。當(dāng)了解了 Python 后,我們喜歡上了它的語法,最后選擇了它。在我們決定之后,這兩種語言都發(fā)生了改進(jìn):Lua 已經(jīng)變成 stackless,而 Python 有了生成器,這個(gè)能提供一些相似的功能。現(xiàn)在任何一種都是安全的選擇。
誰在游戲中使用了 Python
Python 已經(jīng)被使用在很多游戲中,包括:
-
ToonTown -?http://www.toontown.com/
-
EveOnline -?http://www.eve-online.com/
還有很多其它的游戲,只是我們很難確認(rèn),例如至少有一個(gè) PS2 游戲使用了 Python。
同時(shí) Python 也至少用在兩個(gè)游戲引擎中:
-
Game Blender -?http://www.blender.nl/gameBlenderDoc/python.html
-
PyGame -?http://www.pygame.org/
一個(gè)生成腳本示例
下面是一段 Python 代碼示例,它是一個(gè)遞歸生成所有 VC++ 工作區(qū)的簡(jiǎn)單生成腳本。它只有以下幾行:
加上更多的代碼,可以讓這個(gè)腳本 [Dawson02] 分析輸出結(jié)果,然后給團(tuán)隊(duì)中的每個(gè)人發(fā)送一份結(jié)果報(bào)告郵件。不像某些其它腳本語言,上面代碼有很好的可讀性。使用 Python 來寫生成腳本和游戲腳本將會(huì)省卻很多學(xué)習(xí)的時(shí)間。這個(gè)生成腳本示例也顯示了一些對(duì) Python 新手很頭疼的問題。Python 的流程控制由縮進(jìn)指明,而不使用 begin/end 聲明或大括號(hào)。我用了很短的時(shí)間來適應(yīng)這種規(guī)則,最后我發(fā)現(xiàn)這種規(guī)則很有效。我曾經(jīng)不止一次討論過 C/C++ 中的大括號(hào)應(yīng)該寫在哪里,我想 Python 程序員有更高的工作效率,因?yàn)樗麄儾挥没ㄙM(fèi)時(shí)間爭(zhēng)論 K&R 及其它縮進(jìn)風(fēng)格[TR4: indenting style] 的事情。因?yàn)榇a塊由縮進(jìn)定義,編寫時(shí)便不會(huì)出現(xiàn)任何不符合 Python 編譯器規(guī)則的縮進(jìn)(因?yàn)槟菢拥脑挘绦蚓蜁?huì)出錯(cuò))。要注意的是,當(dāng)你混用 TAB 和空格進(jìn)行縮進(jìn)時(shí),可能出現(xiàn)問題。大多數(shù)程序員使用寬度為 3 個(gè)或 4 個(gè)空格的 TAB 縮進(jìn),但是在 Python 編譯器內(nèi)部卻使用 8 個(gè)空格的縮進(jìn),混合使用 TAB 和空格可能導(dǎo)致語法錯(cuò)誤。如果你完全地使用空格或 TAB 進(jìn)行縮進(jìn),并且使用一個(gè)能夠提示混用空格、TAB 縮進(jìn)警告的 IDE,那么便沒有什么問題。
游戲腳本示例
下面的示例是我們的第一個(gè) Python/C++ 游戲中的一些 Python 代碼。這些代碼是 Python 正在執(zhí)行的一個(gè)主循環(huán),它調(diào)用了其它的模塊,這些模塊甚至可以用其它語言編寫:
因此,我們的游戲由 Python 啟動(dòng),并在需要時(shí)調(diào)用 C++ 程序。
它是如何工作的
Python 程序由模塊組成,當(dāng)在一個(gè)源文件中使用另一個(gè)源文件中定義的函數(shù)時(shí),需要導(dǎo)入那個(gè)文件。例如,gameai.py 有一個(gè) UpdateAI 函數(shù),那么在其它 Python 源文件中可以這樣調(diào)用它:
游戲程序員能夠想到的一個(gè)很棒的事情是,如果 UpdateAI() 跑起來很慢,那么可以用 C++ 來重寫它。為了做到這點(diǎn),在 gameai.py 中的函數(shù)和類型需要用 C++ 實(shí)現(xiàn),并且在 Python 中注冊(cè)為原先的模塊名。之后,使用者能夠繼續(xù)導(dǎo)入并使用 gameai 模塊,而不需要任何更改。因此,Python 模塊能夠幫你簡(jiǎn)單地用 Python 搭建你的整個(gè)游戲框架,而在適當(dāng)?shù)牡胤接?C++ 代碼實(shí)現(xiàn)。
粘合代碼 (Glue Code)
如果你自己手工編寫讓 C++ 代碼和 Python 協(xié)同工作的粘合代碼,那將是一件枯燥繁瑣的事情 [TR5: glue code]。一個(gè)能夠產(chǎn)生粘合代碼的系統(tǒng)框架是很重要的。Swig, Boost, CXX 等 [Abrahams01] 能幫你產(chǎn)生代碼,更方便地將 Python 和 C++ 粘合起來。還有 Fubi[Bilas01],它是一個(gè)通用的框架,可以將 C++ 的函數(shù)和類映射到一種腳本語言中。早期,大多數(shù)這些粘合代碼框架都依靠分析 C++ 頭文件工作。因此,它們受到暴露的 C++ 頭文件的限制,并且一些框架不支持從 C++ 類派生出 Python 類。后來,這些框架都有所改進(jìn),所以現(xiàn)在還是值得考慮的。而我們決定做一個(gè)自己的方案,它可以根據(jù)類的 IDL 描述或?qū)С龊瘮?shù)來生成粘合代碼。它的代碼叫做 Yaga,是一個(gè)遞歸命名法,表示 Yaga is A Game Architecture。
一個(gè)典型的 Yaga IDL 代碼如下:
它可以生成以下粘合代碼,還有其它一些代碼:
使用這個(gè)框架可以很簡(jiǎn)單地導(dǎo)出類和函數(shù),從 C++ 類派生 Python 類,將 C++ 的數(shù)組和 vector 映射為 Python 的序列類型,以及更多的事。
內(nèi)存分配
Python 之中任何東西都是對(duì)象,對(duì)象被分配內(nèi)存。因?yàn)樗械膶?duì)象都有引用計(jì)數(shù),所有你不用擔(dān)心釋放內(nèi)存。但是,如果你是在編寫游戲,尤其是控制臺(tái)游戲(譯注:指次時(shí)代及專用游戲機(jī)平臺(tái)游戲),你必需要明白這些內(nèi)存從何處分配而來,以及分配過程會(huì)產(chǎn)生內(nèi)存碎片的嚴(yán)重性。為了控制這個(gè)性能問題,你需要隔離 Python,使其有自己的內(nèi)存分配場(chǎng)。你需要重定向所有的內(nèi)存分配操作到一個(gè)自定義的分配器上,它從一個(gè)固定大小的分配場(chǎng)中分配內(nèi)存。只要你預(yù)留足夠大小的緩沖區(qū),大于最大的 Python 歷史分配額度(原文:leave enough of a buffer above the maximum Python memory footprint),應(yīng)該就能避免內(nèi)存碎片問題。另一個(gè)內(nèi)存問題是沒有釋放的塊。這通常在 Python 中不是問題,因?yàn)槊總€(gè)對(duì)象都有引用計(jì)數(shù),當(dāng)變量離開作用域或者被顯式刪除,其引用計(jì)數(shù)就會(huì)減一,當(dāng)計(jì)數(shù)為 0 時(shí),對(duì)象就被釋放,對(duì)象生命結(jié)束。試想這樣情況,一個(gè)被忘記的變量,它關(guān)聯(lián)了一串其它的對(duì)象,這時(shí)就會(huì)阻礙這些對(duì)象的釋放,所以你應(yīng)該對(duì)清理對(duì)象保持警惕。然而,更糟糕的事情是循環(huán)引用問題,例如:對(duì)象 A 包含對(duì)象 B,但是對(duì)象 B 有一個(gè)回調(diào)指針指向?qū)ο?A,那么這兩個(gè)對(duì)象永遠(yuǎn)都不會(huì)被刪除。Python 的開發(fā)者們意識(shí)到這個(gè)問題,在最近的 Python 版本中加入了一個(gè)垃圾收集器,它搜尋無法訪問到達(dá)的對(duì)象,并將其全部清除。垃圾收集器對(duì)于游戲是很糟的,因?yàn)闊o法預(yù)知它們的運(yùn)行時(shí)間,并且可能運(yùn)行很長(zhǎng)時(shí)間,使得畫面的幀率降低。因此,游戲程序中需要禁用垃圾收集器,這個(gè)做起來很簡(jiǎn)單,隨后在每個(gè)游戲關(guān)卡后顯式地調(diào)用它。垃圾收集器同時(shí)也能告訴你 有多少無法訪問到達(dá)的對(duì)象仍然在分配中,這個(gè)可以幫助你跟蹤循環(huán)引用的情況,之后你可以手工地解決它們,這相當(dāng)于 Python 的內(nèi)存泄露檢查。
性能
如果你用 Python 做一些繁重的浮點(diǎn)計(jì)算工作,和 C++ 的性能相比會(huì)很讓人失望。Python 是一個(gè)慢語言,每個(gè)對(duì)象引用都意味著進(jìn)行哈希表查詢,每個(gè)函數(shù)調(diào)用也一樣。這根本不能和 C++ 的性能相提并論,后者的變量位置和函數(shù)調(diào)用地址在編譯時(shí)就決定了。但這并不意味著 Python 不適合做游戲編程,而是你需要在適當(dāng)?shù)牡攸c(diǎn)用它。如果拿字符串操作或 C++ STL 的 set 和 map 類型操作做對(duì)比,那么 Python 代碼也許會(huì)做地更快。Python 的字符串操作函數(shù)是用 C 寫的,并且 Python 的引用計(jì)數(shù)對(duì)象模型能夠避免一些 C++ string 類的字符串復(fù)制過程。set 和 map 的大多數(shù)操作的復(fù)雜度是 O(log n),而對(duì)于 Python 的哈希表復(fù)雜度則是 O(1)。你一定想,最好不要用 Python 寫 場(chǎng)景圖形遍歷 或 BSP 沖突檢測(cè)代碼。但是如果你用 C++ 寫它們,而后又導(dǎo)出到 Python 中使用,那么你就可以更快地編寫 AI 代碼。請(qǐng)牢記 90/10 原則,這意味著對(duì)于 90% 的代碼,你不必過多操心它們的運(yùn)行時(shí)性能,而代碼的明確表達(dá)力和編碼的效率才是關(guān)鍵。
控制臺(tái)游戲
內(nèi)存和性能問題在控制臺(tái)游戲平臺(tái)上尤其重要。當(dāng)不存在虛擬內(nèi)存可以讓你漫不經(jīng)心做內(nèi)存分配的時(shí)候,保證在獨(dú)立的內(nèi)存分配場(chǎng)中分配 Python 內(nèi)存就顯得格外重要。同時(shí),也要更明智地使用垃圾收集器 (as is using the garbage collector wisely)。控制臺(tái)平臺(tái)沒有鍵盤、鼠標(biāo)和多顯示器,所以在控制臺(tái)平臺(tái)上運(yùn)行 Python 調(diào)試器用起來很不方便。遠(yuǎn)程調(diào)試是關(guān)鍵,它能讓你知道 Python 代碼的運(yùn)行過程。很幸運(yùn),使用免費(fèi)的 HapDebugger[Josephson02] 可以很容易建立遠(yuǎn)程調(diào)試環(huán)境。Python 使用 C 編寫,并且已經(jīng)被移植到多種編譯環(huán)境和平臺(tái)下,包括 PDA。因此,在某個(gè)控制臺(tái)游戲平臺(tái)下 Python 可能已經(jīng)有了很充分的發(fā)展。Python 會(huì)花費(fèi)掉一小部分和控制臺(tái)游戲無關(guān)的內(nèi)存,但是在新一代游戲平臺(tái)上可以不用擔(dān)心這個(gè),它們最小都有 24M 內(nèi)存。
法律問題
推向一種新的語言對(duì)于我們公司來說是個(gè)重大的決定,我覺得在進(jìn)行之前,它定是受到了公司律師們的祝福。律師懂得法律,但他們通常不太懂編程。大多數(shù)程序員在引入開源代碼前都不會(huì)咨詢公司的律師,當(dāng)你確實(shí)問他們時(shí),他們會(huì)認(rèn)為你正在問一些奇怪且偏僻的事情。他們的立即反應(yīng)是,認(rèn)為那是有風(fēng)險(xiǎn)、沒有保證的計(jì)劃。如果你和一個(gè)擅長(zhǎng)知識(shí)產(chǎn)權(quán)的律師長(zhǎng)談,他會(huì)一直向你灌輸“使用開源軟件會(huì)讓你焦頭爛額”的思想。有一些案例指明,在“免費(fèi)發(fā)布”的源碼中包含專利或有版權(quán)的內(nèi)容時(shí),有嚴(yán)重的法律問題隱患。當(dāng)你從商業(yè)軟件供應(yīng)商那里得到授權(quán)代碼時(shí),他們會(huì)保護(hù)你免受法律責(zé)任,但對(duì)于開源軟件沒有人能給予授權(quán)許可 (with open source software there is no one to license it from)。然而,開源社區(qū)對(duì)知識(shí)產(chǎn)權(quán)法律總是很警惕。例如 JPEG 已經(jīng)從它們的開發(fā)庫(kù)中移除了 LZW 算法代碼以避免專利問題 [IJG]。負(fù)責(zé)的程序員會(huì)關(guān)心授權(quán)許可問題,并且通常對(duì) GPL 和 LPGL[FSF01] 以及他們的區(qū)別很熟悉。將開源代碼引入商業(yè)產(chǎn)品存在很多風(fēng)險(xiǎn)。這些風(fēng)險(xiǎn)應(yīng)嚴(yán)肅對(duì)待,但不應(yīng)該阻止對(duì)開源代碼的使用。有很多開源的開發(fā)庫(kù)使用在游戲開發(fā)中,Python 實(shí)在沒什么理由不被使用。
缺點(diǎn)
多語言開發(fā)增加了額外的復(fù)雜層次。同時(shí)調(diào)試兩種語言的代碼很困難,而且必須花費(fèi)時(shí)間維護(hù)綁定兩種語言的粘合代碼。類似 Python 的動(dòng)態(tài)語言沒有編譯時(shí)類型檢查。這種情況初看讓人驚恐,但它的實(shí)際意味著,相比 C++ 你會(huì)遇到各式各樣不同的運(yùn)行時(shí)錯(cuò)誤,通常它們都很容易解決。
不同類型的換行符
UNIX (LF)、Mac OS (CR) 和 Windows (CR LF) 對(duì)待文本文件中一行的結(jié)束有不同的約定,這實(shí)在很糟。Windows 上的 C/C++ 庫(kù)(譯注:指 Windows API 和 VC 運(yùn)行時(shí)庫(kù))會(huì)做換行符轉(zhuǎn)換,所以 UNIX 文件能夠在 Windows 上讀取,可以將 Windows 文件像 UNIX 文件一樣的操作。UNIX 和 Macintosh 文本文件之間的共同點(diǎn)更少,只能依靠假定某個(gè)平臺(tái)上的文件都只是這個(gè)平臺(tái)上曾經(jīng)創(chuàng)建的,這個(gè)假設(shè)進(jìn)行轉(zhuǎn)換。這個(gè)假設(shè)在當(dāng)今的網(wǎng)絡(luò)環(huán)境下站不住腳,Python 也深受其害。直到現(xiàn)在,在 Windows 下寫的 Python 代碼可能無法在 Macintosh 下編譯,反之亦然。這個(gè)問題的解決方法是,在運(yùn)行 Python 代碼前,將 Python 源文件通過一個(gè)文件過濾器(可以用 Python 開發(fā)?)執(zhí)行,另一種方法是以編譯后的字節(jié)碼形式發(fā)布 Python 代碼。但是,這兩種辦法都有缺點(diǎn)。最理想的是在計(jì)算機(jī)工業(yè)中標(biāo)準(zhǔn)化文本文件格式,或者讓所有的文件 IO 庫(kù)實(shí)現(xiàn)讀取任意類型文本文件的能力。這個(gè)問題在蘋果的 OS X 上更加有趣,換行符由運(yùn)行程序的模式而定,你可以運(yùn)行 UNIX 或 Macintosh 兩種模式程序。這會(huì)在一個(gè)系統(tǒng)下出現(xiàn)兩種不同的換行符,甚至不用重啟。Python 的 Macintosh 版本最近修正了這個(gè)問題,在打開文件時(shí)檢查換行符并對(duì)每個(gè)文件進(jìn)行調(diào)整。將所有的換行符都規(guī)定為 UNIX 類型是一種可行的方法,它在所有平臺(tái)下都能工作,但是還是要留心這個(gè)問題。
調(diào)試器問題
很多 Python 程序員認(rèn)為自動(dòng)化測(cè)試和打印語句是他們唯一需要的調(diào)試工具,而使用調(diào)試器會(huì)影響編碼的產(chǎn)能。或許這對(duì)他們來說的確如此,但我已經(jīng)習(xí)慣于進(jìn)行源碼級(jí)調(diào)試,并且不會(huì)輕易放棄它。PythonWin 是一個(gè)在 Windows 下的 Python 調(diào)試器兼 IDE(奇特吧?)。它是免費(fèi)的,有一些不錯(cuò)的功能,但也有一些缺點(diǎn),如:只能在 Windows 下運(yùn)行,無法調(diào)試有自身消息循環(huán)的 Python 程序。在 Humongous 娛樂公司,我們?yōu)?Macintosh 和 Windows 開發(fā)游戲,同時(shí)也涉及控制臺(tái)游戲的開發(fā)。我們需要一種能工作在所有三個(gè)平臺(tái)上的調(diào)試器,而最好的方案就是使用遠(yuǎn)程調(diào)試器。Python 的架構(gòu)使得編寫它的調(diào)試器很容易,再加上其它一些免費(fèi)組件,我們開發(fā)出了自己的 Python 調(diào)試器,我覺得它的效果比 PythonWin 好,并且具有遠(yuǎn)程調(diào)試功能。被調(diào)試的客戶端需要運(yùn)行一些額外代碼。調(diào)試接口是 socket 上的 ASCII 文本,另外,我們還沒考慮將調(diào)試器客戶端移植到更多其它平臺(tái)的問題。因?yàn)槲覀兿M芯﹂_發(fā)游戲本身,而不是語言工具,所以決定再次借用開源的力量。我們?cè)?Python 社區(qū)發(fā)布了 HAP 調(diào)試器 (Humongous Addition to Python),將其作為一個(gè)開源項(xiàng)目[Josephson02]。這是一個(gè)回饋社區(qū)的好機(jī)會(huì),并且我們也從維護(hù)這個(gè)調(diào)試工具的事務(wù)中解放出來。我們還沒有解決的問題是調(diào)試器的性能問題。大多數(shù)編譯式語言實(shí)現(xiàn)調(diào)試斷點(diǎn)的方法是,將常規(guī)指令替換為導(dǎo)致 CPU 異常的指令,如 x86 處理器的 int 3 中斷。這讓程序可以全速執(zhí)行,直到觸發(fā)中斷點(diǎn)。Python 不支持從異常處恢復(fù)執(zhí)行,所以不能使用斷點(diǎn)異常的方法。Python 調(diào)試器處理斷點(diǎn)的方法是 單步檢查代碼,即不停地在問自己“這一行有沒有斷點(diǎn)?”這個(gè)性能影響的后果可能很嚴(yán)重。我們現(xiàn)在減小此影響的方法是,保證開發(fā)機(jī)器要比目標(biāo)機(jī)器快得多。還有,將所有重量級(jí)計(jì)算用 C++ 擴(kuò)展實(shí)現(xiàn),這樣即使 Python 代碼拖慢了調(diào)試器,也不至于讓整個(gè)游戲速度太慢。這是一個(gè)可以解決的問題,只是 Python 的主要開發(fā)者還沒考慮過。
代碼安全和游戲作弊
C++ 程序員有時(shí)開玩笑說,刪除注釋和縮短變量名可以優(yōu)化代碼。然而,在 Python 中確實(shí)如此。Python 代碼在運(yùn)行時(shí)被編譯成字節(jié)碼,并緩存起來以備后續(xù)運(yùn)行,所以刪除注釋的方法不會(huì)起到優(yōu)化程序的效果,但是縮短變量名則是另外一回事。大多數(shù)腳本語言都是在運(yùn)行時(shí)通過名字定位變量的,這也是腳本語言強(qiáng)大的原因之一,因?yàn)樗梢酝黄坪芏嘤?C++ 編譯時(shí)綁定造成的限制。然而,這也意味著變量名會(huì)一直伴隨著代碼而存在(譯注:C/C++ 等傳統(tǒng)編譯式語言則不同,經(jīng)優(yōu)化編譯后的 C/C++ 程序中沒有變量名而只有地址的概念)。游戲程序中包含語義清晰 (scatological) 的變量名,會(huì)被人當(dāng)做笑談。更嚴(yán)重的問題是,如果在多人游戲中使用 Python 腳本,作弊者反編譯 Python 程序后會(huì)得到完整的變量和函數(shù)名,這比起通過反編譯 C++ 程序來破解游戲要更簡(jiǎn)單。
Python 的優(yōu)點(diǎn)
Python 編程很有趣。Python 易于學(xué)習(xí),有更高的生產(chǎn)效率,并且促使你使用另一種思維編程。學(xué)習(xí) Python 編程讓我成為更好的 C++ 程序員。快樂的程序員有更高的學(xué)習(xí)效率和生產(chǎn)效率,他們傾向創(chuàng)造更好的游戲。Humongous 公司中使用 Python 開發(fā)游戲的團(tuán)隊(duì),在整個(gè)公司中擁有最高的工作士氣。Python 游戲編程系統(tǒng)(譯注:應(yīng)指開發(fā)工具、框架、類庫(kù)等)具有很高的生產(chǎn)效率,而且它們?nèi)匀辉诎l(fā)展之中。因?yàn)椴捎昧怂鼈儯覀児?jié)省了很多資金。(原文:Productivity is higher with the Python game programming system, even though development is still being done on it. It is already clear that we will save a lot of money from this switch.)用戶界面的開發(fā),在 C++ 中可能花費(fèi)較長(zhǎng)的時(shí)間,而在 Python 中可以使用一些新意的方式進(jìn)行實(shí)現(xiàn)。通常使用文本文件定義 GUI 元素的位置和關(guān)聯(lián)圖形資源,進(jìn)而定義菜單。在 C++ 中會(huì)使用硬編碼的函數(shù)和控件對(duì)象,掛鉤 GUI 元素;而在 Python 中,可將函數(shù)及對(duì)象名放入文本文件中,并在運(yùn)行時(shí)掃描它們。Python 的動(dòng)態(tài)和內(nèi)省特性 (introspective) 使得做起這些事來很自然。(譯注:C++ 也可使用讀取文本配置方式,自動(dòng)生成菜單,只是用 Python 的反射特性做起來更自然)很多起先我們擔(dān)憂的 Python 語言限制問題都已成為過去。Python 的開發(fā)者們對(duì)該語言進(jìn)行持續(xù)地改進(jìn),有時(shí)他們就像一直在滿足我們對(duì) Python 特性需求的渴望一樣。
游戲存檔和讀檔
C++ 程序員要花費(fèi)很多時(shí)間解決腳本語言中不會(huì)出現(xiàn)的困難問題。例如,用 C++ 進(jìn)行游戲狀態(tài)的存儲(chǔ)和讀取就是一個(gè)麻煩問題,經(jīng)常要編寫大量的代碼。而且這種方法通常會(huì)導(dǎo)致,存檔只能和特定版本的游戲程序配合工作。而在 Python 中,使用 cPickle 模塊可以很方便的解決此問題,它可以存儲(chǔ)和讀取任何復(fù)雜的數(shù)據(jù)結(jié)構(gòu)。下面例子中聲明了一個(gè)對(duì)象 mainObject,通常它是一個(gè)用戶自定義類對(duì)象,包含各種需要存儲(chǔ)的狀態(tài)的句柄,但為簡(jiǎn)單起見,這里只把它做成一個(gè)列表。最初該列表包含數(shù)字 0 和一個(gè)字符串,然后將列表的第一個(gè)元素賦值為另外一個(gè)列表。這個(gè)過程可以繼續(xù)下去,讓 mainObject 包含任意復(fù)雜嵌套層次的對(duì)象,包括循環(huán)引用。
接下來保存著這個(gè) mainObject,這需要兩行代碼。一行導(dǎo)入 cPickle 模塊,另一行打開一個(gè)文件,將對(duì)象保存為二進(jìn)制格式。在開發(fā)時(shí),保存為文本格式很有用,只需省略掉 dump() 的最后一個(gè)參數(shù)即可。
然后是裝載文件數(shù)據(jù),這同樣需要兩行代碼。一行導(dǎo)入 cPickle 模塊,另一行重建 mainObject 對(duì)象,以及包含的子對(duì)象、列表、成員變量等。第三行打印出 mainObject 對(duì)象,可以看出已經(jīng)正確地恢復(fù)了嵌套的列表。
這個(gè) Python 特性在 C++ 基本功能中不存在。
生成器:游戲 AI 的微線程
微線程將對(duì)象狀態(tài)信息放到局部變量中(這是恰當(dāng)?shù)奈恢茫瑥亩鴺O大簡(jiǎn)化 AI 和對(duì)象更新代碼 [Carter01]。可以使用匯編語言的技巧將微線程放進(jìn) C++ 中,但是那樣很凌亂。在最近版本的 Python 中,微線程內(nèi)建于語言之中。現(xiàn)在使用微線程會(huì)工作地很好。在 Python 中它們叫做生成器 (generator),使用它們編寫函數(shù),函數(shù)產(chǎn)生某個(gè)結(jié)果后,控制返回到主程序。主程序稍后可以重新喚醒它們,并從中斷處繼續(xù)運(yùn)行,并保持原來的局部變量值。下面的示例代碼展示創(chuàng)建一個(gè)對(duì)象,并移動(dòng)它們穿過屏幕。這個(gè)簡(jiǎn)單例子并不能從微線程/生成器中得到實(shí)際的好處,它只是基本展示它們?cè)鯓佑脕砗?jiǎn)化 AI 和對(duì)象更新代碼。
即使你不使用生成器,在 Python 中實(shí)現(xiàn) AI 更新方法也比用 C++ 更干凈。因?yàn)槿绻愕哪巢糠?AI 代碼需要一些額外的臨時(shí)狀態(tài)時(shí),Python 可以將它加入到對(duì)象中,然后在不需要時(shí)刪除它。而 C++ 因其靜態(tài)特點(diǎn),不能在運(yùn)行時(shí)加入新的成員變量,這使你的對(duì)象在任何時(shí)候都必須包含所需的所有狀態(tài)。
開始使用 Python
如果你開始使用 Python,第一件事是訪問 Python 的官方網(wǎng)站?http://www.python.org/?下載你的平臺(tái)上的 Python 版本。Python 文檔在?http://www.python.org/doc/current/download.html,也有編譯的 HTML 版本 (CHM) 更便于檢索。Windows 開發(fā)者可以使用 PythonWin 和各種 Win32 擴(kuò)展http://users.bigpond.net.au/mhammond/win32all-142.exe調(diào)試器 HapDebugger?https://sourceforge.net/projects/hapdebugger/你可以下載并編譯 Python 源碼,構(gòu)建自己的 Debug 和 Release 版 Python。Python 2.2 源碼下載ftp://ftp.python.org/pub/python/2.2/Python-2.2.tgz最后,你可以閱讀關(guān)于手工創(chuàng)建 Python 擴(kuò)展的細(xì)節(jié)http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66509,然后選擇一種粘合代碼包來幫你做這些事情
參考
-
[Stroustrup94] Stroustrup, Bjarne "The Design and Evolution of C++", Addison Wesley, 115
-
[Lua01] "The Programming Language Lua"
-
http://www.lua.org/
-
[Python02] Python Language Website
-
http://www.python.org/
-
[Tismer01] Tismer, Christian "Stackless Python"
-
http://www.stackless.com/
-
[Dawson02] Dawson, Bruce "Python Scripts"
-
ftp://ftp.cygnus-software.com/pub/pythonscripts.zip
-
[Abrahams01] Abrahams, David, "Comparisons with Other Systems"
-
http://www.boost.org/libs/python/doc/comparisons.html
-
[Bilas01] Bilas, Scott, "FuBi: Automatic Function Exporting for Scripting and Networking"
-
http://www.gdconf.com/archives/proceedings/2001/bilas.doc
-
[IJG]?http://www.ijg.org/?- docsREADME in the source distribution
-
[FSF01] "What is Copyleft?"
-
http://www.gnu.org/copyleft/
-
[Josephson02] Josephson, Neal "HAP Python Remote Debugger"
-
http://sourceforge.net/projects/hapdebugger/
-
[Carter01] Carter, Simon "Managing AI with Micro-Threads", Game Programming Gems II, Charles River Media, 265-272
- ?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
