J2ME 遊戲開發核心在於「輸入 → 邏輯 → 渲染」迴圈。MIDP 1.0 到 2.0,按鍵處理經歷三代演進,各解決前一代痛點:
| 代數 | API | 核心機制 | 適用版本 | 關鍵限制 |
|---|---|---|---|---|
| 第一代 | keyPressed(int keyCode) | 事件回調、同步處理 | MIDP 1.0+ | 阻塞 UI 執行緒、功能鍵 keyCode 跨裝置差異大 |
| 第二代 | getGameAction(keyCode) | 邏輯映射標準化 | MIDP 1.0+ | 仍綁定 keyPressed 回調、無法脫離事件執行緒 |
| 第三代 | getKeyStates() (GameCanvas) | 輪詢式狀態機、非阻塞 | MIDP 2.0+ | 僅支援遊戲鍵、需 GameCanvas 子類、構造器啟用 |
keyCode 固定 (48-57, 42, 35)// ❌ 硬編碼魔術數字,不可維護、不可移植
if (keyCode == 52) { /* ... */ }
// ✅ 使用 Canvas 定義常數(僅限數字鍵/標準游戲鍵)
if (keyCode == Canvas.KEY_NUM4) { /* ... */ }
致命缺陷:遊戲邏輯、渲染、輸入全部擠在系統事件執行緒,長時間運算導致幀率崩潰、按鍵遺漏、UI 凍結。
| Game Action 常數 | 對應物理鍵 (典型) |
|---|---|
Canvas.UP | 數字鍵 2、導航上 |
Canvas.DOWN | 數字鍵 8、導航下 |
Canvas.LEFT | 數字鍵 4、導航左 |
Canvas.RIGHT | 數字鍵 6、導航右 |
Canvas.FIRE | 數字鍵 5、導航確定 |
Canvas.GAME_A | 數字鍵 1 或 7 |
Canvas.GAME_B | 數字鍵 3 或 9 |
Canvas.GAME_C | 數字鍵 7 或 * |
Canvas.GAME_D | 數字鍵 9 或 # |
protected void keyPressed(int keyCode) {
int action = getGameAction(keyCode); // 映射為邏輯動作
switch (action) {
case Canvas.LEFT: moveLeft(); break;
case Canvas.RIGHT: moveRight(); break;
case Canvas.UP: moveUp(); break;
case Canvas.DOWN: moveDown(); break;
case Canvas.FIRE: fire(); break;
case Canvas.GAME_A: specialA(); break;
// ...
}
}
優勢:同一套邏輯碼跑在 Nokia/Sony/Motorola 上行為一致。殘留問題:仍被迫在 keyPressed 回調中處理,無法脫離系統執行緒。
GameCanvas (MIDP 2.0 Game API)super(true) 啟用按鍵狀態追蹤keyPressed 不再接收遊戲鍵事件(功能鍵仍正常)// 在遊戲迴圈執行緒中任意位置調用
int states = getKeyStates(); // 取得並清空當前狀態位元遮罩
// 位元運算檢測
if ((states & GameCanvas.LEFT_PRESSED) != 0) moveLeft();
if ((states & GameCanvas.RIGHT_PRESSED) != 0) moveRight();
if ((states & GameCanvas.UP_PRESSED) != 0) moveUp();
if ((states & GameCanvas.DOWN_PRESSED) != 0) moveDown();
if ((states & GameCanvas.FIRE_PRESSED) != 0) fire();
flushGraphics() 實現確定性遊戲迴圈public class GameLoopCanvas extends GameCanvas implements Runnable {
private Thread gameThread;
private volatile boolean running = true;
public GameLoopCanvas() {
super(true); // ★ 啟用 keyStates
gameThread = new Thread(this);
gameThread.start();
}
public void run() {
while (running) {
long frameStart = System.currentTimeMillis();
// 1. 輸入輪詢
int keys = getKeyStates();
processInput(keys);
// 2. 邏輯更新
updateGameLogic();
// 3. 渲染
Graphics g = getGraphics();
render(g);
flushGraphics(); // 雙緩衝翻轉
// 4. 幀率控制 (目標 20 FPS = 50ms)
long elapsed = System.currentTimeMillis() - frameStart;
long sleep = Math.max(0, 50 - elapsed);
try { Thread.sleep(sleep); } catch (InterruptedException ignored) {}
}
}
private void processInput(int keys) {
if ((keys & LEFT_PRESSED) != 0) player.moveLeft();
if ((keys & RIGHT_PRESSED) != 0) player.moveRight();
if ((keys & FIRE_PRESSED) != 0) player.shoot();
}
// ... updateGameLogic, render 實作
}
| 專案類型 | 推薦方案 | 理由 |
|---|---|---|
| MIDP 1.0 相容專案 | gameAction + keyPressed | 唯一選擇,善用 switch-case 結構 |
| MIDP 2.0 純遊戲專案 | GameCanvas + keyStates | 效能最佳、架構最清晰、業界標準 |
| 混合 UI (表單+遊戲) | Form/List 做選單,GameCanvas 做遊戲 | 職責分離,各用其長 |
| 需處理功能鍵(軟鍵選單) | GameCanvas 仍保留 keyPressed 處理非遊戲鍵 | 雙軌並行,互不干擾 |
從 keyPressed 到 gameAction 再到 keyStates,J2ME 事件處理演進史,是從「被動回調」走向「主動輪詢」、從「邏輯與渲染耦合」走向「確定性遊戲迴圈」的工程化歷程。掌握第三代技術,意味著你擁有了在 200KB 記憶體、100MHz CPU 上構建流暢遊戲的核心能力——這份底層功力,在今天 Unity/Unreal/Golang 遊戲服務端開發中,依然閃耀著工程美學的光芒。