MIDP 2.0裡面包括一個用來簡化編寫二維游戲的API函數。這個API函數是非常簡湊的,只包括javax.microedition.lcdui.game包裡的五個類。這五個類主要提供了兩個重要的功能:
新的GameCanvas類使得在一個游戲循環體內畫一個screen和響應鍵盤輸入成為可能,而不需要調用系統的paint和input線程。
功能強大而復雜的圖層(layer)API函數可以輕松高效地建立復雜的場景。
muTank Example
利用GameCanvas類創建一個游戲循環(game loop)
GameCanvas類是附加了功能的Canvas類,它提供了立即重畫和檢查設備按鍵狀態的方法。這些新的方法把一個游戲的所有函數(功能)封裝在一個循環體內,並由一個單線程進行控制。為什麼這樣做就非常吸引人阿?先讓我們考慮一下你是如何執行一個使用了Canvas類的典型游戲的:
public void MicroTankCanvas
extends Canvas
implements Runnable {
public void run() {
while (true) {
// Update the game state.
repaint();
// Delay one time step.
}
}
public void paint(Graphics g) {
// Painting code goes here.
}
protected void keyPressed(int keyCode) {
// Respond to key presses here.
}
}
這不是一個美麗的畫面 。運行在應用程序線程中的run()方法,每一個時間段都會刷新游戲。典型的任務是刷新小球或飛行物的位置,繪制人物或飛行器動畫。每一次通過循環體,repaint()方法被用來刷新屏幕。系統把按鍵事件傳送給KeyPressed(),它能適當地刷新游戲狀態。
問題是,每樣東西都在不同的線程裡,游戲代碼在以上三種不同方法裡傳遞很容易混淆。當run()方法裡的主動畫循環體調用repaint()方法時,將沒有辦法確切知道系統什麼時候調用paint()方法。當系統調用KeyPressed()時,也沒有辦法知道程序的另一部分正在進行什麼。如果你KeyPressed()中的代碼將要刷新游戲的狀態,而同一時刻paint()方法將表現屏幕,這時屏幕將會持續非常奇怪的狀態。如果表現屏幕所用時間超過一個單時間段,動畫會看起來顛簸不定或是很奇怪。
GameCanvas類允許你避開常用繪畫(painting)和按鍵消息(key-event)機制,所以所有的游戲邏輯都可以被包括在一個單循環中。首先,GameCanvas類允許你用getGraphics()方法直接訪問Graphics對象。對於所返回的Graphics對象的任何表現(rendering)都可以通過屏幕外緩沖區(offscreen buffer)來實現。你可以用flushGraphics()復制緩沖區到屏幕上,直到屏幕被刷新才會返回。這種方式給你提供比調用repaint()方法更完善的控制。Repaint()方法會立即返回值,以至於你的應用程序不能確定系統什麼時候會調用paint()來刷新屏幕。
GameCanvas類也包含一個用來獲得設備按鍵當前狀態的方法,即所謂得polling技術。你可以通過調用GameCanvas類的getKeyStates()方法,馬上確定哪一個按鍵被按下,從而取代了等待系統調用KeyPressed()方法。
下面是一個使用GameCanvas類的典型的游戲循環體:
public void MicroTankCanvas
extends GameCanvas
implements Runnable {
public void run() {
Graphics g = getGraphics();
while (true) {
// Update the game state.
int keyState = getKeyStates();
// Respond to key presses here.
// Painting code goes here.
flushGraphics();
// Delay one time step.
}
}
}
接下來的例子描述了一個基本的游戲循環體。它向你展現了一個旋轉的“X”,你可以用方向鍵在屏幕上移動它。這裡的Run()方法特別的瘦小,這要多虧了GameCanvas。
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
public class SimpleGameCanvas
extends GameCanvas
implements Runnable {
private boolean mTrucking;
private long mFrameDelay;
private int mX, mY;
private int mState;
public SimpleGameCanvas() {
super(true);
mX = getWidth() / 2;
mY = getHeight() / 2;
mState = 0;
mFrameDelay = 20;
}
public void start() {
mTrucking = true;
Thread t = new Thread(this);
t.start();
}
public void stop() { mTrucking = false; }
public void run() {
Graphics g = getGraphics();
while (mTrucking == true) {
tick();
input();
render(g);
try { Thread.sleep(mFrameDelay); }
catch (InterruptedException ie) {}
}
}
private void tick() {
mState = (mState + 1) % 20;
}
private void input() {
int keyStates = getKeyStates();
if ((keyStates & LEFT_PRESSED) != 0)
mX = Math.max(0, mX - 1);
if ((keyStates & RIGHT_PRESSED) != 0)
mX = Math.min(getWidth(), mX + 1);
if ((keyStates & UP_PRESSED) != 0)
mY = Math.max(0, mY - 1);
if ((keyStates & DOWN_PRESSED) != 0)
mY = Math.min(getHeight(), mY + 1);
}
private void render(Graphics g) {
g.setColor(0xffffff);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(0x0000ff);
g.drawLine(mX, mY, mX - 10 + mState, mY - 10);
g.drawLine(mX, mY, mX + 10, mY - 10 + mState);
g.drawLine(mX, mY, mX + 10 - mState, mY + 10);
g.drawLine(mX, mY, mX - 10, mY + 10 - mState);
flushGraphics();
}
}