Get 10% off orders above 50% with SEPT2025.
Forum begins after the advertisement:
Nulling values in GameManager after repeating a game
Home › Forums › Video Game Tutorial Series › Creating a Rogue-like Shoot-em Up in Unity › Nulling values in GameManager after repeating a game
- This topic has 1 reply, 3 voices, and was last updated 2 weeks ago by
Terence.
-
AuthorPosts
-
September 4, 2025 at 5:32 am #18802::
Greetings, I really enjoy your video series and it inspiered me to make my own version and incorporate map generation as an additional feature. I managed to prepare a system where player have to defeat the boss on a specific dungeon, find a key and then find a portal in order to enter another floor. I made new script explicitely for handling changing floor system. Unfortunetely during test much later that I should do I stumbled upon a problem where after repeating game when player die all variables recently assigned to the GameManager disapear and there is no way to play another time. Since I am a beginner at this I can’t find a possible explanation for this. Is it possible that LevelManager script somehow messes with functionality of a GameManager?
//LevelManager.cs
using UnityEngine; using System.Collections; using System.Collections.Generic; using RoomCoordinates; public class LevelManager : MonoBehaviour { [Header("UI")] [SerializeField] private PopupManager popupManager; public static LevelManager Instance { get; private set; } [Header("Level Settings")] public int currentLevel = 1; public int bossesKilled = 0; public int bossesRequiredForPortal = 1; public bool portalSpawned = false; [Header("Refereces")] public GameObject portalPrefab; public GameObject keyPrefab; public Transform player; public CAMapGenerator mapGenerator; public NewEnemySpawner enemySpawner; //public GameManager gameManager; [Header("Portal Spawn Settings")] public float minDistanceFromPlayer = 10f; public float minDistanceFromWalls = 3f; private float levelStartTime; private List<GameObject> currentEnemies = new List<GameObject>(); private void Awake() { if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); } else { Destroy(gameObject); } } private void Start() { levelStartTime = Time.time; FindReferences(); StartCoroutine(CheckForPortalConditions()); } void FindReferences() { if (player == null) { GameObject playerObj = GameObject.FindGameObjectWithTag("Player"); if (playerObj != null) player = playerObj.transform; } if (mapGenerator == null) mapGenerator = CAMapGenerator.Instance; if (enemySpawner == null) enemySpawner = FindAnyObjectByType<NewEnemySpawner>(); } IEnumerator CheckForPortalConditions() { while (true) { yield return new WaitForSeconds(5f); if (portalSpawned) continue; bool bossCondition = bossesKilled >= bossesRequiredForPortal; if (bossCondition) //lub albo i { SpawnKey(); } } } public void OnBossKilled() { Debug.LogWarning("Boss enemy killed"); bossesKilled++; if (bossesKilled >= bossesRequiredForPortal && !portalSpawned) { if (AudioManager1.Instance != null) AudioManager1.Instance.PlaySoundEffects(AudioManager1.Instance.popup); if (popupManager != null) popupManager.ShowPopup("FLOOR BOSS DEFEATED!\n FIND THE TREASURE!"); SpawnKey(); } } void SpawnKey() { Vector3 spawnPosition = FindSpawnPosition(); if (spawnPosition != Vector3.zero) { GameObject key = Instantiate(keyPrefab, spawnPosition, Quaternion.identity); KeyItem keyItem = key.GetComponent<KeyItem>(); if (keyItem != null) { keyItem.OnKeyCollected += OnKeyCollected; } portalSpawned = true; } } Vector3 FindSpawnPosition() { if (mapGenerator == null || mapGenerator.floorTileMap == null || mapGenerator.rooms == null || mapGenerator.rooms.Count == 0) { Debug.LogWarning("Fail to find map in LevelManager script"); return Vector3.zero; } int attemps = 50; for (int i = 0; i < attemps; i++) { Room randomRoom = mapGenerator.rooms[Random.Range(0, mapGenerator.rooms.Count)]; if (randomRoom.tiles.Count == 0) continue; Coord randomTile = randomRoom.tiles[Random.Range(0, randomRoom.tiles.Count)]; Vector3 worldPos = mapGenerator.floorTileMap.CellToWorld(new Vector3Int(randomTile.tileX, randomTile.tileY, 0)) + new Vector3(0.5f, 0.5f, 0); Collider2D[] colliders = Physics2D.OverlapCircleAll(worldPos, 0.4f); bool canSpawn = true; foreach (Collider2D collider in colliders) { if (collider.CompareTag("Enemy") || collider.CompareTag("Decoration") || collider.CompareTag("Prop") || collider.CompareTag("Player")) { canSpawn = false; break; } } if (canSpawn) { Debug.Log("Key spot found"); return worldPos; } } Debug.LogError($"Place for key not found"); return Vector3.zero; } void OnKeyCollected() { SpawnPortal(); } void SpawnPortal() { if (AudioManager1.Instance != null) AudioManager1.Instance.PlaySoundEffects(AudioManager1.Instance.popup); popupManager.ShowPopup("FIND THE PORTAL"); Vector3 spawnPosition = FindSpawnPosition(); if (spawnPosition != Vector3.zero) { Instantiate(portalPrefab, spawnPosition, Quaternion.identity); } } public void LoadNextLevel() { StartCoroutine(TransitionToNextLevel()); } IEnumerator TransitionToNextLevel() { if (player == null) { yield break; } PlayerStats playerStats = player.GetComponent<PlayerStats>(); if (playerStats == null) { Debug.Log("Fail to get component in playerStats"); yield break; } PlayerInventory inventory = player.GetComponent<PlayerInventory>(); int savedHealth = (int)playerStats.CurrentHealth; int savedLevel = playerStats.level; int savedExp = playerStats.experience; SaveCurrentEnemies(); if (DestructiblePropManager.Instance != null) { DestructiblePropManager.Instance.ReturnAllPropsToPool(); } if (mapGenerator != null) { mapGenerator.GenerateMap(); } yield return new WaitForSeconds(0.1f); if (mapGenerator != null) { Vector3 safeSpawnPos = mapGenerator.GetSafeMainRoomSpawnPosition1(); if (safeSpawnPos != Vector3.zero) { player.position = safeSpawnPos; } else { Debug.LogWarning("Failed to find safe spawn position, using deafult"); } } // RĘCZNIE INICJALIZUJ PROP MANAGER JEŚLI NIE ZROBIŁ TEGO AUTOMATYCZNIE if (DestructiblePropManager.Instance != null && DestructiblePropManager.Instance.propsPool.Count == DestructiblePropManager.Instance.poolSize && DestructiblePropManager.Instance.activePropsCount == 0) { Debug.Log("Manually initializing Prop Manager..."); DestructiblePropManager.Instance.Initialize(mapGenerator.rooms); } playerStats.CurrentHealth = savedHealth; playerStats.level = savedLevel; playerStats.experience = savedExp; RestoreEnemies(); currentLevel++; bossesKilled = 0; portalSpawned = false; levelStartTime = Time.time; IncreaseDifficulty(); if (GameManager.instance != null) { GameManager.instance.IncreaseDungeonLevel(); } if (AudioManager1.Instance != null) AudioManager1.Instance.PlaySoundEffects(AudioManager1.Instance.portal); FindAnyObjectByType<PopupManager>().ShowPopup("NEXT AREA REACHED!"); } void SaveCurrentEnemies() { currentEnemies.Clear(); GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy"); foreach (GameObject enemy in enemies) { currentEnemies.Add(enemy); } } void RestoreEnemies() { foreach (GameObject enemy in currentEnemies) { if (enemy != null) { Vector3 spawnPos = GetRandomSpawnPosition(); enemy.transform.position = spawnPos; } } currentEnemies.Clear(); } Vector3 GetRandomSpawnPosition() { Vector3 spawnPosition = Vector3.zero; if (enemySpawner != null && enemySpawner.relativeSpawnPoints != null && enemySpawner.relativeSpawnPoints.Count > 0) { spawnPosition = enemySpawner.relativeSpawnPoints[UnityEngine.Random.Range(0, enemySpawner.relativeSpawnPoints.Count)].position; } return spawnPosition; } void IncreaseDifficulty() { if (enemySpawner != null) { enemySpawner.allEnemiesLimit = Mathf.RoundToInt(enemySpawner.allEnemiesLimit * 1.2f); } } public void RegisterEnemy(GameObject enemy) { if (!currentEnemies.Contains(enemy)) { currentEnemies.Add(enemy); } } public void UnregisterEnemy(GameObject enemy) { if (currentEnemies.Contains(enemy)) { currentEnemies.Remove(enemy); } } }
GameManager is mostly the same tbh
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using TMPro; public class GameManager : MonoBehaviour { public static GameManager instance; // Define the different states of the game public enum GameState { Gameplay, Paused, GameOver, LevelUp } // Store the current state of the game public GameState currentState; // Store the previous state of the game before it was paused public GameState previousState; [Header("Dungeon Proggresion")] public int dungeonLevel = 1; [Header("Damage Text Settings")] public Canvas damageTextCanvas; public float textFontSize = 20; public TMP_FontAsset textFont; public Camera referenceCamera; [Header("Screens")] public GameObject pauseScreen; public GameObject resultsScreen; public GameObject levelUpScreen; [Header("Current Stat Displays")] public TMP_Text currentHealthDisplay; public TMP_Text currentRecoveryDisplay; public TMP_Text currentMoveSpeedDisplay; public TMP_Text currentMightDisplay; public TMP_Text currentProjectileSpeedDisplay; public TMP_Text currentMagnetDisplay; [Header("Results Screen Displays")] public Image chosenCharacterImage; public TMP_Text chosenCharacterName; public TMP_Text levelReachedDisplay; public TMP_Text timeSurvivedDisplay; public List<Image> chosenWeaponsUI = new List<Image>(6); public List<Image> chosenPassiveItemsUI = new List<Image>(6); [Header("Stopwatch")] public float timeLimit; // The time limit in seconds float stopwatchTime; // The current time elapsed since the stopwatch started public TMP_Text stopwatchDisplay; // Flag to check if the game is over public bool isGameOver = false; // Flag to check if the player is choosing their upgrades public bool choosingUpgrade = false; // Reference to the player's game object public GameObject playerObject; void Awake() { //Warning check to see if there is another singleton of this kind already in the game if (instance == null) { instance = this; //świeżo dodane DontDestroyOnLoad(gameObject); // DODAJ TO - CZYSZCZENIE PRZY STARCIE GRY //CleanupAllPickupsAtStart(); } else { Debug.LogWarning("EXTRA " + this + " DELETED"); Destroy(gameObject); } DisableScreens(); } void Update() { // Define the behavior for each state switch (currentState) { case GameState.Gameplay: // Code for the gameplay state CheckForPauseAndResume(); UpdateStopwatch(); break; case GameState.Paused: // Code for the paused state CheckForPauseAndResume(); break; case GameState.GameOver: // Code for the game over state if (!isGameOver) { isGameOver = true; Time.timeScale = 0f; //Stop the game entirely Debug.Log("Game is over"); DisplayResults(); } break; case GameState.LevelUp: if (!choosingUpgrade) { choosingUpgrade = true; Time.timeScale = 0f; //Pause the game for now Debug.Log("Upgrades shown"); levelUpScreen.SetActive(true); } break; default: Debug.LogWarning("STATE DOES NOT EXIST"); break; } } public void IncreaseDungeonLevel() { dungeonLevel++; } IEnumerator GenerateFloatingTextCoroutine(string text, Transform target, float duration = 1f, float speed = 50f) { // Start generating the floating text. GameObject textObj = new GameObject("Damage Floating Text"); RectTransform rect = textObj.AddComponent<RectTransform>(); TextMeshProUGUI tmPro = textObj.AddComponent<TextMeshProUGUI>(); tmPro.text = text; tmPro.horizontalAlignment = HorizontalAlignmentOptions.Center; tmPro.verticalAlignment = VerticalAlignmentOptions.Middle; tmPro.fontSize = textFontSize; if (textFont) tmPro.font = textFont; rect.position = referenceCamera.WorldToScreenPoint(target.position); // Makes sure this is destroyed after the duration finishes. Destroy(textObj, duration); // Parent the generated text object to the canvas. textObj.transform.SetParent(instance.damageTextCanvas.transform); textObj.transform.SetAsFirstSibling(); // or textObj.transform.SetSiblingIndex(0); // Pan the text upwards and fade it away over time. WaitForEndOfFrame w = new WaitForEndOfFrame(); float t = 0; float yOffset = 0; Vector3 lastKnownposition = target.position; while (t < duration) { // Fade the text to the right alpha value tmPro.color = new Color(tmPro.color.r, tmPro.color.g, tmPro.color.b, 1 - t / duration); if (target) { yOffset += speed * Time.deltaTime; if (rect != null) { rect.position = referenceCamera.WorldToScreenPoint(lastKnownposition + new Vector3(0, yOffset)); } } else { // If target iis dead, just pan up where the text is at if (rect != null) { rect.position += new Vector3(0, speed * Time.deltaTime, 0); } } // Wait for a frame and update the time yield return w; t += Time.deltaTime; } } public static void GenerateFloatingText(string text, Transform target, float duration = 1f, float speed = 1f) { // If the canvas is not set, end the function so we don't // generate any floating text. if (!instance.damageTextCanvas) return; // Find a relevant camera that we can use to convert the world // position to a screen position. if (!instance.referenceCamera) instance.referenceCamera = Camera.main; instance.StartCoroutine(instance.GenerateFloatingTextCoroutine( text, target, duration, speed )); } // Define the method to change the state of the game public void ChangeState(GameState newState) { currentState = newState; } public void PauseGame() { if (currentState != GameState.Paused) { previousState = currentState; ChangeState(GameState.Paused); Time.timeScale = 0f; // Stop the game pauseScreen.SetActive(true); // Enable the pause screen if (playerObject != null) { PlayerStats playerStats = playerObject.GetComponent<PlayerStats>(); if (playerStats != null) { playerStats.RefreshStatusUI(); } } else Debug.LogWarning("Attempt to get actual stats failed."); Debug.Log("Game is paused"); } } public void ResumeGame() { if (currentState == GameState.Paused) { ChangeState(previousState); Time.timeScale = 1f; // Resume the game pauseScreen.SetActive(false); //Disable the pause screen Debug.Log("Game is resumed"); } } // Define the method to check for pause and resume input void CheckForPauseAndResume() { if (Input.GetKeyDown(KeyCode.Escape)) { if (currentState == GameState.Paused) { ResumeGame(); } else { PauseGame(); } } } void DisableScreens() { pauseScreen.SetActive(false); resultsScreen.SetActive(false); levelUpScreen.SetActive(false); } public void GameOver() { timeSurvivedDisplay.text = stopwatchDisplay.text; ChangeState(GameState.GameOver); } void DisplayResults() { resultsScreen.SetActive(true); } public void AssignChosenCharacterUI(CharacterData chosenCharacterData) { chosenCharacterImage.sprite = chosenCharacterData.Icon; chosenCharacterName.text = chosenCharacterData.Name; } public void AssignLevelReachedUI(int levelReachedData) { levelReachedDisplay.text = levelReachedData.ToString(); } public void AssignChosenWeaponsAndPassiveItemsUI(List<PlayerInventory.Slot> chosenWeaponsData, List<PlayerInventory.Slot> chosenPassiveItemsData) { // Check that both lists have the same length if (chosenWeaponsData.Count != chosenWeaponsUI.Count || chosenPassiveItemsData.Count != chosenPassiveItemsUI.Count) { Debug.LogError("Chosen weapons and passive items data lists have different lengths"); return; } // Assign chosen weapons data to chosenWeaponsUI for (int i = 0; i < chosenWeaponsUI.Count; i++) { // Check that the sprite of the corresponding element in chosenWeaponsData is not null if (chosenWeaponsData[i].image.sprite) { // Enable the corresponding element in chosenWeaponsUI and set its sprite to the corresponding sprite in chosenWeaponsData chosenWeaponsUI[i].enabled = true; chosenWeaponsUI[i].sprite = chosenWeaponsData[i].image.sprite; } else { // If the sprite is null, disable the corresponding element in chosenWeaponsUI chosenWeaponsUI[i].enabled = false; } } // Assign chosen passive items data to chosenPassiveItemsUI for (int i = 0; i < chosenPassiveItemsUI.Count; i++) { // Check that the sprite of the corresponding element in chosenPassiveItemsData is not null if (chosenPassiveItemsData[i].image.sprite) { // Enable the corresponding element in chosenPassiveItemsUI and set its sprite to the corresponding sprite in chosenPassiveItemsData chosenPassiveItemsUI[i].enabled = true; chosenPassiveItemsUI[i].sprite = chosenPassiveItemsData[i].image.sprite; } else { // If the sprite is null, disable the corresponding element in chosenPassiveItemsUI chosenPassiveItemsUI[i].enabled = false; } } } void UpdateStopwatch() { stopwatchTime += Time.deltaTime; UpdateStopwatchDisplay(); if (stopwatchTime >= timeLimit) { playerObject.SendMessage("Kill"); } } void UpdateStopwatchDisplay() { // Calculate the number of minutes and seconds that have elapsed int minutes = Mathf.FloorToInt(stopwatchTime / 60); int seconds = Mathf.FloorToInt(stopwatchTime % 60); // Update the stopwatch text to display the elapsed time stopwatchDisplay.text = string.Format("{0:00}:{1:00}", minutes, seconds); } public void StartLevelUp() { AudioManager1.Instance.PlaySoundEffects(AudioManager1.Instance.levelUp); ChangeState(GameState.LevelUp); playerObject.SendMessage("RemoveAndApplyUpgrades"); } public void EndLevelUp() { choosingUpgrade = false; Time.timeScale = 1f; //Resume the game levelUpScreen.SetActive(false); ChangeState(GameState.Gameplay); } }
Sory for this long intro, I wanted to explain it clearly but I’m not sure if I did it proprely.
has upvoted this post. September 4, 2025 at 5:35 am #18803::Hello Patryk! Thanks for sharing your code and explaining your issue in detail. It sounds like you’re encountering a problem where the variables in your
GameManager
disappear after the player dies and you restart the game. Let’s break down the potential causes and solutions.Potential Issues
-
Singleton Pattern: Both
LevelManager
andGameManager
use the singleton pattern withDontDestroyOnLoad
. If you have multiple instances of either manager, it could lead to unexpected behavior. Make sure that you only have one instance of each manager in your scene. -
Game State Reset: When the player dies, you might not be resetting the game state properly. If the
GameManager
is not re-initializing its variables when the game restarts, it could lead to the loss of data. -
Scene Reloading: If you are reloading the scene when the player dies, ensure that you are not inadvertently destroying the
GameManager
instance. If the scene reloads and theGameManager
is not set up to retain its state, it will reset. -
Variable Initialization: Check if you are re-initializing your variables in the
Awake
orStart
methods ofGameManager
when the game restarts. If you are not, the variables may retain their values from the previous game session.
Suggested Solutions
-
Check Singleton Implementation: Ensure that your singleton implementation is correct. You can add debug logs to confirm that only one instance of
GameManager
andLevelManager
exists.void Awake() { if (instance == null) { instance = this; DontDestroyOnLoad(gameObject); Debug.Log("GameManager instance created"); } else { Debug.LogWarning("Another GameManager instance was destroyed"); Destroy(gameObject); } }
-
Reset Game State on Death: When the player dies, make sure to reset the game state properly. You can create a method in
GameManager
to reset all necessary variables.public void ResetGame() { dungeonLevel = 1; // Reset dungeon level // Reset other variables as needed isGameOver = false; // Additional reset logic }
-
Handle Game Over Logic: In your
GameOver
method, ensure that you are calling the reset method before restarting the game or transitioning to the results screen.public void GameOver() { timeSurvivedDisplay.text = stopwatchDisplay.text; ChangeState(GameState.GameOver); ResetGame(); // Reset the game state here }
-
Debugging: Add debug logs to track the values of your variables at different points in the game. This will help you identify where the values are being lost.
Conclusion
By ensuring that your singleton pattern is correctly implemented, resetting your game state properly, and adding debugging information, you should be able to identify and fix the issue with your
GameManager
. If you continue to have problems, feel free to share more details, and I’ll be happy to help further! Good luck with your game development!September 4, 2025 at 11:49 am #18804 -
-
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: