UnityLearn – Intermediate Programming – Pt. 01

September 30, 2019

Intermediate Programming: Unity Game Dev Courses

Swords and Shovels: Game Managers, Loaders, and the Game Loop

Intermediate Programming: Unity Game Dev Courses

Unity Learn Course – Intermediate Programming

What is a GameManager?

Game Systems

Many parts of your game will need to communicate with one another. You can directly makes these connections between different objects as needed as a way to accomplish this. This method however does not scale well, and becomes very complicated and hard to debug.

This is where the concept of having a GameManager comes in. This provides a central location where systems can communicate through. They can also hold important central data.

The tutorial’s basic definition of a GameManager was “A central location for data” which can serve one of the following purposes or both:

  • Determines who can change what
  • Informs other systems of changes

This Swords and Shovels tutorial will specifically use their GameManager for rule management and some informing.

Persistent Systems

The GameManager needs to be accessible to all the systems that need it. The GameManager should be globally accessible for the life of the game.

Unity Containers

They reference Scenes and Prefabs as Unity Containers. They represent collections of assets and use particular scripts to connect them. In Unity, Scenes are generally large collections of assets, while Prefabs are for smaller collections.

One technique is to have a persistent scene which contains all of your managers, since it will most likely even have responsibilities for loading and unloading other scenes. This is the technique that will be used in this tutorial. This also happens to be the technique I was using on my personal tower defense scene management project.

Preparing to Build a GameManager

You most likely will not be able to design and construct the perfect GameManager for your game immediately. Games are organic objects and you will need to change and adapt your systems as you build in most cases. With this in mind, it is important to build in a way that supports growth (but without over designing).

When starting to make your GameManager then, you want to layout the basic requirements you know it must have. The requirements for this tutorial are:

  • Tracks What Level Is Being Played
  • Can Create Other Global Managers (i.e. Audio manager, Game Save manager)
  • Knows the Current State of the Game
  • Can Cleanup Game Systems (i.e. Save game on quit, send message to server indicating how game ended)

Setting Up Your Scenes

This part finally gets into working with the Unity project. There was a package to download that contained all the necessary objects to get you up to speed to start working at this point.

When you create a script named GameManager in Unity now, it gets a unique icon that looks like a gear. This does not particularly do anything special, it is just a visual to help you identify the GameManager since it is normally a more important class.

This step mostly just wanted to have you get the package loaded in, create the GameManager script, and make a new Boot scene that will handle starting everything (as well as put that scene in the build settings). It also mentions how it is important to put all your scenes in the build settings to make sure they are accessible with your system.

FINAL NOTES

There is a actually a lot going on with the package I obtained for this tutorial, so it may be worth looking into the beginner part of this tutorial before progressing with this part. Even though it will most likely cover a lot that I already know, I think there is still a good bit of valuable information for me to gain from going through it.

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 Tutorial Fixing Scene Manager

Tower Defense Tutorial Fixing Scene Manager

June 19, 2019

Tower Defense Tutorial Project

Fixing Problems with the Scene Manager

To help get back into the groove of working on my tower defense project, I went over some scripts that I thought might help fix problems and listed out how they reference each other. (Lower placed scripts reference the higher ones)

Script Reference List

  • PlayerStats
    • LevelPlayerStats
  • WaveSpawner
    • WaveInformation
    • LevelManager

Then I got working on the problems with the scene manager and the method call timings between the different scenes that were giving me errors. There are a lot of issues when references trying to be called before variables are properly set in different scenes, and this offset is leading to a lot of errors where the checks are checking against default values like 0 and null as opposed to their properly set values. So once again, I made a list of problems as I encountered then and possible fixes I tried, including the ones that finally worked and stuck for now.

SCENE PROBLEMS

Problem

Menu buttons do not work

  • Going back to the level select menu screen is not working on either button, so Menu() method must be incorrect
  • The for loop to deactivate level buttons the player should not have reached yet was given the wrong value to start with when reactivating the scene
    • it was setting a value to the active scene’s index and adding that to the for loop check for the level select button array
    • this value was initializing at 0 the first time through for some reason (another scene timing error), but was then being set to 4 when returning (which I believe is the level scene’s index, so this is another scene timing issue, but being properly set to 3 [the index of the level selection scene] would have been bad as well)
Solutions
  • Solution: Just removed the addition of the current active scene build index all together; I think this was added at some point to process the correct level build indices, but that is no longer needed.



Problem

Retry for a level does not work

  • Clicking retry does unload and reload the current level scenes, but it is done so improperly and the timer does not count do to start the first wave ever
  • Just ever unloading a level scene and trying to load it again has the same problems
    • This indicates something is not being properly reset the second (or more) time the scene is loaded
    • Look to individual Level scene and Base scene (created for all levels), especially in Start type methods
    • EnemiesAlive and WaveIndex are not resetting to 0 when scene is unloaded
      • EnemiesAlive specifically is one of the checks to see if a wave should be spawned (spawns a wave when the value is at 0 to make sure the previous wave was defeated)
Solutions
  • Potential Solution: reset these values to 0 when level scenes are loaded
    • tried this by creating a ResetWaveSpawner method in the WaveSpawner itself, then having something reference this on Start with something that is created on Level load
    • started method in Start method of LevelManager script
      • this actually worked for going back to the menu and then trying the level again, but retry still did not work properly
      • I figured this meant the scenes might be loaded/unloaded differently, so I checked my sceneManagerLite and sure enough, the level selection from the menu needed to load both the Base and Level scene, but retry was JUST unloading/reloading the Leve scene, not the Base scene, which is where the LevelManager is located that is resetting the wave spawner on Start
  • Potential Solution: just have the reset called in the Start method of something in the Level scene itself
    • This did allow the level to start properly when returning to the menu and coming back, or with the simple retry. However, there were still some other problems. Now if you beat a level, every time you returned the game won screen would come up and the game would still play in the background. Leaving the level during play and coming back (by menu or retry) would also change the “Rounds Survived” counter at the end, indicating something with the wave counters was not resetting properly.



Problem

Beating Level Once Beats it Forever

  • After beating level, game won screen comes up immediately when going back to the level
  • Back to our game winning bool checks, appears that the WavesFinished bool is not properly reset when returning to a level. This should be false every time the player starts the level, and get set to true when they win. This is happening correctly, but it is never reset to false.
Solutions
  • Possible Solution: reset the WavesFinished bool in WaveSpawner with proper scene timing
    • Actually just added this to the newly created ResetWaveSpawner method I created to deal with the previous resetting issues and this fixed it



Problem

Wave Spawning Coroutine is Off

  • Resetting a level during the spawning of a wave has the rest of the wave spawn immediately (at the beginning of the newly reset level) as well as spawning the second wave (skips the spawn of the first wave all together)
  • This indicates the coroutine spawning the wave might need manually jumped out of on reset and that the wave index is being messed up by their being enemies remaining

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 Multiple Scenes and Controlling Loading

March 25, 2019

Advancing Tower Defense Tutorial

Moving Objects and References to New Scenes

Catlike Coding – Multiple Scenes and Loading Levels
Catlike Coding – Object Management

The Catlike Coding information on Multiple Scenes and Loading levels has a lot on how scenes are loaded in Unity and how to control that process better. I also included the link to the overal Object Management tutorial that this Multiple Scene write up is a part of. That seems like another useful overall source, that could also enhance the application of this Multiple Scene tutorial.

They start by creating a new scene and moving certain objects into it. After setting this up, they correct an issue they had where recompilation messes up the pool scene. This happens because they are using a scriptable object instead of the default monobehaviour for their script. Unity serializes the private fields of Monobehaviours when compiling, but not for Scriptable Objects. This meant CreatePools would get invoked on every recompilation and you would get errors trying to create the same scene several times.

To protect against this issue, they check if the scene is loaded using the Scene Manager isLoaded method. If that is true, they just return on their created method. This requires getting the scene value first since recompilation resets the poolScene scene struct to its default values on recompilation.

Level 01 – Multi-Scene Editing

Main Scene will be their scene for containing everything to run the game, no matter what level they’re playing. It contains the Main Camera, the Event System, and some other parts. Their basic Level 1 scene just contains the Light for now.

They first explore using the Scene Manager LoadScene() method to load their additional scene. This does not work, as LoadScene first unloads all currently open scenes before loading the requested scene. To load the scene in addition to what is already there, they add the LoadSceneMode.Additive as an argument to LoadScene().

The lighting is still incorrect because they need the Level 01 scene to be the active scene. This can be done in script with the SetActiveScene() method. They try placing this in their method for loading the scene, but it does not work. SetActiveScene only works for loaded scenes, and the loaded scene isn’t technically loaded yet even though it was called previously. This is because loading a scene takes some time.

To get around this, you can wait a frame between loading the scene and setting the newly loaded scene to the active scene. This can be done by making LoadLevel into a coroutine and adding a yield return null command between loading the scene and setting it active. This will give the desired result.

If your game takes a while to load, this simple load command may cause undesired results. This is because the game would basically freeze until the loading is finished. To prevent this, scenes can be loaded asynchronously, using the SceneManager.LoadSceneAsync method. This begins the process of loading a scene and returns an AsyncOperation object reference. This can be used to check whether a scene has finished loading. This can also be used to yield in a coroutine. This is how their example looks:

IEnumerator LoadLevel () {
//SceneManager.LoadScene(“Level 1”, LoadSceneMode.Additive);
//yield return null;
yield return SceneManager.LoadSceneAsync(
“Level 1”, LoadSceneMode.Additive
);
SceneManager.SetActiveScene(SceneManager.GetSceneByName(“Level 1”));
}

This coroutine now waits until the scene is completely loaded to continue its processes, but also does not freeze the game while loading. This however creates another issue where Update be called during the loading of the level. This would be bad for most games as you don’t want the player issuing commands before the loading is finished or the level has appeared. This can be corrected by having the Game component disable itself before starting the load, then enabling itself after finishing loading. This time would also be where you would place a loading screen.

Awake gets invoked immediately when a scene is loaded, but the scene does not count as loaded yet. In Start and later Update calls, the scene is considered officially loaded.

They get into creating another level and go into unloading scenes to prevent their level scenes from piling up as the go back and forth loading new instances of them. They start by making sure to keep track of the current scene build index and updating that whenever they load a new scene (this is similar to the approach I took with my Tower Defense scene management).

They then use the SceneManager.UnloadSceneAsync method to unload the old level index, before loading the next scene. They make sure to yield on the unloading as well before loading the next scene. This is their example:

IEnumerator LoadLevel (int levelBuildIndex) {
enabled = false;
if (loadedLevelBuildIndex > 0) {
yield return SceneManager.UnloadSceneAsync(loadedLevelBuildIndex);
}
yield return SceneManager.LoadSceneAsync(
levelBuildIndex, LoadSceneMode.Additive
);
SceneManager.SetActiveScene(
SceneManager.GetSceneByBuildIndex(levelBuildIndex)
);
loadedLevelBuildIndex = levelBuildIndex;
enabled = true;
}

Applications

I could look into the disable/reenable method used in loading the level for my scene management issue. Since I have objects from different scenes that are both trying to reference something in the other at Start, maybe I could try to reenable the components that need to reference each other at the same time and have them create the references OnEnable.

Make sure Scene Management keeps track of the current build indices loaded and update this on scene loads. This can help when you finally need to unload the scenes before loading the next ones.

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.