文章關鍵字:Java 優化 效率 AWT SWT SWING 精簡 繪圖 監聽 遊戲開發 GC DRAW

關於文章中涉及的兩個杜撰概念:

一、繪圖器:眾所周知,Java GUI以paint進行繪圖,以repaint進行影像重新整理,而完成repaint及paint這一連貫過程中所用到繪圖元件,我將其稱為繪圖器。就我個人的體會,繪圖器的呼叫時機應始終處於repaint之後paint之前,即通過repaint觸發重新整理後執行,當其中的具體邏輯完成其對應的影像繪製後,再通過統一介面將其影像插入paint中,為了匹配需要,繪圖器應始終以介面方式實現。

二、監聽器:這裡所說的監聽器,並不是特指某個Listener元件,而是包括Java遊戲中所需的所有監聽器集合。由於Java遊戲中很可能會切換不同的遊戲模式,而不同模式遊戲中需要處理的滑鼠或鍵盤事件也不盡相同。所以在Java遊戲開發中,我們需要一個可替換的監聽器集合,用以變更不同遊戲模式下的不同監聽事件,為了匹配需要,監聽器應始終以介面方式實現。

正文,關於Java遊戲開發中應始終堅持的10項基本原則:

1、始終保持畫布的唯一性。

   現實生活中,人類通過口腔及消化道攝取的營養物質可以被心、肝、脾、肺、腎等內臟吸收,卻沒有人會想給自己的心、肝、脾、肺、腎上也弄個嘴,因為一致性的功能實現只要有一個就足夠了。但是,有時我們不經意的在遊戲中add、remove不同panel或canvas以求轉換畫面的行為,無異於是想給遊戲的心、肝、脾、肺、腎上裝嘴的不智之舉,切忌Java的GUI都是畫出來的,重繪就好,沒有切換元件的必要,否則費力不討好。

2、始終以介面方式轉換監聽及處理影像繪製,務必將檢視及邏輯層分開。

   針對一些混合型別的遊戲,比如SLG+AVG、RPG+STG,我們將面臨不同模式下游戲的監聽器及繪圖器切換問題。

   這時最簡單的抉擇莫過於為每一個遊戲型別都訂製一個對應的皮膚進行切換,這樣雖表面上方省心,但卻也是最費力而不討好的,且不說閃爍問題需要單獨解決,資源佔用問題,光冗餘程式碼就夠人頭痛了。

   其次就是在一個皮膚中針對不同遊戲型別使用switch判斷以切換監聽及繪圖,事件數量少時固然可以,效率也不錯,但稍微多一點恐怕就不那麼簡單,更多時則僅餘鬱悶,同樣不建議使用。

   就我個人所見,解決這一問題的最好方法莫過於沿用MVC模式,以介面方式構建繪圖器及監聽器,當遊戲出現變更時,我們僅僅需要切換監聽及繪圖介面,就可以迅速轉變遊戲內容,而無需區別對待不同的實現,這樣即避免了元件切換的閃爍及延遲,也精簡了程式碼,更有利於開發時的模組劃分。

3、始終以靜態方式載入遊戲常用資源,快取常用物件,並及時釋放無用資源。

   即使歷史發展到今天,Java依舊沒有徹底擺脫其系統資源殺手的可憎面目,GC機制也導致我們無法適時地釋放資源,new的越多,系統也變得越慢,這對於大量使用圖形資源的遊戲來講尤其要命。所以我們要盡一切可能令常用資源靜態化為唯一例項以避免反覆呼叫,而將一些呼叫後不會再使用或很少使用的資源迅速 null以等待GC自動回收。否則,你將發現你的遊戲距離記憶體溢位是那樣的近……

4、始終以迴圈方式展開遊戲,利用執行緒控制遊戲流程,避免出現僵直現象。

   事實上所謂的遊戲開發,在某種程度上不過是由程式設計師製作出的一種夾雜著各類圖形演算法,用以適時地展示各種資源的幻燈程式;唯一的區別在於,普通幻燈程式中人機互動性較弱,而遊戲的人機互動性較強罷了。

   我們都知道,幻燈程式在展示中無論如何跳轉展示頁,也必然有其固定的begin與end頁面,而且也勢必能重複從頭至尾順序迴圈其begin與end,以此構成一個幻燈片。

   實際上游戲製作也一樣,無論遊戲流程如何轉變,遊戲都會有也必然會有一個主流程,或者說一個主迴圈體,這樣我們才能由遊戲開始進行到遊戲結束,而不是從一個結束到另一個結束,也就是說無論遊戲中細節分支有多少,它的主流程處理及判定也必然是順序的。針對這一特性,決定了我們應將遊戲主體程式碼至於一個大的迴圈體之內,再利用執行緒控制迴圈體中的遊戲進度,從而更好的順應這一流程。簡單的說,我們應將迴圈體中每一個使用到的繪圖器都當作於flash中的一楨,而執行緒的各種控制當作時間軸,用以調節不同楨的播放速度及呼叫時機,以此完成各種不同的事件互動。

5、始終在處理複雜繪圖時直接準備貼圖而非由程式繪製。

   我們都知道Java繪圖事實上是GDI實現,因此其繪製複雜畫面的效率也就可想而知。通常強況下,除非當前的效果非程式設計不能實現,或者其所造成的資源損耗確實微小到可以忽略不計,否則最好的方法就是用空間換效率,準備好圖片直接貼上去吧,寧可增加些程式體積,也不要讓玩家因等待的憤怒而問候你祖宗八輩。

6、始終保證repaint僅重新整理需要部分,避免無謂的全域性重繪。

   每repaint一次,事實上就是將paint中的圖形列印到窗體上一次,窗體越大,處理的影像越複雜,repaint所造成的資源損耗也勢必越多,執行效率也勢必越低。但反過來說,由於Java允許我們限定repaint的範圍,因而我們可以將重新整理限定在某一特定區域內,更準確地說我們可以僅在需要變更畫面的位置上才進行重新整理,以此將損耗降低到最低限度,總體上說,即使我們會因為計算重新整理區域額外花費些許時間,總體上講也比全域性repaint要快得多。

7、始終雙緩衝遊戲影像避免閃爍現象發生。

   對於Java繪圖而言,每次呼叫repaint方法時都會清除整個螢幕,然後paint才顯示畫面。而萬一系統速度不夠,在清除背景和繪製影像間的短暫間隔內被使用者看見,就出現了所謂的閃爍現象;簡單來講閃爍的成因就是運算效率不足,使得repaint與paint不連貫造成的。

   針對這種情況,我們需要利用雙緩衝技術加以解決。

   雙緩衝實現其實簡單至極,主要過程就是先建立一個等大小於希望繪製圖形的Image,而後取得其Graphics,每當paint繪圖時我們不直接將影像繪製於paint函式的Graphics上,而是繪製於我們建立的緩衝影像的Graphics上,當繪製完成後再呼叫paint函式提供的 drawImage方法,將整個後臺影像一次畫到螢幕上去。這種方法的優點在於大部分繪製是在後臺進行的。將後臺繪製的影像一次繪製到螢幕上。

   這時只要系統速度正常,我們所看到的繪圖將不再有閃爍現象發生。

8、始終在自繪元件的桌面遊戲中應用AWT或SWT而非Swing。

   眾所周知,Swing(JFC)的GUI是以AWT為基礎在本地窗體繪製而成,相較AWT雖然提供了更為豐富的元件,但也意味著它佔用了更多的資源。而事實上,大多數Java桌面遊戲元件是由開發者所針對性繪製,並非Swing庫提供,也不需要Swing庫支援,我們完全可以放棄Swing而選擇AWT或 SWT(SWT繪圖與AWT/Swing繪圖在方法上略有區別,但本質一樣)這種直接Native而來的介面,實在不需勞動Swing他老人家,平白的耗費掉那些本就因使用Java應用而稀缺的系統資源。

9、始終別忘了在Graphics處理完畢後dispose。

   Graphics的dispose與資料庫Connection的close可謂異曲同工。為此我特意做了一個實驗,在死迴圈中無間隔無優化的反覆 repaint一幅2000X2000的大圖,應用dispose時雖然重新整理很慢並伴隨閃爍但總體講正常,而去掉dispose執行大約一分鐘後萬惡的溢位大神降臨……

   當然,就像Connection應在全部操作完成後才close一樣,Graphics也僅在全部繪圖完畢後才需要dispose,也就是當最後一個paint最後一次draw後,別忘了留個dispose關門,除非你很想看見溢位大神……

10、始終以運算效率為第一優先,可適當放棄程式碼可讀性,可適當違背OO原則。

   以Java進行遊戲開發,最大的問題莫過於系統資源的損耗,在關鍵問題上,就別死抱著OO不放了。


——————華麗的分割線——————

若你能始終堅持以上十點,雖然別指望就此超越C/C++遊戲的執行效率,但已能與Delphi遊戲爭鋒而無愧色,傲視於vb6、Flash、rmxp、rmvx等工具開發的遊戲而鄙夷之(-_-||| )。

但說時容易做時難,有些部分筆者也沒有做到。惰性使然,諸君切勿學我……