Tower Defense Project – Fixing UI Text

September 2, 2019

Tower Defense Tutorial Project

UI Text

GOAL:

Fix game over UI text and look into round text references to see why they update strangely.

NOTES:

For the GameOver screen producing weird text results, my first thought was to the check the inspector references. I know tying the stats into text UI elements uses inspector references, so making sure those were correct was the first check. Sure enough, it looked like the wrong text was dragged into the GameOver UI inspector field. I must have grabbed the wrong one initially, as the generic text was dragged in there as opposed to the text that should be holding the round number.

As for the issue with the round text timing being off, I figured that had to do with the way the coroutine to animate the text was setup. It should count up from 0 to the final round number the player ended on (whether they won or lost), but it was starting on that final round number, THEN going to 0 and counting back up again. This just looks really bad, so it needed fixed. From checking the RoundsSurvived script (which is the same for both the Rounds object in GameOver and LevelCompleted), it looked like the animation coroutine was being set to start, and the next line was setting the roundsText to the playerStats.rounds. This is most likely the issue, as the coroutine starts, the text is immediately set to the rounds value as dictated by the very next line, then the coroutine continues running updating the value until it hits the rounds value again. This fits inline with the incorrect behavior we are getting.

PROBLEM 1

The incorrect text being displayed on the GameOver UI.

TEST 1

Move the Rounds (Text) into the RoundsSurvived inspector field as opposed to the Text (text) for the GameOver screen.

SOLUTION: This was all it was, updating this fixed the issue and the text was all in the proper place on the GameOver UI.

PROBLEM 2

Round counting animation starts at the max value, then immediately goes to 0, THEN counts up to max value again.

TEST 1

I removed the line right after the one initializing the AnimateText coroutine that was setting the rounds text to the rounds value.

SOLUTION: This appeared to solve the issue with some testing. Both the GameOver and CompletedLevel UI would start the round counter at 0, then count up to the correct number of rounds survived.

NEXT STEP:

Make 5 uniquely distinct levels to test the overall system. This includes testing generic gameplay as well as the general level progression and level selection system. As I am using a basic built in Unity way to handle saving progression right now, I have had all the levels unlocked for recent testing. I need to reset that and test that that is tracked properly, especially over several levels to make sure the system holds that many as well as giving more data points for any possible issues.

Tower Defense Project – Testing Losing States

August 28, 2019

Tower Defense Tutorial Project

Testing Losing States

I believe all the moving parts for my scene management system are finally starting to come together and work properly for this project. The losing conditions are the last state I really need to check with. I just need to make sure it properly ends the game when the player loses, does not allow them to progress, displays the proper UI elements, and moves between scenes from the losing state correctly.

GENERAL TESTING

Test #1:

The first test was going to Level 1 from the level select menu and immediately losing.

This seemed to work fine. The game stops when the player loses, and the game over UI comes up. The UI text does not seem to be working properly, so I will have to adjust that. It says the player survived 25 rounds, which is just the default value. I need to tie this into the script controlling the round count.

I think tried Retry and Menu from this losing state and both seemed to work properly. Retry started the level again (and actually reset all the values and waves as well) and Menu took me back to the level select menu.

Test #2:

Open level 2 from the level select menu and immediately lose.

This ended up having the player immediately lose and show the gameover screen as soon as the level loaded. I believe this has to do with how I arrived at the level. I was at the level select menu from the Menu button on the game over (losing screen) of the previous level (Level 1). I think going from losing, directly the the level menu, directly into another level, is missing a reset somewhere.

I did try Retry as soon as I lost level 2 and this reset the level just fine. I also tried selecting Menu and going back to the level, and this only worked properly sometimes. I believe it may have had to do with the timing of the wave.

The issue is that lives in PlayerStats gets set to <=0 to meet the losing condition, but does not get set to a proper value on level start in time. The Base scene loads in, then the Level scene. The Base scene has a check on player lives to see if they have <= 0 for a game over, and the Level scene has LevelInformation which is responsible for setting values like lives. So lives is being checked before being set. This was worked around initially by starting lives at 1 instead of 0, and then anytime the player won they had more than 0 lives so checking a value greater than 0 gave no issue.

POSSIBLE SOLUTIONS: Test #2

My idea to fix this timing issue of the gameIsOver check among other values (like the number of lives) was to move the gameIsOver bool to the GameManager. The GameManager is in the Logic scene, which always persists. This is also where PlayerStats resides, which holds the player lives. My thought with this was that I could have both the gameIsOver bool and the lives be set from the same place at the same time, and with them being in the same exact script, checking against them should be more consistent. Basically, since the game has to be started (gameIsOver == false) as well as having your lives <= 0, if any way of ending the game sets gameIsOver == true, we can make sure gameIsOver is not false until we also set lives first.

SOLUTION: This approach worked (for the most part). I reworked the LevelInitializer script in the Level scene to call StartGame in the GameManager, which just sets the gameIsOver bool to false. LevelInformation is also in the level scene, and this is responsible for setting the lives. This ensures the same scene is setting both the lives and the gameIsOver to false (starting the game). Now the LevelManager checks with the GameManager to see if the gameIsOver is true/false, as well as PlayerStats to see how many lives there are. This basically passes all the value setting and value checking to objects in the Logic scene, which makes sense since it is the omnipresent scene for the project.

This did lead to one issue where losing and retrying the level was not turning the CameraController back on.

PROBLEM: CameraController Bug

The CameraController was not coming back on when losing a level and attempting to retry it.

It turns out that going into a new level was fixing it since the CameraController works by default when loading a new scene, but when the game enters an end state (when we turn off the controller) and we keep the same scene (it’s in the Base scene) like when you call Retry, then it does not come back on.

Test #1:

SOLUTION: Since activating the camera controller was another thing that should happen on level start, I wanted to tie it into the LevelInitializer script. This already had a reference to the LevelManager from my previous attempts at creating Start/End game methods, and the LevelManager had a reference to the camera controller since it was initially solely responsible for controlling the game camera. Using this existing setup, I had the LevelInitializer script go through the LevelManager to enable the CameraController script on the camera object. The LevelManager simply acted as a holder for the camera object and the method for enabling it for this purpose (disabling the camera controller was still handled by the LevelManager when it determined the game had ended).

This ended up working well and gave no errors as far as I could tell.

FINAL NOTES:

With fixing up this last issue, all states and scene transitions should basically be accounted for. Continue, Retry, and Menu all work during gameplay (paused); Continue, and Menu work after beating a level; Retry, and Menu work after losing a level. This should finally be the end of the majority of the scene management work.

NEXT STEP:

The game over UI text is still weird, so I need to fix those references. Also along those lines, it’s only a minor issue, but the game winning text starts at the final round number and THEN counts up again from 0 to the final round number. It isn’t game breaking, but it looks silly.

Tower Defense Tutorial – Fixing “Continue” Scene Errors

August 26, 2019

Tower Defense Tutorial Project

Fixing Scene Transition Between Levels When Continuing

I was able to clean up the scene management enough last time that going through the basic starting game scenes all worked well as far as I could tell. I could get from the main intro scene, to the level select screen, to level 1 without any issues. Progressing to level 2 was not working because of some reference with the enemy spawner object, so I will be looking into that.

PROBLEMS

PROBLEM #1

Level 2 does not load after playing Level 1 because of some reference issue for setting the spawn object.

The error was actually that spawnPoint in WaveInformation was not assigned, so I looked into that and saw that there was a public field for drag/dropping the spawn point object into that I just hadn’t done in Level 2.

SOLUTION: Just setting this public field with the spawn point allowed the level to work properly.

General Testing

I edited Level 2 a bit more just to make sure the data was all being passed over to the WaveSpawner. I made sure to move the starting spawn point just in case that wasn’t being updated and it just worked out visually since they were spawning from the same place as before. I also changed up the map layout and moved the waypoints around to fit this new layout. Running this updated level layout worked as expected, everything was properly updated when opening Level 2.

I had not tested using the Continue button from Level 1 since Level 2 was not working in any capacity until now, so I tried that now. This seemed to open Level 2 fine and ran the level, but it did not end when the last enemies were destroyed. I then tried using the pause menu to retry the level and this gave an error saying the SceneManager tried to SetActive an invalid scene.

Going to Level 2 from the level select menu seems to give no errors. The game ends with the player winning as expected, and the Retry and Menu options from the pause menu both work as intended. Something is being messed up when transferring directly from Level 1 to Level 2 with the Continue button function.

PROBLEM #2

Using the Next level option on the level complete screen is giving some errors. Most notably, the level complete screen does not come up on the next level when the player completes it, and using Retry on the level returns a scene error.

The Retry level error makes sense. The SceneManagerLite holds a reference to an int called currentLevelBuildIndex that is supposed to represent the scene index of the currently loaded level scene. This however is not being properly set when using the NextLevel method, so the RetryLevel method in SceneManagerLite refernces the currentLevelBuildIndex to determine which scene to load, and is actually seing the last level’s index here. So what is happening is that it is loading the last level scene, then unloading it, and then trying to set it active. Since it is now unloaded, there is nothing to set active resulting in the error.

I believe the game not ending is as simple as the GameIsOver bool in the LevelManager not being reset. This is initialized on false, and set to true when the level does end. However, going straight from one level to the next never unloads the Base scene which contains the object holding the LevelManager, so that is never reset to false.

Test #1:

Two fold test: Properly set the currentLevelBuildIndex within the NextLevel method (in a way similar to the SelectLevel method); also reset the GameIsOver bool in LevelManager when using the NextLevel method.

Editing the NextLevel method in the SceneManagerLite was simple enough to just add setting the currentLevelBuildIndex to the proper value, but figuring out exactly where to reset the GameIsOver bool was a bit tricky. I wanted to place it somewhere where the reference to the LevelManager fit in cleanly, but it also needed to be somewhere that could set the value after calling a method like RetryLevel or NextLevel from the SceneManagerLite. (RetryLevel may be important for when the game ends from the player losing the level, or if we want to add a retry option even on completeing the current level). Setting it too early might cause issues where the game tried to end again while loading/unloading scenes.

SOLUTION 1: Setting the currentLevelBuildIndex in the NextLevel method of SceneManagerLite fixed problems loading the scene using NextLevel, as well as fixing the menu errors in game. Now using Retry and Menu after loading into a level with NextLevel work properly.

On the other end, I tried to fix the GameIsOver bool by creating a new script called LevelInitializer and placing it on to the LevelInformation object found in level scenes. This script simply attempts to get a reference to the LevelManager (which now has a static method and reference to be gotten) and ensure the LevelManager is properly reset when a level starts. This is done in the new LevelInitializer script’s Start method, the thought here being that this should be called anytime a level scene is loaded, which is exactly when we want to ensure the LevelManager is reset. However, the game still was not ending after continuing from level 1 to 2.

Test #2:

I only needed to fix the second part of the problem now, and it seemed to be a rather easy fix. It turns out, since the LevelInformation object I added the LevelInitializer script to wasn’t a prefab (it is now thanks to this issue), I only added LevelInitializer to Level 1. So Level 2 did not have this script at all, so it had no way to run the new reset method I created.

SOLUTION 2: I just needed to actually add the LevelInitializer script onto the LevelInformation object in every level scene. Doing this reset the LevelManager as intended so the player can now win when they use the Continue menu option on the level complete screen.

NEXT STEP

The scene management is almost completely finished and looking to work pretty well. The main process to test next will be losing conditions, since I have not checked that in a while. This may have a few kinks that I need to fix out since it has not been through the same testing as winning and progressins, but it should hopefully be some quick easy fixes if any.

Tower Defense Tutorial Fixing More Scene Timing Issues

August 24, 2019

Tower Defense Tutorial Project

Fixing Problems with the Scene Manager

A major issue I may have to deal with is updating the version of this Unity project. To clean out space on my computer, I wanted to condense the versions of Unity I was using on several projects so I removed one of the older 2018 versions I was using originally for this tutorial. I wanted to upgrade it to a 2019 version, so I just used the most recent one I had. I don’t believe this should have very major impacts on this project, but it is worth noting for any extra strange behavior or errors.

First things first, I need to do a bit of play testing to get a feel for the current errors and go over the main scripts (mostly those dealing with scene management) to see where to start. The opening scenes seem to work well (the initial main scene into the level select menu). Selecting level one works ok. Any other level leads to weird errors (not properly closing other scenes so lights add up and UI elements stack on top of each other), which is expected since my work focused on getting level 1 to work first. Retrying while a wave is spawning leads to some interesting behavior (it spawns the second wave instead of the first when the level restarts).

To make it easier to keep track of all the different scripts, I started organizing the scripts folder hierarchy. This began with simply creating any hierarchy at all, as initially I just had all of the scripts in the scripts base folder. While it may be a bit risky since I do move things around from time to time at this stage, I decided it would make sense to make a folder for each scene and organize the scripts by scene basically at the first level. This would help me see which scripts are running in which scenes simply by checking the folders. And when I am in a certain scene, I can just open its corresponding scripts folder to see what all is available.

This was actually a very straight forward way of placing a lot of the scripts. Many of them pretty directly only exist in one scene that makes sense. Some of the more instantiated object related scripts (like for the turrets and the enemies) pretty clearly made sense to just put in the Level scene folder since that’s where they should be instantiated so they were easy to place as well.

PROBLEMS

PROBLEM #1

Restarting a level while the first wave is still spawning will have the second wave spawn first on restart.

This is most likely an error that happens because restarting while the coroutine is running for spawning a wave has the value reset that happens when restarting overridden by the coroutine then ending. Checking the WaveSpawner script affirms my assumption since it has a method called ResetWaveSpawner, which sets the waveIndex to 0 (the first wave) but the coroutine spawning the wave, called SpawnWave, increases the waveIndex by 1 once it is finished running. The coroutine is most likely “finishing” after the reset is happening, so the index gets pushed to 1 immediately (insted of being at 0 as it should).

Test #1:

Add a check to ResetWaveSpawner to see if the SpawnWave coroutine is running and stop it before resetting.

This is a perfect opportunity to try the new way I learned to control the flow of coroutines. Instead of using an outside bool variable that is set to true when the coroutine starts, false when it ends, and checking later if that bool is true/false to stop the coroutine, you can control an IEnumerator reference. You first create an IEnumerator reference variable. You then set this equal to the coroutine you want to start right before starting it, then Start this reference variable as your coroutine. When you want to check if it’s running or not later to determine if it needs stopped, you can just do a check to see if this reference variable is not null and then stop the coroutine.

SOLUTION: This worked perfectly. Restarting at anytime (even immediately when the wave starts) has the proper wave spawn. This also stopped the case of enemies continuing to spawn (which was the same issue of the coroutine running into the next scene start, it was just a longer coroutine in that error).

PROBLEM #2

The level does not end when the player defeats the final wave.

The LevelManager script that ultimately starts the “winning process” has a lot of conditions to start this process. The if statement checking in Update to see if the player has won has 4 separate conditions, so there is a high chance one of these is not acting as it should. This is a good place to start looking for why this error occurs. The conditions are as follows:
if (waveSpawnerReference.isActiveAndEnabled && waveSpawnerReference.WavesFinished() && waveSpawnerReference.enemiesAlive == 0 && GameIsOver == false)
{
WinLevel();
}

waveSpawnerReference.WavesFinished() is a method that does a bool check in the WaveSpawner script, so my first thought was to check there and make sure it was being set properly. I simply tested this condition with GameIsOver == false to prompt winning to see what this did. This resulted in the player winning immediately on the level starting. I then remembered, this is a scene loading timing issue reflected in the WavesFinished method check. This method is:
public bool WavesFinished()
{
if(waveIndex == waves.Length)
{
wavesFinished = true;
}
return wavesFinished;
}
The reason this checks as true immediately is because the method gets called before any of the values are set, so the waveIndex is 0 by default (which is accurate) but the waves.Length, which should be the count of how many waves are in the level, is also defaulted to 0 before being set. This results in this being true at frame one.

Looking at the WaveSpawner, I see why these conditions are not being met simultaneously. The WaveSpawner has an Update check to see if the waveIndex == waves.Length to indicate that the last wave has been spawned, and if this check is true, if disables itself. So the condition including a check for hitting the end of the waves AND seeing if the WaveSpawner is enabled will never be true simultaneously.

Test #1:

Create a DeactivateWaveSpawner method which will allow me to deactivate the spawner with the LevelManager AFTER performing the check to see if it’s active for winning (and just after calling the losing method for continuity).

I also had to tweak the WaveSpawner script a bit since it was in charge of deactivating itself as soon as it spawned the final wave. I wanted it to stay active until the player won or lost, and then have those methods deactivate it. This method however gave me very interesting errors. It still was having the player win immediately the first time I selected level 1, but every subsequent time I selected level 1 from the level select menu, it worked perfectly fine. It started properly and allowed the player to win correctly.

Once again, this is an annoying timing issue. The WaveSpawner actually starts enabled in when starting the game from scratch in the Logic scene. The WaveInformation script in the tested Level scene contains the following:
private void Start()
{
waveSpawnerReference = WaveSpawner.GetInstance();

// Reset the wave spawner each time a level loads
waveSpawnerReference.resetWaveSpawner();

PassWaveInformation();
waveSpawnerReference.enabled = true;
}
What’s happening is that the necessary values are being passed to the WaveSpawner through the PassWaveInformation method and the game is immediately ending with a win and then deactivating the WaveSpawner. Now on returing to level 1, the values needed are already set, and these statements before the final statement which enables the WaveSpawner are fed into nothing (since the WaveSpawner is disabled at that time). Now however, anything that checks the values of WaveSpawner will be checking the correct values as intended since they were set way back when the level was previously started. This is removing any frame perfect timing check errors since everything is set before the WaveSpawner is even turned on, but only when starting the level after the first run.

Test #2:

Edit the conditions in the WavesFinished method in the WaveSpawner to be more accurate.

SOLUTION: The general idea is that this is basically returning a false positive check by immediately checking the default value to meet its only condition, so I am just going to add another check to make sure a default case that doesn’t make sense to ever happen anyway isn’t what one of the current values is set to. So basically I changed the condition from:
if(waveIndex == waves.Length)
to
if(waves.Length != 0 && waves.Length)
No level should ever have 0 waves in it anyway, so this check is safe for my game environment and removes the false positive check with the default values where everything is initially at 0.

Using this method worked perfectly, and may also serve as a useful secondary check regardless to reduce the chances of future wave spawning bugs.

PROBLEM #3

The spawn coroutine is still giving some errors in some scene change circumstances.

The main issue with the spawn coroutine was happening when retrying a level, so I specifically fixed a method which was called at the start of loading a level scene so that it would stop the spawn coroutine. However, Unity still doesn’t like that this coroutine continues in other scene swap scenarios (like going back to the menu). It isn’t a game breaking error, but it still shows up as an error in the logs as the game wants to complete the coroutine but cannot because certain objects are not gone (or destroyed according to Unity). This is not a big issue, but I thought it was an interesting enough problem to record as I basically want a consistent way to stop this coroutine when the scenes change.

My first thought was to have a StopCoroutine call in an OnDisable method since that’s a pretty general practice, but unfortunately that does not work here. It would be in the WaveSpawner script which always exists since it’s part of the logic scene. On second thought, this should work because the WaveSpawner should be disabled anytime we leave the Level and Base scene anyway. Fixing that may lead to a nice solution for fixing this error.

Test #1:

Use an OnDisable method in WaveSpawner to stop the SpawnWave coroutine, and follow this by making sure anytime we leave the Level/Base scene we disable the WaveSpawner.

SOLUTION: I looked to the LevelManager and OverlayCanvasManager in the Base scene as these together deal with all of the UI interactions that lead to leaving the current Level and Base scene combination. The main issue was that using Retry and Menu were not deactivating the WaveSpawner, so I looked to add this deactivation into the OverlayCanvasManager where these button functionalities reside.

Since LevelManager already has a reference to WaveSpawner and was deactivating it, I actually just made the WaveSpawner deactivation into a public method, DeactivateWaveSpawner, which called the WaveSpawner’s DeactivateWaveSpawner method, and gave the OverlayCanvasManager a reference to the LevelManager so it could just use this method. I’m not sure if this is better, but it seemed silly to have two seperate references to the same object for the same reason when these two scripts would always be on the same core object anyway.

I then noticed that all my small methods for switching scenes (Retry, Menu, NextLevel) all called the ResetOverlayUI method, which just sets all the UI elements in the level to inactive before switching scenes just in case they would stick around for any reason. Since they all called this method, I just added the DeactivateWaveSpawner reference here, to the ResetOverlayUI method. Now they all deactivate the WaveSpawner before shifting scenes.

This appeared to work, as the error stopped coming up. I even tested with a very large amount of enemies in one wave just to ensure it should still be running after switching scenes had it not been actively stopped. This also meant I could remove stopping the coroutine from the waveSpawnReset method in WaveSpawner without any issue. This was the method referenced by WaveInformation I initially used to solve the weird double spawning issue when restarting a level. This problem is now more cleanly solved as well.

NEXT STEP

Everything seems to work cleanly with Level 1, so the next step is just making sure any level works.

Tower Defense Scene Management Issues

April 25, 2019

Scene Management Solutions

Identifying Issues and Possible Solutions

Scene Conflict
  • There are two scenes that load at the same time that have scripts within them that want to reference scripts within the other or both reference the Logic scene which needs to be updated simultaneously
  • Even though they should ideally be loading at the same time, one is always loading ‘just before the other’
  • This causes the first scene loading to miss its references since the other scene does not exist to it yet when it goes to get those references
  • The second scene loading in does however load its references properly
  • I’ve tested switching the order of loading and the second scene functions properly consistently, while the first scene has errors
Current Issues
  • Level scene loading second to make sure the nodes can get the BuildManager reference
  • However, the Level scene sets the number of lives the player has (LevelPlayerStats passes lives in to the PlayerStats script in the Logic scene)
  • The LevelManager in the Base scene checks the player lives to see if it should end the game
  • Since the Base scene is loading first, it checks and sees there are 0 lives before LevelPlayerStats can set the correct amount of lives for the level
  • This is setting the game to Game Over immediately
Proposed Solutions

Find way to time events: Setup the scene manager system in a way that it makes sure everything is loaded before attempting to setup references. Functionality of the game also needs to wait some time to make sure the newly loaded scenes have time to set the values necessary in the Logic scene before actually starting so they don’t try to work with default values instead.

Move references: Currently the issue is that no matter which scene loads first, there are reference timing issues with one of them. This could be evaded by placing all of the references that should be done first into the same scene and just making sure that scene loads first.

I believe the timing control solution is the better and more powerful solution, as this will help anytime I really need to have multiple interscene references in the future. That is also the much more difficult solution though. I may go with the simpler solution for now just so I can continue experimenting with the more game design heavy concepts of working on the tower defense game and then come back to figure out the stronger solution later.

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!

Unity Scene Management – Finalizing Scene Loading/Unloading Process

March 19, 2019

Advancing Tower Defense Tutorial

Altering Scene Format

Myriad Games Studio – HOW TO USE THE UNITY SCENEMANAGER

Now we are going to look at fixing our scene loading so that occur with better timing and in the right order. The fading animation timing also needs fixed to work with this new scene loading setup.

We want to look at ordering these operations as: complete animation, complete loading next scene(s), unload current scene. Because we are looking to do several methods in order and only want to proceed when the previous is finished, my first thought was to make them all coroutines and have an overall IEnumerator run them with yield return. As far as I’ve seen, yield return can be used to ensure a coroutine finishing processing before starting the next action, as scene in the SceneLoaderAsync script I am following from Myriad Games in a previous blog post.

I started by removing any scene loading from the FadeOut coroutine so it was strictly dealing with fading out the screen. I then typed out the general scheme I was looking to follow with the timing of my coroutines:

  • IEnumerator Fade Out
  • IEnumerator Load Scene
  • IEnumerator Unload Scene
  • IEnumerator Fade In

This was the general order of actions I was intending to create. The screen should fade out, the next scene(s) should load in, the current scene(s) should unload, and then the screen should fade back in.

I started with a modified version of the SceneLoaderAsync script that I just called SceneLoader. I changed the input parameters of everything from string to int to work with build indices, and I modified LoadScenesInOrder by removing the part on using a loading screen for now as well as adding a line at the end that sets the loaded scene as active. I also added a coroutine that was UnloadSceneRoutine that was just a very minimalistic script to use UnloadSceneAsync method:

private IEnumerator UnloadSceneRoutine(int sceneBuildIndex)
{
SceneManager.UnloadSceneAsync(sceneBuildIndex);

yield return null;
}

Finally I put all of the SceneLoader script into my SceneManagerLite script for now since this is basically what it should be in charge of, and referncing coroutines from another script was giving me more issues than refencing methods, so this was easier for now. This gave me the 4 general coroutines I was looking to set up now.

I then went to test this with my Play method (which determines what scenes should be loaded/unloaded when the player presses Play from the main menu). The SceneLoaderAsync script just put their main coroutine into a method that was solely responsible for starting the coroutine, so I attempted to emulate that concept with my first attempt just to see if it would work. I had the LoadScenesInOrder and UnloadSceneRoutine just in their own methods and had them in the list of 4 actions I wanted to process, so it looked like:

IEnumerator PlayRoutine()
{
yield return StartCoroutine(FadeOut());

LoadScene(levelSelectorBuildIndex);

UnloadScene(mainMenuBuildIndex);

yield return StartCoroutine(FadeIn());
}

This was then just started in the method called Play(), so it would stay correctly tied to the button if was already attached to. I also wasn’t sure if this would be necessary to attach it to the button in the first place as well.

This setup almost worked, but the timing was still a bit weird. Just as the Fade In would happen, you would catch a glimpse of the scene to be unloaded before the actual scene being loaded in would appear. So I then just tried using all coroutines with yield return to see if that helped with the timing:

IEnumerator PlayRoutine()
{
yield return StartCoroutine(FadeOut());

yield return StartCoroutine(LoadScenesInOrder(levelSelectorSceneBuildIndex));

yield return StartCoroutine(UnloadSceneRoutine(mainMenuBuildIndex));

yield return StartCoroutine(FadeIn());

}

And this worked perfectly! Everything looked nice and orderly for my very low load time scene transition. From this, I could see larger scenes might present an issue since the screen would most likely just be black for a while, but for now this was a decent solution. Now I would also need to update my other scene transition methods but thought they would be pretty repetitive so I wanted to look into creating a single scene transition method to make working with these easier.

If I was going to be loading and unloading a single scene every time, this would be pretty straight forward to setup, but there are times where I need to load and unload more scenes than that. The method, called SceneTransition, would simply run the coroutine, SceneTransitionRoutine, which had a template similar to the one seen above in PlayRoutine, except that I added a foreach loop for the loading/unloading sections to go through an array to deal with the variable amount of scenes to deal with. This ended up coming out as this:

private IEnumerator SceneTransitionRoutine (int[] buildIndicesToBeLoaded, int[] buildIndicesToBeUnloaded)
{
yield return StartCoroutine(FadeOut());

foreach (int buildIndex in buildIndicesToBeLoaded)
{
yield return StartCoroutine(LoadScenesInOrder(buildIndex));
}

foreach (int buildIndex in buildIndicesToBeUnloaded)
{
yield return StartCoroutine(UnloadSceneRoutine(buildIndex));
}

yield return StartCoroutine(FadeIn());
}

This obviously would then have the SceneTransition method require int arrays as parameters as well. This solution is not amazing, but it ended up working for me for now. Now the methods connected to the buttons need to have their own int array variables that will just add the scenes to be loaded into one array, and scenes to be unloaded into a separate array. This feels a bit weird since this small project generally only deals with one scene at a time, sometimes with two, but I wanted to try and set something up that could handle more cases in the future. The Play() method was now:

public void Play()
{
int[] scenesToBeLoaded = { levelSelectorSceneBuildIndex };
int[] scenesToBeUnloaded = { mainMenuBuildIndex };

SceneTransition(scenesToBeLoaded, scenesToBeUnloaded);
}

And this also worked! I think this solution will be what I use going forward for the scene management for updating this tower defense tutorial. It’s not perfect, but it gets the job done and allows me to use a multiscene setup.

Last update, so this did give me a bit of trouble working with multiple scenes when determining the active scene. It wasn’t enough to just have the scene I wanted to be active just be the last value in the array, so I added a more controlled way to determine the active scenes. I moved the SetActive method from the loading method, to the overall SceneTransitionRoutine. This would be processed after the scenes were all loaded and unloaded. It would also take an input that was an additional parameter added to the SceneTransitionRoutine parameter list that was a single int that determined what build index would become the active scene. This resolved that issue.

Unity Scene Management – Timed Loading and Build Indexing

March 17, 2019

Advancing Tower Defense Tutorial

Altering Scene Format

Youtube – Unity- Scene Manager and Keeping Objects when Loading/Unloading

By: Egon Everest

Myriad Games Studio – HOW TO USE THE UNITY SCENEMANAGER

I continued to updated the scene management of my tower defense tutorial. One of the biggest additions was that I did decide to create a single location for all of the OverlayCanvas button methods I wanted to use. This was just a script called the OverlayCanvasManager that I added to the GameManager object. This just made sense with how often different UI buttons would do similar actions, like going to the menu. This script then became the sole reference point between the Base Scene and the SceneManagerLite object in the Logic Scene.

As I was working with the references between the OverlayCanvasManager and the SceneManagerLite, it became more clear that the SceneManagerLite should still directly handle all of the actual scene management. The OverlayCanvasManager would then become the reference to use for the OverlayCanvas buttons (since I needed an object to drag in the inspector for the buttons, but that has to be within the same scene), and would basically tell the SceneManagerLite what to do based on what buttons were pressed.

Having the SceneManagerLite be directly responsible for everything scene related also made more sense for holding variable values. Now instead of several scripts needing to have information about different build indices for scenes like the Main Menu or the Current Level, this information could just be kept in this one location and updated much more easily.

PROBLEMS

The first main issue I was having with this setup is that the loading times of the scenes appear to be off. Sometimes the scenes would overlap because the next one would be finished loading before the previous was unloaded. I need to be able to perform some sort of check to do one after the other, or have some simple UI setup to block the process until everything is properly loaded.

The next big issue I had was debugging all of these new scene transitions. I updated every instance of transitioning between scenes, so making sure these all went to the right place took some time. I also had an issue where using the Menu or Retry methods from the OverlayCanvasManager (which tied in to the Menu or RetryLevel methods of the SceneManagerLite) was unloading my Logic Scene and just loading unintended scenes. My debugging led me to believe that this was an issue with the data transfer from the Current Level Scene to the SceneManagerLite.

As part of having all of the build index information be in the SceneManagerLite, I needed a way for it to know the build index of whatever the Current Level Scene was. I could not get this information from anything in the Base Scene, as this has its own build index. To solve this, I created a very simple object in the Level Scene, SceneInformation, that would have a script, SceneInformation, that set the currentLevelBuildIndex value in the SceneManagerLite to this object’s scene build index at Start. This was not happening however, as when I Debugged the Menu method, it was showing me that the currentLevelBuildIndex was 0. This showed me why the Logic Scene was being unloaded, and that this value was not being updated, as it was remaining at its default of 0.

I think what is causing the issue is that I am using the SceneManager GetActiveScene method, and this may not be doing what I thought it did. This is the suggested method to get your scene’s build index normally, but this may not be the case when you have multiple. If there are multiple, the ActiveScene may not necessarily be the one the object is in.

NOTES

From the problems I ran into, I had to do more research on the SceneManager for Unity, specifically what an active scene meant. The active scene is the scene that objects are created in whenever objects are instantiated. So if you have multiple scenes open, instantiated objects do NOT automatically appear in the scene of the script instantiating them. Everything from any scene that is instantiated will go to the one designated active scene. When you add scenese additively, the active scene does not change.

With this information, I updated my SceneManagerLite so its transition would now set the newly loaded scene as the active scene as well. This makes sense as I don’t want the Logic Scene to be where objects are instantiated, so immediately making newly loaded scenes active removes it from being the active scene. This also helped my issue with finding the build index of the Current Level scene. It’s obvious I want that to be the active scene when the player is in game, and now using the SceneManager method GetActiveScene will properly return the build index of the Current Level scene itself.

This ideally works in theory, but I ran into a new issue where I could not set the newly loaded scenes as active because Unity did not think they were loaded yet. I got the error message:

ArgumentException: SceneManager.SetActiveScene failed; scene ‘Level01’ is not loaded and therefore not set to active

This seemed to be caused by an issue where loading scenes takes a bit of time, so it wasn’t loaded when Unity was calling to set the scene to active. I investigated a solution with IEnumerators for this.

Looking into IEnumerators to deal with the timing issue for setting my active scene led me to this great scene management source by Myriad Games Studio. They had the script written out for a clean asynchronous scene loader. I used this as the basic foundation for a Scene Loader of my own. I needed to tweak it some for my additive scene needs, and I decided to not use the Singleton class they used for now just to keep it simpler. This now set the active scene specifically after loading the scene, so this solved that issue! Now however, I found out it was loading the wrong scene index.

The reason I was loading the wrong build index now with my Menu and Retry methods is because the SceneInformation script that was getting that active build index at Start was returning a value of 1. I had it return the active scene build index in Update to see if it ever changed, and it would correctly change to 4 on the second frame. Not only did it return 1 in Start, it also returned 1 on the very first run of Update. Then every other instance was the proper 4. This has something to do with the fact that when I load a scene I set it active as well, so there is some delay between creating the Base Scene and creating the Level Scene where Start functions in the Level Scene run before the Level Scene is set to active.

The fact that I was loading in to the proper level though gave me an idea to fix it. The fact that the right level was initially loading meant somewhere had the build index correct, and that was the Level Selector. With this information, I simply looked into the SceneManagerLite LevelSelect method and set the currentLevelBuildIndex in there to be the value of the index that is passed in. This ensures that whatever index is loaded for the level will also be the index used for the other methods, which actually makes sense.

Unity Scene Management – Creating Project SceneManager

March 16, 2019

Advancing Tower Defense Tutorial

Altering Scene Format

Youtube – Unity- Scene Manager and Keeping Objects when Loading/Unloading

By: Egon Everest

Following in the footsteps of the FAR team from a Unite 2017 talk, I wanted to update my scene management in the tower defense tutorial to match that from the talk. To begin, I wanted to create a general Logic Scene to hold all of my managers and a Base Scene to hold general assets consistent between levels.

To start I needed to break up my current level scenes into their various parts since these will have all of the basics for every scene we are attempting to construct…

Since the Scene Fader will be used constantly as a way to transition between any and all scenes, this was included in the Logic scene that will persist the entire time the game is open. To make this accessible to all the other scenes and scripts however, I chose to have its script create a public static instance of itself. This was copied directly from the way the BuildManager was setup (creating a simple sort of singleton reference from the Brackeys tutorial):

public static SceneFader sceneFaderInstance;

private void Awake()
{
if (sceneFaderInstance != null)
{
Debug.Log(“More than one Scene Fader in scene”);
return;
}
sceneFaderInstance = this;
}

This way I could just go into all the scripts that had a public reference for the Scene Fader to drag into in the Inspector, and have the sceneFader variable to be set to SceneFader.sceneFaderInstance at Start instead of relying on the public reference.

Next I determined that I needed to figure out how to manage loading/unloading these various scenes now. Using basic Unity scene management, my scripts just load one scene at a time and unload all the current scenes. So even though I could test this by dragging in a Level scene and a Logic scene at the same time to make sure the new setup worked, everything would be back to loading one scene at a time as soon as I called a basic LoadScene method. To learn this, I went to look for a new tutorial.

I came across a Scene Manager tutorial dealing with loading/unloading objects in a more controlled manner that seemed perfect for what I was looking to do. There is a Scene Manager method called LoadSceneAsync which gives more options for how you load a scene. You give it a build index as well as a LoadSceneMode.

    LoadSceneMode has two options:

  • Single: the basic way Unity loads scenes where it also unloads all other currently loaded scenes
  • Additive: this just additionaly loads the inserted scene; this does not unload any other scenes

The Additive LoadSceneMode is the first step to accomplishing what I am looking for. At this time, I also decided it may make more sense to add some type of SceneManager object to my Logic scene to hold a SceneManager type script to deal with these functions. I did not want to run into any issues with Unity’s built in SceneManager so I just named all of this SceneManagerLite for now. This would use a similar singleton setup as referenced earlir for the BuildManager and the SceneFader.

Continuing along with this tutorial, this scene management method requires you to unload the currently loaded scene as well when needed. To do this, there was a simple Scene Manager method, UnloadScene. This is currently obsolete however but just needs replaced with UnloadSceneAsync. Similarly to LoadSceneAsync, this requires at minimum the build index of the scene you wish to unload.

So when I started to look into changing all of the scripts to fit within this new scene management scheme, I came across the realization that my Scene Fader is basically my Scene Manager Lite already. Every script that deals with changing scenes acceses the Scene Fader first to perform the “FadeOut” method created, which contains both the simple fade effect as well as the actual LoadScene method constantly being used that I wanted replaced. With this discovery, I renamed my Scene Fader to the Scene Manager Lite and made the necessary edits here. This simplified everything as now I could simply just change the FadeOut method to use the LoadSceneAsync and UnloadSceneAsync methods and it would apply to all the existing calls to that method.

I did end up needing to go through each method call and touching them up because the tutorial set the scene loading method up with a string input, and I wanted to change this to an int input to go with the build index. I figured the overall scene layout and build indices were pretty set at this point, so using build index instead of the scene names would be more consistent and easier to work with in the future as I added levels.

Outside Scene Management

Something important to note is that some of the scripts I wanted to update with the new Scene management would actually be referencing build indices for a scene other than themselves. Each of these are in the OverlayCanvas, which is an object I want to keep in the BaseScene, as all of these objects will remain consistent through every level. These are those scripts:

  • CompleteLevel: deals with functionality of UI menu when player successfully defeats a level
  • GameOver: deals with functionality of UI menu when player fails a level
  • PauseMenu: deals with functionality of UI menu when player pauses the game

This means that when they want to inform the SceneManagerLite of what scenes to load/unload, they will actually want to reference the build index of the LevelScene, not the index of their own scene (BaseScene).

Tower Defense Tutorial – Brackeys – Ep. 28

March 8, 2019

Tower Defense Tutorial

Episode 28 – Winning Levels

Youtube – How to make a Tower Defense Game (E28 WINNING LEVELS) – Unity Tutorial

By: Brackeys
Ep. 28

This tutorial sets up the system for what happens when the player beats a level.

We began by creating a UI panel for all of the UI that should appear when the player completes a level.

Once we had that setup, we looked into creating some functionality for this screen. This started by creating a RoundsSurvived script to just place on any round counting objects (one in the game over and level won screen) to display the text properly. As an added effect, we wanted to animate the round counter to count up from 0 to give it more of an impact than simply displaying the number.

To get this round counter animation effect, we used an IEnumerator for its ability to WaitForSeconds. We created a while loop that would count from 0 to the number of rounds the player survived one at a time, with a WaitForSeconds of 0.05 each time. This provides the rapid counting effect we’re looking for to spice up the round counter.

The tutorial goes over a bug where enemies could die multiple times because of the fact that Destroy(gameObject) is not a very immediate process within Unity. Since our enemy script just checked if health reached 0 or less to determine if an enemy would call the Die method, which would then destroy it, sometimes this would allow it to call the Die method multiple times if it got shot repeatedly in a small time frame. To solve this, we just added a bool isDead, and have the check to call the Die method see if health is at/below 0 AND !isDead to ensure this is only called once (the Die method sets isDead to true).

PROBLEMS

As indicated in the comments for the tutorial video, there is an issue when selecting the Menu button upon completing a level. The Continue method is where the levelReached Player Pref is updated, so if the player simply selects the Menu button instead, the levelReached is never updated so the player will not be able to select the next level in the level selector.

SOLUTIONS

To keep the levelReached and levelToUnlock variable in the CompleteLevel script (as opposed to the Game Manager) and fix this issue in as clean a way as possible, the comment suggested moving these variable updates to an OnEnable method. This is nicer than just adding it to both methods, and keeps it within the same script which is helpful if I intend on making the GameManager into a prefab in the future.

SUMMARY

  • Rule of Thumb: “Bigger” highlight animations for bigger buttons
  • In Unity, if you want your button to be just text, you can also drag the Text element of it into the Target Graphic of the Button (script) so the text is the specifically clickable area
  • IEnumerators are used when you want a method that can be paused to continue at a later time (can be as small as a frame or two)
  • Destroy(gameObject) in Unity is SLOW. This is always a good piece of code to look around if you are having strange bugs/issues.