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

J2ME 遊戲狀態機設計模式實戰:State Pattern 重構鍵盤與繪製邏輯

     編輯:J2ME

痛點:龐大的 keyPressed() 與 paint()

初學者常將所有遊戲狀態 (主選單、幫助、遊戲中、暫停、遊戲結束) 的邏輯塞進單一 Canvas 的 keyPressed() 與 paint(),導致:

  • switch(state) 分支超過 2000 行。
  • 新增狀態需修改核心類,違反開閉原則。
  • 狀態轉換邏輯分散,難以追蹤與測試。

State Pattern 解決方案

將每個遊戲狀態封裝為獨立類別,實作統一介面,委派給當前狀態物件處理輸入與繪製。

1. 定義狀態介面


public interface GameState {
    void onKeyPressed(int keyCode, StateManager sm);
    void paint(Graphics g);
    void onEnter();      // 可選:進入狀態初始化
    void onExit();       // 可選:離開狀態清理
}

2. 具體狀態實作


// 主選單狀態
public class MainMenuState implements GameState {
    private static final String[] ITEMS = {"Start", "Help", "Exit"};
    private int selectedIndex = 0;
    private Image bgImage;

    public MainMenuState() {
        try { bgImage = Image.createImage("/res/menu_bg.png"); } 
        catch (IOException ignored) {}
    }

    public void onKeyPressed(int keyCode, StateManager sm) {
        int action = getGameAction(keyCode);
        if (action == Canvas.UP) {
            selectedIndex = (selectedIndex - 1 + ITEMS.length) % ITEMS.length;
        } else if (action == Canvas.DOWN) {
            selectedIndex = (selectedIndex + 1) % ITEMS.length;
        } else if (action == Canvas.FIRE) {
            switch (selectedIndex) {
                case 0: sm.setState(new PlayState()); break;
                case 1: sm.setState(new HelpState()); break;
                case 2: sm.quitGame(); break;
            }
        }
    }

    public void paint(Graphics g) {
        if (bgImage != null) g.drawImage(bgImage, 0, 0, Graphics.TOP | Graphics.LEFT);
        Font font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_LARGE);
        g.setFont(font);
        for (int i = 0; i < ITEMS.length; i++) {
            g.setColor(i == selectedIndex ? 0xFF0000 : 0xFFFFFF);
            g.drawString(ITEMS[i], getWidth()/2, 100 + i * 40, Graphics.HCENTER | Graphics.TOP);
        }
    }

    public void onEnter() {}
    public void onExit() {}
}

// 幫助狀態
public class HelpState implements GameState {
    private static final String[] LINES = {
        "Use UP/DOWN to navigate",
        "FIRE to select",
        "Press any key to return"
    };

    public void onKeyPressed(int keyCode, StateManager sm) {
        sm.setState(new MainMenuState()); // 任意鍵返回
    }

    public void paint(Graphics g) {
        g.setColor(0x000000);
        g.fillRect(0, 0, getWidth(), getHeight());
        g.setColor(0xFFFFFF);
        Font f = Font.getDefaultFont();
        g.setFont(f);
        for (int i = 0; i < LINES.length; i++) {
            g.drawString(LINES[i], 10, 50 + i * 20, Graphics.LEFT | Graphics.TOP);
        }
    }
    public void onEnter() {}
    public void onExit() {}
}

// 遊戲進行狀態 (簡化)
public class PlayState implements GameState {
    private Player player;
    private Enemy[] enemies;

    public PlayState() {
        player = new Player(100, 100);
        enemies = new Enemy[5];
        for (int i = 0; i < enemies.length; i++) enemies[i] = new Enemy();
    }

    public void onKeyPressed(int keyCode, StateManager sm) {
        int action = getGameAction(keyCode);
        if (action == Canvas.LEFT) player.moveLeft();
        else if (action == Canvas.RIGHT) player.moveRight();
        else if (action == Canvas.UP) player.jump();
        else if (action == Canvas.FIRE) player.shoot();
        else if (keyCode == Canvas.KEY_STAR) sm.setState(new PauseState(this)); // 暫停
    }

    public void paint(Graphics g) {
        // 繪製背景、玩家、敵人、UI...
        g.setColor(0x87CEEB); g.fillRect(0, 0, getWidth(), getHeight()); // 天空
        player.paint(g);
        for (Enemy e : enemies) e.paint(g);
    }

    public void onEnter() { /* 載入關卡資源 */ }
    public void onExit() { /* 釋放關卡資源 */ }
}

3. 狀態管理器


public class StateManager {
    private GameState currentState;
    private final Canvas canvas; // 回調畫布尺寸等
    private final MIDlet midlet;

    public StateManager(Canvas canvas, MIDlet midlet) {
        this.canvas = canvas;
        this.midlet = midlet;
        setState(new MainMenuState()); // 初始狀態
    }

    public void setState(GameState newState) {
        if (currentState != null) currentState.onExit();
        currentState = newState;
        currentState.onEnter();
        canvas.repaint(); // 觸發首幀繪製
    }

    public void quitGame() {
        midlet.destroyApp(false);
        midlet.notifyDestroyed();
    }

    // 委派方法
    public void onKeyPressed(int keyCode) {
        if (currentState != null) currentState.onKeyPressed(keyCode, this);
    }

    public void paint(Graphics g) {
        if (currentState != null) currentState.paint(g);
    }
}

4. Canvas 整合


public class GameCanvas extends Canvas {
    private StateManager sm;

    public GameCanvas(MIDlet midlet) {
        sm = new StateManager(this, midlet);
    }

    protected void keyPressed(int keyCode) {
        sm.onKeyPressed(keyCode);
    }

    protected void paint(Graphics g) {
        sm.paint(g);
    }
}

進階擴展:堆疊式狀態 (疊加選單/對話框)


public class StateManager {
    private final Stack stateStack = new Stack<>();

    public void pushState(GameState state) {
        if (!stateStack.isEmpty()) stateStack.peek().onPause();
        stateStack.push(state);
        state.onEnter();
        canvas.repaint();
    }

    public void popState() {
        if (!stateStack.isEmpty()) {
            stateStack.pop().onExit();
            if (!stateStack.isEmpty()) {
                stateStack.peek().onResume();
            }
        }
        canvas.repaint();
    }

    public GameState current() {
        return stateStack.isEmpty() ? null : stateStack.peek();
    }

    // 委派給 current()
}

適用場景:遊戲中按選單鍵 → pushState(new InGameMenuState()),選單繪製在遊戲畫面上方,選單關閉 → popState() 恢復遊戲。

優勢總結

指標巨型 switchState Pattern
單一職責❌ 違反✅ 每狀態獨立類別
開閉原則❌ 新增狀態改核心✅ 新增類別無需改舊碼
可測試性❌ 難以單元測試✅ 狀態可獨立實例化測試
狀態轉換可視化❌ 分散在 case 中✅ 集中在 setState/pushState
記憶體開銷低 (單一物件)略高 (多物件),可用對象池優化
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved