GameCanvas 實例管理整個遊戲生命週期。public class MyGameCanvas extends GameCanvas implements Runnable, CommandListener {
private static MyGameCanvas instance;
private Graphics g;
private boolean running;
private Thread thread;
private Command startCmd, exitCmd, restartCmd;
private int keyState;
// 按鍵狀態旗標
private boolean keyUp, keyDown, keyLeft, keyRight, keyFire;
private boolean allowInput = true;
public int screenW, screenH;
private boolean gameOver;
protected MyGameCanvas() {
super(true); // 雙緩衝
g = getGraphics();
startCmd = new Command("開始", Command.OK, 1);
exitCmd = new Command("離開", Command.EXIT, 1);
addCommand(startCmd); addCommand(exitCmd);
setCommandListener(this);
screenW = getWidth(); screenH = getHeight();
}
public static synchronized MyGameCanvas getInstance() {
if (instance == null) instance = new MyGameCanvas();
return instance;
}
// ========== 固定幀率迴圈 ==========
public void run() {
final int TARGET_FPS = 20; // 50ms/幀
long last = System.currentTimeMillis();
while (running) {
long now = System.currentTimeMillis();
long elapsed = now - last;
if (elapsed < 50) {
try { Thread.sleep(50 - elapsed); } catch (Exception e) {}
}
last = System.currentTimeMillis();
gameInput();
gameMain();
}
}
// ========== 輸入處理:長按/單擊分離 ==========
private void gameInput() {
if (!allowInput) return;
keyState = getKeyStates();
boolean any = false;
checkKey(UP_PRESSED, () -> keyUp = true, () -> { if (keyUp) { keyUp=false; onKeyReleased(UP); } });
checkKey(DOWN_PRESSED, () -> keyDown = true, () -> { if (keyDown) { keyDown=false; onKeyReleased(DOWN); } });
checkKey(LEFT_PRESSED, () -> keyLeft = true, () -> { if (keyLeft) { keyLeft=false; onKeyReleased(LEFT); } });
checkKey(RIGHT_PRESSED, () -> keyRight = true, () -> { if (keyRight) { keyRight=false; onKeyReleased(RIGHT); } });
checkKey(FIRE_PRESSED, () -> keyFire = true, () -> { if (keyFire) { keyFire=false; onKeyReleased(FIRE); } });
}
private void checkKey(int mask, Runnable onPress, Runnable onRelease) {
if ((keyState & mask) != 0) { onPress.run(); }
else { onRelease.run(); }
}
// 子類別覆寫
protected void onKeyReleased(int key) {}
protected void gameMain() {
g.setColor(0); g.fillRect(0,0,screenW,screenH);
flushGraphics();
}
// ========== 命令與生命週期 ==========
public void start() { if (!running) { running=true; new Thread(this).start(); } }
public void stop() { running=false; }
public void commandAction(Command c, Displayable d) {
String lbl = c.getLabel();
if ("開始".equals(lbl)) { gameInit(); start(); removeCommand(startCmd); addCommand(restartCmd=new Command("重新",Command.OK,1)); }
else if ("重新".equals(lbl)) { stop(); while(thread.isAlive()){}; gameInit(); start(); }
else if ("離開".equals(lbl)) { stop(); /* 通知 MIDlet 銷毀 */ }
}
private void gameInit() {
gameOver=false; allowInput=true;
keyUp=keyDown=keyLeft=keyRight=keyFire=false;
}
public static void cleanUp() { instance=null; }
}
| 模式 | 優勢 |
|---|---|
| Singleton | 避免重複建立大型 Canvas 浪費堆疊/堆記憶體。 |
| 固定時間步長 | 物理/邏輯與渲染解耦,跨裝置行為一致。 |
| 按鍵狀態機 | 區分「按住持續觸發」與「釋放單次觸發」,適合移動/射擊分離。 |
| Command 整合 | 選單操作同樣走 commandAction,統一事件入口。 |
MyGameCanvas,覆寫 gameInit()、gameMain()、onKeyReleased()。Sprite、LayerManager(MIDP 2.0 Game API)管理遊戲物件。SoundPool 模式)與資源載入器。gameMain 邏輯分支。此框架以 < 200 行核心代碼建立專業級 J2ME 遊戲骨架,已驗證於多款商業專案,可直接作為新專案起始模板。