天天看點

[Android遊戲開發]遊戲架構的搭建

通常情況下,遊戲開發的基本架構中,一般包括以下子產品:

  視窗管理(Window management):該子產品負責在Android平台上建立、運作、暫停、恢複遊戲界面等功能。

  輸入子產品(Input):該子產品和視窗管理子產品是密切相關的,用來監測追蹤使用者的輸入(比如觸摸事件、按鍵事件、加速計事件等)。

  檔案輸入輸出(File I/O):此子產品用來讀取assets檔案下圖檔、音頻等資源。

  圖像子產品(Graphics):在實際遊戲開發中,這個子產品或許是最複雜的部分。它負責加載圖檔并把它們繪制到螢幕上。

  音頻子產品(Audio):這個子產品負責在不同的遊戲界面加載音各類頻。

  網絡(networking):如果遊戲提供多人遊戲聯網功能,此子產品就是必須的。

  遊戲架構(Game framework):該子產品把以上各種子產品整合起來,提供一個易用的架構,來輕松地實作我們的遊戲。

  下面對每一個子產品進行詳細的描述。

1. 視窗管理

  我們可以把遊戲的視窗想象成一個可以在它上面繪制内容的畫布。視窗管理子產品負責定制視窗、添加各種UI組建、接受各類使用者的輸入事件。這些UI元件或許可以通過GPU等硬體加速(比如使用了OpenGL ES)。

  該模設計時不是提供接口,而是和遊戲架構整合在一起,之後會有相關的代碼貼出。我們需要記住的是應用程式狀态和視窗事件是該子產品必須處理的事情:

    Create: 當視窗被建立時被調用的方法。

    Pause: 當應用程式由于默寫原因暫停時調用的方法。

    Resume: 當應用程式恢複到前台時調用的方法。

2.輸入子產品

  大部分作業系統中,輸入事件( 比如觸屏事件、按鍵事件)是通過目前的視窗排程(dispatched)的,視窗再進一步把這些事件派發給目前選中的元件。是以我們隻需要關注元件的事件即可。作業系統提供的UI APIs提供了事件分發機制,我們可以很容易地注冊和監聽事件,這也是輸入子產品的主要職責。有兩種處理事件的做法:

  輪詢(Polling):在這種機制下,我們僅檢查輸入裝置的目前狀态,之前和之後的狀态并無儲存。這種輸入事件處理适合處理諸如觸屏按鈕事件,而不适合跟蹤文本的輸入,因為按鍵事件的順序并未儲存。

  基于事件的處理(Event-based handling):這種機制提供了記憶功能的事件處理,比較适合處理文本輸入或者其他需要按鍵次序的操作。

  在Android平台中,主要有三種輸入事件:觸屏事件、按鍵事件和加速計事件,前兩種時間使用輪詢機制和基于事件處理的機制都适合,加速計事件通常是輪詢機制。

  觸屏事件有三種:

    Touch down: 手機觸屏時發生。

    Touch drag: 手指拖動時發生,此前有Touch down事件産生。

    Touch up: 手指擡起時發生。

  每種觸摸事件有相關的輔助資訊:觸屏的位置、指針索引(多點觸摸時用來追蹤識别不同的觸點)

  鍵盤事件包括兩種:

    Key down: 按下鍵盤時觸發。

    Key up: 釋放鍵盤時觸發。

  每種按鍵事件也有相關的輔助資訊:Key-down事件存儲按鍵碼,Key-up事件存儲按鍵碼和實際的Unicode字元。

  加速計事件,系統不停的輪詢加速劑的狀态,并以三位坐标辨別。

  基于以上介紹,下面定義輸入子產品的一些接口,用來輪詢觸屏事件、按鍵事件和加速計事件。代碼如下:

  Input.java

[Android遊戲開發]遊戲架構的搭建
[Android遊戲開發]遊戲架構的搭建
package com.badlogic.androidgames.framework;

import java.util.List;

public interface Input {

    public static class KeyEvent {
        public static final int KEY_DOWN = 0;
        public static final int KEY_UP = 1;

        public int type;
        public int keyCode;
        public char keyChar;
    }

     public static class TouchEvent {
        public static final int TOUCH_DOWN = 0;
        public static final int TOUCH_UP = 1;
        public static final int TOUCH_DRAGGED = 2;

        public int type;
        public int x, y;
        public int pointer;
    }

    public boolean isKeyPressed(int keyCode);

    public boolean isTouchDown(int pointer);

    public int getTouchX(int pointer);

    public int getTouchY(int pointer);

    public float getAccelX();

    public float getAccelY();

    public float getAccelZ();

    public List<KeyEvent> getKeyEvents();

    public List<TouchEvent> getTouchEvents();
}      
[Android遊戲開發]遊戲架構的搭建
[Android遊戲開發]遊戲架構的搭建

  上述定義包括兩個靜态類:KeyEvent 和 TouchEvent。KeyEvent類和TouchEvent類都定義了相關事件的常量。KeyEvent同時定義了幾個存儲事件資訊的變量:類型(type)、按鍵碼(keyCode)和Unicode字元(keyChar)。TouchEvent也一樣,定義了位置資訊(x,y)、某個出觸摸點的ID。比如第一個手指按下,ID是0,第二個手指按下 ID是1;如果兩個手指按下,手指0釋放,手指1保持,又一個手指按下時,ID為已經釋放的0.

  下面是輸入接口中的輪詢方法:Input.isKeyPressed()輸入參數是 keyCode,傳回相應的按鍵是否按下的布爾值;Input.isTouchDown(),Input.getTouchX()和Input.getTouchY()傳回給定指針索引是否按下、對應的橫豎坐标值,注意當對應的指針索引不存在時坐标值是未定義的。Input.getAccelX(), Input.getAccelY(), and Input.getAccelZ() 傳回各自的加速計的坐标值;最後兩個方法用作基于事件處理機制的,他們傳回KeyEvent和TouchEvent執行個體,用于記錄上次事件觸發的資訊,最新的事件在清單的最後。

  通過這些簡單的接口和類的,我們建構了我們的輸入接口。下節繼續分析檔案處理的内容(File I/O)。

3. 檔案讀寫(File I/O)

  讀寫檔案在遊戲開發中是一項十分重要的功能。在Java開發中,我們主要關注InputStream和OutputStream及其執行個體,它們是Java中讀寫檔案的标準方法。遊戲開發中,比較多的是讀取資源檔案,比如配置檔案、圖檔、音頻檔案等;寫入檔案的操作一般在儲存使用者進度和配置資訊時使用。

  下面是檔案讀寫的接口:

  FileIO.java

[Android遊戲開發]遊戲架構的搭建
[Android遊戲開發]遊戲架構的搭建
package com.badlogic.androidgames.framework;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public interface FileIO {
  public InputStream readAsset(String fileName) throws IOException;
  public InputStream readFile(String fileName) throws IOException;
  public OutputStream writeFile(String fileName) throws IOException;
}      
[Android遊戲開發]遊戲架構的搭建
[Android遊戲開發]遊戲架構的搭建

   上述代碼中,我們傳遞一個檔案名作為參數,傳回一個流,具體的執行會在接口的實作中展現。同時我們會抛出一個IOException異常以防止讀寫檔案出錯。當我們完成讀寫後,需要關閉輸入輸出流。Asset 檔案從應用程式的APK檔案中讀取,其他檔案一般從内置存儲器或者SDCard上讀取,分别對應上述代碼中的三個方法。

4.音頻子產品(Audio)

  音頻子產品程式設計從來都是一個複雜的話題。這裡不打算用到一些進階複雜的音頻處理手段,主要是播放一些背景音樂。在書寫代碼前,讓我們了解一下音頻的基礎知識。

  采樣率:定義了每秒從連續信号中提取并組成離散信号的采樣個數,采樣率越高音質越好,機關用赫茲(Hz)來表示,CD一般是44.1KHz。對于每個采樣系統會配置設定一定存儲位(bit數)來表達聲波的聲波振幅狀态,稱之為采樣分辨率或采樣精度,每增加1個bit,表達聲波振幅的狀态數就翻一翻,并且增加6db的動态範圍态,1個2bit的數位音頻系統表達千種狀态,即12db的動态範圍,以此類推。如16bit能夠表達65536種狀态,24bit可以表達多達16777216種狀态。動态範圍是指聲音從最弱到最強的變化範圍,人耳的聽覺範圍通常是20HZ~20KHZ。高的采樣率意味着更多的存儲空間。比如60s的聲音,采樣率8KHz、8bits,大約0.5M,采樣率44KHz、16bits,超過5M,普通的3分鐘的流行歌曲,将會超過15M。

  為了即不降低品質有不太占據空間,很多比較好的壓縮方法被提出來。比如MP3s 和OGGs格式就是網絡中比較流行的壓縮格式。

  可以看到3min的歌曲占了不少空間。當我們播放遊戲的背景音樂時,我們可以把音頻流化而不是預先加載到記憶體。通常背景音樂隻有一個,是以隻需要到磁盤加載一次即可。

  對于一些短的音效,比如爆炸聲和槍的射擊聲,情況有所不同。這些短的音效經常會同時被調用多次,從磁盤對每個執行個體流化這些音效不是一個好的辦法。幸運的是,短的音效并不占用太多的記憶體空間,是以隻需把這些音效提前讀入到記憶體即可,然後可以直接地同時播放這些音效。

  是以我們的代碼需要提供如下功能:

  我們需要一種方法加載音頻檔案,用于流化播放(Music)和記憶體播放(Sound),同時提供控制播放功能。

  相應的接口有三個,Audio、Music和Sound,代碼如下。

  Audio接口Audio.java

package com.badlogic.androidgames.framework;

public interface Audio {
  public Music newMusic(String filename);
  public Sound newSound(String filename);
}      

  Audio接口建立新的Music和 Sound執行個體。一個Music執行個體表示一個流音頻檔案,一個Sound執行個體表示一個儲存在記憶體中的短的音效。方法 Audio.newMusic()和Audio.newSound()都是以檔案名作為參數并抛出IOException以防檔案加載失敗(例如檔案不存在或者檔案損壞等情況)。

  Music接口Music.jva

[Android遊戲開發]遊戲架構的搭建
[Android遊戲開發]遊戲架構的搭建
package com.badlogic.androidgames.framework;

public interface Music {

  public void play();

  public void stop();

  public void pause();

  public void setLooping(boolean looping);

  public void setVolume(float volume);

  public boolean isPlaying();

  public boolean isStopped();

  public boolean isLooping();

  public void dispose();
}      
[Android遊戲開發]遊戲架構的搭建
[Android遊戲開發]遊戲架構的搭建

  Music接口有點複雜,包含了播放音樂流、暫定和停止、循環播放、音量控制(從0到1的浮點數)方法。當然,裡面還有一些getter方法,用來擷取目前音樂執行個體的狀态。當我們不再需要Music 執行個體時, 我們可以銷毀它(dispose方法),這會關閉系統資源,即流化的音頻檔案。

 Sound接口Sound.java

[Android遊戲開發]遊戲架構的搭建
[Android遊戲開發]遊戲架構的搭建
package com.badlogic.androidgames.framework;

public interface Sound {

  public void play(float volume);

  public void dispose();

}      
[Android遊戲開發]遊戲架構的搭建
[Android遊戲開發]遊戲架構的搭建

  Sound接口比較簡單,隻包含play()和dispose()方法。前者以指定的音量為輸入參數,我們可以在任何我們需要的時候播放音效。後者在我們不許Sound執行個體時,我們需要銷毀它以釋放它占用的記憶體空間。

5. 圖像子產品(Graphics)

  最後一個子產品是圖像操作子產品,用來繪制圖像到螢幕上。不過要想高性能的繪制圖像,就不得不了解一些基本的圖像程式設計知識。讓我們從繪制2D圖像開始,首先要了解的一個問題是:圖像究竟是如何繪制到螢幕的?答案相當複雜,我們不需要知道所有的細節。

光栅、像素和幀緩沖(Framebuffers)

  現在的顯示器都是基于光栅的,光栅是一個兩次元的格子組成,也就是像素格。光栅格子的長寬,我們一般用像素來表示。如果仔細觀察顯示器(或者用放大鏡),我們就可以發現顯示器上面有一個一個的格子,這就是像素格或者光栅格。每個像素的位置可以用坐标表示,于是引入了二維坐标系統,這也意味着坐标值是整數。顯示器源源不斷地收到從圖形處理器傳過來的圖像流,解碼每個像素的顔色(程式或者作業系統設定),然後繪制到螢幕上。每秒鐘顯示器會進行多次重新整理,重新整理頻率機關是Hz,比如LCD顯示器主流重新整理率是85Hz。

  圖形處理器需要從一個特殊的存儲區域擷取像素資訊以便顯示在顯示器上,這個區域就叫做視訊記憶體區,或者叫VRAM。這個區域一般稱作幀緩沖區(framebuffer)。是以一個完整的螢幕圖形叫做一個幀。對于每個顯示器栅格中的像素,在幀緩沖區都有一個對應的記憶體位址。當我們需要改變螢幕顯示内容時,我們隻需要簡單地改變幀緩沖區中的内容即可。

  下圖是顯示器栅格和幀緩沖區的簡單示意圖:

[Android遊戲開發]遊戲架構的搭建

垂直同步和雙緩沖

  普通的繪圖方法,當要繪制的對象太複雜,尤其是含有位圖時,這時的畫面會顯示的很慢,對于運動的畫面,會給人“卡”住了的感覺,有時候還會導緻畫面閃爍。于是我們采用雙緩沖技術(采用兩個framebuffer)。 雙緩沖的原理可以這樣形象的了解:把電腦螢幕看作一塊黑闆。首先我們在記憶體環境中建立一個“虛拟“的黑闆,然後在這塊黑闆上繪制複雜的圖形,等圖形全部繪制完畢的時候,再一次性的把記憶體中繪制好的圖形“拷貝”到另一塊黑闆(螢幕)上。采取這種方法可以提高繪圖速度,極大的改善繪圖效果。下面是原理圖:

[Android遊戲開發]遊戲架構的搭建

  要知道什麼是垂直同步,必須要先明白顯示器的工作原理。顯示器上的所有圖像都是一線一線的掃描上去的,無論是隔行掃描還是逐行掃描,顯示器,都有2種同步參數——水準同步和垂直同步。水準同步信号決定了CRT畫出一條橫越螢幕線的時間,垂直同步信号決定了CRT從螢幕頂部畫到底部,再傳回原始位置的時間,而恰恰是垂直同步代表着CRT顯示器的重新整理率水準!

  關閉垂直同步:我們平時運作作業系統一般螢幕重新整理率一般都是在85Hz上下,此時顯示卡就會每按照85Hz的頻率時間來發送一個垂直同步信号,信号和信号的時間間隔是85的分辨率所寫一屏圖像時間。

  打開垂直同步:在遊戲中,或許強勁的顯示卡迅速的繪制完一屏的圖像,但是沒有垂直同步信号的到達,顯示卡無法繪制下一屏,隻有等85機關的信号到達,才可以繪制。這樣fps自然要受到作業系統重新整理率運作值的制約。也就是說,當然打開後如果你的遊戲畫面FPS數能達到或超過你顯示器的重新整理率,這時你的遊戲畫面FPS數被限制為你顯示器的重新整理率。如果達不到會出現不同程度的跳幀現象,FPS與重新整理率差距越大跳幀越嚴重。一般對于高性能的顯示卡建議打卡,遊戲畫面會更好!打開後能防止遊戲畫面高速移動時畫面撕裂現象,比如實況足球等。

  關閉垂直同步,那麼遊戲中作完一屏畫面,顯示卡和顯示器無需等待垂直同步信号,就可以開始下一屏圖像的繪制,自然可以完全發揮顯示卡的實力。

  但是,不要忘記,正是因為垂直同步的存在,才能使得遊戲程序和顯示器重新整理率同步,使得畫面平滑,使得畫面穩定。取消了垂直同步信号,固然可以換來更快的速度,但是在圖像的連續性上,性能勢必打折扣。這也是關閉垂直同步後發現畫面不連續的理論原因!

圖像格式

  比較流行的兩個圖形格式是JPEG和 PNG。JPEG是有損壓縮格式,PNG是無損壓縮格式,是以PNG格式可以百分百重制原始的圖像。有損壓縮格式通常占用少的磁盤空間。我們采用何總壓縮格式取決于我們的磁盤空間。和音頻類似,當我們加載到記憶體中時,我們需要完全地解壓一個圖像。是以,即使你的壓縮圖像在磁盤上隻有20K,在RAM中你依然需要width×height ×color depth的存儲空間。

圖像疊加

  假定有一個我們可以渲染的幀緩沖區(framebuffer),同時有幾個加載到RAM中的圖檔,我們笑需要把RAM中的圖檔逐次放入到幀緩沖區,比如一個背景圖檔和一個前景圖檔如圖所示:

[Android遊戲開發]遊戲架構的搭建

  這個過程就叫做圖像的合成和疊加,我們需要把不同的圖檔合成一個最終顯示的圖檔。繪制圖檔的此項很重要,因為上面的圖檔總會覆寫下面的圖檔。

[Android遊戲開發]遊戲架構的搭建

  上面圖像合成出現了問題:第二張圖檔的白色背景覆寫了第一張背景圖檔。我們怎樣把第二張圖的白色背景消去呢?這就需要alpha混合(alpha blending)。alpha混合是一種把源點的顔色值和目标點的顔色值按照一定的算法進行運算,得到一種透明的效果。

  下面是最終合成圖像的RGB值,公式如下

red = src.red * src.alpha + dst.red * (1 – src.alpha)

blue = src.green * src.alpha + dst.green * (1 – src.alpha)

green = src.blue * src.alpha + dst.blue * (1 – src.alpha)  

  src和dst分别是我們需要混合的源圖像和目标圖像(源圖像相當于人物,目标圖像相當于背景)。下面是一個例子。

src = (1, 0.5, 0.5), src.alpha = 0.5, dst = (0, 1, 0)

red = 1 * 0.5 + 0 * (1 – 0.5) = 0.5

blue = 0.5 * 0.5 + 1 * (1 – 0.5) = 0.75

red = 0.5 * 0.5 + 0 * (1 – 0.5) = 0.25

  效果如下圖所示

[Android遊戲開發]遊戲架構的搭建

  上述公式用了兩次乘法,乘法消耗的時間多,為了提高運算速度,可以進行優化。如

    red = (src.red- dst.red) * src.alpha + dst.red

  Alpha是一個浮點數,我們可以轉換成整數運算,因為一種顔色最多占8Bit,是以Alpha值最多是256,于是我們把Alpha的值乘以256,然後運算的時候再除以256,就得到下面的公式:

    red = (src.red- dst.red) * src.alpha /256+ dst.red

  這裡,Alpha是一個0到256的數值。

  具體到這個例子,我們隻需要把源檔案的白色像素的alpha值設為0即可。最終效果如下圖:

[Android遊戲開發]遊戲架構的搭建

圖像子產品的接口代碼

  通過以上介紹,我們可以開始設計我們的圖像子產品的接口。問下需要實作如下功能:

  • 從磁盤加載圖檔到記憶體中,為以後繪制到螢幕做準備。
  • 用特定顔色清除framebuffer
  • 用指定顔色在framebuffer指定位置繪制像素。
  • 在framebuffer上繪制線條和矩形。
  • 繪制上面記憶體中的圖檔到framebuffer,能夠整個繪制和部分繪制,alpha混合繪制。
  • 得到framebuffer的長寬。

  這裡用兩個接口來實作:Graphics和 Pixmap,下面是Graphics接口:

[Android遊戲開發]遊戲架構的搭建
[Android遊戲開發]遊戲架構的搭建
package com.badlogic.androidgames.framework;

public interface Graphics {

  public static enum PixmapFormat {

    ARGB8888, ARGB4444, RGB565

  }

  public Pixmap newPixmap(String fileName, PixmapFormat format);

  public void clear(int color);

  public void drawPixel(int x, int y, int color);

  public void drawLine(int x, int y, int x2, int y2, int color);

  public void drawRect(int x, int y, int width, int height, int color);

  public void drawPixmap(Pixmap pixmap, int x, int y, int srcX, int srcY, int srcWidth, int srcHeight);

  public void drawPixmap(Pixmap pixmap, int x, int y);

  public int getWidth();

  public int getHeight();

}      
[Android遊戲開發]遊戲架構的搭建
[Android遊戲開發]遊戲架構的搭建

  枚舉 PixmapFormat儲存了該遊戲支援的像素的顔色值(包括透明度)。比如ARGB8888,A表示透明度,R表示紅色,G表示綠色,B表示藍色,他們非别用8位來表示,就是各有256種狀态。            接下來看下接口的方法:

  • Graphics.newPixmap()方法加載指定格式的圖檔。
  • Graphics.clear()方法用特定顔色清除framebuffer。
  • Graphics.drawPixel()方法在framebuffer中指定位置繪制給定顔色的像素。
  • Graphics.drawLine()和 Graphics.drawRect()方法在framebuffer繪制線條和矩形。
  • Graphics.drawPixmap()方法繪制圖像的到framebuffer。(x, y) 坐标指定了framebuffer繪制的起始位置,參數 srcX和srcY指定了圖檔被繪制部分的起始位置。srcWidth和srcHeight制定了繪制的寬度和高度。
  • Graphics.getWidth()和Graphics.getHeight()方法傳回framebuffer的寬度和高度。

  接下來我們看一下Pixmap接口:

[Android遊戲開發]遊戲架構的搭建
[Android遊戲開發]遊戲架構的搭建
//Pixmap接口

package com.badlogic.androidgames.framework;

import com.badlogic.androidgames.framework.Graphics.PixmapFormat;

public interface Pixmap {

  public int getWidth();

  public int getHeight();

  public PixmapFormat getFormat();

  public void dispose();

}      
[Android遊戲開發]遊戲架構的搭建
[Android遊戲開發]遊戲架構的搭建
  • Pixmap.getWidth()和Pixmap.getHeight()方法傳回圖像的寬度和高度。
  • Pixmap.getFormat()傳回圖檔的格式。
  • Pixmap.dispose()方法。Pixmap執行個體使用記憶體資源和其他潛在的系統資源,如果我們不在需要它,我們需要回收資源,這也該方法的作用。

6.遊戲架構

  所有的基礎工作做完後,我們最後來探讨一下遊戲架構本身。我們看下為了運作我們的遊戲,還需要什麼樣的工作要做:

  • 遊戲被分為不同的螢幕(screen),每個螢幕執行着相同的任務:判斷使用者輸入,根據輸入渲染螢幕。一些節目或許不需要任何使用者輸入,但會過段時間後切換到下一螢幕.(如Splash界面)
  • 螢幕需要以某種方法被管理(如我們需要跟蹤目前的螢幕并且能随時切換的下一螢幕)
  • 遊戲需要允許螢幕通路不同的子產品(比如圖像子產品、音頻子產品、輸入子產品等),這樣螢幕才能加載資源,擷取使用者輸入,播放聲音,渲染緩沖區等。因為我們的遊戲是實時遊戲,我們需要目前的螢幕快速的更新。我們是以需要一個主循環來實作。主循環在遊戲退出時結束。每次循環疊代成為一幀,每秒幀的次數我們成為幀速(FPS).
  • 遊戲需要追蹤視窗的狀态(如是否暫停遊戲或者恢複等),并通知産生相應的處理事件。
  • 遊戲架構需要處理視窗的建立、UI元件的建立等

  下面看下一些代碼:

[Android遊戲開發]遊戲架構的搭建
[Android遊戲開發]遊戲架構的搭建
createWindowAndUIComponent();

Input input = new Input();

Graphics graphics = new Graphics();

Audio audio = new Audio();

Screen currentScreen = new MainMenu();

Float lastFrameTime = currentTime();


while( !userQuit() ) {

  float deltaTime = currentTime() – lastFrameTime;

  lastFrameTime = currentTime();

  currentScreen.updateState(input, deltaTime);

  currentScreen.present(graphics, audio, deltaTime);

}

cleanupResources();      
[Android遊戲開發]遊戲架構的搭建
[Android遊戲開發]遊戲架構的搭建

  代碼首先建立了遊戲的視窗和UI元件(createWindowAndUIComponent()方法),接着我們執行個體化了基本的元件,這些能保證遊戲基本功能的實作。我們又執行個體化了我們的起始螢幕,并把它作為目前的螢幕。然後記下目前的時間。

  接着我們進入了主循環,當使用者想退出時我們可以結束主循環。在主循環裡面,計算上一幀和目前幀的時間差,用來計算FPS。最後,我們更新了目前螢幕的狀态并呈現給使用者。updateState方法依賴時間差和輸入狀态,present方法包括渲染螢幕的狀态到framebuffer,播放音頻等。present方法也需要知道上次調用到現在的時間差。

  當主循環結束後,我們就需要清理和釋放各種資源了。

  這就是遊戲工作的流程:處理使用者的輸入、更新狀态、并呈現給使用者。

遊戲和顯示接口

  下面是遊戲運作時需要的接口:

  • 建立視窗進和UI,并建立相應的事件機制
  • 開啟遊戲的主循環
  • 跟蹤目前的螢幕顯示,在每次主循環中讓其更新
  • 把UI線程中的事件轉移到主線程中,并把這些事件傳遞給目前顯示界面,以便同步變化。
  • 確定能通路所有的遊戲基本子產品,如Input, FileIO,Graphics, 和 Audio.

下面是遊戲接口的代碼:

[Android遊戲開發]遊戲架構的搭建
[Android遊戲開發]遊戲架構的搭建
package com.badlogic.androidgames.framework;

public interface Game {

  public Input getInput();

  public FileIO getFileIO();

  public Graphics getGraphics();

  public Audio getAudio();

  public void setScreen(Screen screen);

  public Screen getCurrentScreen();

  public Screen getStartScreen();

}      
[Android遊戲開發]遊戲架構的搭建
[Android遊戲開發]遊戲架構的搭建

  如上述所示,代碼中有一些getter方法,用來傳回子產品的執行個體。

  The Game.getCurrentScreen()方法傳回目前激活的螢幕,之後我們會用一個抽象的類AndroidGame來實作這個接口,這個方法會實作除了Game.getStartScreen()之外所有的方法。實際遊戲中如果我們建立AndroidGame的執行個體,我們需要繼承AndroidGame并且重載Game.getStartScreen()方法,傳回初次顯示螢幕的一個執行個體。

  為了讓大家了解到通過上述方法建構一個遊戲是如何簡單,下面是一個例子(假定我們已經實作了AndroidGame類):

[Android遊戲開發]遊戲架構的搭建
[Android遊戲開發]遊戲架構的搭建
public class MyAwesomeGame extends AndroidGame {

  public Screen getStartScreen () {

    return new MySuperAwesomeStartScreen(this);

  }
}      
[Android遊戲開發]遊戲架構的搭建
[Android遊戲開發]遊戲架構的搭建

  很簡單是吧?所有我們要做的就是執行我們遊戲顯示的起始螢幕。我們繼承的AndroidGame類來做其他工作。從這點來看,AndroidGame 類會要求MySuperAwesomeStartScreen在主循環中更新和重新渲染自己。注意我們把MyAwesomeGame的執行個體傳遞給了MySuperAwesomeStartScreen。

   

  下面是抽象類Screen,之所是抽象類而不是接口,是因為我們可以提前在裡面寫一些子類都用到的方法,減輕子類的實作。代碼如下:

[Android遊戲開發]遊戲架構的搭建
[Android遊戲開發]遊戲架構的搭建
//The Screen Class

package com.badlogic.androidgames.framework;

public abstract class Screen {

  protected final Game game;

  public Screen(Game game) {

    this.game = game;

  }

  public abstract void update(float deltaTime);

  public abstract void present(float deltaTime);

  public abstract void pause();

  public abstract void resume();

  public abstract void dispose();
}      
[Android遊戲開發]遊戲架構的搭建
[Android遊戲開發]遊戲架構的搭建

  構造函數接收Game執行個體,并把它存到一個所有子類可以通路的final變量中。通過這種機制我們可以完成達成兩件事情:

  我們可以通過Game類的執行個體播放音頻、繪制平面、擷取使用者輸入和讀寫檔案。

  在合适時候我們可以通過調用Game.setScreen()設定一個新的目前平面顯示。

  方法 Screen.update() 和 Screen.present():它們會更新平面并同步地顯示。Game執行個體會在主循環中調用它們。

  方法 Screen.pause() 和 Screen.resume()在遊戲暫停和恢複時被調用,同樣這兩個方法也是被Game的執行個體調用的,并通知給目前的平面顯示。

  方法Screen.dispose(),當Game.setScreen()方法被調用時,Screen.dispose()被Game的執行個體調用。通過這個方法Game的執行個體會銷毀目前的顯示螢幕,同時讓其釋放所有相關的系統資源,以便為新的螢幕視窗提供最大的記憶體。Screen.dispose()也是内容持久化的最後一個方法。