發新話題

[分享] 優化Java動畫編程中的顯示效果

優化Java動畫編程中的顯示效果

Java動畫編程有多種實現方法,但它們實現的基本原理是一樣的,即在屏幕上畫出一系列的幀來造成運動的感覺。Java多線程技術是Java動畫編程中普遍運用的技術,它在控制動畫程序的流程和動畫的顯示效果方面起著重要的作用。Java動畫編程中的動畫閃爍和圖像殘缺不全等現象,是Java程序員經常遇到的問題。本文以作者應用實例程序為基礎,闡述如何運用多線程、重載Update、雙緩衝和圖像跟蹤等技巧來解決這類問題,以達到動畫顯示的最佳效果。

Java多線程技術

Java多線程技術簡介

目前,線程(Thread)已經為許多操作系統和應用開發系統所採用。線程是程序的單個控制流,具有順序程序的特點。但是,線程不是一個程序,它僅僅是程序的一個執行序列。線程具有很強的並發功能,在同一時刻可以有多個線程同時處於執行狀態。線程是動態的,具有一定的生命週期,分別經歷從創建、執行、阻塞、直到消亡的過程。Java語言對多線程編程的支持有兩種實現方法:一種是直接繼承Thread類,另一種是實現Runnable接口。Thread類提供了對線程的控制方法,如start(),stop(),run()、suspend()、resume()和sleep()等方法,它們可以對線程的狀態進行控制。

動畫線程的設計與實現

為了每秒中多次更新屏幕,必須創建一個線程來實現動畫的循環,這個循環要跟蹤當前幀並響應週期性的屏幕更新要求。許多Java初學者容易犯的一個錯誤是將動畫循環放在paint()中,這樣佔據了主AWT線程,而主線程將負責所有的繪圖和事件處理。因此,應該生成一個獨立的動畫線程來完成圖像的顯示和更新。例如,在一個Applet框架下,當Applet啟動(Start)時,生成一個動畫線程;在Applet停止(stop)時,終止該動畫線程以釋放它所佔用的CPU資源。下列程序代碼(簡稱「C1」代碼)是該動畫線程的具體實現:
public void start() {        if(animatorThread==null) {                animatorThread=new Thread(this);//開始動畫線程animatorThread.start();        }}public void stop(){                //停止動畫線程                animatorThread=null;}
上面終止動畫線程的時候,並不是調用該動畫線程的stop()方法,而是設置該動畫線程為null。因為如果直接調用線程的stop()方法會強制線程終止所有的執行工作,有時會帶來不好的結果。設置該動畫線程為null,則在run()方法中,由於不滿足循環條件,線程會自然退出。這樣,也進一步優化了該動畫程序。

重載update()和雙緩衝技術消除閃爍

在Java中,動畫發生閃爍有兩個原因:一個是由於在顯示下一幀畫面的時候,調用了repaint()方法;而repaint()方法被調用時,要清除整個背景,然後才調用paint()方法顯示畫面。這樣,在清除背景和繪製圖像的短暫時間間隔內被用戶看見的就是閃爍。另一個是由於paint()方法要進行複雜的計算,繪製每一幀花費的時間太長,圖像中的各個像素值不能同時得到,使得動畫的生成頻率低於顯示器的刷新頻率,從而造成閃爍。

下面兩種方法可以明顯地消除或減弱閃爍。

重載update()方法

當AWT接收到一個Applet的重繪請求時,它就調用Applet的update()方法。缺省情況下,update()方法清除Applet的背景,然後調用paint()方法。重載update()方法就可以將以前在paint()方法中的繪圖代碼包含在update()方法中,從而避免每次重繪時將整個區域清除。既然背景不再自動清除,Java程序員需要自己在update()中完成。

雙緩衝技術

另一種消除幀之間閃爍的方法是使用雙緩衝技術,它在許多動畫Applet中被使用。主要原理是創建一幅後台圖像,將每一幀畫入圖像,然後調用drawImage()方法將整個後台圖像一次畫到屏幕上去。這種方法的優點在於大部分繪製是離屏的。將離屏圖像一次繪至屏幕上,比直接在屏幕上繪製要有效得多。在創建後台圖像前,首先要通過調用createImage()方法生成合適的後台緩衝區,然後獲得在緩衝區做圖的環境(即Graphics類對像)。

下列實例程序代碼(簡稱「C2」代碼)就是這兩種方法的結合使用,雙緩衝技術在重載update()方法中實現。其中,offImage是Image類的對象,offGraphics是Graphics類的對象,這兩個類對象是實現雙緩衝技術的關鍵。相關代碼如下:
public void paint(Graphics g){                update(g);        }        public void update(Graphics g){                Dimension d=getSize();                //如果後台圖像不存在,就創建一個後台圖像if((offGraphics==null)||(d.width!=offDimension.width)  ||(d.height!=offDimension.height)) {                        offDimension=d;                        offImage=createImage(d.width,d.height);                        offGraphics=offImage.getGraphics();                }                //擦除上一幀                offGraphics.setColor(getBackground());                offGraphics.fillRect(0,0,d.width,d.height);                offGraphics.setColor(Color.black);                //將當前的幀輸出到指定的image中                for(int i=0 ; i<10 ; i++){               offGraphics.drawImage(images,frameNumber*5%(d.width/2)                          ,i*d.height/10,this);                }                //輸出指定的後台圖像g.drawImage(offImage,frameNumber*5%(d.width/2),0,this);        }
雙緩衝技術可以使動畫平滑,但有一個缺點,要分配一個後台圖像的緩衝,如果圖像相當大,這將佔用很大一塊內存。

圖像跟蹤與程序的逐步完善

圖像跟蹤

當動畫線程剛剛啟動的時候,由於沒有全部載入圖像,屏幕上顯示的畫面經常是殘缺不全的。這時可以使用MediaTracker或ImageOberver類對像進行圖像跟蹤,待圖像全部載入後,再調用drawImage()方法將圖像輸出到屏幕上去。DrawImage()方法的第四個參數正是ImageObserver類對象,所以可以用ImageObserver類對像進行圖像跟蹤。在實際應用Applet程序的init()方法中實現圖像跟蹤,相當於在動畫線程的DrawImage()方法調用以前就畫了一次圖像,因為動畫線程的初始化過程,即init()方法是先被調用的。下列代碼(簡稱「C3」代碼)展示了init()方法使用MediaTracker類對像來實現跟蹤圖像的載入,代碼如下:
public void init(){ tracker=new MidiaTracker(this); for(int i=1;i<=10;i++){  image[i-1]=getImage(getCodeBase(),"image"+i+" .gif");  //用MediaTracker類對象的addImage()方法跟蹤圖像的載入  tracker.addImage(images[i-1],0); }......}
程序的進一步完善

在「C2」代碼的重載update()方法中加入下列if語句,從而對MediaTracker類對象的圖像跟蹤方法做出判斷,if語句如下:
if(!tracker.checkAll()){ //如果圖像還沒有裝載完畢,則僅清除背景,同時輸出一個狀態          g.clearRect(0,0,d.width,d.height);          g.drawString("Please wait...",0,d.height/2);          return;        }
在「C1」代碼的stop()方法中加入兩行代碼,用以釋放由雙緩衝技術所佔用的內存資源,這時stop()方法改為:
public void stop(){        //停止動畫線程        animatorThread=null;        //釋放用於雙緩衝的內存資源        offGraphics=null;        offImage=null;}
程序修改到此,還有一個小問題,就是動畫線程啟動後,第一幅圖像有時仍有殘留痕跡,而不是隨著圖像的更新而完全擦除掉。如果想解決此問題,只要將「C2」代碼中最後的for()循環和g.drawImage()方法改為如下代碼就可以了。
for(int i=0;i<10;i++){  offGraphics.drawImage(images[frameNumber%10],     ,frameNumber*5%(d.width),i*d.height/10,this);}g.drawImage(offImage,0,0,this);
保持恆定的幀速度

為了使用戶觀看動畫時沒有閃爍感,至少需要達到每秒12幀的速度。更高的幀速度會產生更平滑的動畫。通常,在動畫顯示的每兩幀之間,調用線程的sleep()方法休眠一個固定的時間。這樣做的缺點是使用絕對的延遲時間會使延遲過長,即造成等待時間過長。為了每秒顯示10~20幀圖像,並保持恆定的幀速度(也就是恆定的顯示頻率),在動畫線程的run()方法中可加入如下代碼:
try{        startTime+=delay;        Thread.sleep(Math.max(0,startTime-System.currentTimeMillis()));}catch(InterruptedException e){ break;}
本文著重闡述了優化Java動畫編程的顯示效果的幾種方法。當然,隨著Java技術以及其它計算機技術的發展,還將會有多種優化動畫顯示效果的方法,例如,在提高動畫的幀速度方面,可在Java中調用微軟公司的DirectDraw工具。但是本文所介紹的方法更具有通用性。

TOP

發新話題

本站所有圖文均屬網友發表,僅代表作者的觀點與本站無關,如有侵權請通知版主會盡快刪除。