Unity coroutine stopping for no reason - c#

I'm making my first 2D topdown shooter game. I'm working on the loading part of the game. I have this functioning loading screen function (a method of my GameManager class) I made. I tried swapping between the main title scene and the first level a few times to test it. After loading the first level, then the title screen, when I try loading the first level back again the load screen is stuck.
The whole thing is working on a coroutine, and after some debugging I figured out the problem was that this coroutine stopped executing in a certain part of the code for some reason.
public static bool isLoadingScene { get; private set; } = false;
public void LoadScene(int sceneIndex)
{
// If another scene is already loading, abort
if (isLoadingScene)
{
Debug.LogWarning($"Attempting to load scene {sceneIndex} while another scene is already loading, aborting");
return;
}
isLoadingScene = true;
var sceneLoader = FindObjectOfType<SceneLoader>().gameObject;
if (sceneLoader != null)
{
// Make the loading screen appear
var animator = sceneLoader.GetComponent<Animator>();
animator.SetTrigger("appear");
// Make sure the SceneLoader object is maintained until the next scene
// in order to not make the animation stop
DontDestroyOnLoad(sceneLoader);
}
else // If a SceneLoader is not found in the current scene, throw an error
{
Debug.LogError($"SceneLoader could not be found in {SceneManager.GetActiveScene().name}");
}
// Unload active scene and load new one
StartCoroutine(LoadScene_(sceneIndex, sceneLoader));
}
IEnumerator LoadScene_(int sceneIndex, GameObject sceneLoader)
{
float t = Time.time;
// Start loading the new scene, but don't activate it yet
AsyncOperation load = SceneManager.LoadSceneAsync(sceneIndex, LoadSceneMode.Additive);
load.allowSceneActivation = false;
// Wait until the loading is finished, but also give the loading screen enough
// time to appear
while (load.progress < 0.9f || Time.time <= (t + LoadingScreen_appearTime))
{
yield return null;
}
// Activate loaded scene
load.allowSceneActivation = true;
// Unload old scene
AsyncOperation unload = SceneManager.UnloadSceneAsync(SceneManager.GetActiveScene());
// Wait until it's finished unloading
while (!unload.isDone) yield return null;
// --- THE COROUTINE STOPS HERE DURING THE 3RD LOADING --- //
// Make the loading screen disappear
sceneLoader.GetComponent<Animator>().SetTrigger("disappear");
// Wait until the disappearing animation is over, then destroy the SceneLoader object
// which I set to DontDestroyOnLoad before
yield return new WaitForSeconds(LoadingScreen_disappearTime);
Destroy(sceneLoader);
isLoadingScene = false;
}
Only during the 3rd loading, right after the while (!unload.isDone) yield return null; line the coroutine just stops executing (I know it because I inserted a Debug.Log inside the while loop and it got called, but I inserted one right after and it did NOT get called).
Any suggestions?

I figured out the problem by myself. The GameManager itself was programmed as a singleton, and since there was a GameManager in both scenes that was messing things up. Had to move the GameManager to a separate scene loaded additively.

Related

Why does Unity C# Coroutine execute upon click of an unrelated button

The script is part of a Unity/Vuforia AR app in which the user is able to place various PreFab models via the Vuforia ground detection system. These Prefabs are loaded into AssetBundles in order to manage memory overhead.
The script to accomplish this is executing prematurely.
This Coroutine script is attached to each button. Upon "onClick" the script is intended to load an AssetBundle containing a Prefab, and then instantiate the loaded Prefab object into the AR world.
The problem currently is that the script is executing upon the click on a UI panel opening button, which enables access to the actual placement UI buttons to which the script has been attached.
I arrived at this script the excellent via input from #derHugo. That thread can be found here: Can a Prefab loaded from asssetBundle be passed to Vuforia AnchorBehavior declaration
Here is the script that is attached to the placement buttons
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using Vuforia;
public class anchorManagerBundles : MonoBehaviour
{
public PlaneFinderBehaviour plane;
public ContentPositioningBehaviour planeFinder;
private AnchorBehaviour model;
public string nameOfAssetBundle;
public string nameOfObjectToLoad;
private static bool alreadyLoading;
private static AssetBundle assetBundle;
void Start()
{
// only load the bundle once
if (!alreadyLoading)
{
// set the flag to make sure this is never done again
alreadyLoading = true;
StartCoroutine(LoadAsset(nameOfAssetBundle, nameOfObjectToLoad));
}
else
{
LoadObjectFromBundle(nameOfObjectToLoad);
}
}
private IEnumerator LoadAsset(string assetBundleName, string objectNameToLoad)
{
string filePath = System.IO.Path.Combine(Application.streamingAssetsPath, "AssetBundles");
filePath = System.IO.Path.Combine(filePath, assetBundleName);
if (assetBundle == null)
{
Debug.Log("Failed to Load assetBundle!!");
yield break;
}
{
var assetBundleCreateRequest = AssetBundle.LoadFromFileAsync(filePath);
yield return assetBundleCreateRequest; assetBundle = assetBundleCreateRequest.assetBundle;
}
private IEnumerator LoadObjectFromBundle(string objectNameToLoad)
{
AssetBundleRequest assetRequest = assetBundle.LoadAssetAsync<GameObject>(objectNameToLoad);
yield return assetRequest;
GameObject loadedAsset = (GameObject)assetRequest.asset;
model = loadedAsset.GetComponent<AnchorBehaviour>();
}
public void create()
{
planeFinder.AnchorStage = model;
}
}
The desired/expected result was that upon clicking the UI button, the selected AssetBundle is loaded and the named PreFab is loaded for placement into the AR world upon tapping the screen.
I added a debug break in order to identify if the Asset Bundle is successfully loaded. The error below is then registered when the unrelated UI panel opening button is pressed, which does not have the script attached, indicating that the script is running prematurely.
Failed to Load assetBundle!!
UnityEngine.Debug:Log(Object)
<LoadAsset>d__8:MoveNext() (at Assets/Scripts/anchorManagerBundles.cs:38)
UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
anchorManagerBundles:Start() (at Assets/Scripts/anchorManagerBundles.cs:23)
Then i proceed to the actual placement button, of course the there is no prefab placed because of the premature execution.
There is no content to place at the anchor. Set the "Anchor Stage" field to the content you wish to place.
UnityEngine.Debug:LogError(Object)
Vuforia.ContentPositioningBehaviour:CreateAnchorAndPlaceContent(Func`2, Vector3, Quaternion)
Vuforia.ContentPositioningBehaviour:PositionContentAtPlaneAnchor(HitTestResult)
UnityEngine.Events.UnityEvent`1:Invoke(HitTestResult)
Vuforia.PlaneFinderBehaviour:PerformHitTest(Vector2)
UnityEngine.Events.UnityEvent`1:Invoke(Vector2)
Vuforia.AnchorInputListenerBehaviour:Update()
The question is why does this script execute prematurely, and thus thwarting the loading of the asset bundle and its selected PreFab
Despite the fact you have a { too much there
your check for
if (assetBundle == null)
makes no sense at this point .. it will always be null so you tell your routine "If the assetBundle was not loaded yet .. then please do not load it".
You should move the check to the end. I personally would then invert the check you had in order to skip the loading if it was loaded already.
Note: You also missed the last line
LoadObjectFromBundle(objectNameToLoad);
at the end of LoadAsset so the object is never loaded for the first instance calling LoadAsset. For the others it actually makes no sense to call LoadObjectFromBundle in Start (sorry my bad for not noticing it the first time). The coroutine is not done yet but they will try to load an object from an assetBundle which wasn't set yet.
So I would change it to
private IEnumerator LoadAsset(string assetBundleName, string objectNameToLoad)
{
// can be done in one single call
var filePath = System.IO.Path.Combine(Application.streamingAssetsPath, "AssetBundles", assetBundleName);
// if at this point the assetBundle is already set we can skip the loading
// and directly continue to load the specific object
if (!assetBundle)
{
var assetBundleCreateRequest = AssetBundle.LoadFromFileAsync(filePath);
yield return assetBundleCreateRequest;
assetBundle = assetBundleCreateRequest.assetBundle;
if (!assetBundle)
{
Debug.LogError("Failed! assetBundle could not be loaded!", this);
}
}
else
{
Debug.Log("assetBundle is already loaded! Skipping", this);
}
// YOU ALSO MISSED THIS!!
yield return LoadObjectFromBundle(objectNameToLoad);
}
private IEnumerator LoadObjectFromBundle(string objectNameToLoad)
{
// This time we wait until the assetBundle is actually set to a valid value
// you could simply use
//yield return new WaitUntil(() => assetBundle != null);
// but just to see what happens I would let them report in certain intervals like
var timer = 0f;
while (!assetBundle)
{
timer += Time.deltaTime;
if (timer > 3)
{
timer = 0;
Debug.Log("Still waiting for assetBundle ...", this);
}
yield return null;
}
var assetRequest = assetBundle.LoadAssetAsync<GameObject>(objectNameToLoad);
yield return assetRequest;
var loadedAsset = (GameObject)assetRequest.asset;
model = loadedAsset.GetComponent<AnchorBehaviour>();
// add a final check
if(!model)
{
Debug.LogError("Failed to load object from assetBundle!", this);
}
else
{
Debug.Log("Successfully loaded model!", this);
}
}
// and now make sure you can't click before model is actually set
public void create()
{
if(!model)
{
Debug.LogWarning("model is not loaded yet ...", this);
return;
}
planeFinder.AnchorStage = model;
}

Move/Transfer GameObject to another scene

We tryed different ways to move UI-object to another scene, but we failed. Over object is in Canvas.
Method 1: we used LoadLevelAdditive, but there moved all objects from first scene without over Canvas with it's elements.
Method 2: we used DontDestroyOnLoad. We need to change our element on Canvas. DDOL save last position on the scene, but we can't change object at all.
Can you get some advice, please?
Thanks.
Don't use Application.LoadLevelXXX. These are deprecated functions. If you are using old version of Unity, please update it otherwise, you may not be able to use the solution below.
First, load scene with SceneManager.LoadSceneAsync. Set allowSceneActivation to false so that the scene won't activate automatically after loading.
The main solution to your problem is the SceneManager.MoveGameObjectToScene function which is used to transfer GameObject from one scene to another. Call that after loading the scene then call SceneManager.SetActiveScene to activate the scene. Below is an example of that.
public GameObject UIRootObject;
private AsyncOperation sceneAsync;
void Start()
{
StartCoroutine(loadScene(2));
}
IEnumerator loadScene(int index)
{
AsyncOperation scene = SceneManager.LoadSceneAsync(index, LoadSceneMode.Additive);
scene.allowSceneActivation = false;
sceneAsync = scene;
//Wait until we are done loading the scene
while (scene.progress < 0.9f)
{
Debug.Log("Loading scene " + " [][] Progress: " + scene.progress);
yield return null;
}
OnFinishedLoadingAllScene();
}
void enableScene(int index)
{
//Activate the Scene
sceneAsync.allowSceneActivation = true;
Scene sceneToLoad = SceneManager.GetSceneByBuildIndex(index);
if (sceneToLoad.IsValid())
{
Debug.Log("Scene is Valid");
SceneManager.MoveGameObjectToScene(UIRootObject, sceneToLoad);
SceneManager.SetActiveScene(sceneToLoad);
}
}
void OnFinishedLoadingAllScene()
{
Debug.Log("Done Loading Scene");
enableScene(2);
Debug.Log("Scene Activated!");
}

Timer keeps continuously loading a scene instead of just once

I wrote a simple timer script, after the timer reaches 0 it loads a new scene. But it keeps continuously loading the scene instead of once not allowing the scene it loads to be played. I just need it to load the scene once.
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using UnityEngine.SceneManagement;
public class Timer : MonoBehaviour
{
public float timelimit;
public Text text;
public void ChangeScene(int changeTheScene)
{
SceneManager.LoadScene(changeTheScene);
}
void Update()
{
timelimit -= Time.deltaTime;
text.text = "TimerText:" + Mathf.Round(timelimit);
if (timelimit < 0)
{
timelimit = 0;
SceneManager.LoadScene(1);
}
}
}
NOTE: your code as described won't cause the issue you describe. I see two possibilities:
your newly loaded scene has the same component in it, which repeatedly loads the scene
you are actually loading the scene additively so this component continues to run after the load.
Assuming the latter, if you follow your code through, you'll see that your if statement has a condition that will always be true once the timelimit is reached:
Every frame you subtract a number from timelimit. Then, if timelimit is now less than zero, set timelimit to zero and load the scene.
If you've set timelimit to zero in the previous frame, and then subtract a number, it will always be less than zero: you'll always load the scene again on each subsequent frame.
Try instead using a boolean variable to track whether you've loaded the scene or not. Or alternatively, destroy the component as soon as you load the scene, so that your code stops running.
If the problem is actually that you have this component in your new scene, too... consider removing it! :-)
EDIT:
Try instead using a boolean variable to track whether you've loaded
the scene or not.
public class Timer : MonoBehaviour
{
public float timelimit;
public Text text;
static bool loadedScene = false;
public void ChangeScene(int changeTheScene)
{
//SceneManager.LoadScene(changeTheScene);
}
void Update()
{
//Exit if we have already loaded scene
if (loadedScene)
{
//Destroy Timer Text
Destroy(text.gameObject);
//Destroy this Timer GameObject and Script
Destroy(gameObject);
return;
}
timelimit -= Time.deltaTime;
text.text = "TimerText:" + Mathf.Round(timelimit);
if (timelimit < 0)
{
timelimit = 0;
loadedScene = true; //We have loaded Scene so mark it true
SceneManager.LoadScene(1);
}
}
}
You're timelimit might not be initialized. Did you make sure it isn't set to 0 in the Unity Editor?

Loading new scene in background

I'm creating a Unity application targeting the Samsung Gear VR. I currently have two scenes:
The initial scene
Second scene, with big quantity of data (it takes too much time to load the scene).
From the first scene, I want to load the second scene in background, and switch to it once it has been loaded. While the new scene is loading in background, the user should keep the ability to move their head to see any part of the VR environment.
I'm using SceneManager.LoadSceneAsync but it's not working:
// ...
StartCoroutiune(loadScene());
// ...
IEnumerator loadScene(){
AsyncOperation async = SceneManager.LoadAsyncScene("Scene", LoadSceneMode.Single);
async.allowSceneActivation = false;
while(async.progress < 0.9f){
progressText.text = async.progress+"";
}
while(!async.isDone){
yield return null;
}
async.allowSceneActivation = true;
}
With that code, the scene never changes.
I've tried this the typical SceneManager.LoadScene("name") in which case the scene changes correctly after 30 seconds.
This should work
while(async.progress < 0.9f){
progressText.text = async.progress.ToString();
yield return null;
}
Secondly, I've seen cases where isDone is never set to true, unless the scene has activated. Remove these lines:
while(!async.isDone){
yield return null;
}
On top of that, you are locking your code in that first while loop. Add a yield so the application can continue loading your code.
So your entire code looks like this:
IEnumerator loadScene(){
AsyncOperation async = SceneManager.LoadAsyncScene("Scene", LoadSceneMode.Single);
async.allowSceneActivation = false;
while(async.progress <= 0.89f){
progressText.text = async.progress.ToString();
yield return null;
}
async.allowSceneActivation = true;
}
The biggest culprit to your problem is the locking in the first while loop, though.

Fading out animated Unity loading screen

I am trying to have a simple animated loading screen between my menu scene and the game scene. I am trying to do this by loading the game scene in my loading scene asynchronously. I also want the loading screen to fade in and fade out.
I got the fade-in to work. However, I have two problems which I have been working on for hours, but without any succes. These problems are:
I cannot get the fade-out to work. I tried setting the 'allowSceneActivation' to false for my asynchronous loading, however this causes the loading to not occur at all. Removing this line makes the game load, but it then lacks the fade out.
The animation works very (and I mean VERY) choppy. I understand that the game is loading stuff, so I expect it to be bad, but it's litterally doing a frame every 2 seconds. I tried using a low thread priority (see code below), but no luck. I found people with similar problems, but the frames turned out reasonable when using a lower thread priority.
This is my code for the loading screen:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class LoadIntro : MonoBehaviour {
private bool loaded;
private bool fadingOut;
private bool loading;
AsyncOperation async;
void Start(){
loaded = false;
fadingOut = false;
loading = false;
Application.backgroundLoadingPriority = ThreadPriority.Low;
}
// Use this for initialization
void Update() {
//wait for loading screen to fade in, then execute once
if (!GameObject.Find ("SceneFader").GetComponent<Image> ().enabled && !loaded && !loading) {
loading = true;
async = Application.LoadLevelAsync(mainMenuButtons.leveltoload);
async.allowSceneActivation = false;
StartCoroutine (LoadLevel (async));
}
//if next scene is loaded, start fading out loading screen
if (loaded) {
GameObject.Find ("SceneFader").GetComponent<SceneFadeInOut> ().FadeToBlack();
fadingOut = true;
}
//when faded out, switch to new scene
if (GameObject.Find ("SceneFader").GetComponent<Image> ().color.a >= 0.95f && loaded) {
async.allowSceneActivation = true;
}
}
IEnumerator LoadLevel(AsyncOperation async){
yield return async;
Debug.Log("Loading complete");
loaded = true;
}
}
I have a seperate piece of code for the actual fading, which the code above calls:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class SceneFadeInOut : MonoBehaviour
{
public float fadeSpeed = 1.5f; // Speed that the screen fades to and from black.
private bool sceneStarting = true; // Whether or not the scene is still fading in.
public bool sceneEnding = false;
public string scene;
private Image fadeTexture;
void Awake ()
{
fadeTexture = GetComponent<Image>();
}
void Update ()
{
// If the scene is starting...
if(sceneStarting)
// ... call the StartScene function.
StartScene();
if (sceneEnding)
EndScene();
}
void FadeToClear ()
{
// Lerp the colour of the texture between itself and transparent.
fadeTexture.color = Color.Lerp(fadeTexture.color, Color.clear, fadeSpeed * Time.deltaTime);
}
public void FadeToBlack ()
{
// Lerp the colour of the texture between itself and black.
fadeTexture.color = Color.Lerp(fadeTexture.color, Color.black, fadeSpeed * Time.deltaTime);
}
void StartScene ()
{
// Fade the texture to clear.
FadeToClear();
// If the texture is almost clear...
if(fadeTexture.color.a <= 0.05f)
{
// ... set the colour to clear and disable the GUITexture.
fadeTexture.color = Color.clear;
fadeTexture.enabled = false;
// The scene is no longer starting.
sceneStarting = false;
}
}
public void EndScene ()
{
// Make sure the texture is enabled.
fadeTexture.enabled = true;
// Start fading towards black.
FadeToBlack();
// If the screen is almost black...
if (fadeTexture.color.a >= 0.95f) {
// ... reload the level.
if (scene == "") Application.Quit();
else Application.LoadLevel (scene);
}
}
}
Does anyone have an idea how to solve the issues described above? I've litterally tried every topic I could find, but none of them seem to work. Building my game did not resolve the issues either.
Many thanks in advance!
You are fading out when scene loading gets done. What are you expecting when loading is done? :)
//if next scene is loaded, start fading out loading screen
if (async.isDone) {
GameObject.Find ("SceneFader").GetComponent<SceneFadeInOut> ().FadeToBlack();
fadingOut = true;
}
Obviously it will change the scene and you code doesn't getting enough time to perform fade-out operation. :)
For instance if consider your point. You wrote,
//if next scene is loaded, start fading out loading screen
if (loaded) {
GameObject.Find ("SceneFader").GetComponent<SceneFadeInOut> ().FadeToBlack();
fadingOut = true;
}
//when faded out, switch to new scene
if (GameObject.Find ("SceneFader").GetComponent<Image> ().color.a >= 0.95f && loaded) {
async.allowSceneActivation = true;
}
in Update. Here your loaded check doing 2 things.
1- Start fading out.
2- Switching scene.
Again, why it should wait for fading it out completely while its getting loaded and you are checking alpha >= 0.95 which should execute at first frame when you get loaded to true, because I believe that in first frame alpha would be greater than 0.95.

Categories

Resources