Java游戲開(kāi)發(fā)中應(yīng)始終堅(jiān)持的10項(xiàng)基本原則

字號(hào):

關(guān)于文章中涉及的兩個(gè)杜撰概念:
    一、繪圖器:眾所周知,Java GUI以paint進(jìn)行繪圖,以repaint進(jìn)行圖像刷新,而完成repaint及paint這一連貫過(guò)程中所用到繪圖組件,我將其稱(chēng)為繪圖器。就我個(gè)人的體會(huì),繪圖器的調(diào)用時(shí)機(jī)應(yīng)始終處于repaint之后paint之前,即通過(guò)repaint觸發(fā)刷新后執(zhí)行,當(dāng)其中的具體邏輯完成其對(duì)應(yīng)的圖像繪制后,再通過(guò)統(tǒng)一接口將其圖像插入paint中,為了匹配需要,繪圖器應(yīng)始終以接口方式實(shí)現(xiàn)。
    二、監(jiān)聽(tīng)器:這里所說(shuō)的監(jiān)聽(tīng)器,并不是特指某個(gè)Listener組件,而是包括Java游戲中所需的所有監(jiān)聽(tīng)器集合。由于Java游戲中很可能會(huì)切換不同的游戲模式,而不同模式游戲中需要處理的鼠標(biāo)或鍵盤(pán)事件也不盡相同。所以在Java游戲開(kāi)發(fā)中,我們需要一個(gè)可替換的監(jiān)聽(tīng)器集合,用以變更不同游戲模式下的不同監(jiān)聽(tīng)事件,為了匹配需要,監(jiān)聽(tīng)器應(yīng)始終以接口方式實(shí)現(xiàn)。
    正文,關(guān)于Java游戲開(kāi)發(fā)中應(yīng)始終堅(jiān)持的10項(xiàng)基本原則:
    1、始終保持畫(huà)布的性。
    現(xiàn)實(shí)生活中,人類(lèi)通過(guò)口腔及消化道攝取的營(yíng)養(yǎng)物質(zhì)可以被心、肝、脾、肺、腎等內(nèi)臟吸收,卻沒(méi)有人會(huì)想給自己的心、肝、脾、肺、腎上也弄個(gè)嘴,因?yàn)橐恢滦缘墓δ軐?shí)現(xiàn)只要有一個(gè)就足夠了。但是,有時(shí)我們不經(jīng)意的在游戲中add、remove不同panel或canvas以求轉(zhuǎn)換畫(huà)面的行為,無(wú)異于是想給游戲的心、肝、脾、肺、腎上裝嘴的不智之舉,切忌Java的GUI都是畫(huà)出來(lái)的,重繪就好,沒(méi)有切換組件的必要,否則費(fèi)力不討好。
    2、始終以接口方式轉(zhuǎn)換監(jiān)聽(tīng)及處理圖像繪制,務(wù)必將視圖及邏輯層分開(kāi)。
    針對(duì)一些混合類(lèi)型的游戲,比如SLG+AVG、RPG+STG,我們將面臨不同模式下游戲的監(jiān)聽(tīng)器及繪圖器切換問(wèn)題。
    這時(shí)最簡(jiǎn)單的抉擇莫過(guò)于為每一個(gè)游戲類(lèi)型都訂制一個(gè)對(duì)應(yīng)的面板進(jìn)行切換,這樣雖表面上方省心,但卻也是最費(fèi)力而不討好的,且不說(shuō)閃爍問(wèn)題需要單獨(dú)解決,資源占用問(wèn)題,光冗余代碼就夠人頭痛了。
    其次就是在一個(gè)面板中針對(duì)不同游戲類(lèi)型使用switch判斷以切換監(jiān)聽(tīng)及繪圖,事件數(shù)量少時(shí)固然可以,效率也不錯(cuò),但稍微多一點(diǎn)恐怕就不那么簡(jiǎn)單,更多時(shí)則僅余郁悶,同樣不建議使用。
    就我個(gè)人所見(jiàn),解決這一問(wèn)題的方法莫過(guò)于沿用MVC模式,以接口方式構(gòu)建繪圖器及監(jiān)聽(tīng)器,當(dāng)游戲出現(xiàn)變更時(shí),我們僅僅需要切換監(jiān)聽(tīng)及繪圖接口,就可以迅速轉(zhuǎn)變游戲內(nèi)容,而無(wú)需區(qū)別對(duì)待不同的實(shí)現(xiàn),這樣即避免了組件切換的閃爍及延遲,也精簡(jiǎn)了代碼,更有利于開(kāi)發(fā)時(shí)的模塊劃分。
    3、始終以靜態(tài)方式加載游戲常用資源,緩存常用對(duì)象,并及時(shí)釋放無(wú)用資源。
    即使歷史發(fā)展到今天,Java依舊沒(méi)有徹底擺脫其系統(tǒng)資源殺手的可憎面目,GC機(jī)制也導(dǎo)致我們無(wú)法適時(shí)地釋放資源,new的越多,系統(tǒng)也變得越慢,這對(duì)于大量使用圖形資源的游戲來(lái)講尤其要命。所以我們要盡一切可能令常用資源靜態(tài)化為實(shí)例以避免反復(fù)調(diào)用,而將一些調(diào)用后不會(huì)再使用或很少使用的資源迅速 null以等待GC自動(dòng)回收。否則,你將發(fā)現(xiàn)你的游戲距離內(nèi)存溢出是那樣的近……
    4、始終以循環(huán)方式展開(kāi)游戲,利用線(xiàn)程控制游戲流程,避免出現(xiàn)僵直現(xiàn)象。
    事實(shí)上所謂的游戲開(kāi)發(fā),在某種程度上不過(guò)是由程序員制作出的一種夾雜著各類(lèi)圖形算法,用以適時(shí)地展示各種資源的幻燈程序;的區(qū)別在于,普通幻燈程序中人機(jī)交互性較弱,而游戲的人機(jī)交互性較強(qiáng)罷了。
    我們都知道,幻燈程序在展示中無(wú)論如何跳轉(zhuǎn)展示頁(yè),也必然有其固定的begin與end頁(yè)面,而且也勢(shì)必能重復(fù)從頭至尾順序循環(huán)其begin與end,以此構(gòu)成一個(gè)幻燈片。
    實(shí)際上游戲制作也一樣,無(wú)論游戲流程如何轉(zhuǎn)變,游戲都會(huì)有也必然會(huì)有一個(gè)主流程,或者說(shuō)一個(gè)主循環(huán)體,這樣我們才能由游戲開(kāi)始進(jìn)行到游戲結(jié)束,而不是從一個(gè)結(jié)束到另一個(gè)結(jié)束,也就是說(shuō)無(wú)論游戲中細(xì)節(jié)分支有多少,它的主流程處理及判定也必然是順序的。針對(duì)這一特性,決定了我們應(yīng)將游戲主體代碼至于一個(gè)大的循環(huán)體之內(nèi),再利用線(xiàn)程控制循環(huán)體中的游戲進(jìn)度,從而更好的順應(yīng)這一流程。簡(jiǎn)單的說(shuō),我們應(yīng)將循環(huán)體中每一個(gè)使用到的繪圖器都當(dāng)作于flash中的一楨,而線(xiàn)程的各種控制當(dāng)作時(shí)間軸,用以調(diào)節(jié)不同楨的播放速度及調(diào)用時(shí)機(jī),以此完成各種不同的事件交互。
    5、始終在處理復(fù)雜繪圖時(shí)直接準(zhǔn)備貼圖而非由程序繪制。
    我們都知道Java繪圖事實(shí)上是GDI實(shí)現(xiàn),因此其繪制復(fù)雜畫(huà)面的效率也就可想而知。通常強(qiáng)況下,除非當(dāng)前的效果非編程不能實(shí)現(xiàn),或者其所造成的資源損耗確實(shí)微小到可以忽略不計(jì),否則的方法就是用空間換效率,準(zhǔn)備好圖片直接貼上去吧,寧可增加些程序體積,也不要讓玩家因等待的憤怒而問(wèn)候你祖宗八輩。
    6、始終保證repaint僅刷新需要部分,避免無(wú)謂的全局重繪。
    每repaint一次,事實(shí)上就是將paint中的圖形打印到窗體上一次,窗體越大,處理的圖像越復(fù)雜,repaint所造成的資源損耗也勢(shì)必越多,運(yùn)行效率也勢(shì)必越低。但反過(guò)來(lái)說(shuō),由于Java允許我們限定repaint的范圍,因而我們可以將刷新限定在某一特定區(qū)域內(nèi),更準(zhǔn)確地說(shuō)我們可以?xún)H在需要變更畫(huà)面的位置上才進(jìn)行刷新,以此將損耗降低到最低限度,總體上說(shuō),即使我們會(huì)因?yàn)橛?jì)算刷新區(qū)域額外花費(fèi)些許時(shí)間,總體上講也比全局repaint要快得多。
    7、始終雙緩沖游戲圖像避免閃爍現(xiàn)象發(fā)生。
    對(duì)于Java繪圖而言,每次調(diào)用repaint方法時(shí)都會(huì)清除整個(gè)屏幕,然后paint才顯示畫(huà)面。而萬(wàn)一系統(tǒng)速度不夠,在清除背景和繪制圖像間的短暫間隔內(nèi)被用戶(hù)看見(jiàn),就出現(xiàn)了所謂的閃爍現(xiàn)象;簡(jiǎn)單來(lái)講閃爍的成因就是運(yùn)算效率不足,使得repaint與paint不連貫造成的。
    針對(duì)這種情況,我們需要利用雙緩沖技術(shù)加以解決。
    雙緩沖實(shí)現(xiàn)其實(shí)簡(jiǎn)單至極,主要過(guò)程就是先創(chuàng)建一個(gè)等大小于希望繪制圖形的Image,而后取得其Graphics,每當(dāng)paint繪圖時(shí)我們不直接將圖像繪制于paint函數(shù)的Graphics上,而是繪制于我們創(chuàng)建的緩沖圖像的Graphics上,當(dāng)繪制完成后再調(diào)用paint函數(shù)提供的 drawImage方法,將整個(gè)后臺(tái)圖像一次畫(huà)到屏幕上去。這種方法的優(yōu)點(diǎn)在于大部分繪制是在后臺(tái)進(jìn)行的。將后臺(tái)繪制的圖像一次繪制到屏幕上。
    這時(shí)只要系統(tǒng)速度正常,我們所看到的繪圖將不再有閃爍現(xiàn)象發(fā)生。
    8、始終在自繪組件的桌面游戲中應(yīng)用AWT或SWT而非Swing。
    眾所周知,Swing(JFC)的GUI是以AWT為基礎(chǔ)在本地窗體繪制而成,相較AWT雖然提供了更為豐富的組件,但也意味著它占用了更多的資源。而事實(shí)上,大多數(shù)Java桌面游戲組件是由開(kāi)發(fā)者所針對(duì)性繪制,并非Swing庫(kù)提供,也不需要Swing庫(kù)支持,我們完全可以放棄Swing而選擇AWT或 SWT(SWT繪圖與AWT/Swing繪圖在方法上略有區(qū)別,但本質(zhì)一樣)這種直接Native而來(lái)的界面,實(shí)在不需勞動(dòng)Swing他老人家,平白的耗費(fèi)掉那些本就因使用Java應(yīng)用而稀缺的系統(tǒng)資源。
    9、始終別忘了在Graphics處理完畢后dispose。
    Graphics的dispose與數(shù)據(jù)庫(kù)Connection的close可謂異曲同工。為此我特意做了一個(gè)實(shí)驗(yàn),在死循環(huán)中無(wú)間隔無(wú)優(yōu)化的反復(fù) repaint一幅2000X2000的大圖,應(yīng)用dispose時(shí)雖然刷新很慢并伴隨閃爍但總體講正常,而去掉dispose運(yùn)行大約一分鐘后萬(wàn)惡的溢出大神降臨……
    當(dāng)然,就像Connection應(yīng)在全部操作完成后才close一樣,Graphics也僅在全部繪圖完畢后才需要dispose,也就是當(dāng)最后一個(gè)paint最后一次draw后,別忘了留個(gè)dispose關(guān)門(mén),除非你很想看見(jiàn)溢出大神……
    10、始終以運(yùn)算效率為第一優(yōu)先,可適當(dāng)放棄代碼可讀性,可適當(dāng)違背OO原則。
    以Java進(jìn)行游戲開(kāi)發(fā),的問(wèn)題莫過(guò)于系統(tǒng)資源的損耗,在關(guān)鍵問(wèn)題上,就別死抱著OO不放了。
    若你能始終堅(jiān)持以上十點(diǎn),雖然別指望就此超越C/C++游戲的運(yùn)行效率,但已能與Delphi游戲爭(zhēng)鋒而無(wú)愧色,傲視于vb6、Flash、rmxp、rmvx等工具開(kāi)發(fā)的游戲而鄙夷之。