首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> J2ME

J2ME 長按鍵處理最佳實踐:旗標法兼容無 keyRepeated 裝置

     編輯:J2ME

問題背景

MIDP 低階事件:

  • keyPressed(int keyCode):按下瞬間觸發一次。
  • keyReleased(int keyCode):釋放瞬間觸發一次。
  • keyRepeated(int keyCode):長按持續觸發 (間隔由裝置決定)。

痛點:大量低端裝置 不支援 keyRepeated ( Canvas.hasRepeatEvents() == false ),導致遊戲中「長按方向鍵持續移動」失效。

解決方案:旗標輪詢法 (Flag + Game Loop Polling)

  1. keyPressed 設 leftPressed = true。
  2. keyReleased 設 leftPressed = false。
  3. 遊戲主迴圈 (獨立執行緒) 以固定頻率 (如 50ms) 輪詢旗標,if (leftPressed) moveLeft();。
  4. 若裝置支援 keyRepeated,可在其中同步呼叫移動邏輯,獲得更低延遲手感。

完整可執行範例

import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;

public class KeyHoldDemo extends MIDlet {
    private Display display;
    private GameCanvas canvas;

    protected void startApp() {
        display = Display.getDisplay(this);
        canvas = new GameCanvas();
        display.setCurrent(canvas);
        new Thread(canvas).start(); // 遊戲迴圈執行緒
    }
    protected void pauseApp() {}
    protected void destroyApp(boolean u) {}
}

class GameCanvas extends Canvas implements Runnable {
    // 狀態
    private boolean leftPressed, rightPressed;
    private int px, py;                 // 角色位置
    private String lastAction = "";

    public GameCanvas() {
        px = getWidth() / 2;
        py = getHeight() / 2;
    }

    // --- 事件處理 ---
    public void keyPressed(int keyCode) {
        int action = getGameAction(keyCode);
        switch (action) {
            case LEFT:  leftPressed  = true;  lastAction = "LEFT";  break;
            case RIGHT: rightPressed = true;  lastAction = "RIGHT"; break;
            case UP:    py = Math.max(0, py - 5); lastAction = "UP";    break; // 即時單步
            case DOWN:  py = Math.min(getHeight(), py + 5); lastAction = "DOWN"; break;
            case FIRE:  lastAction = "FIRE"; break;
        }
        repaint();
    }

    public void keyReleased(int keyCode) {
        int action = getGameAction(keyCode);
        switch (action) {
            case LEFT:  leftPressed  = false; break;
            case RIGHT: rightPressed = false; break;
        }
        lastAction = "";
        repaint();
    }

    // 若裝置支援 keyRepeated,同步處理可降低感知延遲
    public void keyRepeated(int keyCode) {
        int action = getGameAction(keyCode);
        if (action == LEFT)  moveLeft();
        else if (action == RIGHT) moveRight();
        repaint();
    }

    // --- 遊戲迴圈 ---
    public void run() {
        while (true) {
            if (leftPressed)  moveLeft();
            if (rightPressed) moveRight();

            try { Thread.sleep(50); } // 20 FPS 輪詢
            catch (InterruptedException e) { break; }
        }
    }

    private void moveLeft() {
        if (px > 0) px -= 3;
    }
    private void moveRight() {
        if (px < getWidth()) px += 3;
    }

    // --- 繪製 ---
    protected void paint(Graphics g) {
        g.setColor(0xFFFFFF); g.fillRect(0, 0, getWidth(), getHeight());
        g.setColor(0x000000);
        g.drawString("Action: " + lastAction, 10, 10, Graphics.TOP|Graphics.LEFT);
        g.drawString("Player", px, py, Graphics.HCENTER|Graphics.TOP);
    }
}

關鍵設計要點

要點說明
獨立執行緒跑遊戲迴圈避免阻塞事件分發執行緒,確保 keyPressed/Released 能即時更新旗標
固定 Thread.sleep(50)約 20 FPS,平衡流暢度與省電;可依遊戲類型調整 (動作遊戲 30~60 FPS)
旗標用 boolean 而非 int原子讀寫,無需同步;若需多鍵組合 (斜向) 可用 int bitmask
getGameAction() 統一鍵碼屏蔽裝置鍵碼差異 (Nokia 左鍵 -6, Moto -21 等)
保留 keyRepeated 處理支援的裝置可獲得「零延遲」首次重複觸發,手感更佳

進階擴充:多鍵組合 (斜向移動)

// 用位掩碼取代多個 boolean
private int keyMask = 0;
private static final int LEFT  = 1;
private static final int RIGHT = 2;
private static final int UP    = 4;
private static final int DOWN  = 8;

public void keyPressed(int keyCode) {
    switch (getGameAction(keyCode)) {
        case LEFT:  keyMask |= LEFT;  break;
        case RIGHT: keyMask |= RIGHT; break;
        case UP:    keyMask |= UP;    break;
        case DOWN:  keyMask |= DOWN;  break;
    }
}
public void keyReleased(int keyCode) {
    switch (getGameAction(keyCode)) {
        case LEFT:  keyMask &= ~LEFT;  break;
        case RIGHT: keyMask &= ~RIGHT; break;
        case UP:    keyMask &= ~UP;    break;
        case DOWN:  keyMask &= ~DOWN;  break;
    }
}
public void run() {
    while (true) {
        if ((keyMask & LEFT) != 0)  moveLeft();
        if ((keyMask & RIGHT) != 0) moveRight();
        if ((keyMask & UP) != 0)    moveUp();
        if ((keyMask & DOWN) != 0)  moveDown();
        // 斜向自動合成:同時按 LEFT+UP → 左上
        Thread.sleep(50);
    }
}

常見錯誤與避坑

  1. 在 paint() 中處理移動 → 幀率不固定、事件延遲、畫面撕裂。必須在獨立迴圈。
  2. 用 Thread.sleep() 在事件執行緒 → 凍結 UI、漏收 keyReleased。
  3. 旗標未清除 (如 keyReleased 漏寫) → 角色自動奔跑無法停止。
  4. 忽略 hasRepeatEvents() → 未在支援裝置上啟用原生重複,手感差一檔。

結語

「旗標輪詢法」是 J2ME 遊戲處理長按鍵的**通用、穩健、零依賴**標準解法。配合 getGameAction() 屏蔽鍵碼差異、獨立執行緒固定幀率輪詢、保留 keyRepeated 加成,即可在從 2003 年功能機到 2010 年 Symbian 智慧機的全谱系裝置上獲得一致的操作手感。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved