Skip to content

Archived on 2026-10-24. This is a historical implementation note, not current setup instructions or an active task list. Original claims, code examples, file paths, and completion statements have not all been revalidated. Use the current documentation and issue register for maintenance decisions.

MenuController GameMenu Integration

Overview

This document describes the unified back button handling between MenuController and GameMenuManager to properly support settings toggling during gameplay.

Problem

Original GameMenuManager Issues

  1. Duplicate Pause Tracking: Had both isGamePaused in GameMenuManager and GameStateService.isPaused in GameManager
  2. Inconsistent Back Button: Settings was toggled manually, but subpages within settings needed normal navigation
  3. Missing State Checks: Could open settings during rewards or game over
  4. Resume Bug: Closing settings during rewards would resume time incorrectly
  • Has initialPage (main menu) that's always visible
  • Back on root → Shows quit confirmation (exitPage)
  • Navigation: Push/pop through pages normally
  • Clean, predictable behavior

GameMenu Pattern (unique requirements)

  • No visible root page during gameplay
  • Settings is "toggled" by back button, not always visible
  • Settings has subpages (audio, controls, etc.) that need normal navigation
  • Different from MainMenu pattern

Solution

Unified Back Button Logic

The new approach respects GameMenuManager's unique requirements while integrating cleanly with MenuController:

private void HandleBackButton()
{
    // 1. Check if we can open settings (not during reward or game over)
    if (!GameManager.instance.GameState.CanPause)
    {
        Debug.LogWarning("Cannot open settings during reward or game over");
        return;
    }

    // 2. Check current state using GameState (single source of truth)
    bool isPaused = GameManager.instance?.isPaused ?? false;
    bool isSettingsOpen = menuController.IsPageInStack(settingsPage);

    if (!isPaused && !isSettingsOpen)
    {
        // Scenario 1: Game running, settings closed → Open settings
        GameManager.instance.PauseGame();
        menuController.PushPage(settingsPage);
    }
    else if (isPaused && isSettingsOpen)
    {
        // Check if we're on settings root or a subpage
        bool isOnSettingsRoot = menuController.IsPageOnTopOfStack(settingsPage);

        if (isOnSettingsRoot)
        {
            // Scenario 2: Settings root open → Close settings and resume
            GameManager.instance.isPaused = false; // This calls UpdateTimeScale
            menuController.PopPage();
        }
        else
        {
            // Scenario 3: Settings subpage open → Navigate back normally
            menuController.PopPage();
        }
    }
}

State Flow

Gameplay State:
  Stack: Empty
  isPaused: false
  isSettingsOpen: false
  → Back press → Pause, push settings

Settings Root State:
  Stack: [settingsPage]
  isPaused: true
  isSettingsOpen: true
  isOnSettingsRoot: true
  → Back press → Pop settings, resume

Settings Subpage (e.g., Audio):
  Stack: [settingsPage, audioPage]
  isPaused: true
  isSettingsOpen: true
  isOnSettingsRoot: false
  → Back press → Pop audio (normal navigation)

Back to Settings Root:
  Stack: [settingsPage]
  isPaused: true
  isSettingsOpen: true
  isOnSettingsRoot: true
  → Back press → Pop settings, resume

Key Changes

1. Removed Duplicate State Tracking

Before:

private bool isGamePaused = false; // GameMenuManager
// AND
public bool isPaused { get; set; } // GameStateService

After:

// Only use GameManager.GameState.isPaused (single source of truth)
bool isPaused = GameManager.instance?.isPaused ?? false;

2. Proper Settings Subpage Navigation

Settings subpages (audio, controls, etc.) now navigate normally through the page stack:

Settings (root)
  ├── Audio (subpage)
  ├── Controls (subpage)
  ├── Video (subpage)
  └── Gameplay (subpage)

Navigation flow: - Press Settings button → Navigate to subpage - Press Back → Return to Settings root - Press Back → Close settings and resume game

3. Integration with GameState Blocking

Settings can only be opened when GameState.CanPause returns true:

public bool CanPause => !isRewarding && !isGameOver;

This prevents: - ❌ Opening settings during rewards - ❌ Opening settings during game over - ❌ Opening settings during critical animations

4. Fixed Resume Bug

Problem: Old code called ResumeGame() directly, which would set Time.timeScale = 1 even if rewards were still active.

Solution: Set isPaused = false, which calls UpdateTimeScale():

GameManager.instance.isPaused = false; // Automatically calls UpdateTimeScale()

UpdateTimeScale() checks ALL states:

public void UpdateTimeScale()
{
    Time.timeScale = ShouldTimeBePaused ? 0f : 1f;
}

public bool ShouldTimeBePaused => isPaused || isRewarding || isGameOver;

Result: Time stays paused during rewards, even after settings close.

5. Clean Gate Animation

Added logic to close settings before gate animation (play again/quit):

public void ShowPlayAgainConfirmation()
{
    // Close settings if open
    if (menuController.IsPageInStack(settingsPage))
    {
        while (menuController.IsPageInStack(settingsPage))
        {
            menuController.PopPage();
        }
    }

    menuController.StartCoroutine(menuController.GateAnimation(true));
}

This prevents settings from interfering with gate animations.

Benefits

1. Single Source of Truth

  • ✅ Only GameStateService tracks pause state
  • ✅ No duplicate/conflicting state
  • ✅ Easier to debug and maintain

2. Proper Navigation

  • ✅ Settings subpages navigate normally
  • ✅ Settings root toggles pause
  • ✅ Clear state transitions

3. Integration with GameState

  • ✅ Respects CanPause blocking logic
  • ✅ Cannot open during rewards/game over
  • ✅ Time scale automatically managed

4. Bug Fixes

  • ✅ Settings no longer resume time during rewards
  • ✅ Consistent with unified state system
  • ✅ No more duplicate pause tracking

5. Clean Architecture

  • ✅ GameMenuManager handles toggle logic
  • ✅ MenuController handles page navigation
  • ✅ GameState handles state management
  • ✅ Clear separation of concerns

Comparison: MainMenu vs GameMenu

Aspect MainMenuManager GameMenuManager
Root Page Main menu (always visible) None during gameplay
Initial Page Main menu null
Exit Page Quit confirmation null
Back on Root Show quit confirmation N/A (no visible root)
Settings Normal page in stack Toggled by back button
Pause N/A Pauses game on open
Resume N/A Resumes on close

Settings Subpage Requirements

For settings to have subpages (Audio, Controls, Video, Gameplay), ensure:

  1. Subpages are separate Page components:

    public class AudioPage : Page { }
    public class ControlsPage : Page { }
    public class VideoPage : Page { }
    public class GameplayPage : Page { }
    

  2. Navigation buttons push subpages:

    public void OpenAudioPage()
    {
        menuController.PushPage(audioPage);
    }
    

  3. Back button handles navigation:

  4. Already implemented in HandleBackButton()
  5. Checks if on settings root or subpage
  6. Navigates appropriately

Testing Checklist

  • [ ] Back button opens settings during gameplay
  • [ ] Back button closes settings and resumes game
  • [ ] Can navigate to settings subpages (audio, controls, etc.)
  • [ ] Back button in settings subpages navigates to settings root
  • [ ] Cannot open settings during rewards
  • [ ] Cannot open settings during game over
  • [ ] Settings close before gate animation (play again/quit)
  • [ ] Time pauses when settings open
  • [ ] Time resumes when settings close
  • [ ] Time stays paused during rewards (settings closed)
  • [ ] No duplicate pause state tracking
  • [ ] Settings subpages navigate normally

Files Modified

GameMenuManager.cs

Changes: - Removed isGamePaused private field - Updated HandleBackButton() to use GameState - Added logic for settings root vs subpage navigation - Added settings cleanup before gate animation - Integrated with GameState.CanPause blocking

Integration Points

The integration works through MenuController's existing system:

menuController.Configure(
    initialPage: null,        // No visible root during gameplay
    exitPage: null,           // No exit page
    firstFocusItem: firstFocusItem,
    onRootBackPressed: HandleBackButton,  // Our custom handler
    onGateAnimationComplete: HandleGateComplete
);

HandleBackButton() is called by MenuController when back is pressed and page stack is empty (root).

Future Improvements

  1. Add Settings State Tracking
  2. Track which settings tab is active
  3. Save/restore settings state

  4. Add Settings Page Enum

    public enum SettingsPage
    {
        None,
        Root,
        Audio,
        Controls,
        Video,
        Gameplay
    }
    

  5. Add Settings History

  6. Remember last opened settings subpage
  7. Return to same subpage when reopened

  8. Add Settings Animation

  9. Animate settings open/close
  10. Smooth transitions