6.游戏框架
所有的基础工作做完后,我们最后来探讨一下游戏框架本身。我们看下为了运行我们的游戏,还需要什么样的工作要做:
下面看下一些代码:
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方法也需要知道上次调用到现在的时间差。
当主循环结束后,我们就需要清理和释放各种资源了。
这就是游戏工作的流程:处理用户的输入、更新状态、并呈现给用户。
游戏和显示接口
下面是游戏运行时需要的接口:
下面是游戏接口的代码:
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()也是内容持久化的最后一个方法。