[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

復制代碼
復制代碼
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();
}
復制代碼
復制代碼

  上述定義包括兩個靜態類: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

復制代碼
復制代碼
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;
}
復制代碼
復制代碼

   上述代碼中,我們傳遞一個文件名作為參數,返回一個流,具體的執行會在接口的實現中體現。同時我們會拋出一個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

復制代碼
復制代碼
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();
}
復制代碼
復制代碼

  Music接口有點復雜,包含了播放音樂流、暫定和停止、循環播放、音量控制(從0到1的浮點數)方法。當然,里面還有一些getter方法,用來獲取當前音樂實例的狀態。當我們不再需要Music 實例時, 我們可以銷毀它(dispose方法),這會關閉系統資源,即流化的音頻文件。

 Sound接口Sound.java

復制代碼
復制代碼
package com.badlogic.androidgames.framework;

public interface Sound {

  public void play(float volume);

  public void dispose();

}
復制代碼
復制代碼

  Sound接口比較簡單,只包含play()和dispose()方法。前者以指定的音量為輸入參數,我們可以在任何我們需要的時候播放音效。后者在我們不許Sound實例時,我們需要銷毀它以釋放它占用的內存空間。

 

5. 圖像模塊(Graphics)

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

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

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

  圖形處理器需要從一個特殊的存儲區域獲取像素信息以便顯示在顯示器上,這個區域就叫做視頻內存區,或者叫VRAM。這個區域一般稱作幀緩沖區(framebuffer)。因此一個完整的屏幕圖形叫做一個幀。對於每個顯示器柵格中的像素,在幀緩沖區都有一個對應的內存地址。當我們需要改變屏幕顯示內容時,我們只需要簡單地改變幀緩沖區中的內容即可。

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

垂直同步和雙緩沖

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

  要知道什么是垂直同步,必須要先明白顯示器的工作原理。顯示器上的所有圖像都是一線一線的掃描上去的,無論是隔行掃描還是逐行掃描,顯示器,都有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中的圖片逐次放入到幀緩沖區,比如一個背景圖片和一個前景圖片如圖所示:

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

  上面圖像合成出現了問題:第二張圖片的白色背景覆蓋了第一張背景圖片。我們怎樣把第二張圖的白色背景消去呢?這就需要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

  效果如下圖所示

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

    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即可。最終效果如下圖:

 

圖像模塊的接口代碼

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

  • 從磁盤加載圖片到內存中,為以后繪制到屏幕做准備。
  • 用特定顏色清除framebuffer
  • 用指定顏色在framebuffer指定位置繪制像素。
  • 在framebuffer上繪制線條和矩形。
  • 繪制上面內存中的圖片到framebuffer,能夠整個繪制和部分繪制,alpha混合繪制。
  • 得到framebuffer的長寬。

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

復制代碼
復制代碼
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();

}
復制代碼
復制代碼

  枚舉 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接口:
復制代碼
復制代碼
//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();

}
復制代碼
復制代碼
  • Pixmap.getWidth()和Pixmap.getHeight()方法返回圖像的寬度和高度。
  • Pixmap.getFormat()返回圖片的格式。
  • Pixmap.dispose()方法。Pixmap實例使用內存資源和其他潛在的系統資源,如果我們不在需要它,我們需要回收資源,這也該方法的作用。

6.游戲框架

  所有的基礎工作做完后,我們最后來探討一下游戲框架本身。我們看下為了運行我們的游戲,還需要什么樣的工作要做:

  • 游戲被分為不同的屏幕(screen),每個屏幕執行着相同的任務:判斷用戶輸入,根據輸入渲染屏幕。一些節目或許不需要任何用戶輸入,但會過段時間后切換到下一屏幕.(如Splash界面)
  • 屏幕需要以某種方法被管理(如我們需要跟蹤當前的屏幕並且能隨時切換的下一屏幕)
  • 游戲需要允許屏幕訪問不同的模塊(比如圖像模塊、音頻模塊、輸入模塊等),這樣屏幕才能加載資源,獲取用戶輸入,播放聲音,渲染緩沖區等。因為我們的游戲是實時游戲,我們需要當前的屏幕快速的更新。我們因此需要一個主循環來實現。主循環在游戲退出時結束。每次循環迭代成為一幀,每秒幀的次數我們成為幀速(FPS).
  • 游戲需要追蹤窗口的狀態(如是否暫停游戲或者恢復等),並通知產生相應的處理事件。
  • 游戲框架需要處理窗口的建立、UI組件的創建等

  下面看下一些代碼:

復制代碼
復制代碼
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();
復制代碼
復制代碼

  代碼首先創建了游戲的窗口和UI組件(createWindowAndUIComponent()方法),接着我們實例化了基本的組件,這些能保證游戲基本功能的實現。我們又實例化了我們的起始屏幕,並把它作為當前的屏幕。然后記下當前的時間。

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

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

  這就是游戲工作的流程:處理用戶的輸入、更新狀態、並呈現給用戶。

游戲和顯示接口

  下面是游戲運行時需要的接口:

  • 建立窗口進和UI,並建立相應的事件機制
  • 開啟游戲的主循環
  • 跟蹤當前的屏幕顯示,在每次主循環中讓其更新
  • 把UI線程中的事件轉移到主線程中,並把這些事件傳遞給當前顯示界面,以便同步變化。
  • 確保能訪問所有的游戲基本模塊,如Input, FileIO,Graphics, 和 Audio.

下面是游戲接口的代碼:

復制代碼
復制代碼
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();

}
復制代碼
復制代碼

  如上述所示,代碼中有一些getter方法,用來返回模塊的實例。

  The Game.getCurrentScreen()方法返回當前激活的屏幕,之后我們會用一個抽象的類AndroidGame來實現這個接口,這個方法會實現除了Game.getStartScreen()之外所有的方法。實際游戲中如果我們創建AndroidGame的實例,我們需要繼承AndroidGame並且重載Game.getStartScreen()方法,返回初次顯示屏幕的一個實例。

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

復制代碼
復制代碼
public class MyAwesomeGame extends AndroidGame {

  public Screen getStartScreen () {

    return new MySuperAwesomeStartScreen(this);

  }
}
復制代碼
復制代碼

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

   

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

復制代碼
復制代碼
//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();
}
復制代碼
復制代碼

  構造函數接收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()也是內容持久化的最后一個方法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM