跳轉到

遊戲狀態與 Overlay

Pacman 有開始、遊戲中、勝利與失敗四個階段,遊戲中還能暫停。角色更新、畫面訊息和按鈕都需要讀取同一個狀態,因此範例使用 g_stateg_paused 表示整局流程。

g_state 階段 畫面與行為
0 開始 顯示標題與 Start,角色不移動
1 遊戲中 顯示剩餘豆子與 Pause,角色更新
2 勝利 顯示 YOU WIN 與 Restart
3 失敗 顯示 GAME OVER 與 Restart

Pacman 與 Ghost 的 OnUpdate() 都在狀態不是 1 或暫停時直接 return。遊戲物件仍存在並繪製,背景畫面保持在目前關卡;Overlay 再於其上顯示狀態訊息。

狀態畫面

ScoreOverlay 覆寫 Draw()。遊戲進行時顯示剩餘豆子;其他階段先以半透明黑色壓暗整個視窗,再繪製置中的訊息。

class ScoreOverlay : public Overlay {
public:
    void Draw() override {
        if (g_state == 1) {
            DrawText(TextFormat("Pellets: %d", g_pellets_left),
                     8, 8, 20, YELLOW);
            if (g_paused) DrawBigText("PAUSED", ORANGE);
            return;
        }

        DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(),
                      Fade(BLACK, 0.6f));
        if (g_state == 0)
            DrawBigText("PAC-MAN", YELLOW);
        else if (g_state == 2)
            DrawBigText("YOU WIN!", GREEN);
        else if (g_state == 3)
            DrawBigText("GAME OVER", RED);
    }

private:
    static void DrawBigText(const char* message, Color color) {
        constexpr int kFontSize = 40;
        const int text_width = MeasureText(message, kFontSize);
        DrawText(message,
                 GetScreenWidth() / 2 - text_width / 2,
                 GetScreenHeight() / 2 - 70,
                 kFontSize,
                 color);
    }
};

Overlay 使用視窗像素座標,也能直接呼叫 raylib。它在所有 GridObject 後繪製,因此壓暗矩形會覆蓋整個關卡,後面加入的按鈕又會繪製在 ScoreOverlay 上方。

依狀態啟用按鈕

Button 的預設 OnUpdate() 處理 hover 與點擊,Draw() 繪製按鈕。衍生按鈕先檢查遊戲狀態,只在適用階段呼叫基底實作。

class StartButton : public Button {
public:
    StartButton(int x, int y, int width, int height)
        : Button("Start", x, y, width, height) {}

    void OnClick() override { g_state = 1; }

    void OnUpdate() override {
        if (g_state == 0) Button::OnUpdate();
    }

    void Draw() override {
        if (g_state == 0) Button::Draw();
    }
};

RestartButton 只在狀態 2 或 3 啟用。點擊時重建所有 GridObject,保留已加入的 Overlay 和素材。

void OnClick() override {
    BuildLevel(*g_game, g_level);
    g_state = 1;
}

BuildLevel() 呼叫 ClearObjects(),重設 g_pellets_leftg_pausedg_mazeg_player,再從已驗證的 g_level 建立新 instance。ClearObjects() 不刪除 Overlay,也不卸載 pacman.db,所以按鈕與素材可以跨關卡重用。

組裝遊戲

main() 依資料相依順序組裝程式:讀取地圖、建立 Engine、載入素材、建立關卡、加入 Overlay,最後開始主迴圈。

g_level = LoadLevelMap("map.txt");

GridEngine game(g_level.cols, g_level.rows, 32);
g_game = &game;
game.LoadAssets("pacman.db");
game.set_background_color(BLACK);

BuildLevel(game, g_level);
g_state = 0;

game.AddOverlay(new ScoreOverlay());
game.AddOverlay(new StartButton(button_x, button_y,
                                kButtonWidth, kButtonHeight));
game.AddOverlay(new RestartButton(button_x, button_y,
                                  kButtonWidth, kButtonHeight));
game.AddOverlay(new PauseButton(g_level.cols * 32 - 88, 6, 82, 24));

game.Run();

這個順序也決定 Overlay 的繪製先後。ScoreOverlay 最早加入,Start、Restart 和 Pause 在它之後繪製。Overlay 不使用 GridObject 的 z-index。

Summary

  • 全局遊戲狀態協調角色更新、勝負畫面與按鈕。
  • Overlay 以像素座標繪製在全部 GridObject 上方。
  • Button 可依遊戲階段選擇是否呼叫基底更新與繪製。
  • ClearObjects() 重建關卡時保留 Overlay 與已載入素材。