Coroutine doesn't work in Unity - c#

I need to make a screenshot in Unity. I did it the next way:
public void Capture(){
StartCoroutine(CaptureScreenshot());
}
private IEnumerator CaptureScreenshot(){
GameObject canvas = GameObject.Find("Canvas");
canvas.SetActive(false); // hide all buttons
yield return new WaitForEndOfFrame();
string timestamp = DateTime.Now.ToString("dd_MMMM_hh_mm_ss_tt");
Application.CaptureScreenshot("screenshot" + timestamp + ".png");
Debug.Log("Screenshot was captured.");
yield return new WaitForEndOfFrame();
canvas.SetActive(true); // restore all buttons
yield return null;
}
When I invoke Capture(), canvas hides but neither screenshot takes nor text logs.
What is wrong with this code? Thanks for response.

Because your script is running on a object in the canvas when you do canvas.SetActive(false); that also disables the script, preventing the coroutine from continuing from yield return new WaitForEndOfFrame();.
Move the script to a game object that does not live on the canvas and it should solve the problem.

You could also do this.
foreach(Transform t in GameObject.Find("Canvas").transform)
{
if(transform.gameObject.name != t.gameObject.name)
t.gameObject.SetActive(false);
}
Be sure to re-enable them with
foreach(Transform t in GameObject.Find("Canvas").transform)
{
t.gameObject.SetActive(true);
}

Related

How do I fix my WaitForSeconds code that involves BoxColliders?

Recently I have decided to start creating a game, and during this time I have run into many problems, most I have been able to fix myself but I'm having trouble with this. Basically, I want my code to make a Box Collider appear when clicked, and then disappear after a certain amount of time.
Here's my code:
void Start()
{
StartCoroutine(bruh())
}
void Update()
{
IEnumerator bruh()
{
if (Input.GetMouseButtonDown(0))
{
this.GetComponent<BoxCollider2D>().enabled = true;
yield return new WaitForSeconds(2);
}
else
{
this.GetComponent<BoxCollider2D>().enabled = false;
}
}
}
Your co-routine wasn't doing anything after it resumed from the yield return. I assume you want;
...
this.GetComponent<BoxCollider2D>().enabled = true;
yield return new WaitForSeconds(2);
this.GetComponent<BoxCollider2D>().enabled = false;
...
Possible new up the WaitForSeconds and then return that because it looks like you are just creating it not using it.

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!");
}

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.

Disable GameObject but continue to run Coroutine

I have a BonusController script as a component of a Bonus gameObject. This Bonus must be destroyed on collision but has to "act" for a few seconds. I have started a coroutine for this, but the script stops its execution (I think it's because I set the gameObject inactive). Here is the code of BonusController:
void OnTriggerEnter2D(Collider2D col)
{
StartCoroutine(speedUp(1));
gameObject.SetActive(false);
}
IEnumerator speedUp(float seconds)
{
Debug.Log("before");
yield return new WaitForSeconds(seconds);
Debug.Log("after"); // <--- This is never called
}
How can I remove the object and don't stop the coroutine script execution?
Can't you just disable the mesh renderer and collider? This way the gameobject will still exists, but the user won't be able to see it.
You cannot pull the ground on which you are standing. :)
Just disable the SpriteRenderer as you are using 2D methods. And keep the object alive and enable.
{
StartCoroutine(speedUp(1));
//gameObject.SetActive (false);
GetComponent<SpriteRenderer> ().enabled = false;
GetComponent<Collider2D> ().enabled = false;
// Above line will deactivate the first collider it will find.
}
IEnumerator speedUp(float seconds)
{
Debug.Log("before");
yield return new WaitForSeconds(seconds);
Debug.Log("after"); // <--- This is never called
}
In order to destroy the object after some time, call destroy inside the coroutine after the yield.
yield return WaitForSeconds(second);
Destroy(this);
Now it is properly destroyed and will free up memory opposed to still being there, invisible, but taking up resources.

Categories

Resources