Unity Scene Management - Fixing New Scenes to Work

March 21, 2019

Advancing Tower Defense Tutorial

Moving Objects and References to New Scenes

Myriad Games Studio - HOW TO USE THE UNITY SCENEMANAGER

Now that I had the scenes loading properly enough, I actually needed to get all the references fixed finally. I had not altered the references since looking to implement the concept of having an all time Logic Scene, a Base Scene, and a Level Scene, so some of the references were broken when I split them up, especially between the Base Scene and Level Scene. This provided an interesting challenge since I did not want to make objects/scripts in the Base Scene public static, so I wanted to look to a way to have them communicate to each other through the Logic Scene.

Currently, I think all of my GameManager functionality does not work being in the Base Scene. I am looking to split some of its functionality so that I can move the GameManager to the Logic Scene, and keep the more level related info in a new LevelManager that would be in the base scene.

There are also some scripts on the GameManager object I want to move into the Level Scene. These would include: WaveSpawner (controls the information on enemies in the wave, how many waves, etc.), PlayerStats (controls values such as the player gold and lives).

Some general thoughts I had looking to advance all of these scripts to get them to work in the new scene setup:

  • Moved GameManager to Logic Scene with: WaveSpawner, PlayerStats, GameManager
  • Added Singleton pattern to GameManager
  • GameManager should start inactive [Same with WaveSpawner and PlayerStats, so entire GameManager object] (Only active during level)
  • While the WaveSpawner would be in the Logic Scene, I wanted to move the wave information to the individual Level Scenes
    • WaveSpawner should solely be responsible for taking in the information from the scene and then performing functions
    • The wave information, WaveInformation script, would be an object/script in the individual Level Scenes that could be modified to pass on varying information to the acutal WaveSpawner
    • This separates the WaveSpawner functionality from the various information a designer could mess with in individual scenes

From the tutorial I watched on persisting data between scenes, I learned more about static variables and how to control them a bit better. Following this I changed all my singleton pattern referenced manager objects from they themselves being public static, to just static. Then I created a method for each of them simply called GetInstance that is a public static method that simply returns the static instance variable I set for each of those classes. This may be cleaner with properties, but I don't quite know how to use those well enough currently so I am using this safer method approach for now. The scripts affected were: SceneManagerLite, GameManager, PlayerStats, WaveSpawner.

I also decided to clean up my PlayerStats script a lot to make it much more secure using properties with public methods. I changed it to the following:

public class PlayerStats : MonoBehaviour
{
#region Variables
static PlayerStats playerStatsInstance;

private int _playerMoney;
public int playerMoney { get { return _playerMoney; } }

private int _playerLives;
public int playerLives { get { return _playerLives; } }

public int rounds = 0;

#endregion


#region Unity Methods

private void Start()
{
if (playerStatsInstance != null)
{
Debug.Log("More than one Player Stats Instance in scene");
return;
}
playerStatsInstance = this;

rounds = 0;
}

public static PlayerStats GetInstance()
{
return playerStatsInstance;
}

public void AddMoney(int moneyToAdd)
{
_playerMoney += moneyToAdd;
}

public void SetLives(int numberOfLives)
{
_playerLives = numberOfLives;
}

public void LoseLives(int numberOfLivesLost)
{
_playerLives -= numberOfLivesLost;
}


#endregion
}

I changed playerMoney and playerLives to properties that could only be altered through the public methods added below, to keep these from randomly being set to crazy values throughout editing. This was also done in hopes that it would make doing the cross scene references easier to manage.

Now that all the singleton pattern objects were cleaned up, as well as significantly restructuring PlayerStats, I moved on to the WaveSpawner. I found that it made more sense to move the UI Text reference in the WaveSpawner to the actual UI object itself in the BaseScene. I created a new TimerUI script that I placed directly on the UI Text object dealing with time and just moved the snippet of code dealing with that from WaveSpawner to here, and just had the TimerUI script reference the WaveSpawner to get the time value. This just cleaned up WaveSpawner a bit to focus solely on spawning waves. I also fixed the SpawnWave method to work with the new PlayerStats setup, as rounds are also found there. This just meant changing a direct variable alteration to an indirect method alteration (from rounds++ to AddRound()).

Next I really needed to clean up the GameManager and the LevelManager as their purposes were a bit unclear. I decided that for the most part, the GameManager should just deal with what levels the player has unlocked. This was the only big "player stat" that needed recorded over multiple plays and levels. The LevelManager would then deal with actually determining when the player won or lost a level, as well as controlling the UI elements. This did help clean both of these up a lot and make their purposes clearer. The LevelManager dealt with the UI elements, the GameIsOver bool, and actually determining if the player won or lost the level. The GameManager then just updated the levels unlocked when calling WinLevel() method in LevelManager.

After all of this updating, it was time for more testing. It got through the Logic Scene well, and started with the MainMenu. Clicking Play got into the LevelSelect scene fine. I went into level 1, and it looked like the timer and money text were working properly, but no enemies were spawning (I also still have my issue where the Nodes are in a scene separate from the BuildManager that I need to fix). It appeared the WaveSpawner was turning itself off before receiving the WaveInformation from the Level Scene. I checked with a Debug.Log and this did in fact seem to be the case. The WaveSpawner checks in its Update function if the waveIndex (wave number it is up to for spawning) is equal to the total number of elements in the wave array (total amount of waves that should be spawned this level), and if this is the case, it turns itself off (to avoid errors with going outside of the array range). This however is causing it to turn off immediately because waveIndex starts at 0 and goes up to reach waves.Length, but since it doesn't have the waveInformation in time, it thinks waves.Length is also 0 so it turns off instantly.

I think my issue with the Nodes is similar to that of the WaveSpawner. They get a reference to the BuildManager at Start, but it isn't happening in time with how the scenes load so they don't have anything to reference, and therefore never get the reference. Then they just never have the reference they need the rest of the time. So I need a way to enforce the order things happen between loading and running the scripts. This could be something I could solve with more use of IEnumerators and coroutines possibly.

The node solution is more difficult because both objects/scripts dealing with nodes are loaded in separate scenes at the same time. The BuildManager is in the Base Scene, while the Nodes are all in the Level Scene. These both get loaded at the same time. It turns out that by modifying my scene build index array setup I can alter which scene is loaded first, and both orders give me varying issues. Swapping the Base Scene to load first fixed the Node issue, as now the BuildManager exists first so the Nodes can get their reference, but then the game was immediatel ending (both winning and losing) since the win and lose conditions were met immediately as they require the Level Scene to pass the life and wave information to the Logic Scene to properly determine the game state. I need something that makes sure everything is loaded at the same time before starting the Start functions.

SOLUTION

Wave Spawner Issue

I remembered that the WaveSpawner being in the Logic Scene means it actually exists all of the time (I didn't get the only activate in levels working properly yet, for similar reasons). This meant that by the time the level started, it had already been way past its Start function and into its Update function. This meant it was turning itself off way before I even entered the level. I then checked to see if it was at least receiving the WaveInformation even though it was disabled and it was. Knowing this, I just needed to reenable the script after giving it the WaveInformation. So directly after WaveInformation method PassWaveInformation(), I just also enabled the WaveSpawner script and it worked perfectly!

Comments

Popular posts from this blog

Online Multiplayer Networking Solution Tutorial Using Unity and Mirror - Tutorial by: Jason Weimann

Sebastian Lague A* Tutorial Series - Path Smoothing - Pt. 08 and Pt. 09

Exporting FBX from Houdini with Color