GameOverScreen.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. using UnityEngine;
  2. using UnityEngine.UIElements;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. #if UNITY_EDITOR
  6. using UnityEditor;
  7. #endif
  8. /// <summary>
  9. /// Manages the Game Over screen with statistics and final results using UI Toolkit
  10. /// Shows when all player characters die
  11. /// </summary>
  12. public class GameOverScreen : MonoBehaviour
  13. {
  14. [Header("UI Toolkit Settings")]
  15. public UIDocument uiDocument;
  16. public VisualTreeAsset gameOverTemplate;
  17. public PanelSettings panelSettings;
  18. [Header("Statistics Settings")]
  19. public bool showDetailedStats = true;
  20. [Header("Audio")]
  21. public AudioClip gameOverMusic;
  22. public AudioClip defeatSound;
  23. private GameStatistics gameStats;
  24. private VisualElement rootElement;
  25. [System.Serializable]
  26. public class GameStatistics
  27. {
  28. [Header("Time & Travel")]
  29. public float totalPlayTime = 0f;
  30. public float totalTravelTime = 0f;
  31. public int daysTravaeled = 0;
  32. public int locationsVisited = 0;
  33. [Header("Combat")]
  34. public int battlesWon = 0;
  35. public int battlesLost = 0;
  36. public int enemiesSlain = 0;
  37. public int totalDamageDealt = 0;
  38. public int totalDamageTaken = 0;
  39. [Header("Social & Quests")]
  40. public int peopleHelped = 0;
  41. public int peopleSaved = 0;
  42. public int questsCompleted = 0;
  43. public int questsFailed = 0;
  44. [Header("Character Development")]
  45. public int finalFameLevel = 0;
  46. public int totalExperienceGained = 0;
  47. public int levelsGained = 0;
  48. [Header("Economic")]
  49. public int totalGoldEarned = 0;
  50. public int totalGoldSpent = 0;
  51. public int itemsBought = 0;
  52. public int itemsSold = 0;
  53. [Header("Survival")]
  54. public int charactersLost = 0;
  55. public int timesRested = 0;
  56. public int healthPotionsUsed = 0;
  57. public GameStatistics()
  58. {
  59. // Initialize with default values
  60. }
  61. /// <summary>
  62. /// Calculate derived statistics
  63. /// </summary>
  64. public int GetTotalBattles() => battlesWon + battlesLost;
  65. public float GetWinRate() => GetTotalBattles() > 0 ? (float)battlesWon / GetTotalBattles() * 100f : 0f;
  66. public int GetNetGold() => totalGoldEarned - totalGoldSpent;
  67. public float GetDamageRatio() => totalDamageTaken > 0 ? (float)totalDamageDealt / totalDamageTaken : 0f;
  68. }
  69. void Awake()
  70. {
  71. // Initialize game statistics
  72. gameStats = new GameStatistics();
  73. // Set up UI Document component
  74. SetupUIDocument();
  75. if (uiDocument != null && uiDocument.visualTreeAsset != null)
  76. {
  77. rootElement = uiDocument.rootVisualElement;
  78. // Hide game over panel initially
  79. if (rootElement != null)
  80. rootElement.style.display = DisplayStyle.None;
  81. // Set up button events
  82. SetupUICallbacks();
  83. }
  84. }
  85. /// <summary>
  86. /// Set up the UIDocument component with proper references
  87. /// </summary>
  88. private void SetupUIDocument()
  89. {
  90. // Get or create UIDocument component
  91. if (uiDocument == null)
  92. uiDocument = GetComponent<UIDocument>();
  93. if (uiDocument == null)
  94. uiDocument = gameObject.AddComponent<UIDocument>();
  95. // Load the UXML template if not assigned
  96. if (gameOverTemplate == null)
  97. {
  98. gameOverTemplate = Resources.Load<VisualTreeAsset>("UI/BattleSceneUI/GameOverScreen");
  99. if (gameOverTemplate == null)
  100. {
  101. #if UNITY_EDITOR
  102. // Try alternative path in editor
  103. gameOverTemplate = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/UI/BattleSceneUI/GameOverScreen.uxml");
  104. #endif
  105. }
  106. }
  107. // Load panel settings if not assigned
  108. if (panelSettings == null)
  109. {
  110. panelSettings = Resources.Load<PanelSettings>("MainSettings");
  111. if (panelSettings == null)
  112. {
  113. // Try alternative panel settings
  114. panelSettings = Resources.Load<PanelSettings>("UI/TravelPanelSettings");
  115. if (panelSettings == null)
  116. {
  117. // Try to find any PanelSettings in the project
  118. var allPanelSettings = Resources.FindObjectsOfTypeAll<PanelSettings>();
  119. if (allPanelSettings.Length > 0)
  120. panelSettings = allPanelSettings[0];
  121. }
  122. }
  123. }
  124. // Configure the UIDocument
  125. if (uiDocument != null)
  126. {
  127. uiDocument.visualTreeAsset = gameOverTemplate;
  128. uiDocument.panelSettings = panelSettings;
  129. // Load and apply stylesheet
  130. var stylesheet = Resources.Load<StyleSheet>("UI/BattleSceneUI/GameOverScreen");
  131. if (stylesheet == null)
  132. {
  133. #if UNITY_EDITOR
  134. stylesheet = AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/UI/BattleSceneUI/GameOverScreen.uss");
  135. #endif
  136. }
  137. if (stylesheet != null && uiDocument.rootVisualElement != null)
  138. {
  139. uiDocument.rootVisualElement.styleSheets.Add(stylesheet);
  140. }
  141. if (gameOverTemplate == null)
  142. {
  143. Debug.LogError("GameOverScreen: Could not load GameOverScreen.uxml template!");
  144. }
  145. if (panelSettings == null)
  146. {
  147. Debug.LogWarning("GameOverScreen: No PanelSettings found. UI may not display correctly.");
  148. }
  149. }
  150. }
  151. /// <summary>
  152. /// Set up UI element callbacks for the game over interface
  153. /// </summary>
  154. private void SetupUICallbacks()
  155. {
  156. if (rootElement == null) return;
  157. // Set up Restart Game button
  158. var restartButton = rootElement.Q<Button>("RestartButton");
  159. if (restartButton != null)
  160. {
  161. restartButton.clicked += RestartGame;
  162. }
  163. // Set up Main Menu button
  164. var mainMenuButton = rootElement.Q<Button>("MainMenuButton");
  165. if (mainMenuButton != null)
  166. {
  167. mainMenuButton.clicked += ReturnToMainMenu;
  168. }
  169. // Set up keyboard input for space key (restart)
  170. rootElement.RegisterCallback<KeyDownEvent>(OnKeyDown);
  171. // Make sure the root element can receive focus for keyboard events
  172. rootElement.focusable = true;
  173. }
  174. /// <summary>
  175. /// Handle keyboard input for the game over UI
  176. /// </summary>
  177. private void OnKeyDown(KeyDownEvent evt)
  178. {
  179. if (evt.keyCode == KeyCode.Space || evt.keyCode == KeyCode.Return)
  180. {
  181. RestartGame();
  182. }
  183. else if (evt.keyCode == KeyCode.Escape)
  184. {
  185. ReturnToMainMenu();
  186. }
  187. }
  188. /// <summary>
  189. /// Show the game over screen with current statistics
  190. /// </summary>
  191. public void ShowGameOver(List<GameObject> defeatedPlayers = null)
  192. {
  193. // Calculate final statistics
  194. CalculateFinalStatistics(defeatedPlayers);
  195. // Show the panel
  196. if (rootElement != null)
  197. {
  198. rootElement.style.display = DisplayStyle.Flex;
  199. rootElement.Focus(); // Allow keyboard input
  200. Time.timeScale = 0f; // Pause the game
  201. }
  202. // Update UI elements
  203. UpdateGameOverUI();
  204. // Play audio
  205. PlayGameOverAudio();
  206. Debug.Log("💀 Game Over screen displayed");
  207. }
  208. /// <summary>
  209. /// Calculate final statistics before showing game over
  210. /// </summary>
  211. private void CalculateFinalStatistics(List<GameObject> defeatedPlayers)
  212. {
  213. // Calculate total play time
  214. gameStats.totalPlayTime = Time.time;
  215. // Calculate characters lost
  216. if (defeatedPlayers != null)
  217. {
  218. gameStats.charactersLost = defeatedPlayers.Count;
  219. }
  220. // Try to get additional stats from game systems
  221. GatherStatisticsFromSystems();
  222. Debug.Log($"💀 Final statistics calculated - {gameStats.enemiesSlain} enemies slain, {gameStats.battlesWon} battles won");
  223. }
  224. /// <summary>
  225. /// Gather statistics from various game systems
  226. /// </summary>
  227. private void GatherStatisticsFromSystems()
  228. {
  229. // Try to get stats from GameManager if available
  230. var gameManager = GameManager.Instance;
  231. if (gameManager != null)
  232. {
  233. // Count enemies slain (this battle)
  234. var allEnemies = gameManager.enemyCharacters;
  235. gameStats.enemiesSlain += allEnemies.Count(enemy =>
  236. {
  237. var character = enemy?.GetComponent<Character>();
  238. return character != null && character.IsDead;
  239. });
  240. }
  241. // Try to get travel statistics
  242. GatherTravelStatistics();
  243. // Try to get combat statistics
  244. GatherCombatStatistics();
  245. // Try to get social statistics
  246. GatherSocialStatistics();
  247. }
  248. /// <summary>
  249. /// Gather travel-related statistics
  250. /// </summary>
  251. private void GatherTravelStatistics()
  252. {
  253. // TODO: Integrate with travel system when available
  254. // For now, use placeholder values
  255. gameStats.daysTravaeled = Mathf.RoundToInt(gameStats.totalPlayTime / 60f); // Rough estimate
  256. gameStats.locationsVisited = Random.Range(5, 15); // Placeholder
  257. }
  258. /// <summary>
  259. /// Gather combat-related statistics
  260. /// </summary>
  261. private void GatherCombatStatistics()
  262. {
  263. // TODO: Integrate with combat statistics system when available
  264. // For now, estimate based on current battle
  265. gameStats.battlesLost = 1; // This battle was lost
  266. gameStats.totalDamageDealt = Random.Range(50, 200); // Placeholder
  267. gameStats.totalDamageTaken = Random.Range(100, 300); // Placeholder
  268. }
  269. /// <summary>
  270. /// Gather social and quest statistics
  271. /// </summary>
  272. private void GatherSocialStatistics()
  273. {
  274. // TODO: Integrate with quest and social systems when available
  275. gameStats.peopleHelped = Random.Range(0, 5); // Placeholder
  276. gameStats.questsCompleted = Random.Range(0, 3); // Placeholder
  277. gameStats.questsFailed = Random.Range(0, 2); // Placeholder
  278. gameStats.finalFameLevel = Random.Range(1, 5); // Placeholder
  279. }
  280. /// <summary>
  281. /// Update the game over UI with statistics using UI Toolkit
  282. /// </summary>
  283. private void UpdateGameOverUI()
  284. {
  285. if (rootElement == null) return;
  286. // Update title
  287. var titleLabel = rootElement.Q<Label>("GameOverTitle");
  288. if (titleLabel != null)
  289. {
  290. titleLabel.text = "GAME OVER";
  291. }
  292. // Populate statistics sections
  293. PopulateStatisticsUI();
  294. }
  295. /// <summary>
  296. /// Populate all statistics sections in the UI
  297. /// </summary>
  298. private void PopulateStatisticsUI()
  299. {
  300. // Time & Travel Statistics
  301. PopulateTimeAndTravelStats();
  302. // Combat Statistics
  303. PopulateCombatStats();
  304. // Social & Quest Statistics
  305. PopulateSocialStats();
  306. // Assessment
  307. PopulateAssessment();
  308. }
  309. /// <summary>
  310. /// Populate time and travel statistics
  311. /// </summary>
  312. private void PopulateTimeAndTravelStats()
  313. {
  314. var playTimeLabel = rootElement.Q<Label>("PlayTimeValue");
  315. if (playTimeLabel != null)
  316. playTimeLabel.text = FormatTime(gameStats.totalPlayTime);
  317. var daysLabel = rootElement.Q<Label>("DaysTraveledValue");
  318. if (daysLabel != null)
  319. daysLabel.text = gameStats.daysTravaeled.ToString();
  320. var locationsLabel = rootElement.Q<Label>("LocationsVisitedValue");
  321. if (locationsLabel != null)
  322. locationsLabel.text = gameStats.locationsVisited.ToString();
  323. }
  324. /// <summary>
  325. /// Populate combat statistics
  326. /// </summary>
  327. private void PopulateCombatStats()
  328. {
  329. var battlesWonLabel = rootElement.Q<Label>("BattlesWonValue");
  330. if (battlesWonLabel != null)
  331. battlesWonLabel.text = gameStats.battlesWon.ToString();
  332. var battlesLostLabel = rootElement.Q<Label>("BattlesLostValue");
  333. if (battlesLostLabel != null)
  334. battlesLostLabel.text = gameStats.battlesLost.ToString();
  335. var winRateLabel = rootElement.Q<Label>("WinRateValue");
  336. if (winRateLabel != null)
  337. winRateLabel.text = $"{gameStats.GetWinRate():F1}%";
  338. var enemiesSlainLabel = rootElement.Q<Label>("EnemiesSlainValue");
  339. if (enemiesSlainLabel != null)
  340. enemiesSlainLabel.text = gameStats.enemiesSlain.ToString();
  341. var damageDealtLabel = rootElement.Q<Label>("DamageDealtValue");
  342. if (damageDealtLabel != null)
  343. damageDealtLabel.text = gameStats.totalDamageDealt.ToString();
  344. var damageTakenLabel = rootElement.Q<Label>("DamageTakenValue");
  345. if (damageTakenLabel != null)
  346. damageTakenLabel.text = gameStats.totalDamageTaken.ToString();
  347. var charactersLostLabel = rootElement.Q<Label>("CharactersLostValue");
  348. if (charactersLostLabel != null)
  349. charactersLostLabel.text = gameStats.charactersLost.ToString();
  350. }
  351. /// <summary>
  352. /// Populate social and quest statistics
  353. /// </summary>
  354. private void PopulateSocialStats()
  355. {
  356. var peopleHelpedLabel = rootElement.Q<Label>("PeopleHelpedValue");
  357. if (peopleHelpedLabel != null)
  358. peopleHelpedLabel.text = gameStats.peopleHelped.ToString();
  359. var livesSavedLabel = rootElement.Q<Label>("LivesSavedValue");
  360. if (livesSavedLabel != null)
  361. livesSavedLabel.text = gameStats.peopleSaved.ToString();
  362. var questsCompletedLabel = rootElement.Q<Label>("QuestsCompletedValue");
  363. if (questsCompletedLabel != null)
  364. questsCompletedLabel.text = gameStats.questsCompleted.ToString();
  365. var questsFailedLabel = rootElement.Q<Label>("QuestsFailedValue");
  366. if (questsFailedLabel != null)
  367. questsFailedLabel.text = gameStats.questsFailed.ToString();
  368. var fameLevelLabel = rootElement.Q<Label>("FameLevelValue");
  369. if (fameLevelLabel != null)
  370. fameLevelLabel.text = gameStats.finalFameLevel.ToString();
  371. var goldEarnedLabel = rootElement.Q<Label>("GoldEarnedValue");
  372. if (goldEarnedLabel != null)
  373. goldEarnedLabel.text = gameStats.totalGoldEarned.ToString();
  374. var goldSpentLabel = rootElement.Q<Label>("GoldSpentValue");
  375. if (goldSpentLabel != null)
  376. goldSpentLabel.text = gameStats.totalGoldSpent.ToString();
  377. var netWorthLabel = rootElement.Q<Label>("NetWorthValue");
  378. if (netWorthLabel != null)
  379. netWorthLabel.text = gameStats.GetNetGold().ToString();
  380. }
  381. /// <summary>
  382. /// Populate the final assessment section
  383. /// </summary>
  384. private void PopulateAssessment()
  385. {
  386. var assessmentLabel = rootElement.Q<Label>("AssessmentText");
  387. if (assessmentLabel != null)
  388. assessmentLabel.text = GetFinalMessage();
  389. }
  390. /// <summary>
  391. /// Generate the statistics text for display
  392. /// </summary>
  393. private string GenerateStatisticsText()
  394. {
  395. var stats = new System.Text.StringBuilder();
  396. stats.AppendLine("=== FINAL STATISTICS ===\n");
  397. // Time & Travel
  398. stats.AppendLine("📅 JOURNEY SUMMARY");
  399. stats.AppendLine($"Total Play Time: {FormatTime(gameStats.totalPlayTime)}");
  400. stats.AppendLine($"Days Traveled: {gameStats.daysTravaeled}");
  401. stats.AppendLine($"Locations Visited: {gameStats.locationsVisited}");
  402. stats.AppendLine();
  403. // Combat
  404. stats.AppendLine("⚔️ COMBAT RECORD");
  405. stats.AppendLine($"Battles Won: {gameStats.battlesWon}");
  406. stats.AppendLine($"Battles Lost: {gameStats.battlesLost}");
  407. stats.AppendLine($"Win Rate: {gameStats.GetWinRate():F1}%");
  408. stats.AppendLine($"Enemies Slain: {gameStats.enemiesSlain}");
  409. if (showDetailedStats)
  410. {
  411. stats.AppendLine($"Damage Dealt: {gameStats.totalDamageDealt}");
  412. stats.AppendLine($"Damage Taken: {gameStats.totalDamageTaken}");
  413. }
  414. stats.AppendLine();
  415. // Social & Quests
  416. stats.AppendLine("🤝 SOCIAL IMPACT");
  417. stats.AppendLine($"People Helped: {gameStats.peopleHelped}");
  418. stats.AppendLine($"Lives Saved: {gameStats.peopleSaved}");
  419. stats.AppendLine($"Quests Completed: {gameStats.questsCompleted}");
  420. stats.AppendLine($"Quests Failed: {gameStats.questsFailed}");
  421. stats.AppendLine($"Final Fame Level: {gameStats.finalFameLevel}");
  422. stats.AppendLine();
  423. // Character Development
  424. if (showDetailedStats)
  425. {
  426. stats.AppendLine("📈 CHARACTER GROWTH");
  427. stats.AppendLine($"Experience Gained: {gameStats.totalExperienceGained}");
  428. stats.AppendLine($"Levels Gained: {gameStats.levelsGained}");
  429. stats.AppendLine($"Characters Lost: {gameStats.charactersLost}");
  430. stats.AppendLine();
  431. }
  432. // Economic
  433. stats.AppendLine("💰 ECONOMIC SUMMARY");
  434. stats.AppendLine($"Gold Earned: {gameStats.totalGoldEarned}");
  435. stats.AppendLine($"Gold Spent: {gameStats.totalGoldSpent}");
  436. stats.AppendLine($"Net Worth: {gameStats.GetNetGold()}");
  437. // Final message
  438. stats.AppendLine();
  439. stats.AppendLine(GetFinalMessage());
  440. return stats.ToString();
  441. }
  442. /// <summary>
  443. /// Get a final message based on performance
  444. /// </summary>
  445. private string GetFinalMessage()
  446. {
  447. float winRate = gameStats.GetWinRate();
  448. int heroicScore = gameStats.peopleHelped + gameStats.peopleSaved + gameStats.questsCompleted;
  449. if (winRate >= 80f && heroicScore >= 10)
  450. return "You died as a legendary hero, remembered for ages!";
  451. else if (winRate >= 60f && heroicScore >= 5)
  452. return "You fought valiantly and helped many before falling.";
  453. else if (gameStats.enemiesSlain >= 10)
  454. return "Though you fell, your enemies remember your fierce combat.";
  455. else if (heroicScore >= 3)
  456. return "Your kind deeds will be remembered by those you helped.";
  457. else if (gameStats.daysTravaeled >= 10)
  458. return "You traveled far but met an unfortunate end.";
  459. else
  460. return "Your adventure was brief but not without purpose.";
  461. }
  462. /// <summary>
  463. /// Format time in minutes and seconds
  464. /// </summary>
  465. private string FormatTime(float timeInSeconds)
  466. {
  467. int minutes = Mathf.FloorToInt(timeInSeconds / 60f);
  468. int seconds = Mathf.FloorToInt(timeInSeconds % 60f);
  469. return $"{minutes:D2}:{seconds:D2}";
  470. }
  471. /// <summary>
  472. /// Play game over audio
  473. /// </summary>
  474. private void PlayGameOverAudio()
  475. {
  476. var audioSource = GetComponent<AudioSource>();
  477. if (audioSource != null)
  478. {
  479. if (defeatSound != null)
  480. {
  481. audioSource.PlayOneShot(defeatSound);
  482. }
  483. if (gameOverMusic != null)
  484. {
  485. audioSource.clip = gameOverMusic;
  486. audioSource.loop = true;
  487. audioSource.Play();
  488. }
  489. }
  490. }
  491. /// <summary>
  492. /// Restart the game
  493. /// </summary>
  494. private void RestartGame()
  495. {
  496. Time.timeScale = 1f; // Resume time
  497. Debug.Log("💀 Restarting game...");
  498. // TODO: Implement proper game restart logic
  499. // For now, reload the current scene
  500. UnityEngine.SceneManagement.SceneManager.LoadScene(
  501. UnityEngine.SceneManagement.SceneManager.GetActiveScene().name
  502. );
  503. }
  504. /// <summary>
  505. /// Return to main menu
  506. /// </summary>
  507. private void ReturnToMainMenu()
  508. {
  509. Time.timeScale = 1f; // Resume time
  510. Debug.Log("💀 Returning to main menu...");
  511. // TODO: Load main menu scene
  512. // For now, just hide the game over screen
  513. if (rootElement != null)
  514. rootElement.style.display = DisplayStyle.None;
  515. }
  516. /// <summary>
  517. /// Hide the game over screen (for testing)
  518. /// </summary>
  519. public void HideGameOver()
  520. {
  521. if (rootElement != null)
  522. rootElement.style.display = DisplayStyle.None;
  523. Time.timeScale = 1f;
  524. }
  525. /// <summary>
  526. /// Update a specific statistic
  527. /// </summary>
  528. public void UpdateStatistic(string statName, int value)
  529. {
  530. switch (statName.ToLower())
  531. {
  532. case "enemiesslain":
  533. gameStats.enemiesSlain += value;
  534. break;
  535. case "battlewon":
  536. gameStats.battlesWon += value;
  537. break;
  538. case "battleslost":
  539. gameStats.battlesLost += value;
  540. break;
  541. case "peoplehelped":
  542. gameStats.peopleHelped += value;
  543. break;
  544. case "questscompleted":
  545. gameStats.questsCompleted += value;
  546. break;
  547. case "goldearned":
  548. gameStats.totalGoldEarned += value;
  549. break;
  550. default:
  551. Debug.LogWarning($"Unknown statistic: {statName}");
  552. break;
  553. }
  554. }
  555. /// <summary>
  556. /// Get current statistics (for external access)
  557. /// </summary>
  558. public GameStatistics GetStatistics()
  559. {
  560. return gameStats;
  561. }
  562. }