Move buttons to HUD Manager

This commit is contained in:
Michal Pikulski
2025-11-09 13:23:03 +01:00
parent a80aed8eb7
commit 0c9a388433
18 changed files with 2326 additions and 3998 deletions

View File

@@ -25,7 +25,6 @@ public class AppleAudioSource : MonoBehaviour
_audioMixer = AudioManager.Instance.audioMixer;
InitializeAudioSource();
audioSource.playOnAwake = false;
}

View File

@@ -2,6 +2,7 @@ using UnityEngine;
using SkiaSharp.Unity;
using Input;
using AppleHills.Core;
using UI;
using UnityEngine.Events;
public class AppSwitcher : MonoBehaviour
@@ -31,6 +32,7 @@ public class AppSwitcher : MonoBehaviour
public void OpenAppSwitcher()
{
PlayerHudManager.Instance.HideAllHudExcept(gameObject);
rainbow.SetActive(true);
//Activate players
@@ -67,6 +69,8 @@ public class AppSwitcher : MonoBehaviour
// Hide the exit button
exitButton.SetActive(false);
PlayerHudManager.Instance.ShowAllHud();
}
public void GamesHiddenAnimationEvent()

View File

@@ -37,8 +37,9 @@ namespace UI.CardSystem
[SerializeField] private float boosterDisappearDuration = 0.5f;
[SerializeField] private CinemachineImpulseSource impulseSource;
[SerializeField] private ParticleSystem openingParticleSystem;
[SerializeField] private Transform albumIcon; // Target for card fly-away animation
[SerializeField] private GameObject albumIcon; // Target for card fly-away animation and dismiss button
private Button _dismissButton; // Button to close/dismiss the booster opening page
private int _availableBoosterCount;
private BoosterPackDraggable _currentBoosterInCenter;
private List<BoosterPackDraggable> _activeBoostersInSlots = new List<BoosterPackDraggable>();
@@ -57,12 +58,32 @@ namespace UI.CardSystem
if (canvasGroup == null)
canvasGroup = gameObject.AddComponent<CanvasGroup>();
// Get dismiss button from albumIcon GameObject
if (albumIcon != null)
{
_dismissButton = albumIcon.GetComponent<Button>();
if (_dismissButton != null)
{
_dismissButton.onClick.AddListener(OnDismissButtonClicked);
}
else
{
Debug.LogWarning("[BoosterOpeningPage] albumIcon does not have a Button component!");
}
}
// UI pages should start disabled
gameObject.SetActive(false);
}
private void OnDestroy()
{
// Unsubscribe from dismiss button
if (_dismissButton != null)
{
_dismissButton.onClick.RemoveListener(OnDismissButtonClicked);
}
// Unsubscribe from slot events
if (centerOpeningSlot != null)
{
@@ -81,6 +102,18 @@ namespace UI.CardSystem
{
_availableBoosterCount = count;
}
/// <summary>
/// Called when the dismiss button (albumIcon) is clicked
/// </summary>
private void OnDismissButtonClicked()
{
if (UIPageController.Instance != null)
{
UIPageController.Instance.PopPage();
Debug.Log("[BoosterOpeningPage] Dismiss button clicked, popping page from stack");
}
}
public override void TransitionIn()
{
@@ -768,7 +801,7 @@ namespace UI.CardSystem
yield return new WaitForSeconds(0.5f);
// Animate cards to album icon (or center if no icon assigned) with staggered delays
Vector3 targetPosition = albumIcon != null ? albumIcon.position : Vector3.zero;
Vector3 targetPosition = albumIcon != null ? albumIcon.transform.position : Vector3.zero;
int cardIndex = 0;
foreach (GameObject cardObj in _currentRevealedCards)

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using Core;
using Core.Lifecycle;
using UnityEngine;
using UnityEngine.InputSystem;
namespace UI.Core
@@ -9,12 +10,19 @@ namespace UI.Core
/// <summary>
/// Manages UI page transitions and maintains a stack of active pages.
/// Pages are pushed onto a stack for navigation and popped when going back.
/// Supports both scene-based pages and prefab-based instantiation.
/// </summary>
public class UIPageController : ManagedBehaviour
{
private static UIPageController _instance;
public static UIPageController Instance => _instance;
[Header("Canvas Management")]
[SerializeField] private Transform mainCanvas;
// Cache instantiated pages to avoid re-creating them
private Dictionary<GameObject, UIPage> _prefabInstanceCache = new Dictionary<GameObject, UIPage>();
private Stack<UIPage> _pageStack = new Stack<UIPage>();
public UIPage CurrentPage => _pageStack.Count > 0 ? _pageStack.Peek() : null;
public IEnumerable<UIPage> PageStack => _pageStack;
@@ -48,7 +56,17 @@ namespace UI.Core
{
base.OnDestroy();
// Clean up event subscription when the controller is destroyed
// Clean up cached instances
foreach (var cachedPage in _prefabInstanceCache.Values)
{
if (cachedPage != null)
{
Destroy(cachedPage.gameObject);
}
}
_prefabInstanceCache.Clear();
// Clean up event subscription
if (_cancelAction != null)
{
_cancelAction.performed -= OnCancelActionPerformed;
@@ -85,6 +103,86 @@ namespace UI.Core
Logging.Debug($"[UIPageController] Pushed page: {page.PageName}");
}
/// <summary>
/// Pushes a page from a prefab. Instantiates on first use, reuses on subsequent calls.
/// </summary>
/// <param name="pagePrefab">The prefab containing a UIPage component</param>
public void PushPageFromPrefab(GameObject pagePrefab)
{
if (pagePrefab == null)
{
Debug.LogError("[UIPageController] Cannot push null prefab");
return;
}
UIPage pageInstance = GetOrInstantiatePage(pagePrefab);
if (pageInstance != null)
{
PushPage(pageInstance); // Use existing push logic
}
}
/// <summary>
/// Gets cached page instance or instantiates a new one from prefab.
/// Searches for UIPage component on root GameObject first, then in first-order children.
/// </summary>
private UIPage GetOrInstantiatePage(GameObject pagePrefab)
{
// Check cache first
if (_prefabInstanceCache.TryGetValue(pagePrefab, out UIPage cachedPage))
{
if (cachedPage != null) // Null check in case it was destroyed
{
return cachedPage;
}
else
{
_prefabInstanceCache.Remove(pagePrefab);
}
}
// Validate mainCanvas
if (mainCanvas == null)
{
Debug.LogError("[UIPageController] mainCanvas is not assigned! Cannot instantiate page prefab.");
return null;
}
// Instantiate new page
GameObject pageObject = Instantiate(pagePrefab, mainCanvas);
// Try to find UIPage component on root first
UIPage pageComponent = pageObject.GetComponent<UIPage>();
// If not found on root, search first-order children (in hierarchy order)
if (pageComponent == null)
{
Transform pageTransform = pageObject.transform;
for (int i = 0; i < pageTransform.childCount; i++)
{
pageComponent = pageTransform.GetChild(i).GetComponent<UIPage>();
if (pageComponent != null)
{
Logging.Debug($"[UIPageController] Found UIPage component on child: {pageComponent.gameObject.name}");
break;
}
}
}
if (pageComponent == null)
{
Debug.LogError($"[UIPageController] Prefab {pagePrefab.name} does not have a UIPage component on root or first-order children!");
Destroy(pageObject);
return null;
}
// Cache it
_prefabInstanceCache[pagePrefab] = pageComponent;
Logging.Debug($"[UIPageController] Instantiated and cached page: {pageComponent.PageName}");
return pageComponent;
}
/// <summary>
/// Pops the current page from the stack and shows the previous page.
/// </summary>
@@ -112,20 +210,40 @@ namespace UI.Core
}
/// <summary>
/// Clears all pages from the stack.
/// Clears all pages from the stack and optionally destroys cached instances.
/// </summary>
public void ClearStack()
/// <param name="destroyCachedInstances">If true, destroys all cached prefab instances</param>
public void ClearStack(bool destroyCachedInstances = false)
{
if (_pageStack.Count <= 0) return;
// Hide current page
UIPage currentPage = _pageStack.Peek();
currentPage.TransitionOut();
if (_pageStack.Count > 0)
{
// Hide current page
UIPage currentPage = _pageStack.Peek();
currentPage.TransitionOut();
}
// Clear stack
_pageStack.Clear();
// Optionally destroy cached instances
if (destroyCachedInstances)
{
foreach (var cachedPage in _prefabInstanceCache.Values)
{
if (cachedPage != null)
{
Destroy(cachedPage.gameObject);
}
}
_prefabInstanceCache.Clear();
Logging.Debug("[UIPageController] Cleared page stack and destroyed cached instances");
}
else
{
Logging.Debug("[UIPageController] Cleared page stack (cached instances retained)");
}
OnPageChanged?.Invoke(null);
Logging.Debug("[UIPageController] Cleared page stack");
}
/// <summary>

View File

@@ -0,0 +1,69 @@
using UI.Core;
using UnityEngine;
using UnityEngine.UI;
namespace UI
{
/// <summary>
/// HUD button that opens a UI page from a prefab when clicked.
/// Implements ITouchInputConsumer as a dummy (no touch handling).
/// </summary>
[RequireComponent(typeof(Button))]
public class HudMenuButton : MonoBehaviour, ITouchInputConsumer
{
[Header("Page Configuration")]
[SerializeField] private GameObject pagePrefab;
[Tooltip("Optional: Custom name for debug logging")]
[SerializeField] private string buttonName = "HudButton";
private Button _button;
private void Awake()
{
_button = GetComponent<Button>();
if (_button != null)
{
_button.onClick.AddListener(OnButtonClicked);
}
else
{
Debug.LogError($"[HudMenuButton] {buttonName} missing Button component!");
}
}
private void OnDestroy()
{
if (_button != null)
{
_button.onClick.RemoveListener(OnButtonClicked);
}
}
private void OnButtonClicked()
{
if (pagePrefab == null)
{
Debug.LogError($"[HudMenuButton] {buttonName} has no page prefab assigned!");
return;
}
if (PlayerHudManager.Instance == null)
{
Debug.LogError($"[HudMenuButton] {buttonName} cannot find PlayerHudManager instance!");
return;
}
Debug.Log($"[HudMenuButton] {buttonName} opening page from prefab: {pagePrefab.name}");
UIPageController.Instance.PushPageFromPrefab(pagePrefab);
}
#region ITouchInputConsumer - Dummy Implementation
// Required by interface but not used
public void OnTap(Vector2 position) { }
public void OnHoldStart(Vector2 position) { }
public void OnHoldMove(Vector2 position) { }
public void OnHoldEnd(Vector2 position) { }
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e29407f14afc4428a48d3d1b4245b210
timeCreated: 1762686499

View File

@@ -1,154 +1,333 @@
using Bootstrap;
using Core;
using Core.Lifecycle;
using UI.Core;
using Cinematics;
using UnityEngine;
using Core;
using System;
using UnityEngine.UI;
using UnityEngine.Playables;
using Core.Lifecycle;
using JetBrains.Annotations;
using UnityEngine.UI;
using System.Linq;
using System.Collections.Generic;
public class PlayerHudManager : ManagedBehaviour
namespace UI
{
public enum UIMode { Overworld, Puzzle, Minigame, HideAll };
public UIMode currentUIMode;
private AppSwitcher _appSwitcher;
public GameObject landscapeObject;
public GameObject portraitObject;
public GameObject cinematicsParentObject;
public GameObject CinematicBackground;
public GameObject appSwitcher;
public GameObject eagleEye;
[HideInInspector] public Image cinematicSprites;
[HideInInspector] public Image cinematicBackgroundSprites;
[HideInInspector] public GameObject currentCinematicPlayer;
[HideInInspector] public PlayableDirector playableDirector;
private static PlayerHudManager _instance;
public static PlayerHudManager Instance => _instance;
private new void Awake()
/// <summary>
/// Manages player HUD elements and their visibility.
/// Works alongside UIPageController (must be on same GameObject).
/// Automatically hides HUD when pages open, shows when all pages close.
/// </summary>
public class PlayerHudManager : ManagedBehaviour
{
base.Awake();
if (Instance != null)
{
Destroy(this);
return;
}
public enum UIMode { Overworld, Puzzle, Minigame, HideAll };
_instance = this;
InitializeReferences();
}
protected override void OnManagedAwake()
{
if (SceneManagerService.Instance != null)
{
SceneManagerService.Instance.SceneLoadCompleted += NewSceneLoaded;
}
if (SceneManagerService.Instance.CurrentGameplayScene == "AppleHillsOverworld")
{
NewSceneLoaded("AppleHillsOverworld");
}
if (SceneManagerService.Instance.CurrentGameplayScene == "StartingScene")
{
// TODO: Hide all UI until cinematics have played
NewSceneLoaded("AppleHillsOverworld");
}
}
private void InitializeReferences()
{
currentCinematicPlayer = landscapeObject;
playableDirector = cinematicsParentObject.GetComponent<PlayableDirector>();
cinematicSprites = currentCinematicPlayer.GetComponent<Image>();
cinematicBackgroundSprites = CinematicBackground.GetComponent<Image>();
}
private void UpateCinematicReferences(GameObject newCinematicPlayer)
{
currentCinematicPlayer = newCinematicPlayer;
cinematicSprites = currentCinematicPlayer.GetComponent<Image>();
}
public void SetPortraitMode(bool portraitModeEnable)
{
if (portraitModeEnable)
{
UpateCinematicReferences(portraitObject);
}
else
{
UpateCinematicReferences(landscapeObject);
}
}
public void NewSceneLoaded(string sceneName)
{
switch (sceneName)
{
case "AppleHillsOverworld":
UpdateUIMode(UIMode.Overworld);
break;
case "Quarry":
UpdateUIMode(UIMode.Puzzle);
break;
case "DivingForPictures":
UpdateUIMode(UIMode.Minigame);
break;
public UIMode currentUIMode;
}
}
[Header("HUD Management")]
[SerializeField] private Transform hudButtonsContainer; // Parent object containing all HUD buttons
[Header("Cinematic References")]
public GameObject landscapeObject;
public GameObject portraitObject;
public GameObject cinematicsParentObject;
public GameObject CinematicBackground;
[Header("HUD Elements")]
public GameObject appSwitcher;
public GameObject eagleEye;
public void UpdateUIMode(UIMode mode)
{
switch (mode)
[HideInInspector] public Image cinematicSprites;
[HideInInspector] public Image cinematicBackgroundSprites;
[HideInInspector] public GameObject currentCinematicPlayer;
[HideInInspector] public PlayableDirector playableDirector;
private static PlayerHudManager _instance;
public static PlayerHudManager Instance => _instance;
private UIPageController _uiPageController;
private AppSwitcher _appSwitcherComponent;
private new void Awake()
{
case UIMode.Overworld:
// Update currentUIMode var
currentUIMode = UIMode.Overworld;
// Show app switcher
appSwitcher.SetActive(true);
// Hide eagle eye
eagleEye.SetActive(false);
break;
case UIMode.Puzzle:
// Update currentUIMode var
currentUIMode = UIMode.Puzzle;
// Hide app switcher
appSwitcher.SetActive(false);
// show eagle eye
eagleEye.SetActive(true);
break;
case UIMode.Minigame:
// Update currentUIMode var
currentUIMode = UIMode.Minigame;
// Hide app switcher
appSwitcher.SetActive(false);
// Hide birds eye
eagleEye.SetActive(false);
break;
base.Awake();
if (Instance != null)
{
Destroy(this);
return;
}
_instance = this;
// Get UIPageController on same GameObject
_uiPageController = GetComponent<UIPageController>();
if (_uiPageController == null)
{
UnityEngine.Debug.LogError("[PlayerHudManager] UIPageController not found on same GameObject!");
}
// Get AppSwitcher component reference
if (appSwitcher != null)
{
_appSwitcherComponent = appSwitcher.GetComponent<AppSwitcher>();
}
InitializeReferences();
}
protected override void OnManagedAwake()
{
// Subscribe to UIPageController page changes for auto HUD management
if (_uiPageController != null)
{
_uiPageController.OnPageChanged += HandlePageStackChanged;
}
// Subscribe to CinematicsManager events for HUD management during cinematics
if (CinematicsManager.Instance != null)
{
CinematicsManager.Instance.OnCinematicStarted += HandleCinematicStarted;
CinematicsManager.Instance.OnCinematicStopped += HandleCinematicStopped;
// Check if a cinematic is already playing
if (CinematicsManager.Instance.IsCinematicPlaying)
{
HideAllHud();
Logging.Debug("[PlayerHudManager] Cinematic already playing on init, hiding HUD");
}
}
if (SceneManagerService.Instance != null)
{
SceneManagerService.Instance.SceneLoadCompleted += NewSceneLoaded;
}
if (SceneManagerService.Instance.CurrentGameplayScene == "AppleHillsOverworld")
{
NewSceneLoaded("AppleHillsOverworld");
}
if (SceneManagerService.Instance.CurrentGameplayScene == "StartingScene")
{
// TODO: Hide all UI until cinematics have played
NewSceneLoaded("AppleHillsOverworld");
}
}
protected override void OnDestroy()
{
base.OnDestroy();
// Unsubscribe from events
if (_uiPageController != null)
{
_uiPageController.OnPageChanged -= HandlePageStackChanged;
}
if (CinematicsManager.Instance != null)
{
CinematicsManager.Instance.OnCinematicStarted -= HandleCinematicStarted;
CinematicsManager.Instance.OnCinematicStopped -= HandleCinematicStopped;
}
if (SceneManagerService.Instance != null)
{
SceneManagerService.Instance.SceneLoadCompleted -= NewSceneLoaded;
}
}
private void InitializeReferences()
{
currentCinematicPlayer = landscapeObject;
playableDirector = cinematicsParentObject.GetComponent<PlayableDirector>();
cinematicSprites = currentCinematicPlayer.GetComponent<Image>();
cinematicBackgroundSprites = CinematicBackground.GetComponent<Image>();
}
private void UpateCinematicReferences(GameObject newCinematicPlayer)
{
currentCinematicPlayer = newCinematicPlayer;
cinematicSprites = currentCinematicPlayer.GetComponent<Image>();
}
public void SetPortraitMode(bool portraitModeEnable)
{
if (portraitModeEnable)
{
UpateCinematicReferences(portraitObject);
}
else
{
UpateCinematicReferences(landscapeObject);
}
}
public void NewSceneLoaded(string sceneName)
{
switch (sceneName)
{
case "AppleHillsOverworld":
UpdateUIMode(UIMode.Overworld);
break;
case "Quarry":
UpdateUIMode(UIMode.Puzzle);
break;
case "DivingForPictures":
UpdateUIMode(UIMode.Minigame);
break;
}
}
public void UpdateUIMode(UIMode mode)
{
switch (mode)
{
case UIMode.Overworld:
// Update currentUIMode var
currentUIMode = UIMode.Overworld;
// Show app switcher
appSwitcher.SetActive(true);
// Hide eagle eye
eagleEye.SetActive(false);
break;
case UIMode.Puzzle:
// Update currentUIMode var
currentUIMode = UIMode.Puzzle;
// Hide app switcher
appSwitcher.SetActive(false);
// show eagle eye
eagleEye.SetActive(true);
break;
case UIMode.Minigame:
// Update currentUIMode var
currentUIMode = UIMode.Minigame;
// Hide app switcher
appSwitcher.SetActive(false);
// Hide birds eye
eagleEye.SetActive(false);
break;
}
}
public void ToggleAppSwitcher(bool boo)
{
appSwitcher.SetActive(boo);
}
/// <summary>
/// Hides all HUD button elements
/// </summary>
public void HideAllHud()
{
SetHudVisibility(false);
}
/// <summary>
/// Shows all HUD button elements
/// </summary>
public void ShowAllHud()
{
SetHudVisibility(true);
}
/// <summary>
/// Hides all HUD elements except the specified exceptions
/// </summary>
/// <param name="exceptions">GameObjects to keep visible</param>
public void HideAllHudExcept(params GameObject[] exceptions)
{
if (hudButtonsContainer == null)
{
Logging.Warning("[PlayerHudManager] HUD buttons container not assigned");
return;
}
HashSet<GameObject> exceptionSet = new HashSet<GameObject>(exceptions);
foreach (Transform child in hudButtonsContainer)
{
bool shouldShow = exceptionSet.Contains(child.gameObject);
child.gameObject.SetActive(shouldShow);
}
Logging.Debug($"[PlayerHudManager] Hidden HUD except {exceptions.Length} exceptions");
}
/// <summary>
/// Common method to set visibility of all HUD elements
/// </summary>
private void SetHudVisibility(bool visible)
{
if (hudButtonsContainer == null)
{
Logging.Warning("[PlayerHudManager] HUD buttons container not assigned");
return;
}
foreach (Transform child in hudButtonsContainer)
{
child.gameObject.SetActive(visible);
}
Logging.Debug($"[PlayerHudManager] {(visible ? "Shown" : "Hidden")} all HUD elements");
}
/// <summary>
/// Automatically manages HUD visibility based on page stack state
/// </summary>
private void HandlePageStackChanged(UIPage currentPage)
{
if (_uiPageController == null) return;
// Use LINQ Count() on IEnumerable<UIPage> PageStack property
int stackCount = _uiPageController.PageStack.Count();
if (stackCount == 1 && currentPage != null)
{
// First page just opened - hide HUD
HideAllHud();
Logging.Debug("[PlayerHudManager] Page opened, hiding HUD");
}
else if (stackCount == 0)
{
// Last page closed - show HUD
ShowAllHud();
Logging.Debug("[PlayerHudManager] All pages closed, showing HUD");
}
// If stackCount > 1, we're navigating between pages, keep HUD hidden
}
/// <summary>
/// Called when a cinematic starts playing
/// </summary>
private void HandleCinematicStarted()
{
HideAllHud();
Logging.Debug("[PlayerHudManager] Cinematic started, hiding HUD");
}
/// <summary>
/// Called when a cinematic stops playing
/// </summary>
private void HandleCinematicStopped()
{
ShowAllHud();
Logging.Debug("[PlayerHudManager] Cinematic stopped, showing HUD");
}
/// <summary>
/// Convenience method to push a page from prefab using the UIPageController
/// </summary>
public void PushPageFromPrefab(GameObject pagePrefab)
{
if (_uiPageController != null)
{
_uiPageController.PushPageFromPrefab(pagePrefab);
}
else
{
UnityEngine.Debug.LogError("[PlayerHudManager] Cannot push page - UIPageController not found!");
}
}
}
public void ToggleAppSwitcher(bool boo)
{
appSwitcher.SetActive(boo);
}
}