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()也是內容持久化的最后一個方法。
PS: 歡迎關注公眾號"Devin說",會不定期更新Java相關技術知識。