天天看點

Java遊戲程式設計初步 (附錄部分有moveball的完整源代碼)

         德國人Fabian Birzele 編寫的遊戲開發指南書         網站:http://www.javacooperation.gmxhome.de/             源碼

及指南動畫展示下載下傳:http://www.javacooperation.gmxhome.de/Downloads/Tutorial.zip    

現在流行的遊戲似乎都是用C或C++來開發的。在java平台上幾乎沒有很大型及可玩的流行遊戲。由于java是個新生語言,他的許多特性還有待大家的發掘,但是我們不能否認Java在遊戲程式設計方面的強大性。本文将帶領大家一步一步學習編寫Java遊戲。最終打造屬于自己的Java遊戲。

  在開始之前我們得确認你已經安裝了Java JDK,并已經安裝了浏覽器軟體如IE。本章是以Internet為開發對象,一步一步教大家認識Java的Thread、Applets….以及遊戲程式設計要注意的一些方方面面。并在每一小部分附上了相應的源代碼以供大家參考,最後我們還會對我們的遊戲程式進行指導性的提示。在文章中我們還穿插了很多建設性的問題,讓讀者參與到我們的開發中來。但是由于本章指在帶領大家進入這個門檻,大部分知識并不會很詳細說明,有興趣的讀者可查閱相關的資料補充。在開始之前我們還得确認你已經知道類,繼承和java語言的一些基本屬性了。

  基本applet

  Applets是一種Web浏覽器上的小程式,由于applet對系統而言絕對安全,是以它做的事比aaplication有限,但是對于用戶端的程式,applets仍然是個很強大的工具。為了浏覽和運作友善,我們就以applet為開發對象。

  開發Applets程式,我們得繼承Applet類,并覆寫必要的函數,下面幾個函數控制了Web頁面上的applet生成與執行。

函數 作用 Init() 這個函數會被自動調用,執行applet的初始化動作—包括元件在版面上的配置設定,你一定得覆寫它 Start() 每當浏覽器顯示applet内容時,都會調用它,讓applet開啟其正規工作(尤其是那些被stop()關閉的工作),調用init()之後也會調用這個函數 Stop() 每當浏覽器不顯示内容時,都會調用它。讓applet關閉某些耗資源的工作,調用destory()之後也會調用這個函數 Destroy() 浏覽器将applet自網頁移除之際,便會調用它,以執行”applet不再被使用”應該做的最後釋放資源等動作 Paint() 讓你在Applet界面上進行相應的繪畫動作,每次重新整理時都會重畫

  所有的applet檔案源檔案名和java應用程式一樣都是.java為擴充名,編譯後的執行檔案擴充名為.class,由于在applet中已經沒有了main()函數,它是和html自動內建,是以我們要執行applet,要在html源檔案中放入一特定的标簽(tag),才能告訴網頁如何裝載并執行這個applet,這裡有一點要注意,我們執行的網頁必須能執行java程式。

  普通Html 源碼格式

<HTML>

<APPLET CODE="HelloWorld.class" WIDTH=300 HEIGHT=500>

</APPLET>

</HTML

  <applet code ="HelloWorld.class" width=300 height=500>這行即applet的執行處。

  applet 執行文檔為 ="HelloWorld.class" 告訴網頁”applet ”擴充檔案為HelloWorld.class

  width 和 height 告訴浏覽器這個顯示的applet的大小

  有關标簽(tag)的說明,大家可在網上找到很多相關的說明文檔。

  線程

  由于apllet,java應用程式的執行都和線程有關。我們來大概了解一下線程的概念。

  線程也稱為輕型程序 (LWP)。每個線程隻能在單個程序的作用域内活動、協作和資料交換,并且在計算資源方面非常廉價。線程需要作業系統的支援,是以不是所有的機器都提供線程。Java 程式設計語言,作為相當新的一種語言,已将線程支援與語言本身合為一體,這樣就對線程提供了強健的支援。

  Thread 類是一個具體的類,即不是抽象類,該類封裝了線程的行為。要建立一個線程,程式員必須建立一個從 Thread 類導出的新類。程式員必須覆寫 Thread 的 run() 函數來完成有用的工作。使用者并不直接調用此函數;而是必須調用 Thread 的 start() 函數,該函數再調用 run()。

  但是使用Thread類實作線程,增加了程式的類層次,是以一般程式員都由另一個java線程接口Runnable接口來實作,Runnable接口隻有一個函數run(),此函數必須由實作了此接口的類實作。

  線程中有幾個重要的方法是我們得了解:

   Thread.start(): 啟動一個線程

   Thread.stop(): 停止一個線程

   Thread.sleep(time in milliseconds): 暫停線程在一個等待時間内。

動畫技術

  自由降落動畫

  了解了一些基本概念後,下面我們就開始我們的實質性的工作。我們設計一個球從螢幕頂上降落到螢幕下面,程式實作比較簡單,但是這是遊戲動畫中不可少的一部分。在開始之前我們來看看我們的applet開始語句。

import java.awt.*;

import java.applet.*;

public class Ball

extends Applet implements Runnable

{

public void init() { }

public void start() { }

public void stop() { }

public void destroy() { }

public void run () { }

public void paint (Graphics g) { }

}

  在開始函數中我們要建立程式的主線程,并啟動這個線程。一旦做好這些準備工作以後,當applet第一次被顯示時,就會建立線程對象的一個執行個體,并把this對象作為建構方法的參數,之後就可以啟動動畫了

public void start ()

{

// 定義一個新的線程

Thread th = new Thread (this);

// 啟動線程

th.start ();

}

  現在我們來看看線程的run方法,它在循環while(),中每隔20毫秒重畫動畫場景。sleep這個方法很重要,如果在run循環中沒有這部分,圓的重畫動作将執行得很快,其他方法将得不到有效執行,也即我們在螢幕上将看不到球的顯示。

public void run ()

{

//

while (true)

{

// 重畫applet畫面

repaint();

try

{

// 暫停線程20毫秒

Thread.sleep (20);

}

catch (InterruptedException ex)

{

}

}

}

  我們接着讀下去之前,有幾個問題需要回答。你也許會問,浏覽器調用Java小程式的start和stop方法嗎? run 方法是如何被調用的? 情況是這樣的,當浏覽器啟動了一個内部線程時,就相應地啟動了applet 的運作。當網頁顯示時,就啟動了applet的start 方法。Start方法建立一個線程對象,并把applet自身傳送給線程,以實作run方法。

  此時,兩個線程在運作:由浏覽器啟動的初始線程,以及處理動畫的線程。快速檢視applet的start方法,可以知道它建立了線程,并啟動了它。類似地,當網頁被隐藏後,applet的stop方法就調用了線程的stop方法。

  注意:在Applets和Threads中的 start/stop子程式

  在Applet 和Thread 兩個類中都有start和stop方法,但它們的功能不同。一旦Applet 顯示時,就調用applet的start方法,一旦applet 隐藏時,就調用applet的stop 方法。相反,線程的start方法将調用run方法,線程的stop方法将停止正在執行的線程。

public void paint(Graphics g);

  paint() 方法所傳入的參數—— java.awt.Graphics 對象将是一個經裁剪的相關顯示區的圖像代表(而不會是整個顯示區)。我們對圓球圖形元素的繪制就是在通過重寫 paint()方法,在其中對傳入的Graphics 對象g進行操作完成的。

  當我們應用程式的邏輯要對系統界面進行更新時,調用 repaint() 方法來通知AWT線程進行重新整理操作。repaint() 方法實際會讓 AWT線程去調用另外一個方法,update。update方法在預設情況下會做兩件事,一是清除目前區域内容,二是調用其 paint()方法完成實際繪制工作。paint、repaint、update 三個方法關系如圖所示:

Java遊戲程式設計初步 (附錄部分有moveball的完整源代碼)

  但是如何讓我們的圓運動呢?這裡我們利用函數Graphics 類的fillOval函數來設定了圓的起始位置x,y。現在我們隻要線上程run方法中每機關時間增大y的值,線程将在每一個機關時間内重畫圓的位置。每機關時間y值越大,下降的速度就會越快。在螢幕上我們就将看到這個圓球做自由降落運動。 如下代碼所示:

while (true)

{

// 設定動畫移動速度

y +=1;

}

public void paint (Graphics g)

{

//設定球的顔色

g.setColor (Color.blue);

// 從x,y位置處畫一個實心的圓

g.fillOval (x , y, 2 * r, 2 * r);

}

  在這之前我們需要在開始處設定一些變量,定義好x,y的預設位置值。r 在此處是我們畫的圓的半徑大小。

int x = 100;

int y = 20;

int r = 10;

  我們的自由降落的動畫就完了。是不是很簡單,如果還有地方不明白,大家可在此處下載下傳完整的代碼及應用程式。看看真實的示範效果和代碼。下面每一部分我們也将在最後附上相應的源代碼及應用程式下載下傳。如果大家有興趣,可改變y的值,及x的值,你會得到不同的下降效果。

雙緩沖,消除閃爍

  大家可能注意到了上面例子中的我們下降的圓看起來不是很清晰,帶着很嚴重的閃爍。這種現象在寫遊戲程式中是普遍存在的現象。這是由于我們的repaint()函數導緻的結果,由于它在調用paint()函數前會自動清除螢幕,是以在一個毫秒内我們會看到一個空白的螢幕,在快速的變換操作中就出現了閃爍現象。

  解決這種閃爍現象有幾種方法,下面是兩種方法的列舉說明,其他的方式大家可以自己嘗試。

  第一種:我們始終不清除螢幕顯示,但是這個方法會帶來個附作用,我們下降的圓不在是一個圓了,而是一條直線,因為它的下降過程中沒有了斷點,保留了所有的圓球的影象。我們隻要在Ball.java内加上如下代碼update(Graphics g) {paint(g)},你就會看到一條很長的線拉出來。有興趣的朋友可以試試。

  第二種:使用雙緩沖機制(Double buffering)

  現在大部分的遊戲都是采用雙緩沖機制來解決螢幕的閃爍現象,我們就以此為例來進行說明,有關緩沖區及相關緩沖機制的概念,大家可參考附錄的緩沖說明。

  而我們的程式中簡單的說就是在顯示我們想要的圖畫之前,把所有的圖畫先在背景繪制好并存放到相應的圖像變量中去。當需要顯示時直接複制到前台螢幕就可以了。

  具體實作:

   1.首先我們用createImage方法建立一背景圖像類變量

   2.然後使用getGraphics()方法得到目前圖像的圖形關聯

   3.在背景處理所有相關的處理,如清除螢幕,背景繪畫等等

  當完成所有的背景工作後,複制已經繪制好的圖像到前台,并覆寫前台的存在圖像。這樣我們的所有操作都是在背景前行,在螢幕顯示新的圖像前,這些内容都已經存在于背景了。是以你也将在任何時刻都看不到空螢幕的存在。也即代表閃爍消除了。

  下面我們來看看相關的代碼說明:

  在開始之前我們得先在程式的開始部分聲明兩個執行個體變量用來存儲背景圖畫。如下:

private Image bgImage;

private Graphics bg;

  然後我們利用update()方法來實作雙緩沖機制。

  Update()方法要實作下面三個步驟:

   1.清除螢幕上的元件

   2.設定相關聯元件的前景色

   3.調用paint方法重畫螢幕

public void update (Graphics g)

{

// 初始化buffer

if (bgImage == null)

{

bgImage = createImage (this.getSize().width, this.getSize().height);

bg = bgImage.getGraphics ();

}

// 背景清屏,即設定圓球元件和背景一樣的顔色,大小

bg.setColor (getBackground ());

bg.fillRect (0, 0, this.getSize().width, this.getSize().height);

// 繪制相應的元素元件

bg.setColor (getForeground());

paint (bg);

// 在螢幕上重畫已經繪制好的圓

g.drawImage (bgImage, 0, 0, this);

}

  此處g 為螢幕圖形,bg為g的背景關聯。而bgimage包含了bg圖形。請于此處來看看我們的源代碼例子及示範效果。

  改變運動方向

  我們已經解決了動畫的兩個很重要的問題,移動動畫和閃爍消除。但是我們很快會發現一個問題,球從螢幕頂上落下來後,就不見了。這可不是我們所需要的。我們要的是一個生動的畫面。如何讓我們的球不穿過螢幕而始終在螢幕上活動呢?在開始之前,我建議大家自己想辦法解決,如果你能自己處理好了。你的水準将會有一個很大的提高。如果沒有想出好辦法,沒關系,下面我們将很詳細的說明球的方向改變的技術。

  不知道大家注意了沒有,在上面我們說到球的移動時,我們是通過增加y的值,讓線程重畫新的圓位置和圖形。如果改變y的值的大小球的下降速度也會改變。不錯,這就是我們的解決方法 ,我們隻要用一個變量來存儲這個速度的大小而不用固定的值。線上程執行也即run方法處我們用代碼改變速度的方向,球的方向也會改變。即設定這個變量”speed”為”-1”。當然在設定值前我們要進行判斷,你是想讓球穿過螢幕從别一邊開始顯示,還是來回反彈呢!如果想來回反彈,我們隻要不讓球的半徑值超過applet螢幕顯示區域就可以了。此處我們用r/2來表示球的半徑。

//反彈下落球

if (y > appletsize_y – r/2)

{

// 改變方向

x_speed = -1;

}

// 反彈上升球

else if ( < r/2)

{

// 改變方向

x_speed = +1;

}

  至于如何讓球穿過從螢幕頂上重新下降,我們在此沒有說明,也不會說明了。留給大家自己去想想,已經很簡單了。在下面我們附上了兩種方式的源代碼和執行檔案。如果大家運作程式,大家可能會發現,我們的球的大小和速度有一些改變。這裡是為了更好的反應示範效果。

多媒體

  使用多媒體聲音

  多媒體功能在遊戲中是必不少的一部分,優美的音樂,漂亮的界面往往是一個成功遊戲必需具備的條件。

  在開始之前我們先了解一下主要的小型聲音檔案類型:

  AU - (擴充名為AU或SND)适用于短的聲音檔案,為Solaris和下一代機器的通用檔案格式,也是JAVA平台的标準的音頻格式。AU類型檔案使用的三種典型音頻格式為: 8位μ-law類型(通常采樣頻率為8kHz), 8位線性類型,以及16位線性類型。

  WAV - (擴充名為WAV)由 Microsoft和 IBM共同開發,對WAV的支援已經被加進Windows 95并且被延伸到Windows 98. WAV檔案能存儲各種格式包括μ-law,a-law和 PCM (線性)資料。他們幾乎能被所有支援聲音的Windows應用程式播放。

  AIFF - (擴充名為AIF或IEF)音頻互換檔案格式是為Macintosh計算機和Silicon Graphics (SGI)計算機所共用的标準音頻檔案格式。AIFF和 AIFF-C幾乎是相同的,除了後者支援例如μ-law和 IMA ADPCM類型的壓縮。

  MIDI - (擴充名為MID)樂器數字接口MIDI是為音樂制造業所認可的标準,主要用于控制諸如合成器和聲霸卡之類的裝置。

  在JDK1.0上,java隻支援*.au格式的聲音檔案,但是java2的API以及聲音包提供了很強大的對聲音技術的支援。而此部分為了讓大家快速掌握遊戲程式設計的基本知識,我們僅使用了AudioClip接口類來實作播放"*.wav"。如果大家有興趣可參考sun java網站的聲音sapmle,上面提供了完備的執行個體和教程說明。

  使用AudioClip接口比較簡單,我們隻要執行個體對象,加載聲音檔案後,再在任何地方播放即可。恢複和播放聲音最簡單的方法是通過Applet類的play()方法。

  AudioClip接口

   1.播放 play

   2.循環 loop

   3.停止 stop

  啟動和停止聲音檔案,或循環播放,你必須用 applet的 getAudioClip方法把它裝載進入 AudioClip對象,getAudioClip方法要用一個或兩個參數,當作播放的訓示。第一個或唯一的一個參數是 URL參數,用來訓示聲音檔案的位置,第二參數是檔案夾路徑指針。

  下列代碼行舉例說明加載聲音檔案進入剪貼對象: 下面的"gun.wav"是指目前目錄下的聲音檔案。我們也可用*.au格式的檔案代替。

AudioClip co = getAudioClip(getCodeBase(), "gun.wav");

  getAudioClip()方法僅僅能被applet内調用。随着JAVA2的引入,應用程式也能用Applet類的newAudioClip方法裝入聲音檔案。前一例子可以改寫如下以用于Java應用程式:

AudioClip co = newAudioClip(“gun.wav”)

  我們現在可在任何地方使用方法play()播放我們的聲音了。play()一旦被調用立刻開始恢複和播放聲音。但這有一點要注意:如果聲音檔案不能被查找,将不會有出錯資訊,僅僅是沉默。

源代碼及應用程式請于此處下載下傳.

  圖檔處理技術

  圖檔的處理和聲音的處理在一樣簡單。設定圖檔變量,得到圖形,最後繪制圖形。我們就直接從代碼來分析。在此我們繪制一幅applet的背景圖。開始繪制前,我們先要聲明圖形變量,用來存放圖形檔案。

Image backImage;

// 加載圖檔檔案

backImgage = getImage (getCodeBase (), "black.gif");

  下面在我們的paint()方法中利用函數drawImage繪制我們圖形。

g.drawImage (backImage, 0, 0, this);

  DrawImage參數中的blackImage即我們得到的圖形,而後面的0,0分别代表圖形的x坐标和y坐标.this:為圖形代表的類,這裡指的即picture類。在這裡建議大家使用*.gif格式的圖檔檔案。因為如果是internet網上,檔案的大小也決定了你的applet加載時的快慢,沒有人很願意等很長時間來玩你的遊戲,即使你的遊戲比較出色。源代碼及示範程式下載下傳.

  大家在玩遊戲時是不是見過人物圖像行走?動物來回跑動的動畫?這些都是基于圖形技術來實作的。我們隻要把上面的代碼稍微修改,用數組變量來存儲我們得到的圖形檔案組,再利用drawImage()方法播放出來就可實作動畫圖檔的播放.

Image[] backImage;

// 加載圖檔檔案

for (int i=4,i<backImage.length,i++)

{

backImgage[i] = getImage (getCodeBase (), "t1"+i+".gif");

}

  大家可參考JDK包中的Animation例子,它就是一個很好的播放一組圖檔檔案的例子。

事件處理

  滑鼠監聽技術

  玩遊戲時,不管是小型的撲克牌和大型的RPG遊戲,都要參與者溶入到遊戲的角色當中。不錯,互動,遊戲有了互動的功能才可以說是一個完整的遊戲。即使是程式設計遊戲如機器人足球,Robocode都要程式員參與編寫代碼,觀察比賽。有兩種主流方法可實作遊戲的互動:滑鼠和鍵盤。當然還包括手操杆等,但現在大部分Pc機上使用的還是滑鼠和鍵盤。我們就以這兩項為基礎來說明遊戲中事件的響應過程。

  要判斷相應的滑鼠所進行的動作:是點選,還是移動。我們必須對我們滑鼠進行監聽。要監聽滑鼠事件就必須調用這些接口之一,或擴充一個滑鼠擴充卡(mouse adapters) 類. AWT 提供了兩種監聽接口(listener interface): java.awt.event.MouseListener 和 java.awt.event.MouseMotionListener.

  現在我設計一個滑鼠事件,當點選applet螢幕時,下降的球向反方向運動。以實作了對遊戲的簡單控制。

  MouseListener一共有5個方法,主要用來實作滑鼠的點選事件。這裡要注意一點:由于MouseListener是接口我們要在實作的類中重載它的所有方法.

  Mouse點選事件

   · mousePressed() 當使用者按下滑鼠按鈕時發生.

   · mouseReleased() 當使用者松開滑鼠按鈕時發生.

   · mouseClicked() 當使用者按下并松開滑鼠按鈕時發生. 使用者在選擇或輕按兩下圖示的時候通常會點選滑鼠按鈕. 使用者如果在松開滑鼠之前移動滑鼠,點選不會導緻滑鼠相應事件出現.

   · 因為點選滑鼠是按下滑鼠和松開滑鼠的結合, 在事件配置設定給 mouseClicked() 方法之前, mousePressed() 和 mouseReleased() 方法已同時被調用.

  滑鼠狀态處理:

   mouseEntered() 當滑鼠離開目前元件并進入你所監聽的元件時激活事件.

   mouseExited() 當滑鼠離開你所監聽的元件時發生.

  Mouse 移動事件

  滑鼠移動主要通過接口MouseMotionListener來實作:

   mouseDragged() 當使用者按下滑鼠按鈕并在松開之前進行移動時發生.在mouseDragged() 後松開滑鼠不會導緻mouseClicked().

   mouseMoved() 當滑鼠在元件上移動而 不時拖動時發生.

  依據我們的遊戲設計,我們在這要使用到MouseListener接口。實作接口後。我們要在init()函數加入監聽器addMouseLisener(),來監聽對applet的響應事件。

  知道了滑鼠事件的處理,我們再來回顧一下上面提到的球反彈設計,現在我們要如何處理了球的控制呢?讓我們想一想,不錯,可能你已經發現了,我們照樣可通過改變speed方向來實作回彈控制操作。在mousePressed(){}事件中加入下面的代碼,我們的回彈控制就設計完成。

speed = -4

  記得在釋放applet資源時,我們要釋放mouseListener資源。在destory()中加入

removeMouseListener(this);

  可能有些朋友會使用mouseDown()方法,mouseDown()在此我建議大家不要再使用這個方法了,它已經是被淘汰的産品。是為了相容JDK1.0而帶到JDK1.4中來的。

鍵盤監聽技術

  知道了滑鼠的操作處理,鍵盤的操作處理就很簡單了。我們隻要實作keyListener接口,并在相應的事件中加入我們要實作的代碼。

   KeyPressed: 當按鍵時發生

   KeyReleased:當翻譯鍵時發生

   KeyTyped:當打擊鍵時發生

  由于在後面我們設計的遊戲中我們不會使用到鍵盤操作,鍵盤事件處理我們就交給大家自己去實作。

  現在我們來回顧一下我們能做什麼了?移動一個物體,加載聲音和圖檔,用滑鼠對遊戲進行一定的控制。哦,我的天,我們已經可以做自己的很簡單的遊戲了。是的,你可以了,我認為在此,大家可以放下教程,把自己小時候一直想玩的遊戲,把自己學程式時一直想做的遊戲自己進行設計實作,這對你的幫助将是非常大的。對你的程式設計水準也是一個很大的提高。

  當然如果你仍然認為自己認識還不是很深,下面讓我們來設計一個完整的遊戲。這将是一個很有意思的過程。

  第一個遊戲-"保衛者"

  主線思路:

  真正做自己的遊戲是總是很興奮。在開始任何事情之前,我們都要有個好的設計,遊戲更不例外。下面我們就以上面的例子為本。設計一個”保衛者”的遊戲。遊戲思路本身很簡單,從螢幕的頂端不斷的有炸彈落下來,而我們這些”保衛者” 要在它們着地之前,用滑鼠點選讓它反彈回去,不讓它落到地面上來,但是球在上升過程中我們也要注意不讓它撞到頂上。如果撞到頂上或地畫,你的生命點數都會減少。每點中一個炸彈你的分數就會增加。當你的生命點數為零。”Game Over”。

  設計結構:

  1.子產品設計:

  遊戲的結構很簡單,由三個子產品組成。

  Denfen類:Denfen類控制整個遊戲主線程,初始化炸彈類,并繪制螢幕上的炸彈數量及處理炸彈的移動,并監聽滑鼠事件

  Bomb類:主要是判斷炸彈的速度,方向,是否撞到地面和點選事件

  Denfense類:主要用來處理遊戲者的記分和生命點數

  2.方法實作:

  Denfen:

  init(): 初始化所有對象,包括聲音檔案的加載,Bomb類的生成

  run(): 處理炸彈的下降運動

  paint(...):繪制炸彈及相關的資料記錄顯示

  update(...): 實作螢幕圖像的雙緩沖,消除閃爍

  mouseProcess (...): 利用mouseEvent事件監聽來處理滑鼠按下事件,并根據滑鼠當時的x坐标和y坐标判斷是否點中炸彈。

  addBomb():利用預設值來動态實作bomb的生成,這裡我們利用了數組來記錄的。預設值是3,大家可依據自己的愛好增加或減少記錄。

  Denfenser:

  Score:積分

  Life:生命點

  AddScore():增加遊戲者的積分

  Death():減少遊戲者的生命點數

  getScore():獲得目前的積分數

  getLife():獲得目前的生命點數

  Bomb:

  Bomb(...): 構造函數,初始化炸彈的位置,聲音,顔色等相關變量的值.

  down():處理bomb的下降

  isRebound ():反向回彈炸彈的方向,并根據積分來加快炸彈的下降速度

  userHit (int x, int y):遊戲者是否點中炸彈。

  wasHitEarth(): 判斷炸彈是否撞擊到地面或頂面,如果是生命點将減少。

  DrawBomb(Graphics g): 繪制Bomb圖象。

  3.工作原理:

  首先我們在init()方法中加載所有遊戲必要的資源,包括聲音,滑鼠事件的監聽、背景等相關設定。利用addBomb()方法增加bomb的數量、初始位置及初始化顔色。再利用start()啟動線程。線程調用run()方法,處理炸彈下降運動down()。Repaint()會在每一個機關時間調用paint()方法不斷的重新整理螢幕,paint()調用Bomb.addBomb()繪制炸彈。當遊戲者按下滑鼠,mousePress()事件激活,判斷是否點中了炸彈。如果點中addScore()自動加1分。如果沒有點中炸彈,炸彈繼續下降,當撞到螢幕wasHitEarth()方法激活,其内調用death()方法,減少Denfenser.life生命點,同時audio.play()處理聲音的播放,用以提示遊戲者。當你的生命點數小于0時”Game Over”。

  這個遊戲并不是很完善。下面提到一些改進方法,大家可以動手試試。做出适合自己的遊戲風格來。具體的源代碼及實作過程請大家從這裡下載下傳.

  4.遊戲的改進:

  背景的替換,本例的背景用的是函數setBackground(),大家可用相應的圖形來代替。

  炸彈數量的增加,為了減少複雜度,例子用到的炸彈數量是固定值3,我們可根據積分的多少,在遊戲中動态的增加炸彈的數量。

  等級的設定,本遊戲中沒有等級的功能。如果大家在遊戲中加入等級,依據不同的等級不斷的變換遊戲的模式,這将是很有意思的過程。

  模式改變。我們可以在遊戲中實作自己的模式。如消滅炸彈。點一個炸彈,就讓炸彈從螢幕上消滅。

  我們還可以增加一個遊戲者,加大遊戲的可玩性。增加鍵盤的處理功能。加大遊戲的靈活性。

  還有很多很多的處理和玩法,這都等着你去發掘。相信java 遊戲程式設計将會是一個很有意思的學習過程。

附錄一:緩沖機制

  . 緩沖區 緩沖區用來儲存着色的像素(影像)在視訊記憶體中的區域。緩沖區的大小由解析度和色深決定,例如800x600,16bit色的緩沖區就占用800x600x2(16bit=2bytes)的記憶體區域。

  (1) 前置Buffer是目前顯示在螢幕上的緩沖區,後置Buffer是尚未顯示在螢幕上的緩沖區。

  (2) Single Buffering使用一個前置緩沖區,在着色的同時影像立即顯示在螢幕上。是以當螢幕更新影像時會出現閃爍的現象。Single Buffering在目前的程式中已很少使用。

  (3) Double Buffering則使用兩個緩沖區,一個前置Buffer,一個後置Buffer。所謂前置和後置是相對而言的。前置緩存的像素在螢幕上顯示的同時,顯示卡正在緊張地着色後置緩存中的像素。

  後置緩存的像素上色完畢後是以Vsync信号的形式等待。在前置緩存和後置緩存交換後,新一輪的着色工作又重新開始。這正如舞台話劇中前台和背景的演員一般。在前台演員表演時,背景的演員仍在進行最後的排練。前台的演員下場時正是背景演員登場的時間。唯一不同的是前置和後置緩存是循環輪番上陣,而演員表演完畢一般都不再出場。目前大多數遊戲内定都使用Double Buffering。

  (4) Triple Buffering使用一個前置緩存和兩個後置緩存。在着色完第一個後置緩沖區的資料後,立即開始處理第二個後置緩沖區。今天,不少新遊戲都采用的是Triple Buffering,Trible Buffering正逐漸成為發展的趨勢,因為它沒有Vsync(螢幕的垂直重新整理頻率)等待的時間,遊戲也将更加流暢。Triple Buffering也是3Dmark2000測試的内定值設定。

附錄二:  moveball 的完整源代碼如下

import java.applet.*;

import java.awt.*;

public class moveball extends Applet implements Runnable

{

 // 變量初始化

 int x_pos = 10;        // 圓球的X坐标(相對于左上角)

 int y_pos = 100;      // 圓球的Y坐标(相對于左上角)

 int radius = 20;       // 圓球的半徑

 // 聲明兩個執行個體變量

 private Image dbImage;

 private Graphics dbg;

 public void init()

 {

  setBackground (Color.blue);

 }

 public void start ()

 {

  // 定義一個新線程

  Thread th = new Thread (this);

  // 啟動線程

  th.start ();

 }

 public void stop()

 {

 }

 public void destroy()

 {

 }

 public void run ()

 {

          // 設定低優先級的線程

  Thread.currentThread().setPriority(Thread.MIN_PRIORITY);

            while (true) {

           // 控制圓球的運動軌迹和速度

   x_pos++ ;

           // 重繪applet

   repaint();

   try

   {

            //線程暫停20毫秒

    Thread.sleep (20);

   }

   catch (InterruptedException ex)

   {

   }

   // 設定為線程的最高優先級

   Thread.currentThread().setPriority(Thread.MAX_PRIORITY);

  }

 }

 public void update (Graphics g)

 {

     //  初始化 buffer

 if (dbImage == null)

 {

 dbImage = createImage (this.getSize().width, this.getSize().height);

 dbg = dbImage.getGraphics ();

 }

    //背景清屏,即設定圓球元件和背景一樣的顔色,大小

 dbg.setColor (getBackground ());

 dbg.fillRect (0, 0, this.getSize().width, this.getSize().height);

    // 繪制相應的元素元件

 dbg.setColor (getForeground());

 paint (dbg);

    // 在螢幕上重畫已經繪制好的圓

 g.drawImage (dbImage, 0, 0, this);

 }

 public void paint (Graphics g)

 {

   //設定球的顔色

  g.setColor  (Color.red);

  // 從x,y位置處畫一個實心的圓

  g.fillOval (x_pos - radius, y_pos - radius, 2 * radius, 2 * radius);

 }

}