Forum begins after the advertisement:
[Part 25] Fix for play mode not working in level scenes
Home › Forums › Video Game Tutorial Series › Creating a Rogue-like Shoot-em Up in Unity › [Part 25] Fix for play mode not working in level scenes
- This topic has 0 replies, 1 voice, and was last updated 5 days, 17 hours ago by
Yip Si Yuan.
Viewing 1 post (of 1 total)
-
AuthorPosts
-
May 28, 2025 at 1:52 am #18213::
After completing Part 25 of the Vampire Survivors tutorial (Bad*ss Level Select Screen), you might notice that playing directly from a level scene in the editor doesn’t work as expected.
I’ve found a fix for this — it just requires a few small changes to the GameManager.cs script.
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 } private const float DEFAULT_TIME_LIMIT = 1800f; private const float DEFAULT_CLOCK_SPEED = 1f; // Helper properties private float ClockSpeed => UILevelSelector.currentLevel?.clockSpeed ?? DEFAULT_CLOCK_SPEED; private float TimeLimit => UILevelSelector.currentLevel?.timeLimit ?? DEFAULT_TIME_LIMIT; // 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("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; int stackedLevelUps = 0; // If we try to StartLevelUp() multiple times. [Header("Results Screen Displays")] public Image chosenCharacterImage; public TMP_Text chosenCharacterName; public TMP_Text levelReachedDisplay; public TMP_Text timeSurvivedDisplay; [Header("Stopwatch")] public float timeLimit; // The time limit in seconds float stopwatchTime; // The current time elapsed since the stopwatch started public TMP_Text stopwatchDisplay; bool levelEnded = false; // Has the time limit been reached? public GameObject reaperPrefab; // Spawns after time limit has been reached; PlayerStats[] players; // Tracks all players. // Getters for parity with older scripts. public bool isGameOver { get { return currentState == GameState.Paused; } } public bool choosingUpgrade { get { return currentState == GameState.LevelUp; } } // Gives us the time since the level has started. public float GetElapsedTime() { return stopwatchTime; } // Sums up the curse stat of all players and returns the value. public static float GetCumulativeCurse() { if (!instance) return 1; float totalCurse = 0; foreach (PlayerStats p in instance.players) { totalCurse += p.Actual.curse; } return Mathf.Max(1, totalCurse); } // Sum up the levels of all players and returns the value. public static int GetCumulativeLevels() { if (!instance) return 1; int totalLevel = 0; foreach (PlayerStats p in instance.players) { totalLevel += p.level; } return Mathf.Max(1, totalLevel); } void Awake() { players = FindObjectsOfType<PlayerStats>();
timeLimit = UILevelSelector.currentLevel.timeLimit;timeLimit = TimeLimit; //Warning check to see if there is another singleton of this kind already in the game if (instance == null) { instance = this; } else { Debug.LogWarning("EXTRA " + this + " DELETED"); Destroy(gameObject); } DisableScreens(); } void Update() { 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: case GameState.LevelUp: break; default: Debug.LogWarning("STATE DOES NOT EXIST"); break; } } 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.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) { // If the RectTransform is missing for whatever reason, end this loop. if (!rect) break; // Fade the text to the right alpha value. tmPro.color = new Color(tmPro.color.r, tmPro.color.g, tmPro.color.b, 1 - t / duration); // Update the enemy's position if it is still around. if (target) lastKnownPosition = target.position; // Pan the text upwards. yOffset += speed * Time.deltaTime; rect.position = referenceCamera.WorldToScreenPoint(lastKnownPosition + new Vector3(0, yOffset)); // 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) { previousState = currentState; currentState = newState; } public void PauseGame() { if (currentState != GameState.Paused) { ChangeState(GameState.Paused); Time.timeScale = 0f; // Stop the game pauseScreen.SetActive(true); // Enable the pause screen } } public void ResumeGame() { if (currentState == GameState.Paused) { ChangeState(previousState); Time.timeScale = 1f; // Resume the game pauseScreen.SetActive(false); //Disable the pause screen } } // 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; // Set the Game Over variables here. ChangeState(GameState.GameOver); Time.timeScale = 0f; //Stop the game entirely DisplayResults(); } 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 Vector2 GetRandomPlayerLocation() { int chosenPlayer = Random.Range(0, players.Length); return new Vector2(players[chosenPlayer].transform.position.x, players[chosenPlayer].transform.position.y); } void UpdateStopwatch() {stopwatchTime += Time.deltaTime * UILevelSelector.currentLevel.clockSpeed;stopwatchTime += Time.deltaTime * ClockSpeed; UpdateStopwatchDisplay(); if (stopwatchTime >= timeLimit && !levelEnded) { levelEnded = true; // Set the enemy/event spawner GameObject inactive to stop enemies from spawning and kill the remaining enemies onscreen. FindObjectOfType<SpawnManager>().gameObject.SetActive(false); foreach (EnemyStats e in FindObjectsOfType<EnemyStats>()) e.SendMessage("Kill"); // Spawn the Reaper off-camera Vector2 reaperOffset = Random.insideUnitCircle * 50f; Vector2 spawnPosition = GetRandomPlayerLocation() + reaperOffset; Instantiate(reaperPrefab, spawnPosition, Quaternion.identity); } } 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() { ChangeState(GameState.LevelUp); // If the level up screen is already active, record it. if (levelUpScreen.activeSelf) stackedLevelUps++; else { levelUpScreen.SetActive(true); Time.timeScale = 0f; //Pause the game for now foreach (PlayerStats p in players) p.SendMessage("RemoveAndApplyUpgrades"); } } public void EndLevelUp() { Time.timeScale = 1f; //Resume the game levelUpScreen.SetActive(false); ChangeState(GameState.Gameplay); if (stackedLevelUps > 0) { stackedLevelUps--; StartLevelUp(); } } }The new changes check if
currentLevel
in theUILevelSelector
is null, and if so, set default values to DEFAULT_TIME_LIMIT and DEFAULT_CLOCK_SPEED. After these changes you should now be able to play the level scenes in the editor directly.has upvoted this post. -
AuthorPosts
Viewing 1 post (of 1 total)
- You must be logged in to reply to this topic.
Advertisement below: