CombatEventPopupUXML.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. using UnityEngine;
  2. using UnityEngine.UIElements;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. /// <summary>
  6. /// Beautiful UI popup for combat event encounters
  7. /// Shows enemy details and provides options to attack or run away
  8. /// Uses UXML template for cleaner UI structure
  9. /// </summary>
  10. public class CombatEventPopupUXML : MonoBehaviour
  11. {
  12. [Header("UI References")]
  13. public UIDocument uiDocument;
  14. public VisualTreeAsset popupTemplate; // UXML template
  15. public StyleSheet popupStyleSheet; // USS styles
  16. [Header("Popup Settings")]
  17. public float animationDuration = 0.3f;
  18. public float backgroundBlurIntensity = 0.5f;
  19. // Events
  20. public System.Action<bool> OnCombatDecision; // true = attack, false = run away
  21. // UI Elements (from UXML)
  22. private VisualElement popupContainer;
  23. private VisualElement backgroundOverlay;
  24. private VisualElement popupPanel;
  25. private Label eventTitleLabel;
  26. private Label eventDescriptionLabel;
  27. private VisualElement enemyListContainer;
  28. private VisualElement enemyItemTemplate;
  29. private Button attackButton;
  30. private Button runAwayButton;
  31. // Current combat data
  32. private BattleEventData currentBattleData;
  33. private TravelEventContext currentContext;
  34. private bool isPopupActive = false;
  35. void Awake()
  36. {
  37. // Find UI Document if not assigned
  38. if (uiDocument == null)
  39. uiDocument = GetComponent<UIDocument>();
  40. if (uiDocument == null)
  41. {
  42. Debug.LogError("CombatEventPopupUXML: No UIDocument found! Please assign one.");
  43. return;
  44. }
  45. SetupUI();
  46. }
  47. void SetupUI()
  48. {
  49. var root = uiDocument.rootVisualElement;
  50. Debug.Log($"🔧 SetupUI: Root element size: {root.resolvedStyle.width}x{root.resolvedStyle.height}");
  51. // Load and instantiate UXML template
  52. if (popupTemplate != null)
  53. {
  54. // Clone the template
  55. var template = popupTemplate.Instantiate();
  56. root.Add(template);
  57. Debug.Log($"✅ UXML template instantiated and added to root");
  58. // Apply style sheet
  59. if (popupStyleSheet != null)
  60. {
  61. root.styleSheets.Add(popupStyleSheet);
  62. Debug.Log($"✅ USS stylesheet applied");
  63. }
  64. else
  65. {
  66. Debug.LogWarning("⚠️ No USS stylesheet assigned");
  67. }
  68. }
  69. else
  70. {
  71. Debug.LogWarning("No UXML template assigned, creating UI programmatically as fallback");
  72. CreateUIFallback(root);
  73. }
  74. // Cache UI element references
  75. CacheUIElements(root);
  76. // Setup event handlers
  77. if (backgroundOverlay != null)
  78. {
  79. // Block all mouse events from reaching the map below
  80. backgroundOverlay.RegisterCallback<ClickEvent>(OnBackgroundClick);
  81. backgroundOverlay.RegisterCallback<MouseDownEvent>(OnBackgroundMouseDown);
  82. backgroundOverlay.RegisterCallback<MouseUpEvent>(OnBackgroundMouseUp);
  83. backgroundOverlay.RegisterCallback<MouseMoveEvent>(OnBackgroundMouseMove);
  84. backgroundOverlay.RegisterCallback<WheelEvent>(OnBackgroundWheelEvent);
  85. }
  86. if (attackButton != null)
  87. attackButton.clicked += OnAttackClicked;
  88. if (runAwayButton != null)
  89. runAwayButton.clicked += OnRunAwayClicked;
  90. // Initially hide the popup
  91. HidePopup();
  92. }
  93. void CacheUIElements(VisualElement root)
  94. {
  95. // Cache references to UI elements by name/class
  96. popupContainer = root.Q<VisualElement>("combat-event-popup");
  97. backgroundOverlay = root.Q<VisualElement>("background-overlay");
  98. popupPanel = root.Q<VisualElement>("popup-panel");
  99. eventTitleLabel = root.Q<Label>("event-title");
  100. eventDescriptionLabel = root.Q<Label>("event-description");
  101. enemyListContainer = root.Q<VisualElement>("enemy-list");
  102. enemyItemTemplate = root.Q<VisualElement>("enemy-item-template");
  103. attackButton = root.Q<Button>("attack-button");
  104. runAwayButton = root.Q<Button>("run-away-button");
  105. // Debug what we found
  106. Debug.Log($"🔍 UI Elements found:");
  107. Debug.Log($" popupContainer: {(popupContainer != null ? "✅" : "❌")}");
  108. Debug.Log($" backgroundOverlay: {(backgroundOverlay != null ? "✅" : "❌")}");
  109. Debug.Log($" popupPanel: {(popupPanel != null ? "✅" : "❌")}");
  110. Debug.Log($" eventTitleLabel: {(eventTitleLabel != null ? "✅" : "❌")}");
  111. Debug.Log($" attackButton: {(attackButton != null ? "✅" : "❌")}");
  112. Debug.Log($" runAwayButton: {(runAwayButton != null ? "✅" : "❌")}");
  113. // Hide the template
  114. if (enemyItemTemplate != null)
  115. enemyItemTemplate.style.display = DisplayStyle.None;
  116. // Debug missing elements
  117. if (popupContainer == null) Debug.LogError("Could not find 'combat-event-popup' element");
  118. if (attackButton == null) Debug.LogError("Could not find 'attack-button' element");
  119. if (runAwayButton == null) Debug.LogError("Could not find 'run-away-button' element");
  120. }
  121. /// <summary>
  122. /// Fallback UI creation if UXML template is not available
  123. /// </summary>
  124. void CreateUIFallback(VisualElement root)
  125. {
  126. // Create basic popup structure programmatically
  127. popupContainer = new VisualElement();
  128. popupContainer.name = "combat-event-popup";
  129. popupContainer.style.position = Position.Absolute;
  130. popupContainer.style.top = 0;
  131. popupContainer.style.left = 0;
  132. popupContainer.style.right = 0;
  133. popupContainer.style.bottom = 0;
  134. popupContainer.style.justifyContent = Justify.Center;
  135. popupContainer.style.alignItems = Align.Center;
  136. backgroundOverlay = new VisualElement();
  137. backgroundOverlay.name = "background-overlay";
  138. backgroundOverlay.style.position = Position.Absolute;
  139. backgroundOverlay.style.top = 0;
  140. backgroundOverlay.style.left = 0;
  141. backgroundOverlay.style.right = 0;
  142. backgroundOverlay.style.bottom = 0;
  143. backgroundOverlay.style.backgroundColor = new Color(0, 0, 0, 0.7f);
  144. popupPanel = new VisualElement();
  145. popupPanel.name = "popup-panel";
  146. popupPanel.style.backgroundColor = new Color(0.15f, 0.15f, 0.2f, 0.95f);
  147. popupPanel.style.borderTopLeftRadius = 10;
  148. popupPanel.style.borderTopRightRadius = 10;
  149. popupPanel.style.borderBottomLeftRadius = 10;
  150. popupPanel.style.borderBottomRightRadius = 10;
  151. popupPanel.style.minWidth = 400;
  152. popupPanel.style.paddingTop = 20;
  153. popupPanel.style.paddingBottom = 20;
  154. popupPanel.style.paddingLeft = 20;
  155. popupPanel.style.paddingRight = 20;
  156. eventTitleLabel = new Label("*** COMBAT ENCOUNTER! ***");
  157. eventTitleLabel.name = "event-title";
  158. eventTitleLabel.style.fontSize = 24;
  159. eventTitleLabel.style.unityTextAlign = TextAnchor.MiddleCenter;
  160. eventDescriptionLabel = new Label();
  161. eventDescriptionLabel.name = "event-description";
  162. enemyListContainer = new VisualElement();
  163. enemyListContainer.name = "enemy-list";
  164. var buttonContainer = new VisualElement();
  165. buttonContainer.style.flexDirection = FlexDirection.Row;
  166. buttonContainer.style.justifyContent = Justify.SpaceAround;
  167. buttonContainer.style.marginTop = 20;
  168. runAwayButton = new Button();
  169. runAwayButton.name = "run-away-button";
  170. runAwayButton.text = "[FLEE] RUN AWAY";
  171. attackButton = new Button();
  172. attackButton.name = "attack-button";
  173. attackButton.text = "[FIGHT] ATTACK!";
  174. // Assemble the UI
  175. buttonContainer.Add(runAwayButton);
  176. buttonContainer.Add(attackButton);
  177. popupPanel.Add(eventTitleLabel);
  178. popupPanel.Add(eventDescriptionLabel);
  179. popupPanel.Add(enemyListContainer);
  180. popupPanel.Add(buttonContainer);
  181. popupContainer.Add(backgroundOverlay);
  182. popupContainer.Add(popupPanel);
  183. root.Add(popupContainer);
  184. }
  185. /// <summary>
  186. /// Show the combat encounter popup with battle data
  187. /// </summary>
  188. public void ShowCombatEncounter(BattleEventData battleData, TravelEventContext context, string description)
  189. {
  190. if (isPopupActive)
  191. {
  192. Debug.LogWarning("Combat popup is already active!");
  193. return;
  194. }
  195. currentBattleData = battleData;
  196. currentContext = context;
  197. isPopupActive = true;
  198. Debug.Log($"🎭 Showing combat popup with {battleData.enemyCount} {battleData.enemyType}(s)");
  199. // Update UI content
  200. UpdatePopupContent(battleData, description);
  201. // Show popup with animation
  202. ShowPopup();
  203. }
  204. void UpdatePopupContent(BattleEventData battleData, string description)
  205. {
  206. // Update description
  207. if (eventDescriptionLabel != null)
  208. eventDescriptionLabel.text = description;
  209. // Clear existing enemy items
  210. if (enemyListContainer != null)
  211. {
  212. var existingItems = enemyListContainer.Children().Where(child =>
  213. child.name != "enemy-item-template").ToList();
  214. foreach (var item in existingItems)
  215. {
  216. enemyListContainer.Remove(item);
  217. }
  218. // Create enemy display items
  219. CreateEnemyDisplays(battleData);
  220. }
  221. }
  222. void CreateEnemyDisplays(BattleEventData battleData)
  223. {
  224. if (battleData.enemyCharacterData == null)
  225. {
  226. Debug.LogWarning("No enemy character data available for display");
  227. return;
  228. }
  229. var enemyData = battleData.enemyCharacterData;
  230. // Create enemy item element
  231. VisualElement enemyItem;
  232. if (enemyItemTemplate != null)
  233. {
  234. // Clone the template by creating a new instance from the UXML
  235. enemyItem = CreateEnemyItemFromTemplate();
  236. }
  237. else
  238. {
  239. // Create manually if no template
  240. enemyItem = CreateEnemyItemFallback();
  241. }
  242. // Update enemy information
  243. var nameLabel = enemyItem.Q<Label>("enemy-name");
  244. var statsLabel = enemyItem.Q<Label>("enemy-stats");
  245. var weaponLabel = enemyItem.Q<Label>("enemy-weapon");
  246. var threatIndicator = enemyItem.Q<VisualElement>("threat-indicator");
  247. var threatLabel = enemyItem.Q<Label>("threat-label");
  248. if (nameLabel != null)
  249. nameLabel.text = $"{battleData.enemyCount}x {enemyData.enemyName}";
  250. if (statsLabel != null)
  251. statsLabel.text = $"HP: {enemyData.maxHealth} | AC: {enemyData.armorClass} | Threat: {enemyData.threatLevel}";
  252. if (weaponLabel != null && enemyData.preferredWeapon != null)
  253. weaponLabel.text = $"[WEAPON] {enemyData.preferredWeapon.itemName}";
  254. // Update threat indicator
  255. if (threatIndicator != null && threatLabel != null)
  256. {
  257. threatLabel.text = $"T{enemyData.threatLevel}";
  258. // Remove existing threat classes
  259. threatIndicator.RemoveFromClassList("threat-low");
  260. threatIndicator.RemoveFromClassList("threat-medium");
  261. threatIndicator.RemoveFromClassList("threat-high");
  262. threatIndicator.RemoveFromClassList("threat-extreme");
  263. // Add appropriate threat class
  264. if (enemyData.threatLevel <= 2)
  265. threatIndicator.AddToClassList("threat-low");
  266. else if (enemyData.threatLevel <= 4)
  267. threatIndicator.AddToClassList("threat-medium");
  268. else if (enemyData.threatLevel <= 6)
  269. threatIndicator.AddToClassList("threat-high");
  270. else
  271. threatIndicator.AddToClassList("threat-extreme");
  272. }
  273. // Add hover effect
  274. enemyItem.RegisterCallback<MouseEnterEvent>(evt =>
  275. {
  276. enemyItem.AddToClassList("enemy-item-hover");
  277. });
  278. enemyItem.RegisterCallback<MouseLeaveEvent>(evt =>
  279. {
  280. enemyItem.RemoveFromClassList("enemy-item-hover");
  281. });
  282. enemyListContainer.Add(enemyItem);
  283. }
  284. VisualElement CreateEnemyItemFromTemplate()
  285. {
  286. // Create a new enemy item based on the template structure
  287. var item = new VisualElement();
  288. item.style.flexDirection = FlexDirection.Row;
  289. item.style.justifyContent = Justify.SpaceBetween;
  290. item.style.alignItems = Align.Center;
  291. item.style.marginBottom = 8;
  292. item.style.paddingTop = 8;
  293. item.style.paddingBottom = 8;
  294. item.style.paddingLeft = 10;
  295. item.style.paddingRight = 10;
  296. item.style.backgroundColor = new Color(0.2f, 0.2f, 0.25f, 0.6f);
  297. item.style.borderTopLeftRadius = 4;
  298. item.style.borderTopRightRadius = 4;
  299. item.style.borderBottomLeftRadius = 4;
  300. item.style.borderBottomRightRadius = 4;
  301. item.AddToClassList("enemy-item");
  302. // Enemy info container
  303. var infoContainer = new VisualElement();
  304. infoContainer.name = "enemy-info";
  305. infoContainer.AddToClassList("enemy-info");
  306. infoContainer.style.flexDirection = FlexDirection.Column;
  307. infoContainer.style.flexGrow = 1;
  308. // Enemy name label
  309. var nameLabel = new Label();
  310. nameLabel.name = "enemy-name";
  311. nameLabel.AddToClassList("enemy-name");
  312. nameLabel.style.fontSize = 16;
  313. nameLabel.style.color = new Color(1f, 0.9f, 0.7f, 1f);
  314. // Enemy stats label
  315. var statsLabel = new Label();
  316. statsLabel.name = "enemy-stats";
  317. statsLabel.AddToClassList("enemy-stats");
  318. statsLabel.style.fontSize = 12;
  319. statsLabel.style.color = new Color(0.8f, 0.8f, 0.8f, 1f);
  320. // Enemy weapon label
  321. var weaponLabel = new Label();
  322. weaponLabel.name = "enemy-weapon";
  323. weaponLabel.AddToClassList("enemy-weapon");
  324. weaponLabel.style.fontSize = 11;
  325. weaponLabel.style.color = new Color(0.7f, 0.7f, 1f, 1f);
  326. // Threat indicator
  327. var threatIndicator = new VisualElement();
  328. threatIndicator.name = "threat-indicator";
  329. threatIndicator.AddToClassList("threat-indicator");
  330. threatIndicator.style.width = 60;
  331. threatIndicator.style.height = 20;
  332. threatIndicator.style.borderTopLeftRadius = 10;
  333. threatIndicator.style.borderTopRightRadius = 10;
  334. threatIndicator.style.borderBottomLeftRadius = 10;
  335. threatIndicator.style.borderBottomRightRadius = 10;
  336. threatIndicator.style.justifyContent = Justify.Center;
  337. threatIndicator.style.alignItems = Align.Center;
  338. threatIndicator.style.backgroundColor = new Color(0.3f, 0.8f, 0.3f, 1f);
  339. // Threat label
  340. var threatLabel = new Label();
  341. threatLabel.name = "threat-label";
  342. threatLabel.AddToClassList("threat-label");
  343. threatLabel.style.fontSize = 11;
  344. threatLabel.style.color = Color.white;
  345. // Assemble the structure
  346. threatIndicator.Add(threatLabel);
  347. infoContainer.Add(nameLabel);
  348. infoContainer.Add(statsLabel);
  349. infoContainer.Add(weaponLabel);
  350. item.Add(infoContainer);
  351. item.Add(threatIndicator);
  352. return item;
  353. }
  354. VisualElement CreateEnemyItemFallback()
  355. {
  356. var item = new VisualElement();
  357. item.style.flexDirection = FlexDirection.Row;
  358. item.style.justifyContent = Justify.SpaceBetween;
  359. item.style.alignItems = Align.Center;
  360. item.style.marginBottom = 8;
  361. item.style.paddingTop = 8;
  362. item.style.paddingBottom = 8;
  363. item.style.paddingLeft = 10;
  364. item.style.paddingRight = 10;
  365. item.style.backgroundColor = new Color(0.2f, 0.2f, 0.25f, 0.6f);
  366. item.style.borderTopLeftRadius = 4;
  367. item.style.borderTopRightRadius = 4;
  368. item.style.borderBottomLeftRadius = 4;
  369. item.style.borderBottomRightRadius = 4;
  370. var infoContainer = new VisualElement();
  371. infoContainer.name = "enemy-info";
  372. infoContainer.style.flexDirection = FlexDirection.Column;
  373. infoContainer.style.flexGrow = 1;
  374. var nameLabel = new Label();
  375. nameLabel.name = "enemy-name";
  376. nameLabel.style.fontSize = 16;
  377. nameLabel.style.color = new Color(1f, 0.9f, 0.7f, 1f);
  378. var statsLabel = new Label();
  379. statsLabel.name = "enemy-stats";
  380. statsLabel.style.fontSize = 12;
  381. statsLabel.style.color = new Color(0.8f, 0.8f, 0.8f, 1f);
  382. var weaponLabel = new Label();
  383. weaponLabel.name = "enemy-weapon";
  384. weaponLabel.style.fontSize = 11;
  385. weaponLabel.style.color = new Color(0.7f, 0.7f, 1f, 1f);
  386. var threatIndicator = new VisualElement();
  387. threatIndicator.name = "threat-indicator";
  388. threatIndicator.style.width = 60;
  389. threatIndicator.style.height = 20;
  390. threatIndicator.style.borderTopLeftRadius = 10;
  391. threatIndicator.style.borderTopRightRadius = 10;
  392. threatIndicator.style.borderBottomLeftRadius = 10;
  393. threatIndicator.style.borderBottomRightRadius = 10;
  394. threatIndicator.style.justifyContent = Justify.Center;
  395. threatIndicator.style.alignItems = Align.Center;
  396. threatIndicator.style.backgroundColor = new Color(0.3f, 0.8f, 0.3f, 1f);
  397. var threatLabel = new Label();
  398. threatLabel.name = "threat-label";
  399. threatLabel.style.fontSize = 11;
  400. threatLabel.style.color = Color.white;
  401. threatIndicator.Add(threatLabel);
  402. infoContainer.Add(nameLabel);
  403. infoContainer.Add(statsLabel);
  404. infoContainer.Add(weaponLabel);
  405. item.Add(infoContainer);
  406. item.Add(threatIndicator);
  407. return item;
  408. }
  409. void ShowPopup()
  410. {
  411. if (popupContainer != null)
  412. {
  413. Debug.Log($"🎭 ShowPopup: Setting display to Flex, current display: {popupContainer.style.display}");
  414. popupContainer.style.display = DisplayStyle.Flex;
  415. // Add animation classes
  416. popupContainer.RemoveFromClassList("popup-hidden");
  417. popupContainer.RemoveFromClassList("popup-exit");
  418. popupContainer.AddToClassList("popup-enter");
  419. // Check if UI Document is active and panel settings are configured
  420. if (uiDocument != null && uiDocument.panelSettings != null)
  421. {
  422. Debug.Log($"✅ UIDocument active: {uiDocument.enabled}, Panel Settings: {uiDocument.panelSettings.name}");
  423. }
  424. else
  425. {
  426. Debug.LogError("❌ UIDocument or Panel Settings not configured properly!");
  427. }
  428. Debug.Log("✅ Combat popup displayed");
  429. }
  430. else
  431. {
  432. Debug.LogError("❌ Cannot show popup - popupContainer is null!");
  433. }
  434. }
  435. public void HidePopup()
  436. {
  437. if (popupContainer != null)
  438. {
  439. // Add exit animation
  440. popupContainer.RemoveFromClassList("popup-enter");
  441. popupContainer.AddToClassList("popup-exit");
  442. // Hide after animation
  443. this.ExecuteAfterDelay(() =>
  444. {
  445. popupContainer.style.display = DisplayStyle.None;
  446. popupContainer.AddToClassList("popup-hidden");
  447. }, animationDuration);
  448. isPopupActive = false;
  449. Debug.Log("✅ Combat popup hidden");
  450. }
  451. }
  452. void OnBackgroundClick(ClickEvent evt)
  453. {
  454. // Block the click event from reaching elements below
  455. evt.StopPropagation();
  456. // Optionally allow closing popup by clicking background
  457. // Uncomment if you want this behavior:
  458. // OnRunAwayClicked();
  459. }
  460. void OnBackgroundMouseDown(MouseDownEvent evt)
  461. {
  462. // Block mouse down events from reaching the map below
  463. evt.StopPropagation();
  464. }
  465. void OnBackgroundMouseUp(MouseUpEvent evt)
  466. {
  467. // Block mouse up events from reaching the map below
  468. evt.StopPropagation();
  469. }
  470. void OnBackgroundMouseMove(MouseMoveEvent evt)
  471. {
  472. // Block mouse move events from reaching the map below
  473. evt.StopPropagation();
  474. }
  475. void OnBackgroundWheelEvent(WheelEvent evt)
  476. {
  477. // Block wheel/scroll events from reaching the map below
  478. evt.StopPropagation();
  479. }
  480. void OnAttackClicked()
  481. {
  482. Debug.Log("🗡️ Player chose to ATTACK!");
  483. HidePopup();
  484. OnCombatDecision?.Invoke(true);
  485. }
  486. void OnRunAwayClicked()
  487. {
  488. Debug.Log("🏃 Player chose to RUN AWAY!");
  489. HidePopup();
  490. OnCombatDecision?.Invoke(false);
  491. }
  492. /// <summary>
  493. /// Check if a screen position is within the UI bounds to block map interaction
  494. /// </summary>
  495. public bool IsPointWithinUI(Vector2 screenPosition)
  496. {
  497. if (!isPopupActive || popupContainer == null || uiDocument == null)
  498. {
  499. return false;
  500. }
  501. // Convert screen position to panel-relative position
  502. Vector2 panelPosition = RuntimePanelUtils.ScreenToPanel(uiDocument.rootVisualElement.panel, screenPosition);
  503. // Check if the position is within the popup container bounds
  504. bool withinBounds = popupContainer.worldBound.Contains(panelPosition);
  505. return withinBounds;
  506. }
  507. /// <summary>
  508. /// Public getters for external access
  509. /// </summary>
  510. public bool IsVisible => isPopupActive;
  511. }
  512. /// <summary>
  513. /// Extension method for delayed execution
  514. /// </summary>
  515. public static class MonoBehaviourExtensions
  516. {
  517. public static void ExecuteAfterDelay(this MonoBehaviour mono, System.Action action, float delay)
  518. {
  519. mono.StartCoroutine(ExecuteAfterDelayCoroutine(action, delay));
  520. }
  521. private static System.Collections.IEnumerator ExecuteAfterDelayCoroutine(System.Action action, float delay)
  522. {
  523. yield return new WaitForSeconds(delay);
  524. action?.Invoke();
  525. }
  526. }