| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656 |
- using UnityEngine;
- using UnityEngine.UIElements;
- using System.Collections.Generic;
- using System.Linq;
- /// <summary>
- /// Beautiful UI popup for combat event encounters
- /// Shows enemy details and provides options to attack or run away
- /// Uses UXML template for cleaner UI structure
- /// </summary>
- public class CombatEventPopupUXML : MonoBehaviour, IClickBlocker
- {
- [Header("UI References")]
- public UIDocument uiDocument;
- public VisualTreeAsset popupTemplate; // UXML template
- public StyleSheet popupStyleSheet; // USS styles
- [Header("Popup Settings")]
- public float animationDuration = 0.3f;
- public float backgroundBlurIntensity = 0.5f;
- // Events
- public System.Action<bool> OnCombatDecision; // true = attack, false = run away
- // UI Elements (from UXML)
- private VisualElement popupContainer;
- private VisualElement backgroundOverlay;
- private VisualElement popupPanel;
- private Label eventTitleLabel;
- private Label eventDescriptionLabel;
- private VisualElement enemyListContainer;
- private VisualElement enemyItemTemplate;
- private Button attackButton;
- private Button runAwayButton;
- // Current combat data
- private BattleEventData currentBattleData;
- private TravelEventContext currentContext;
- private bool isPopupActive = false;
- void Awake()
- {
- // Find UI Document if not assigned
- if (uiDocument == null)
- uiDocument = GetComponent<UIDocument>();
- if (uiDocument == null)
- {
- Debug.LogError("CombatEventPopupUXML: No UIDocument found! Please assign one.");
- return;
- }
- SetupUI();
- }
- void OnEnable()
- {
- // Register with ClickManager when this component is enabled
- if (ClickManager.Instance != null)
- {
- ClickManager.Instance.RegisterClickBlocker(this);
- }
- }
- void OnDisable()
- {
- // Unregister from ClickManager when this component is disabled
- if (ClickManager.Instance != null)
- {
- ClickManager.Instance.UnregisterClickBlocker(this);
- }
- }
- void OnDestroy()
- {
- // Ensure we're unregistered when destroyed
- if (ClickManager.Instance != null)
- {
- ClickManager.Instance.UnregisterClickBlocker(this);
- }
- }
- void SetupUI()
- {
- var root = uiDocument.rootVisualElement;
- Debug.Log($"🔧 SetupUI: Root element size: {root.resolvedStyle.width}x{root.resolvedStyle.height}");
- // Load and instantiate UXML template
- if (popupTemplate != null)
- {
- // Clone the template
- var template = popupTemplate.Instantiate();
- root.Add(template);
- Debug.Log($"✅ UXML template instantiated and added to root");
- // Apply style sheet
- if (popupStyleSheet != null)
- {
- root.styleSheets.Add(popupStyleSheet);
- Debug.Log($"✅ USS stylesheet applied");
- }
- else
- {
- Debug.LogWarning("⚠️ No USS stylesheet assigned");
- }
- }
- else
- {
- Debug.LogWarning("No UXML template assigned, creating UI programmatically as fallback");
- CreateUIFallback(root);
- }
- // Cache UI element references
- CacheUIElements(root);
- // Setup event handlers
- if (backgroundOverlay != null)
- {
- // Block all mouse events from reaching the map below
- backgroundOverlay.RegisterCallback<ClickEvent>(OnBackgroundClick);
- backgroundOverlay.RegisterCallback<MouseDownEvent>(OnBackgroundMouseDown);
- backgroundOverlay.RegisterCallback<MouseUpEvent>(OnBackgroundMouseUp);
- backgroundOverlay.RegisterCallback<MouseMoveEvent>(OnBackgroundMouseMove);
- backgroundOverlay.RegisterCallback<WheelEvent>(OnBackgroundWheelEvent);
- }
- if (attackButton != null)
- attackButton.clicked += OnAttackClicked;
- if (runAwayButton != null)
- runAwayButton.clicked += OnRunAwayClicked;
- // Initially hide the popup
- HidePopup();
- }
- void CacheUIElements(VisualElement root)
- {
- // Cache references to UI elements by name/class
- popupContainer = root.Q<VisualElement>("combat-event-popup");
- backgroundOverlay = root.Q<VisualElement>("background-overlay");
- popupPanel = root.Q<VisualElement>("popup-panel");
- eventTitleLabel = root.Q<Label>("event-title");
- eventDescriptionLabel = root.Q<Label>("event-description");
- enemyListContainer = root.Q<VisualElement>("enemy-list");
- enemyItemTemplate = root.Q<VisualElement>("enemy-item-template");
- attackButton = root.Q<Button>("attack-button");
- runAwayButton = root.Q<Button>("run-away-button");
- // Debug what we found
- Debug.Log($"🔍 UI Elements found:");
- Debug.Log($" popupContainer: {(popupContainer != null ? "✅" : "❌")}");
- Debug.Log($" backgroundOverlay: {(backgroundOverlay != null ? "✅" : "❌")}");
- Debug.Log($" popupPanel: {(popupPanel != null ? "✅" : "❌")}");
- Debug.Log($" eventTitleLabel: {(eventTitleLabel != null ? "✅" : "❌")}");
- Debug.Log($" attackButton: {(attackButton != null ? "✅" : "❌")}");
- Debug.Log($" runAwayButton: {(runAwayButton != null ? "✅" : "❌")}");
- // Hide the template
- if (enemyItemTemplate != null)
- enemyItemTemplate.style.display = DisplayStyle.None;
- // Debug missing elements
- if (popupContainer == null) Debug.LogError("Could not find 'combat-event-popup' element");
- if (attackButton == null) Debug.LogError("Could not find 'attack-button' element");
- if (runAwayButton == null) Debug.LogError("Could not find 'run-away-button' element");
- }
- /// <summary>
- /// Fallback UI creation if UXML template is not available
- /// </summary>
- void CreateUIFallback(VisualElement root)
- {
- // Create basic popup structure programmatically
- popupContainer = new VisualElement();
- popupContainer.name = "combat-event-popup";
- popupContainer.style.position = Position.Absolute;
- popupContainer.style.top = 0;
- popupContainer.style.left = 0;
- popupContainer.style.right = 0;
- popupContainer.style.bottom = 0;
- popupContainer.style.justifyContent = Justify.Center;
- popupContainer.style.alignItems = Align.Center;
- backgroundOverlay = new VisualElement();
- backgroundOverlay.name = "background-overlay";
- backgroundOverlay.style.position = Position.Absolute;
- backgroundOverlay.style.top = 0;
- backgroundOverlay.style.left = 0;
- backgroundOverlay.style.right = 0;
- backgroundOverlay.style.bottom = 0;
- backgroundOverlay.style.backgroundColor = new Color(0, 0, 0, 0.7f);
- popupPanel = new VisualElement();
- popupPanel.name = "popup-panel";
- popupPanel.style.backgroundColor = new Color(0.15f, 0.15f, 0.2f, 0.95f);
- popupPanel.style.borderTopLeftRadius = 10;
- popupPanel.style.borderTopRightRadius = 10;
- popupPanel.style.borderBottomLeftRadius = 10;
- popupPanel.style.borderBottomRightRadius = 10;
- popupPanel.style.minWidth = 400;
- popupPanel.style.paddingTop = 20;
- popupPanel.style.paddingBottom = 20;
- popupPanel.style.paddingLeft = 20;
- popupPanel.style.paddingRight = 20;
- eventTitleLabel = new Label("*** COMBAT ENCOUNTER! ***");
- eventTitleLabel.name = "event-title";
- eventTitleLabel.style.fontSize = 24;
- eventTitleLabel.style.unityTextAlign = TextAnchor.MiddleCenter;
- eventDescriptionLabel = new Label();
- eventDescriptionLabel.name = "event-description";
- enemyListContainer = new VisualElement();
- enemyListContainer.name = "enemy-list";
- var buttonContainer = new VisualElement();
- buttonContainer.style.flexDirection = FlexDirection.Row;
- buttonContainer.style.justifyContent = Justify.SpaceAround;
- buttonContainer.style.marginTop = 20;
- runAwayButton = new Button();
- runAwayButton.name = "run-away-button";
- runAwayButton.text = "[FLEE] RUN AWAY";
- attackButton = new Button();
- attackButton.name = "attack-button";
- attackButton.text = "[FIGHT] ATTACK!";
- // Assemble the UI
- buttonContainer.Add(runAwayButton);
- buttonContainer.Add(attackButton);
- popupPanel.Add(eventTitleLabel);
- popupPanel.Add(eventDescriptionLabel);
- popupPanel.Add(enemyListContainer);
- popupPanel.Add(buttonContainer);
- popupContainer.Add(backgroundOverlay);
- popupContainer.Add(popupPanel);
- root.Add(popupContainer);
- }
- /// <summary>
- /// Show the combat encounter popup with battle data
- /// </summary>
- public void ShowCombatEncounter(BattleEventData battleData, TravelEventContext context, string description)
- {
- if (isPopupActive)
- {
- Debug.LogWarning("Combat popup is already active!");
- return;
- }
- currentBattleData = battleData;
- currentContext = context;
- isPopupActive = true;
- Debug.Log($"🎭 Showing combat popup with {battleData.enemyCount} {battleData.enemyType}(s)");
- // Update UI content
- UpdatePopupContent(battleData, description);
- // Show popup with animation
- ShowPopup();
- }
- void UpdatePopupContent(BattleEventData battleData, string description)
- {
- // Update description
- if (eventDescriptionLabel != null)
- eventDescriptionLabel.text = description;
- // Clear existing enemy items
- if (enemyListContainer != null)
- {
- var existingItems = enemyListContainer.Children().Where(child =>
- child.name != "enemy-item-template").ToList();
- foreach (var item in existingItems)
- {
- enemyListContainer.Remove(item);
- }
- // Create enemy display items
- CreateEnemyDisplays(battleData);
- }
- }
- void CreateEnemyDisplays(BattleEventData battleData)
- {
- if (battleData.enemyCharacterData == null)
- {
- Debug.LogWarning("No enemy character data available for display");
- return;
- }
- var enemyData = battleData.enemyCharacterData;
- // Create enemy item element
- VisualElement enemyItem;
- if (enemyItemTemplate != null)
- {
- // Clone the template by creating a new instance from the UXML
- enemyItem = CreateEnemyItemFromTemplate();
- }
- else
- {
- // Create manually if no template
- enemyItem = CreateEnemyItemFallback();
- }
- // Update enemy information
- var nameLabel = enemyItem.Q<Label>("enemy-name");
- var statsLabel = enemyItem.Q<Label>("enemy-stats");
- var weaponLabel = enemyItem.Q<Label>("enemy-weapon");
- var threatIndicator = enemyItem.Q<VisualElement>("threat-indicator");
- var threatLabel = enemyItem.Q<Label>("threat-label");
- if (nameLabel != null)
- nameLabel.text = $"{battleData.enemyCount}x {enemyData.enemyName}";
- if (statsLabel != null)
- statsLabel.text = $"HP: {enemyData.maxHealth} | AC: {enemyData.armorClass} | Threat: {enemyData.threatLevel}";
- if (weaponLabel != null && enemyData.preferredWeapon != null)
- weaponLabel.text = $"[WEAPON] {enemyData.preferredWeapon.itemName}";
- // Update threat indicator
- if (threatIndicator != null && threatLabel != null)
- {
- threatLabel.text = $"T{enemyData.threatLevel}";
- // Remove existing threat classes
- threatIndicator.RemoveFromClassList("threat-low");
- threatIndicator.RemoveFromClassList("threat-medium");
- threatIndicator.RemoveFromClassList("threat-high");
- threatIndicator.RemoveFromClassList("threat-extreme");
- // Add appropriate threat class
- if (enemyData.threatLevel <= 2)
- threatIndicator.AddToClassList("threat-low");
- else if (enemyData.threatLevel <= 4)
- threatIndicator.AddToClassList("threat-medium");
- else if (enemyData.threatLevel <= 6)
- threatIndicator.AddToClassList("threat-high");
- else
- threatIndicator.AddToClassList("threat-extreme");
- }
- // Add hover effect
- enemyItem.RegisterCallback<MouseEnterEvent>(evt =>
- {
- enemyItem.AddToClassList("enemy-item-hover");
- });
- enemyItem.RegisterCallback<MouseLeaveEvent>(evt =>
- {
- enemyItem.RemoveFromClassList("enemy-item-hover");
- });
- enemyListContainer.Add(enemyItem);
- }
- VisualElement CreateEnemyItemFromTemplate()
- {
- // Create a new enemy item based on the template structure
- var item = new VisualElement();
- item.style.flexDirection = FlexDirection.Row;
- item.style.justifyContent = Justify.SpaceBetween;
- item.style.alignItems = Align.Center;
- item.style.marginBottom = 8;
- item.style.paddingTop = 8;
- item.style.paddingBottom = 8;
- item.style.paddingLeft = 10;
- item.style.paddingRight = 10;
- item.style.backgroundColor = new Color(0.2f, 0.2f, 0.25f, 0.6f);
- item.style.borderTopLeftRadius = 4;
- item.style.borderTopRightRadius = 4;
- item.style.borderBottomLeftRadius = 4;
- item.style.borderBottomRightRadius = 4;
- item.AddToClassList("enemy-item");
- // Enemy info container
- var infoContainer = new VisualElement();
- infoContainer.name = "enemy-info";
- infoContainer.AddToClassList("enemy-info");
- infoContainer.style.flexDirection = FlexDirection.Column;
- infoContainer.style.flexGrow = 1;
- // Enemy name label
- var nameLabel = new Label();
- nameLabel.name = "enemy-name";
- nameLabel.AddToClassList("enemy-name");
- nameLabel.style.fontSize = 16;
- nameLabel.style.color = new Color(1f, 0.9f, 0.7f, 1f);
- // Enemy stats label
- var statsLabel = new Label();
- statsLabel.name = "enemy-stats";
- statsLabel.AddToClassList("enemy-stats");
- statsLabel.style.fontSize = 12;
- statsLabel.style.color = new Color(0.8f, 0.8f, 0.8f, 1f);
- // Enemy weapon label
- var weaponLabel = new Label();
- weaponLabel.name = "enemy-weapon";
- weaponLabel.AddToClassList("enemy-weapon");
- weaponLabel.style.fontSize = 11;
- weaponLabel.style.color = new Color(0.7f, 0.7f, 1f, 1f);
- // Threat indicator
- var threatIndicator = new VisualElement();
- threatIndicator.name = "threat-indicator";
- threatIndicator.AddToClassList("threat-indicator");
- threatIndicator.style.width = 60;
- threatIndicator.style.height = 20;
- threatIndicator.style.borderTopLeftRadius = 10;
- threatIndicator.style.borderTopRightRadius = 10;
- threatIndicator.style.borderBottomLeftRadius = 10;
- threatIndicator.style.borderBottomRightRadius = 10;
- threatIndicator.style.justifyContent = Justify.Center;
- threatIndicator.style.alignItems = Align.Center;
- threatIndicator.style.backgroundColor = new Color(0.3f, 0.8f, 0.3f, 1f);
- // Threat label
- var threatLabel = new Label();
- threatLabel.name = "threat-label";
- threatLabel.AddToClassList("threat-label");
- threatLabel.style.fontSize = 11;
- threatLabel.style.color = Color.white;
- // Assemble the structure
- threatIndicator.Add(threatLabel);
- infoContainer.Add(nameLabel);
- infoContainer.Add(statsLabel);
- infoContainer.Add(weaponLabel);
- item.Add(infoContainer);
- item.Add(threatIndicator);
- return item;
- }
- VisualElement CreateEnemyItemFallback()
- {
- var item = new VisualElement();
- item.style.flexDirection = FlexDirection.Row;
- item.style.justifyContent = Justify.SpaceBetween;
- item.style.alignItems = Align.Center;
- item.style.marginBottom = 8;
- item.style.paddingTop = 8;
- item.style.paddingBottom = 8;
- item.style.paddingLeft = 10;
- item.style.paddingRight = 10;
- item.style.backgroundColor = new Color(0.2f, 0.2f, 0.25f, 0.6f);
- item.style.borderTopLeftRadius = 4;
- item.style.borderTopRightRadius = 4;
- item.style.borderBottomLeftRadius = 4;
- item.style.borderBottomRightRadius = 4;
- var infoContainer = new VisualElement();
- infoContainer.name = "enemy-info";
- infoContainer.style.flexDirection = FlexDirection.Column;
- infoContainer.style.flexGrow = 1;
- var nameLabel = new Label();
- nameLabel.name = "enemy-name";
- nameLabel.style.fontSize = 16;
- nameLabel.style.color = new Color(1f, 0.9f, 0.7f, 1f);
- var statsLabel = new Label();
- statsLabel.name = "enemy-stats";
- statsLabel.style.fontSize = 12;
- statsLabel.style.color = new Color(0.8f, 0.8f, 0.8f, 1f);
- var weaponLabel = new Label();
- weaponLabel.name = "enemy-weapon";
- weaponLabel.style.fontSize = 11;
- weaponLabel.style.color = new Color(0.7f, 0.7f, 1f, 1f);
- var threatIndicator = new VisualElement();
- threatIndicator.name = "threat-indicator";
- threatIndicator.style.width = 60;
- threatIndicator.style.height = 20;
- threatIndicator.style.borderTopLeftRadius = 10;
- threatIndicator.style.borderTopRightRadius = 10;
- threatIndicator.style.borderBottomLeftRadius = 10;
- threatIndicator.style.borderBottomRightRadius = 10;
- threatIndicator.style.justifyContent = Justify.Center;
- threatIndicator.style.alignItems = Align.Center;
- threatIndicator.style.backgroundColor = new Color(0.3f, 0.8f, 0.3f, 1f);
- var threatLabel = new Label();
- threatLabel.name = "threat-label";
- threatLabel.style.fontSize = 11;
- threatLabel.style.color = Color.white;
- threatIndicator.Add(threatLabel);
- infoContainer.Add(nameLabel);
- infoContainer.Add(statsLabel);
- infoContainer.Add(weaponLabel);
- item.Add(infoContainer);
- item.Add(threatIndicator);
- return item;
- }
- void ShowPopup()
- {
- if (popupContainer != null)
- {
- Debug.Log($"🎭 ShowPopup: Setting display to Flex, current display: {popupContainer.style.display}");
- popupContainer.style.display = DisplayStyle.Flex;
- // Add animation classes
- popupContainer.RemoveFromClassList("popup-hidden");
- popupContainer.RemoveFromClassList("popup-exit");
- popupContainer.AddToClassList("popup-enter");
- // Check if UI Document is active and panel settings are configured
- if (uiDocument != null && uiDocument.panelSettings != null)
- {
- Debug.Log($"✅ UIDocument active: {uiDocument.enabled}, Panel Settings: {uiDocument.panelSettings.name}");
- }
- else
- {
- Debug.LogError("❌ UIDocument or Panel Settings not configured properly!");
- }
- Debug.Log("✅ Combat popup displayed");
- }
- else
- {
- Debug.LogError("❌ Cannot show popup - popupContainer is null!");
- }
- }
- public void HidePopup()
- {
- if (popupContainer != null)
- {
- // Add exit animation
- popupContainer.RemoveFromClassList("popup-enter");
- popupContainer.AddToClassList("popup-exit");
- // Hide after animation
- this.ExecuteAfterDelay(() =>
- {
- popupContainer.style.display = DisplayStyle.None;
- popupContainer.AddToClassList("popup-hidden");
- }, animationDuration);
- isPopupActive = false;
- Debug.Log("✅ Combat popup hidden");
- }
- }
- void OnBackgroundClick(ClickEvent evt)
- {
- // Block the click event from reaching elements below
- evt.StopPropagation();
- // Optionally allow closing popup by clicking background
- // Uncomment if you want this behavior:
- // OnRunAwayClicked();
- }
- void OnBackgroundMouseDown(MouseDownEvent evt)
- {
- // Block mouse down events from reaching the map below
- evt.StopPropagation();
- }
- void OnBackgroundMouseUp(MouseUpEvent evt)
- {
- // Block mouse up events from reaching the map below
- evt.StopPropagation();
- }
- void OnBackgroundMouseMove(MouseMoveEvent evt)
- {
- // Block mouse move events from reaching the map below
- evt.StopPropagation();
- }
- void OnBackgroundWheelEvent(WheelEvent evt)
- {
- // Block wheel/scroll events from reaching the map below
- evt.StopPropagation();
- }
- void OnAttackClicked()
- {
- Debug.Log("🗡️ Player chose to ATTACK!");
- HidePopup();
- OnCombatDecision?.Invoke(true);
- }
- void OnRunAwayClicked()
- {
- Debug.Log("🏃 Player chose to RUN AWAY!");
- HidePopup();
- OnCombatDecision?.Invoke(false);
- }
- /// <summary>
- /// IClickBlocker implementation: Check if this UI should block clicks at the given position
- /// </summary>
- public bool IsBlockingClick(Vector2 screenPosition)
- {
- if (!isPopupActive || popupContainer == null || uiDocument == null)
- {
- return false;
- }
- // Convert screen position to panel-relative position
- Vector2 panelPosition = RuntimePanelUtils.ScreenToPanel(uiDocument.rootVisualElement.panel, screenPosition);
- // Check if the position is within the popup container bounds
- bool withinBounds = popupContainer.worldBound.Contains(panelPosition);
- return withinBounds;
- }
- /// <summary>
- /// Legacy method for backward compatibility - now delegates to IsBlockingClick
- /// Check if a screen position is within the UI bounds to block map interaction
- /// </summary>
- public bool IsPointWithinUI(Vector2 screenPosition)
- {
- return IsBlockingClick(screenPosition);
- }
- /// <summary>
- /// Public getters for external access
- /// </summary>
- public bool IsVisible => isPopupActive;
- }
- /// <summary>
- /// Extension method for delayed execution
- /// </summary>
- public static class MonoBehaviourExtensions
- {
- public static void ExecuteAfterDelay(this MonoBehaviour mono, System.Action action, float delay)
- {
- mono.StartCoroutine(ExecuteAfterDelayCoroutine(action, delay));
- }
- private static System.Collections.IEnumerator ExecuteAfterDelayCoroutine(System.Action action, float delay)
- {
- yield return new WaitForSeconds(delay);
- action?.Invoke();
- }
- }
|