構建 J2ME 遊戲的骨架:基於 GameCanvas 的單例框架、固定幀率主循環、鍵盤事件系統 (按下/釋放/長按區分)、命令菜單 (開始/重來/退出)。
MyGameCanvaspublic class MyGameCanvas extends GameCanvas implements Runnable, CommandListener {
// 單例
private static MyGameCanvas instance;
public static synchronized MyGameCanvas getInstance() {
if (instance == null) instance = new MyGameCanvas();
return instance;
}
public static void cleanJob() { instance = null; }
// 狀態
private Graphics g;
private boolean running;
private Thread thread;
private int frameRate = 50; // ms, ~20 FPS
// 輸入狀態
private boolean allowInput;
public boolean keyUp, keyDown, keyLeft, keyRight, keyFire;
public boolean keyEvent; // 本幀是否有按鍵
// 命令
private Command startCmd, exitCmd, restartCmd;
// 遊戲變量
public int screenW, screenH;
public boolean gameOver;
// ... 自定義變量
protected MyGameCanvas() {
super(true); // 雙緩衝
g = getGraphics();
screenW = getWidth(); screenH = getHeight();
// 命令菜單
startCmd = new Command("Start", Command.OK, 1);
exitCmd = new Command("Exit", Command.EXIT, 1);
addCommand(startCmd); addCommand(exitCmd);
setCommandListener(this);
// 一次性初始化 (載入圖片、聲音等)
}
// 主循環
public void run() {
long start, end, diff;
while (running) {
start = System.currentTimeMillis();
gameInput(); // 輸入
gameMain(); // 邏輯+渲染
end = System.currentTimeMillis();
diff = end - start;
if (diff < frameRate) {
try { Thread.sleep(frameRate - diff); }
catch (InterruptedException ignored) {}
}
}
}
// 輸入處理:區分按下/長按/釋放
private void gameInput() {
if (!allowInput) return;
int ks = getKeyStates();
keyEvent = false;
checkKey(ks, UP_PRESSED, () -> keyUp = true, () -> keyUp = false);
checkKey(ks, DOWN_PRESSED, () -> keyDown = true, () -> keyDown = false);
checkKey(ks, LEFT_PRESSED, () -> keyLeft = true, () -> keyLeft = false);
checkKey(ks, RIGHT_PRESSED, () -> keyRight = true, () -> keyRight = false);
checkKey(ks, FIRE_PRESSED, () -> keyFire = true, () -> keyFire = false);
// keyEvent = keyUp|keyDown|keyLeft|keyRight|keyFire (任意為 true)
}
private void checkKey(int ks, int mask, Runnable onPress, Runnable onRelease) {
if ((ks & mask) != 0) { onPress.run(); keyEvent = true; }
else { onRelease.run(); }
}
// 邏輯+渲染入口 (子類/外部實現)
private void gameMain() {
g.setColor(0x000000);
g.fillRect(0, 0, screenW, screenH); // 清屏
// background.paint(g); plane.paint(g); ...
flushGraphics(); // 雙緩衝翻轉
}
// 生命週期
public void start() { if (!running) { running=true; thread=new Thread(this); thread.start(); } }
public void stop() { running=false; }
private void gameInit() { gameOver=false; /* 重置變量 */ }
// 命令響應
public void commandAction(Command c, Displayable d) {
String cmd = c.getLabel();
if ("Start".equals(cmd)) { gameInit(); start(); removeCommand(startCmd); addCommand(restartCmd=new Command("Restart",Command.OK,1)); }
else if ("Restart".equals(cmd)) { stop(); while(thread.isAlive()); gameInit(); start(); }
else if ("Exit".equals(cmd)) { stop(); /* 通知 MIDlet 銷毀 */ }
}
}
| 決策 | 理由 |
|---|---|
| 單例模式 | GameCanvas 佔用大量堆內存 (雙緩衝),全程僅需一個實例,避免重複創建 OOM |
| 固定幀率 + 睡眠補齊 | 確保邏輯確定性,節省電量,避免空跑佔用 CPU |
| 輸入狀態機 (按下/釋放/長按) | 支援「按住移動」與「單擊觸發」不同語義,關鍵於動作遊戲手感 |
| 命令菜單驅動狀態流轉 | Start → Running → Restart/Exit,清晰管理遊戲生命週期 |
雙緩衝 flushGraphics() | 消除閃爍,MIDP 2.0 標準特性 |