using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.TextCore.Text; namespace BulletHellTemplate { /// /// Manages the game maps, including loading scenes, spawning characters, /// and unlocking new maps upon game completion. /// public class GameManager : MonoBehaviour { #region Public Fields [Header("Currency earned in matches")] [Tooltip("Currency used in the game")] public string goldCurrency = "GO"; [Header("Map Name MainMenu")] [Tooltip("Name of the main menu scene")] public string mainMenuScene = "Home"; [Header("BattlePass earn EXP for winning?")] [Tooltip("Battle Pass EXP awarded for winning a match")] public int BattlePassEXP = 210; /// /// Singleton reference to the GameManager. /// public static GameManager Singleton; #endregion #region Private Fields /// /// Stores the ID of the current map being played. /// private int currentMapId; #endregion #region Unity Callbacks private void Awake() { // Implement singleton pattern if (Singleton == null) { Singleton = this; } else { Destroy(gameObject); } DontDestroyOnLoad(gameObject); } #endregion #region Public Methods /// /// Starts the game with the specified MapId and stores the current map ID. /// /// The ID of the map to load. public void StartGame(int mapId) { currentMapId = mapId; // Store the ID of the current map foreach (var map in GameInstance.Singleton.mapInfoData) { if (map.mapId == mapId) { bool isUnlocked = map.isUnlocked || PlayerSave.GetUnlockedMaps().Contains(map.mapId); Debug.Log(isUnlocked); if (isUnlocked) { StartCoroutine(LoadSceneAndSpawnCharacter(map)); } else { Debug.LogError("Map is not unlocked."); } return; } } Debug.LogError("MapId not found."); } /// /// Ends the game, displays a win/lose screen, and returns to the main menu. /// If the player wins, it checks if the current map is the latest unlocked map and unlocks the next map if true. /// /// Whether the player won the game. public async void EndGame(bool won) { AudioManager.Singleton.StopAllAudioPlay(); UIGameplay.Singleton.DisplayEndGameScreen(won); // Apply gold earned int goldEarned = MonetizationManager.GetCurrency(goldCurrency) + GameplayManager.Singleton.GetGainGold(); MonetizationManager.SetCurrency(goldCurrency, goldEarned); if (won) { RewardManagerPopup.Singleton.TrySaveCompletedMap(currentMapId); if (IsLatestUnlockedMap(currentMapId)) { UnlockNextMap(); } } // Update quest progress based on the current game results UpdateQuestProgress(won); // Save the number of monsters killed to the player's Firestore document int monstersKilled = GameplayManager.Singleton.GetMonstersKilled(); if (BackendManager.Singleton != null) { await BackendManager.Singleton.SaveMonstersKilledAsync(monstersKilled); } // Add Battle Pass EXP if applicable if (BattlePassEXP > 0 && BattlePassManager.Singleton != null) { BattlePassManager.Singleton.AddXP(BattlePassEXP); } } /// /// Adds the specified amount of experience to the player's account, /// allowing for multiple level-ups if enough EXP is provided. /// Stops granting experience if max level is reached. /// /// The amount of experience to add to the account. public async void AddAccountExp(int exp) { int currentLevel = PlayerSave.GetAccountLevel(); int maxLevel = GameInstance.Singleton.accountLevels.accountMaxLevel; // Do not grant EXP if the account has reached max level if (currentLevel >= maxLevel) { Debug.Log("Maximum level reached. No EXP granted."); return; } int updatedExp = PlayerSave.GetAccountCurrentExp() + exp; PlayerSave.SetAccountCurrentExp(updatedExp); bool canLevelUp = true; while (canLevelUp) { currentLevel = PlayerSave.GetAccountLevel(); int currentExp = PlayerSave.GetAccountCurrentExp(); // If during leveling up the account reaches max level, // reset EXP to 0 and stop processing if (currentLevel >= maxLevel) { PlayerSave.SetAccountCurrentExp(0); Debug.Log("Reached maximum level. Excess EXP discarded."); break; } int requiredExpToLevelUp = GameInstance.Singleton.GetAccountExpForLevel(currentLevel); if (currentExp >= requiredExpToLevelUp) { int newLevel = currentLevel + 1; int newCurrentExp = currentExp - requiredExpToLevelUp; PlayerSave.SetAccountLevel(newLevel); PlayerSave.SetAccountCurrentExp(newCurrentExp); if (BackendManager.Singleton != null) { await BackendManager.Singleton.UpdatePlayerAccountLevel(newLevel, newCurrentExp); } } else { canLevelUp = false; } } } /// /// Adds the specified amount of Battle Pass EXP to the player. /// /// The amount of Battle Pass EXP to add. public void AddBattlePassExp(int exp) { if (BattlePassManager.Singleton != null) { BattlePassManager.Singleton.AddXP(exp); } } public async void SaveCharacterBasicInfo(int characterId) { if (BackendManager.Singleton != null) { int level = PlayerSave.GetCharacterLevel(characterId); int currentExp = PlayerSave.GetCharacterCurrentExp(characterId); int masteryLevel = PlayerSave.GetCharacterMasteryLevel(characterId); int masteryExp = PlayerSave.GetCharacterCurrentMasteryExp(characterId); int skinId = PlayerSave.GetCharacterSkin(characterId); await BackendManager.Singleton.SaveCharacterBasicInfoAsync(characterId, skinId, level, currentExp, masteryLevel, masteryExp); } } /// /// Adds the specified amount of experience to a particular character. /// /// The character's ID. /// The amount of EXP to add to the character. public bool AddCharacterExp(int characterId, int exp) { int updatedExp = PlayerSave.GetCharacterCurrentExp(characterId) + exp; PlayerSave.SetCharacterCurrentExp(characterId, updatedExp); return true; } /// /// Adds mastery experience to a character, handling multiple level-ups. /// Stops granting experience if max mastery level is reached. /// /// The ID of the character. /// Amount of mastery experience to add. public bool AddCharacterMasteryExp(int characterId, int exp) { int maxMasteryLevel = GameInstance.Singleton.characterMastery.maxMasteryLevel - 1; int _currentMasteryLevel = PlayerSave.GetCharacterMasteryLevel(characterId); if (_currentMasteryLevel >= maxMasteryLevel) { Debug.Log("Maximum mastery level reached. No EXP granted."); return false; } int updatedMasteryExp = PlayerSave.GetCharacterCurrentMasteryExp(characterId) + exp; PlayerSave.SetCharacterCurrentMasteryExp(characterId, updatedMasteryExp); bool canLevelUp = true; while (canLevelUp) { int currentMasteryLevel = PlayerSave.GetCharacterMasteryLevel(characterId); int currentMasteryExp = PlayerSave.GetCharacterCurrentMasteryExp(characterId); if (currentMasteryLevel >= maxMasteryLevel) { PlayerSave.SetCharacterCurrentMasteryExp(characterId, 0); Debug.Log("Reached max mastery level. Excess EXP removed."); break; } int requiredExpToLevelUpMastery = GameInstance.Singleton.GetMasteryExpForLevel(currentMasteryLevel); if (currentMasteryExp >= requiredExpToLevelUpMastery) { int newMasteryLevel = currentMasteryLevel + 1; int newCurrentMasteryExp = currentMasteryExp - requiredExpToLevelUpMastery; PlayerSave.SetCharacterMasteryLevel(characterId, newMasteryLevel); PlayerSave.SetCharacterCurrentMasteryExp(characterId, newCurrentMasteryExp); currentMasteryLevel = newMasteryLevel; currentMasteryExp = newCurrentMasteryExp; } else { canLevelUp = false; } } return true; } /// /// Retrieves the ID of the current map being played. /// /// The ID of the current map. public int GetCurrentMapId() { return currentMapId; } #endregion #region Private Methods /// /// Coroutine that loads the specified map scene asynchronously and, /// once the scene is fully loaded, spawns the player's character at the designated start position. /// Uses the LoadingManager to display the loading screen during the process. /// /// The map data containing the scene to be loaded. /// An IEnumerator used for coroutine execution. private IEnumerator LoadSceneAndSpawnCharacter(MapInfoData map) { yield return LoadingManager.Singleton.LoadSceneWithLoadingScreen(map.scene); // Spawn the player's character after the scene has been loaded SpawnCharacterAtStartPosition(map); } /// /// Spawns the player's character at the starting position in the loaded scene. /// The character prefab is instantiated at a default position (Vector3.zero), /// and the character's data is set based on the game instance. /// Additionally, it spawns the correct UIGameplay Canvas based on the specified platform type. /// /// The map data used for setting the minimap image if available. private void SpawnCharacterAtStartPosition(MapInfoData map) { Vector3 startPosition = Vector3.zero; // Check if the character prefab is set in the GameInstance if (GameInstance.Singleton.characterEntity != null) { // Instantiate the character at the start position CharacterEntity character = Instantiate(GameInstance.Singleton.characterEntity, startPosition, Quaternion.identity); // Set the character's data using the GameInstance character.SetCharacterData(GameInstance.Singleton.GetCharacterData()); // Get the correct UIGameplay instance based on the specified platform type UIGameplay uiGameplayPrefab = GameInstance.Singleton.GetUIGameplayForPlatform(); if (uiGameplayPrefab != null) { // Instantiate UIGameplay and store the instance var uiGameplayInstance = Instantiate(uiGameplayPrefab, Vector3.zero, Quaternion.identity); // Ensure the instance has the necessary components if (uiGameplayInstance.minimapImage != null) { if (map.mapMinimapImage != null) { // Assign the map's sprite to the minimap uiGameplayInstance.minimapImage.sprite = map.mapMinimapImage; } else { // Assign the unlockedSprite if map's sprite is null uiGameplayInstance.minimapImage.sprite = uiGameplayInstance.unlockedSprite; } } else { Debug.LogWarning("MinimapImage is not set in the UIGameplay prefab."); } } } else { // Log an error if the character prefab is not set Debug.LogError("CharacterPrefab is not set in the GameInstance.Singleton."); } } /// /// Determines if the map is unlocked based on PlayerPrefs or default settings in the ScriptableObject. /// /// The ID of the map. /// True if the map is unlocked, false otherwise. private bool IsMapUnlocked(int mapId) { // Check if the map is marked as unlocked in the editor foreach (var map in GameInstance.Singleton.mapInfoData) { if (map.mapId == mapId && map.isUnlocked) { return true; } } // If not, check if it is unlocked in the player's saved data return PlayerSave.GetUnlockedMaps().Contains(mapId); } /// /// Checks if the specified map ID is the latest unlocked map. /// This ensures that only the latest map can unlock the next one. /// /// The ID of the map to check. /// True if the map is the latest unlocked or marked as unlocked in the editor, false otherwise. private bool IsLatestUnlockedMap(int mapId) { List unlockedMaps = PlayerSave.GetUnlockedMaps(); MapInfoData[] allMaps = GameInstance.Singleton.mapInfoData; // Check if no maps are unlocked if (unlockedMaps.Count == 0) { // If no maps are unlocked, consider the current map as the latest if it has isUnlocked marked in the editor foreach (var map in allMaps) { if (map.mapId == mapId && map.isUnlocked) { return true; } } } // Check if the current map is the latest unlocked return unlockedMaps.Contains(mapId) && unlockedMaps[unlockedMaps.Count - 1] == mapId; } /// /// Unlocks the next map in the list of map data if the player has completed the latest unlocked map. /// The next map is unlocked and saved to the player's unlocked maps list. /// private void UnlockNextMap() { List unlockedMaps = PlayerSave.GetUnlockedMaps(); MapInfoData[] allMaps = GameInstance.Singleton.mapInfoData; int lastUnlockedMapId = 0; if (unlockedMaps.Count > 0) { lastUnlockedMapId = unlockedMaps[unlockedMaps.Count - 1]; } else { for (int i = 0; i < allMaps.Length; i++) { if (allMaps[i].isUnlocked) { lastUnlockedMapId = allMaps[i].mapId; break; } } } // Find the next map to unlock for (int i = 0; i < allMaps.Length; i++) { if (allMaps[i].mapId == lastUnlockedMapId && i + 1 < allMaps.Length) { // Unlock the next map by adding its ID to the list int nextMapId = allMaps[i + 1].mapId; unlockedMaps.Add(nextMapId); // Save the updated list of unlocked maps PlayerSave.SetUnlockedMaps(unlockedMaps); break; } } } /// /// Updates the progress of all relevant quests based on the game's outcome. /// /// Whether the player won the game. private void UpdateQuestProgress(bool won) { QuestItem[] quests = GameInstance.Singleton.questData; // Assume questData is an array CharacterEntity character = GameplayManager.Singleton.GetCharacterEntity(); int monstersKilled = GameplayManager.Singleton.GetMonstersKilled(); foreach (QuestItem quest in quests) { // Skip if the quest is already completed if (PlayerSave.IsQuestCompleted(quest.questId)) continue; QuestRequirement requirement = quest.requirement; int currentProgress = PlayerSave.LoadQuestProgress(quest.questId); switch (requirement.requirementType) { case QuestRequirementType.KillMonster: // Accumulate the total monsters killed currentProgress += monstersKilled; PlayerSave.SaveQuestProgress(quest.questId, currentProgress); break; case QuestRequirementType.KillMonsterWithSpecificCharacter: if (character != null && character.GetCharacterData() == requirement.targetCharacter) { currentProgress += monstersKilled; PlayerSave.SaveQuestProgress(quest.questId, currentProgress); } break; case QuestRequirementType.CompleteMap: // Increment progress if the player won on the required map if (won && currentMapId == requirement.targetMap.mapId) { currentProgress++; PlayerSave.SaveQuestProgress(quest.questId, currentProgress); } break; case QuestRequirementType.CompleteMapWithSpecificCharacter: // Increment progress if the player won on the required map with the required character if (won && currentMapId == requirement.targetMap.mapId && character != null && character.GetCharacterData() == requirement.targetCharacter) { currentProgress++; PlayerSave.SaveQuestProgress(quest.questId, currentProgress); } break; case QuestRequirementType.LevelUpAccount: break; case QuestRequirementType.LevelUpCharacter: break; } } } private CharacterData GetCharacterDataById(int characterId) { if (GameInstance.Singleton == null || GameInstance.Singleton.characterData == null) return null; foreach (CharacterData item in GameInstance.Singleton.characterData) if (item.characterId == characterId) return item; return null; } /// /// Initiates the process of returning to the main menu by loading the main menu scene. /// public void ReturnToMainMenu() { StartCoroutine(LoadMainMenuAndLoadPlayerInfo()); } /// /// Initiates the process of loading a specific scene. /// /// The name of the scene to load. public void LoadSpecificScene(string scene) { StartCoroutine(LoadAsyncSpecificScene(scene)); } /// /// Coroutine that loads the main menu scene asynchronously and, /// once the scene is fully loaded, it calls the method to load the player's information. /// Uses the LoadingManager to display the loading screen during the process. /// /// An IEnumerator used for coroutine execution. private IEnumerator LoadMainMenuAndLoadPlayerInfo() { yield return LoadingManager.Singleton.LoadSceneWithLoadingScreen(mainMenuScene); // After the scene is loaded, call the method to load player information UIMainMenu.Singleton.LoadPlayerInfo(); } /// /// Coroutine that loads a specific scene asynchronously. /// Uses the LoadingManager to display the loading screen during the process. /// /// The name of the scene to load. /// An IEnumerator used for coroutine execution. private IEnumerator LoadAsyncSpecificScene(string scene) { yield return LoadingManager.Singleton.LoadSceneWithLoadingScreen(scene); } #endregion } }