| 方法 | 觸發時機 | 支援度 |
|---|---|---|
keyPressed(int keyCode) | 按鍵按下 | 所有手機 |
keyReleased(int keyCode) | 按鍵釋放 | 所有手機 |
keyRepeated(int keyCode) | 長按持續觸發 | 大多數不支援,實際由 keyPressed 重複呼叫 |
實務上僅需實作 keyPressed 與 keyReleased 即可滿足所有需求。
keyCode 為廠商定義原始值,不同手機、模擬器與真機均可能不同。例如按數字鍵 1,不一定對應 keyCode=1。
解決方案:建立邏輯鍵常數映射表,啟動時或設定畫面讓使用者按鍵自動記錄對應 keyCode。
int keyValue; // 只能記住最後一個按鍵
public void keyPressed(int keyCode) {
System.out.println("KeyCode: " + keyCode); // 除錯用
keyValue = keyCode;
}
// 邏輯判斷
if (keyValue == NUM6) { gameState = 3; keyValue = -1; }
致命缺陷:若玩家極快連按 6 又按 4,keyValue 被 4 覆寫,狀態 2 的判斷完全漏掉 6 被按過。
利用 int 32 位元,每個位元代表一個邏輯鍵狀態:
// 邏輯鍵位元定義
static final int GAME_UP = 1 << 0; // 0000 0001
static final int GAME_DOWN = 1 << 1; // 0000 0010
static final int GAME_LEFT = 1 << 2; // 0000 0100
static final int GAME_RIGHT = 1 << 3; // 0000 1000
static final int GAME_FIRE = 1 << 4; // 0001 0000
// ... 最多 32 鍵
int keyState = 0; // 當前按鍵狀態位元圖
public void keyPressed(int keyCode) {
switch (keyCode) {
case NUM_UP: keyState |= GAME_UP; break;
case NUM_DOWN: keyState |= GAME_DOWN; break;
case NUM_LEFT: keyState |= GAME_LEFT; break;
case NUM_RIGHT: keyState |= GAME_RIGHT; break;
case NUM_FIRE: keyState |= GAME_FIRE; break;
}
}
public void keyReleased(int keyCode) {
switch (keyCode) {
case NUM_UP: keyState &= ~GAME_UP; break;
case NUM_DOWN: keyState &= ~GAME_DOWN; break;
case NUM_LEFT: keyState &= ~GAME_LEFT; break;
case NUM_RIGHT: keyState &= ~GAME_RIGHT; break;
case NUM_FIRE: keyState &= ~GAME_FIRE; break;
}
}
// 檢查某鍵是否被按住 (含長按)
boolean isHeld(int gameKey) {
return (keyState & gameKey) != 0;
}
// 檢查某鍵是否在「這一幀」被按下 (需配合幀緩衝)
boolean isPressedThisFrame(int gameKey) {
return (framePressedKeys & gameKey) != 0;
}
問題:keyState 隨時可能被中斷修改,若在遊戲邏輯中間直接讀取,會導致同一幀內判斷結果不一致。
解決:每幀開始時快照一份不變的副本供邏輯使用。
int frameKeyState; // 本幀按鍵狀態快照
int framePressedKeys; // 本幀新按下的鍵
int frameReleasedKeys; // 本幀釋放的鍵
// 每幀開頭呼叫 (在 run() 迴路最前面)
private void acquireInput() {
frameKeyState = keyState;
framePressedKeys = pressedKeysBuffer;
frameReleasedKeys = releasedKeysBuffer;
pressedKeysBuffer = 0;
releasedKeysBuffer = 0;
}
// 修正後的回調
public void keyPressed(int keyCode) {
int bit = getGameKeyBit(keyCode); // 映射到 GAME_* 常數
keyState |= bit;
pressedKeysBuffer |= bit; // 記錄這一幀有被按下
}
public void keyReleased(int keyCode) {
int bit = getGameKeyBit(keyCode);
keyState &= ~bit;
releasedKeysBuffer |= bit; // 記錄這一幀有被釋放
}
// 遊戲邏輯中只用快照判斷
if ((frameKeyState & GAME_RIGHT) != 0) { /* 角色向右移動 */ }
if ((framePressedKeys & GAME_FIRE) != 0) { /* 發射子彈 */ }
// Game.java 片段
static final int NUM4 = 52; // 實測填入
static final int NUM6 = 54;
static final int GAME_NUM4 = 1 << 4;
static final int GAME_NUM6 = 1 << 6;
int keyState = 0;
int frameKeyState, framePressedKeys;
public void keyPressed(int keyCode) {
if (keyCode == NUM4) keyState |= GAME_NUM4;
else if (keyCode == NUM6) keyState |= GAME_NUM6;
}
public void keyReleased(int keyCode) {
if (keyCode == NUM4) keyState &= ~GAME_NUM4;
else if (keyCode == NUM6) keyState &= ~GAME_NUM6;
}
private void acquireInput() {
frameKeyState = keyState;
framePressedKeys = pressedKeysBuffer; // 需額外維護 pressedKeysBuffer
pressedKeysBuffer = 0;
}
// 狀態機判斷
switch (gameState) {
case 2:
if ((framePressedKeys & GAME_NUM6) != 0) {
gameState = 3;
framePressedKeys &= ~GAME_NUM6; // 清除已處理
}
break;
case 3:
if ((framePressedKeys & GAME_NUM4) != 0) {
gameState = 2;
framePressedKeys &= ~GAME_NUM4;
}
break;
}
frameKeyState) 與瞬時按下 (framePressedKeys)。這套機制雖簡單,卻是商業級 J2ME 遊戲輸入系統的核心骨架。