Implement Fort Fight minigame (#75)

Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com>
Reviewed-on: #75
This commit is contained in:
2025-12-04 01:18:29 +00:00
parent bb8d600af2
commit e60d516e7e
127 changed files with 21544 additions and 128 deletions

View File

@@ -0,0 +1,252 @@
using Core;
using Core.Lifecycle;
using Minigames.FortFight.Core;
using Minigames.FortFight.Data;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Minigames.FortFight.UI
{
/// <summary>
/// Generic reusable ammunition button that displays projectile data.
/// Shows icon, availability state, cooldown progress, and turns remaining.
/// </summary>
public class AmmoButton : ManagedBehaviour
{
#region Inspector References
[Header("UI Components")]
[Tooltip("Icon image for the projectile")]
[SerializeField] private Image iconImage;
[Tooltip("Background overlay that greys out the entire button when on cooldown")]
[SerializeField] private Image cooldownBackgroundImage;
[Tooltip("Radial fill overlay for cooldown visualization")]
[SerializeField] private Image cooldownFillImage;
[Tooltip("Text displaying turns remaining")]
[SerializeField] private TextMeshProUGUI turnsRemainingText;
[Tooltip("Button component")]
[SerializeField] private Button button;
[Tooltip("Visual indicator for selected state (optional border/glow)")]
[SerializeField] private GameObject selectedIndicator;
#endregion
#region State
private ProjectileConfig projectileConfig;
private AmmunitionManager ammunitionManager;
private SlingshotController slingshotController;
private int playerIndex;
private bool isSelected;
#endregion
#region Initialization
/// <summary>
/// Initialize the button with projectile config and system references.
/// Call this after instantiation to configure the button.
/// </summary>
public void Initialize(ProjectileConfig config, AmmunitionManager ammoManager, SlingshotController slingshot, int playerIdx)
{
projectileConfig = config;
ammunitionManager = ammoManager;
slingshotController = slingshot;
playerIndex = playerIdx;
// Setup UI from projectile config
if (iconImage != null && config.icon != null)
{
iconImage.sprite = config.icon;
}
// Setup cooldown background (hidden by default)
if (cooldownBackgroundImage != null)
{
cooldownBackgroundImage.gameObject.SetActive(false);
}
// Setup cooldown fill (hidden by default)
if (cooldownFillImage != null)
{
cooldownFillImage.fillAmount = 0f;
cooldownFillImage.type = Image.Type.Filled;
cooldownFillImage.fillMethod = Image.FillMethod.Radial360;
cooldownFillImage.fillOrigin = (int)Image.Origin360.Top;
cooldownFillImage.gameObject.SetActive(false);
}
// Setup turns text (hidden by default)
if (turnsRemainingText != null)
{
turnsRemainingText.gameObject.SetActive(false);
}
// Setup button
if (button != null)
{
button.onClick.AddListener(OnButtonClicked);
}
// Subscribe to ammunition events
if (ammunitionManager != null)
{
ammunitionManager.OnAmmoSelected += HandleAmmoSelected;
ammunitionManager.OnAmmoCooldownStarted += HandleCooldownStarted;
ammunitionManager.OnAmmoCooldownCompleted += HandleCooldownCompleted;
}
// Initial update
UpdateVisuals();
}
internal override void OnManagedDestroy()
{
base.OnManagedDestroy();
// Unsubscribe from events
if (ammunitionManager != null)
{
ammunitionManager.OnAmmoSelected -= HandleAmmoSelected;
ammunitionManager.OnAmmoCooldownStarted -= HandleCooldownStarted;
ammunitionManager.OnAmmoCooldownCompleted -= HandleCooldownCompleted;
}
// Remove button listener
if (button != null)
{
button.onClick.RemoveListener(OnButtonClicked);
}
}
#endregion
#region Update
private void Update()
{
// Update visuals every frame
UpdateVisuals();
}
/// <summary>
/// Update all visual elements based on current state
/// </summary>
private void UpdateVisuals()
{
if (projectileConfig == null || ammunitionManager == null) return;
// Get current cooldown state for this player
int turnsRemaining = ammunitionManager.GetCooldownRemaining(projectileConfig.projectileType, playerIndex);
bool isAvailable = ammunitionManager.IsAmmoAvailable(projectileConfig.projectileType, playerIndex);
bool onCooldown = turnsRemaining > 0;
// Show/hide cooldown background overlay
if (cooldownBackgroundImage != null)
{
cooldownBackgroundImage.gameObject.SetActive(onCooldown);
}
// Update cooldown fill (0 = no fill, 1 = full fill)
if (cooldownFillImage != null)
{
if (onCooldown && projectileConfig.cooldownTurns > 0)
{
float fillAmount = (float)turnsRemaining / projectileConfig.cooldownTurns;
cooldownFillImage.fillAmount = fillAmount;
cooldownFillImage.gameObject.SetActive(true);
}
else
{
cooldownFillImage.gameObject.SetActive(false);
}
}
// Update turns remaining text
if (turnsRemainingText != null)
{
if (onCooldown)
{
turnsRemainingText.text = turnsRemaining.ToString();
turnsRemainingText.gameObject.SetActive(true);
}
else
{
turnsRemainingText.gameObject.SetActive(false);
}
}
// Update button interactability
if (button != null)
{
button.interactable = isAvailable;
}
// Update selected indicator
if (selectedIndicator != null)
{
selectedIndicator.SetActive(isSelected);
}
}
#endregion
#region Button Click
/// <summary>
/// Called when button is clicked - selects this ammo type
/// </summary>
private void OnButtonClicked()
{
if (projectileConfig == null || ammunitionManager == null) return;
// Try to select this ammo type for this player
bool selected = ammunitionManager.SelectAmmo(projectileConfig.projectileType, playerIndex);
if (selected && slingshotController != null)
{
// Update slingshot with new ammo config
slingshotController.SetAmmo(projectileConfig);
Logging.Debug($"[AmmoButton] Player {playerIndex} selected {projectileConfig.displayName}");
}
}
#endregion
#region Event Handlers
private void HandleAmmoSelected(ProjectileType selectedType, int selectedPlayerIndex)
{
// Only update if this event is for our player
if (selectedPlayerIndex != playerIndex)
return;
// Update selected state - check if this is our player's current selection
isSelected = (selectedType == projectileConfig.projectileType);
}
private void HandleCooldownStarted(ProjectileType type, int cooldownTurns)
{
// Visual update handled in UpdateVisuals()
}
private void HandleCooldownCompleted(ProjectileType type)
{
// Visual update handled in UpdateVisuals()
if (type == projectileConfig.projectileType)
{
Logging.Debug($"[AmmoButton] {projectileConfig.displayName} ready!");
}
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d18726bad651464fbc4e49f8c95c0c37
timeCreated: 1764770308

View File

@@ -0,0 +1,157 @@
using Core;
using Core.Lifecycle;
using Minigames.FortFight.Core;
using Minigames.FortFight.Data;
using UnityEngine;
namespace Minigames.FortFight.UI
{
/// <summary>
/// Manages ammunition UI panel for a specific player.
/// Shows/hides based on turn state and initializes buttons with player context.
/// </summary>
public class AmmunitionPanel : ManagedBehaviour
{
#region Inspector References
[Header("Player Configuration")]
[Tooltip("Which player this panel belongs to (0 = Player 1, 1 = Player 2)")]
[SerializeField] private int playerIndex = 0;
[Header("References")]
[Tooltip("This player's slingshot controller")]
[SerializeField] private SlingshotController slingshotController;
// Note: AmmunitionManager and TurnManager accessed via singletons
[Header("UI")]
[Tooltip("Array of ammo buttons in this panel")]
[SerializeField] private AmmoButton[] ammoButtons;
[Tooltip("Root GameObject to show/hide entire panel")]
[SerializeField] private GameObject panelRoot;
#endregion
#region Initialization
internal override void OnManagedAwake()
{
base.OnManagedAwake();
// Validate references
if (slingshotController == null)
{
Logging.Error($"[AmmunitionPanel] Player {playerIndex}: Slingshot controller not assigned!");
}
if (ammoButtons == null || ammoButtons.Length == 0)
{
Logging.Warning($"[AmmunitionPanel] Player {playerIndex}: No ammo buttons assigned!");
}
// Use assigned panelRoot or fall back to this GameObject
if (panelRoot == null)
{
panelRoot = gameObject;
}
}
internal override void OnManagedStart()
{
base.OnManagedStart();
// Initialize ammo buttons with player context
InitializeButtons();
// Subscribe to turn events via singleton
if (TurnManager.Instance != null)
{
TurnManager.Instance.OnTurnStarted += HandleTurnStarted;
}
// Start hidden
SetPanelVisibility(false);
}
internal override void OnManagedDestroy()
{
base.OnManagedDestroy();
// Unsubscribe from events
if (TurnManager.Instance != null)
{
TurnManager.Instance.OnTurnStarted -= HandleTurnStarted;
}
}
/// <summary>
/// Initialize all ammo buttons with player-specific configuration
/// </summary>
private void InitializeButtons()
{
if (AmmunitionManager.Instance == null || slingshotController == null || ammoButtons == null)
{
return;
}
// Get available projectile types from settings
var availableTypes = AmmunitionManager.Instance.GetAvailableProjectileTypes();
var settings = GameManager.GetSettingsObject<AppleHills.Core.Settings.IFortFightSettings>();
if (settings == null)
{
Logging.Error($"[AmmunitionPanel] Player {playerIndex}: Could not get FortFightSettings!");
return;
}
for (int i = 0; i < ammoButtons.Length && i < availableTypes.Count; i++)
{
if (ammoButtons[i] != null)
{
var config = settings.GetProjectileConfig(availableTypes[i]);
if (config != null)
{
ammoButtons[i].Initialize(config, AmmunitionManager.Instance, slingshotController, playerIndex);
Logging.Debug($"[AmmunitionPanel] Player {playerIndex}: Initialized button for {config.displayName}");
}
}
}
}
#endregion
#region Turn Events
/// <summary>
/// Called when any player's turn starts - show/hide panel accordingly
/// </summary>
private void HandleTurnStarted(PlayerData player, TurnState turnState)
{
// Only show panel when it's this player's turn (not during transitions)
bool shouldShow = player.PlayerIndex == playerIndex &&
(turnState == TurnState.PlayerOneTurn || turnState == TurnState.PlayerTwoTurn);
SetPanelVisibility(shouldShow);
if (shouldShow)
{
Logging.Debug($"[AmmunitionPanel] Showing panel for Player {playerIndex}");
}
}
/// <summary>
/// Show or hide the entire panel
/// </summary>
private void SetPanelVisibility(bool visible)
{
if (panelRoot != null)
{
panelRoot.SetActive(visible);
}
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1963617e4d104c199c3a66d671b8d8a2
timeCreated: 1764771029

View File

@@ -0,0 +1,263 @@
using Core;
using Core.Lifecycle;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using Minigames.FortFight.Fort;
namespace Minigames.FortFight.UI
{
/// <summary>
/// Displays HP bar and percentage for a fort
/// </summary>
public class FortHealthUI : ManagedBehaviour
{
#region Inspector Properties
[Header("UI References")]
[SerializeField] private Slider hpSlider;
[SerializeField] private TextMeshProUGUI hpPercentageText;
[SerializeField] private TextMeshProUGUI fortNameText;
[Header("Visual Feedback")]
[SerializeField] private Image fillImage;
[SerializeField] private Color healthyColor = Color.green;
[SerializeField] private Color damagedColor = Color.yellow;
[SerializeField] private Color criticalColor = Color.red;
[Header("Auto-Binding (for Dynamic Spawning)")]
[SerializeField] private bool autoBindToFort = true;
[SerializeField] private bool isPlayerFort = true;
[Tooltip("Leave empty to auto-find")]
[SerializeField] private Core.FortManager fortManager;
[Header("Debug Display")]
[Tooltip("Show numerical HP values (current/max)")]
[SerializeField] private bool debugDisplay = false;
[Tooltip("Text field to display 'current/max' HP values")]
[SerializeField] private TextMeshProUGUI debugHpText;
#endregion
#region Private State
private FortController trackedFort;
#endregion
#region Lifecycle
internal override void OnManagedStart()
{
base.OnManagedStart();
Logging.Debug($"[FortHealthUI] OnManagedStart - autoBindToFort: {autoBindToFort}, isPlayerFort: {isPlayerFort}");
// Auto-bind to dynamically spawned forts
if (autoBindToFort)
{
SetupAutoBinding();
}
else
{
Logging.Warning($"[FortHealthUI] Auto-bind disabled! HP UI will not update.");
}
}
internal override void OnManagedDestroy()
{
base.OnManagedDestroy();
// Unsubscribe from fort events
if (trackedFort != null)
{
trackedFort.OnFortDamaged -= OnFortDamaged;
}
// Unsubscribe from fort manager
if (fortManager != null)
{
fortManager.OnPlayerFortSpawned -= OnFortSpawned;
fortManager.OnEnemyFortSpawned -= OnFortSpawned;
}
}
#endregion
#region Auto-Binding
private void SetupAutoBinding()
{
Logging.Debug($"[FortHealthUI] SetupAutoBinding called for {(isPlayerFort ? "PLAYER" : "ENEMY")} fort");
// Get FortManager via singleton
fortManager = Core.FortManager.Instance;
if (fortManager == null)
{
Logging.Error($"[FortHealthUI] CRITICAL: FortManager.Instance is NULL! HP UI will not work.");
return;
}
Logging.Debug($"[FortHealthUI] FortManager found. Checking if fort already spawned...");
// Check if fort already spawned (missed the spawn event)
FortController existingFort = isPlayerFort ? fortManager.PlayerFort : fortManager.EnemyFort;
if (existingFort != null)
{
Logging.Debug($"[FortHealthUI] Fort already exists, binding immediately: {existingFort.FortName}");
BindToFort(existingFort);
return;
}
Logging.Debug($"[FortHealthUI] Fort not spawned yet. Subscribing to spawn events...");
// Subscribe to appropriate spawn event for future spawn
if (isPlayerFort)
{
fortManager.OnPlayerFortSpawned += OnFortSpawned;
Logging.Debug($"[FortHealthUI] ✅ Subscribed to OnPlayerFortSpawned event");
}
else
{
fortManager.OnEnemyFortSpawned += OnFortSpawned;
Logging.Debug($"[FortHealthUI] ✅ Subscribed to OnEnemyFortSpawned event");
}
}
private void OnFortSpawned(FortController fort)
{
Logging.Debug($"[FortHealthUI] 🎯 OnFortSpawned event received! Fort: {fort?.FortName ?? "NULL"}");
if (fort == null)
{
Logging.Error($"[FortHealthUI] Fort is NULL in spawn callback!");
return;
}
BindToFort(fort);
}
#endregion
#region Setup
/// <summary>
/// Bind this UI to a specific fort
/// </summary>
public void BindToFort(FortController fort)
{
if (fort == null)
{
Logging.Warning("[FortHealthUI] Cannot bind to null fort!");
return;
}
// Unsubscribe from previous fort
if (trackedFort != null)
{
trackedFort.OnFortDamaged -= OnFortDamaged;
}
trackedFort = fort;
// Subscribe to fort events
trackedFort.OnFortDamaged += OnFortDamaged;
// Initialize UI
if (fortNameText != null)
{
fortNameText.text = fort.FortName;
}
UpdateDisplay();
Logging.Debug($"[FortHealthUI] Bound to fort: {fort.FortName}. Event subscription successful.");
}
#endregion
#region Event Handlers
private void OnFortDamaged(float damage, float hpPercentage)
{
Logging.Debug($"[FortHealthUI] OnFortDamaged received! Damage: {damage}, HP%: {hpPercentage:F1}%, Fort: {trackedFort?.FortName}");
UpdateDisplay();
}
#endregion
#region Display Update
private void UpdateDisplay()
{
if (trackedFort == null)
{
Logging.Warning("[FortHealthUI] UpdateDisplay called but trackedFort is null!");
return;
}
float hpPercent = trackedFort.HpPercentage;
Logging.Debug($"[FortHealthUI] UpdateDisplay - Fort: {trackedFort.FortName}, HP: {hpPercent:F1}%");
// Update slider
if (hpSlider != null)
{
hpSlider.value = hpPercent / 100f;
Logging.Debug($"[FortHealthUI] Slider updated to {hpSlider.value:F2}");
}
else
{
Logging.Warning("[FortHealthUI] hpSlider is null!");
}
// Update text
if (hpPercentageText != null)
{
hpPercentageText.text = $"{hpPercent:F0}%";
Logging.Debug($"[FortHealthUI] Text updated to {hpPercentageText.text}");
}
else
{
Logging.Warning("[FortHealthUI] hpPercentageText is null!");
}
// Update debug HP display (current/max)
if (debugHpText != null)
{
if (debugDisplay)
{
debugHpText.gameObject.SetActive(true);
float currentHp = trackedFort.CurrentHp;
float maxHp = trackedFort.MaxHp;
debugHpText.text = $"{currentHp:F0}/{maxHp:F0}";
}
else
{
debugHpText.gameObject.SetActive(false);
}
}
// Update color based on HP
if (fillImage != null)
{
if (hpPercent > 60f)
{
fillImage.color = healthyColor;
}
else if (hpPercent > 30f)
{
fillImage.color = damagedColor;
}
else
{
fillImage.color = criticalColor;
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ed9e5253aa2a40c9a9028767466456b1
timeCreated: 1764592117

View File

@@ -0,0 +1,234 @@
using Core;
using Core.Lifecycle;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Minigames.FortFight.UI
{
/// <summary>
/// Game Over UI - displays when a fort is defeated.
/// Shows match time, winner, and restart button.
/// </summary>
public class GameOverUI : ManagedBehaviour
{
#region Inspector Properties
[Header("UI References")]
[Tooltip("Root GameObject to show/hide the entire UI")]
[SerializeField] private GameObject rootPanel;
[Tooltip("Text showing elapsed time")]
[SerializeField] private TextMeshProUGUI elapsedTimeText;
[Tooltip("Text showing winner")]
[SerializeField] private TextMeshProUGUI winnerText;
[Tooltip("Restart button")]
[SerializeField] private Button restartButton;
[Header("Optional Visuals")]
[Tooltip("Optional canvas group for fade-in")]
[SerializeField] private CanvasGroup canvasGroup;
#endregion
#region Lifecycle
internal override void OnManagedAwake()
{
base.OnManagedAwake();
// Validate references
if (rootPanel == null)
{
Logging.Error("[GameOverUI] Root panel not assigned!");
}
if (elapsedTimeText == null)
{
Logging.Warning("[GameOverUI] Elapsed time text not assigned!");
}
if (winnerText == null)
{
Logging.Warning("[GameOverUI] Winner text not assigned!");
}
if (restartButton == null)
{
Logging.Error("[GameOverUI] Restart button not assigned!");
}
// Setup button listener
if (restartButton != null)
{
restartButton.onClick.AddListener(OnRestartClicked);
}
// Ensure canvas group exists for fade
if (canvasGroup == null && rootPanel != null)
{
canvasGroup = rootPanel.GetComponent<CanvasGroup>();
}
// Start hidden
Hide();
}
internal override void OnManagedDestroy()
{
base.OnManagedDestroy();
// Remove button listener
if (restartButton != null)
{
restartButton.onClick.RemoveListener(OnRestartClicked);
}
}
#endregion
#region Event Handlers
private void OnRestartClicked()
{
Logging.Debug("[GameOverUI] Restart button clicked, reloading scene...");
RestartGame();
}
#endregion
#region Display
/// <summary>
/// Show the game over UI with match results
/// Called by FortFightGameManager when game ends
/// </summary>
public void Show()
{
if (rootPanel != null)
{
rootPanel.SetActive(true);
}
// Get game manager for elapsed time
var gameManager = Core.FortFightGameManager.Instance;
if (gameManager != null)
{
float elapsedTime = gameManager.ElapsedGameTime;
UpdateElapsedTime(elapsedTime);
// Determine winner
DetermineWinner();
}
// Optional: Fade in
if (canvasGroup != null)
{
canvasGroup.alpha = 0f;
StartCoroutine(FadeIn());
}
Logging.Debug("[GameOverUI] Game over UI shown");
}
/// <summary>
/// Hide the game over UI
/// </summary>
public void Hide()
{
if (rootPanel != null)
{
rootPanel.SetActive(false);
}
}
/// <summary>
/// Update the elapsed time display
/// </summary>
private void UpdateElapsedTime(float seconds)
{
if (elapsedTimeText == null) return;
// Format as MM:SS
int minutes = Mathf.FloorToInt(seconds / 60f);
int secs = Mathf.FloorToInt(seconds % 60f);
elapsedTimeText.text = $"{minutes:00}:{secs:00}";
}
/// <summary>
/// Determine and display the winner
/// </summary>
private void DetermineWinner()
{
if (winnerText == null) return;
var fortManager = Core.FortManager.Instance;
if (fortManager == null) return;
bool playerDefeated = fortManager.PlayerFort?.IsDefeated ?? false;
bool enemyDefeated = fortManager.EnemyFort?.IsDefeated ?? false;
if (playerDefeated && enemyDefeated)
{
winnerText.text = "DRAW!";
}
else if (playerDefeated)
{
winnerText.text = "PLAYER TWO WINS!";
}
else if (enemyDefeated)
{
winnerText.text = "PLAYER ONE WINS!";
}
else
{
winnerText.text = "GAME OVER";
}
}
/// <summary>
/// Fade in the UI over time
/// </summary>
private System.Collections.IEnumerator FadeIn()
{
float duration = 0.5f;
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
if (canvasGroup != null)
{
canvasGroup.alpha = Mathf.Lerp(0f, 1f, elapsed / duration);
}
yield return null;
}
if (canvasGroup != null)
{
canvasGroup.alpha = 1f;
}
}
#endregion
#region Restart
/// <summary>
/// Restart the game by reloading the current scene
/// </summary>
private void RestartGame()
{
// Use Unity's SceneManager to reload current scene
string currentScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
Logging.Debug($"[GameOverUI] Reloading scene: {currentScene}");
UnityEngine.SceneManagement.SceneManager.LoadScene(currentScene);
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ffb13ad5109340eba06f9c02082ece94
timeCreated: 1764806274

View File

@@ -0,0 +1,204 @@
using Core;
using UnityEngine;
using TMPro;
using UI.Core;
using Minigames.FortFight.Core;
using Minigames.FortFight.Data;
using Pixelplacement;
namespace Minigames.FortFight.UI
{
/// <summary>
/// Main gameplay UI page for Fort Fight minigame.
/// Displays turn info. Turn actions now handled via slingshot input system.
/// </summary>
public class GameplayPage : UIPage
{
[Header("UI Elements")]
[SerializeField] private TextMeshProUGUI turnIndicatorText;
[SerializeField] private TextMeshProUGUI currentPlayerText;
[Header("Optional Visual Elements")]
[SerializeField] private CanvasGroup canvasGroup;
[SerializeField] private GameObject playerActionPanel;
[SerializeField] private GameObject aiTurnPanel;
private TurnManager turnManager;
#region Initialization
internal override void OnManagedAwake()
{
base.OnManagedAwake();
// Validate references
ValidateReferences();
// Note: takeActionButton is no longer used - turns handled via slingshot input
// Set up canvas group
if (canvasGroup == null)
{
canvasGroup = GetComponent<CanvasGroup>();
}
}
internal override void OnManagedStart()
{
base.OnManagedStart();
// Get turn manager reference via singleton
turnManager = TurnManager.Instance;
if (turnManager != null)
{
turnManager.OnTurnStarted += OnTurnStarted;
turnManager.OnTurnEnded += OnTurnEnded;
}
else
{
Logging.Error("[GameplayPage] TurnManager not found!");
}
}
private void ValidateReferences()
{
if (turnIndicatorText == null)
{
Logging.Warning("[GameplayPage] Turn indicator text not assigned!");
}
if (currentPlayerText == null)
{
Logging.Warning("[GameplayPage] Current player text not assigned!");
}
// Note: takeActionButton and actionButtonText are no longer used
// Turns are now handled automatically via slingshot input system
}
#endregion
#region Turn Events
/// <summary>
/// Called when a new turn starts
/// </summary>
private void OnTurnStarted(PlayerData currentPlayer, TurnState turnState)
{
Logging.Debug($"[GameplayPage] Turn started - Player: {currentPlayer.PlayerName}, State: {turnState}");
UpdateTurnUI(currentPlayer, turnState);
}
/// <summary>
/// Called when the current turn ends
/// </summary>
private void OnTurnEnded(PlayerData player)
{
Logging.Debug($"[GameplayPage] Turn ended for {player.PlayerName}");
}
#endregion
#region UI Updates
/// <summary>
/// Update the UI to reflect current turn state
/// </summary>
private void UpdateTurnUI(PlayerData currentPlayer, TurnState turnState)
{
// Update turn counter
if (turnIndicatorText != null && turnManager != null)
{
turnIndicatorText.text = $"Turn {turnManager.TurnCount + 1}";
}
// Update current player display
if (currentPlayerText != null)
{
currentPlayerText.text = $"{currentPlayer.PlayerName}'s Turn";
}
// Show/hide appropriate panels based on turn state
if (turnState == TurnState.AITurn)
{
// AI turn - hide player controls
if (playerActionPanel != null)
{
playerActionPanel.SetActive(false);
}
if (aiTurnPanel != null)
{
aiTurnPanel.SetActive(true);
}
}
else if (turnState == TurnState.PlayerOneTurn || turnState == TurnState.PlayerTwoTurn)
{
// Player turn - show controls
if (playerActionPanel != null)
{
playerActionPanel.SetActive(true);
}
if (aiTurnPanel != null)
{
aiTurnPanel.SetActive(false);
}
}
}
#endregion
#region Transitions
protected override void DoTransitionIn(System.Action onComplete)
{
// Simple fade in if canvas group is available
if (canvasGroup != null)
{
canvasGroup.alpha = 0f;
Tween.CanvasGroupAlpha(canvasGroup, 1f, transitionDuration, 0f, Tween.EaseOut,
completeCallback: () => onComplete?.Invoke());
}
else
{
onComplete?.Invoke();
}
}
protected override void DoTransitionOut(System.Action onComplete)
{
// Simple fade out if canvas group is available
if (canvasGroup != null)
{
Tween.CanvasGroupAlpha(canvasGroup, 0f, transitionDuration, 0f, Tween.EaseIn,
completeCallback: () => onComplete?.Invoke());
}
else
{
onComplete?.Invoke();
}
}
#endregion
#region Cleanup
internal override void OnManagedDestroy()
{
base.OnManagedDestroy();
// Unsubscribe from turn manager events
if (turnManager != null)
{
turnManager.OnTurnStarted -= OnTurnStarted;
turnManager.OnTurnEnded -= OnTurnEnded;
}
}
#endregion
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e22e2729514a5ee40bb2d4c23fbeb75f

View File

@@ -0,0 +1,150 @@
using Core;
using UnityEngine;
using UnityEngine.UI;
using UI.Core;
using Minigames.FortFight.Core;
using Minigames.FortFight.Data;
using Pixelplacement;
namespace Minigames.FortFight.UI
{
/// <summary>
/// UI page for selecting single-player or two-player mode
/// </summary>
public class ModeSelectionPage : UIPage
{
[Header("Mode Selection Buttons")]
[SerializeField] private Button singlePlayerButton;
[SerializeField] private Button twoPlayerButton;
[Header("Optional Visual Elements")]
[SerializeField] private GameObject titleText;
[SerializeField] private CanvasGroup canvasGroup;
#region Initialization
internal override void OnManagedAwake()
{
base.OnManagedAwake();
// Validate button references
if (singlePlayerButton == null)
{
Logging.Error("[ModeSelectionPage] Single player button not assigned!");
}
else
{
singlePlayerButton.onClick.AddListener(OnSinglePlayerSelected);
}
if (twoPlayerButton == null)
{
Logging.Error("[ModeSelectionPage] Two player button not assigned!");
}
else
{
twoPlayerButton.onClick.AddListener(OnTwoPlayerSelected);
}
// Set up canvas group if available
if (canvasGroup == null)
{
canvasGroup = GetComponent<CanvasGroup>();
}
}
#endregion
#region Button Callbacks
/// <summary>
/// Called when single player button is clicked
/// </summary>
private void OnSinglePlayerSelected()
{
Logging.Debug("[ModeSelectionPage] Single player mode selected");
if (FortFightGameManager.Instance != null)
{
FortFightGameManager.Instance.SelectGameMode(FortFightGameMode.SinglePlayer);
}
else
{
Logging.Error("[ModeSelectionPage] FortFightGameManager instance not found!");
}
}
/// <summary>
/// Called when two player button is clicked
/// </summary>
private void OnTwoPlayerSelected()
{
Logging.Debug("[ModeSelectionPage] Two player mode selected");
if (FortFightGameManager.Instance != null)
{
FortFightGameManager.Instance.SelectGameMode(FortFightGameMode.TwoPlayer);
}
else
{
Logging.Error("[ModeSelectionPage] FortFightGameManager instance not found!");
}
}
#endregion
#region Transitions
protected override void DoTransitionIn(System.Action onComplete)
{
// Simple fade in if canvas group is available
if (canvasGroup != null)
{
canvasGroup.alpha = 0f;
Tween.CanvasGroupAlpha(canvasGroup, 1f, transitionDuration, 0f, Tween.EaseOut,
completeCallback: () => onComplete?.Invoke());
}
else
{
onComplete?.Invoke();
}
}
protected override void DoTransitionOut(System.Action onComplete)
{
// Simple fade out if canvas group is available
if (canvasGroup != null)
{
Tween.CanvasGroupAlpha(canvasGroup, 0f, transitionDuration, 0f, Tween.EaseIn,
completeCallback: () => onComplete?.Invoke());
}
else
{
onComplete?.Invoke();
}
}
#endregion
#region Cleanup
internal override void OnManagedDestroy()
{
base.OnManagedDestroy();
// Unsubscribe from button events
if (singlePlayerButton != null)
{
singlePlayerButton.onClick.RemoveListener(OnSinglePlayerSelected);
}
if (twoPlayerButton != null)
{
twoPlayerButton.onClick.RemoveListener(OnTwoPlayerSelected);
}
}
#endregion
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3c1ab5687204db54eaa4ea7f812b3c06