Java游戲開發(fā)中應始終堅持的10項基本原則

字號:

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