Unity, C#: Delay on function not working? Waitforseconds? - c#

I am creating a VR app using Google Cardboard in Unity and I have succeeded in switching from VR to a normal view with this function:
public GameObject[] cardboardObjects;
public GameObject[] monoObjects;
public bool switched = false;
// Turn on or off VR mode
void ActivateVRMode(bool goToVR) {
foreach (GameObject cardboardThing in cardboardObjects) {
cardboardThing.SetActive(goToVR);
}
foreach (GameObject monoThing in monoObjects) {
monoThing.SetActive(!goToVR);
}
Cardboard.SDK.VRModeEnabled = goToVR;
// Tell the game over screen to redisplay itself if necessary
//gameObject.GetComponent<GameController>().RefreshGameOver();
}
I then call this function on a button to switch to mono view:
public void Switch() {
ActivateVRMode(false);
switched = true;
Debug.Log (switched+"No VR!");
}
I then want to have the mono view on a timer, as in once the user switches to non-VR mode it remains in that for only a set amount of time (3 seconds or so) before switching back to VR.
I tried to do this by flipping a boolean and using WaitForSeconds, however the view remains in mono view and nothing happens:
IEnumerator Switchback()
{
yield return new WaitForSeconds(3);
ActivateVRMode(true);
switched = false;
Debug.Log (switched+"VR!");
}
void Update () {
if (switched == true) {
Switchback ();
}
//Debug.Log ("updating");
}
What am I doing wrong here?

You have forgotten to call the Switchback function as a Coroutine
Instead of calling Switchback() you need to call StartCoroutine(Switchback()) in your Update() function.

Related

C# Coroutine doesnt works in Unity

Im trying to make a basic platformer game and my level controller script isnt working, it needs to load loading scene first and then nextlevel but it constantly loads the nextlevel
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class LevelController : MonoBehaviour
{
[SerializeField] string nextLevel;
private Gem[] gems;
void OnEnable()
{
gems = FindObjectsOfType<Gem>();
}
IEnumerator Wait()
{
yield return new WaitForSeconds(3);
}
void Update()
{
if (ReadyToNextLevel())
{
SceneManager.LoadScene("Loading");
StartCoroutine(Wait());
SceneManager.LoadScene(nextLevel);
}
}
bool ReadyToNextLevel()
{
foreach (var gem in gems)
{
if (gem.gameObject.activeSelf)
{
return false;
}
}
return true;
}
}
There are two big issues in your code.
Starting a Coroutine does not delay the method that started the routine!
If you want something happening after the Coroutine is finished you need to either move it into the routine itself or use a callback.
However, the thing is: You are loading a new scene, namely the "Loading" scene -> The current scene is unloaded -> the object this script is on gets destroyed -> the routine would no further executed.
Except your object is DontDestroyOnLoad which seems not the case from your code.
So in order to solve both you will need to make sure this object is not destroyed when another scene is loaded, at least not until it finished the loading process.
You could do this like e.g.
public class LevelController : MonoBehaviour
{
[SerializeField] string nextLevel;
private Gem[] gems;
void OnEnable()
{
gems = FindObjectsOfType<Gem>();
// Makes sure this is not destroyed when another scene is load
DontDestroyOnLoad(gameObject);
}
bool alreadyLoading;
IEnumerator Wait(Action whenDone)
{
yield return new WaitForSeconds(3);
// invoke the callback action
whenDone?.Invoke();
}
void Update()
{
if (ReadyToNextLevel() && ! alreadyLoading)
{
alreadyLoading = true;
SceneManager.LoadScene("Loading");
StartCoroutine(Wait(() =>
{
// This will be done after the routine finished
SceneManager.LoadScene(nextLevel);
// Now we don't need this object anymore
Destroy(gameObject);
}));
}
}
bool ReadyToNextLevel()
{
foreach (var gem in gems)
{
if (gem.gameObject.activeSelf)
{
return false;
}
}
return true;
}
}
EDIT: see derHugo's answer. They are more familiar with Unity than I am and point out that your script will be unloaded on scene change.
Your Wait() coroutine yields immediately, so StartCoroutine(Wait()) will return immediately and load the next scene.
If you want to wait three seconds before loading, then put the load inside the coroutine.
Like this:
private bool isLoading;
void Update()
{
if (!isLoading && ReadyToNextLevel())
StartCoroutine(LoadNextLevel());
}
IEnumerator LoadNextLevel()
{
isLoading = true;
SceneManager.LoadScene("Loading");
yield return new WaitForSeconds(3);
SceneManager.LoadScene(nextLevel);
isLoading = false;
}
See Coroutine help for more details
there are two reasons why this doesn't work, firstly when you load into the loading scene the gameObject is destroyed because you haven't told it to DontDestroyOnLoad, so the script will be sort of 'deleted' from the current scene. Second of all, when you call the coroutine, it calls the coroutine and then immediately continuous on to the next line of code, it doesn't wait for the coroutine to finish before moving on. I think derHugo covered how to fix these.

How to resume game after pressing back in menu

Hey I am trying to make a pause menu in my game. When escape is pressed pause game go to menu, but now I want to be able to press back in menu and resume my game. So far I can only pause game and cant press back. Also if i press Play in menu it starts at my tutorial scene and not the current scene. Is there a smart way to do this? Without resetting my game.
`using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class MenuScript : MonoBehaviour
{
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetKey(KeyCode.Escape))
{
if (Time.timeScale == 0)
{
Time.timeScale = 1;
}
else
{
Time.timeScale = 0;
}
SceneManager.LoadScene("Menu");
}
}
}`
I get that this isn't straightforward problem since you seem new to Unity.
You got the idea correctly, changing the time scale will freeze all agents on the scene. HOWEVER, if you load a new scene you'll need to reload the game scene - losing any data you had (and that is not what you want). My advice is creating an overlay element (UI) on the game scene and just show/hide it. There are multiple tutorials online use this as an starting point. Let me know if you require more help.
code sample
// Update is called once per frame
void Update()
{
if (Input.GetKey(KeyCode.Escape))
{
if (Time.timeScale == 0)
{
Time.timeScale = 1;
pauseMenu.gameObject.setActive(true);
}
else
{
Time.timeScale = 0;
pauseMenu.gameObject.setActive(false);
}
}
}
You will need a reference to the pauseMenu game object attached on this script using the Unity editor.
Depending on what you need, this would work as well.
private bool _isPaused;
private void Update(){
if (Input.GetKeyUp(KeyCode.Escape))
{
_isPaused = !_isPaused;
if (_isPaused)
{
//Do Pause Logic here
}
else
{
//Do Unpause Logic Here
}
}
}

Unity animation from foreach loop c#

I have a UI text that I update in script off camera then uses an animation to have it slide across the screen. The script uses a foreach loop since the text change and number of times the animation runs are variable. It always skips the third animation (it will print the effect in console but will not play that animation), even when 4 or 5 effects are called.
private Animation Anim;
public Text NBtext;
public GameObject NBEffect, Tut, TouchInput;
public IEnumerator NiceBowlingEffects(List<string> Effects, bool FirstFrame)
{
Anim = GetComponent<Animation>();
NBEffect.SetActive(true);
yield return new WaitForSeconds(.2f); //give frame ect a chance to load.
foreach (var Effect in Effects)
{
NBtext.text = Effect;
Print(Effect);
Anim.Play();
yield return new WaitForSeconds(Anim.clip.length);
}
NBEffect.SetActive(false);
if (FirstFrame)
{
Tut.SetActive(true);
}
TouchInput.SetActive(true);
}
try changing "WaitForSeconds" to "WaitForSecondsRealtime" in the foreach loop, and tell me if it fixed it
Hopefully this helps someone but to finally get this to work for Nice Bowling I had to add a time delay between changing the text and playing the animation. Here's the code that's running on Nice Bowling right now.
public IEnumerator NiceBowlingEffects(List<string> Effects, bool FirstFrame)
{
Anim = GetComponent<Animation>();
NBEffect.SetActive(true);
yield return new WaitForSecondsRealtime(.1f); //give frame ect a chance to load.
foreach (var Effect in Effects)
{
NBtext.text = Effect;
yield return new WaitForSecondsRealtime(.1f); //text time to change
Anim.Play();
yield return new WaitForSecondsRealtime(Anim.clip.length +.01f);
}
NBEffect.SetActive(false);
if (FirstFrame)
{
Tut.SetActive(true);
Tut.GetComponent<UISprite>().Trigger();
}
TouchInput.SetActive(true);
}

GetKeyDown doesn't seem to be working. Unity C#

Making a little prototype at work and have been following some tutorials on how to do UI. Everything seems to be working except for the fact that hitting "space" while playing doesn't pause the game. I added some debug logs to try and catch where it was breaking, but I get no log output when hitting the space key. Which I think means something is breaking before or at the point where it should be looking for GetKeyDown.
In editor I have the Resume button and "Paused" text all set with the tag "ShowOnPause". If I comment out the hidePaused in the start function they appear correctly so I don't think it's a error in the editor.
If anyone sees anything else I could be doing better please share. Still trying to learn this stuff.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class UIManager : MonoBehaviour
{
// Array of gameObjects for the Pause Menu
GameObject[] pauseObjects;
void Start ()
{
//Game is running at start
Time.timeScale = 1;
// Assign all objects with the ShowOnPause to pauseObjects
pauseObjects = GameObject.FindGameObjectsWithTag("ShowOnPause");
//Hide all pause UI at start
hidePaused();
Debug.Log("UI Hidden");
}
void Update ()
{
// If the space key is pressed
if(Input.GetKeyDown(KeyCode.Space))
{
Debug.Log("Key Pressed");
// and if the game is running
if(Time.timeScale == 1)
{
// Pause the game and show Pause Menu
Time.timeScale = 0;
showPaused();
Debug.Log("Paused");
}
// or if the game is already paused
else if (Time.timeScale == 0)
{
// Resume the game and hide the Pause Menu
Time.timeScale = 1;
hidePaused();
Debug.Log("UnPaused");
}
}
}
// The function for activating gameobjects in the Pause Menu array
public void showPaused()
{
foreach(GameObject g in pauseObjects)
{
g.SetActive(true);
Debug.Log("Showing UI");
}
}
// The Function for deactivating the gameobjects in the Pause Menu array
public void hidePaused()
{
foreach(GameObject g in pauseObjects)
{
g.SetActive(false);
Debug.Log("Hiding UI");
}
}
// Function for the Resume button in the Pause Menu
public void Resume()
{
if(Time.timeScale == 1)
{
Time.timeScale = 0;
showPaused();
}
else if (Time.timeScale == 0)
{
Time.timeScale = 1;
hidePaused();
}
}
}
try this
if(Input.GetKeyDown("space"))

WaitForSeconds() Coroutine causes Game to freeze Indefinitely (Edited)

I have a game with four scenes, a menu scene, a loading scene, and two game scenes. All is well, when I am transitioning from my menu scene to my game scenes, but whenever I transition from my game scenes back to my menu scene or reload the game scene, the loading scene simply stops responding. I get a warning message that says "NetworkManager detected a script reload in the editor. This has caused the network to be shut down" only when I try to reload the currently active game scene. This issue is also present when I play in the build! I used print statements to trace down where my code stopped running, and I figured out that it was the Yield Return New WaitForSeconds() which caused the game to freeze. Why is that?
I have two scripts that controls transitioning. One simple script called on UIButtons for telling the second more complex script called in the preload scene to load the scene it's supposed to load and create animations. I have made sure that I was loading on to the correct scene, and that all of the scenes were added into my build settings.
The following pictures show the loading scene not responding. The first picture shows what happens when I try to reload the current game scene, and the second picture shows what happens when I try to load the menu scene:
My Loading Scene Script:
public class LoadingScreenManager : MonoBehaviour {
[Header("Loading Visuals")]
public Image loadingIcon;
public Image loadingDoneIcon;
public Text loadingText;
public Image progressBar;
public Image fadeOverlay;
[Header("Timing Settings")]
public float waitOnLoadEnd = 0.25f;
public float fadeDuration = 0.25f;
[Header("Loading Settings")]
public LoadSceneMode loadSceneMode = LoadSceneMode.Single;
public ThreadPriority loadThreadPriority;
[Header("Other")]
// If loading additive, link to the cameras audio listener, to avoid multiple active audio listeners
public AudioListener audioListener;
AsyncOperation operation;
Scene currentScene;
public static int sceneToLoad = -1;
// IMPORTANT! This is the build index of your loading scene. You need to change this to match your actual scene index
static int loadingSceneIndex = 1;
public static void LoadScene(int levelNum) {
Application.backgroundLoadingPriority = ThreadPriority.High;
sceneToLoad = levelNum;
SceneManager.LoadScene(loadingSceneIndex);
}
void Start() {
if (sceneToLoad < 0)
return;
fadeOverlay.gameObject.SetActive(true); // Making sure it's on so that we can crossfade Alpha
currentScene = SceneManager.GetActiveScene();
StartCoroutine(LoadAsync(sceneToLoad));
}
private IEnumerator LoadAsync(int levelNum) {
ShowLoadingVisuals();
yield return null;
FadeIn();
StartOperation(levelNum);
float lastProgress = 0f;
// operation does not auto-activate scene, so it's stuck at 0.9
while (DoneLoading() == false) {
yield return null;
if (Mathf.Approximately(operation.progress, lastProgress) == false) {
progressBar.fillAmount = operation.progress;
lastProgress = operation.progress;
}
}
if (loadSceneMode == LoadSceneMode.Additive)
audioListener.enabled = false;
ShowCompletionVisuals();
//THE PRINT STATEMENT WORKS FINE RIGHT HERE! The value of waitOnLoadEnd is only 1
yield return new WaitForSeconds(waitOnLoadEnd);
//THE PRINT STATEMENT STOPS RUNNING RIGHT HERE!
FadeOut();
yield return new WaitForSeconds(fadeDuration);
if (loadSceneMode == LoadSceneMode.Additive)
SceneManager.UnloadScene(currentScene.name);
else
operation.allowSceneActivation = true;
}
private void StartOperation(int levelNum) {
Application.backgroundLoadingPriority = loadThreadPriority;
operation = SceneManager.LoadSceneAsync(levelNum, loadSceneMode);
if (loadSceneMode == LoadSceneMode.Single)
operation.allowSceneActivation = false;
}
private bool DoneLoading() {
return (loadSceneMode == LoadSceneMode.Additive && operation.isDone) || (loadSceneMode == LoadSceneMode.Single && operation.progress >= 0.9f);
}
void FadeIn() {
fadeOverlay.CrossFadeAlpha(0, fadeDuration, true);
}
void FadeOut() {
fadeOverlay.CrossFadeAlpha(1, fadeDuration, true);
}
void ShowLoadingVisuals() {
loadingIcon.gameObject.SetActive(true);
loadingDoneIcon.gameObject.SetActive(false);
progressBar.fillAmount = 0f;
loadingText.text = "LOADING...";
}
void ShowCompletionVisuals() {
loadingIcon.gameObject.SetActive(false);
loadingDoneIcon.gameObject.SetActive(true);
progressBar.fillAmount = 1f;
loadingText.text = "LOADING DONE";
}
}
Script on UIButtons that call the above script:
public class LoadingSceneButton : MonoBehaviour {
public void LoadSceneWithLoadingScreen(int sceneNumber){
if (sceneNumber < 0 || sceneNumber >= SceneManager.sceneCountInBuildSettings) {
Debug.LogWarning ("Can't Load Scene, because It Doesn't Exist!");
}
LoadingScreenManager.LoadScene (sceneNumber);
}
}
(1) Don't use "print", please use this:
Debug.Log("fadeDuration is ....... " , fadeDuration.ToString("f4");
add that line of code just before you call FadeOut and also please add it inside FadeOut
(2) problems with CrossFadeAlpha
Please note that CrossFadeAlpha is extremely difficult to use! It's a real pain! It only works on UnityEngine.UI.Graphic, and it's tricky when used with coroutines.
public static void FadeOut(this Graphic g)
{
g.GetComponent<CanvasRenderer>().SetAlpha(1f);
g.CrossFadeAlpha(0f,.15f,false);
}
(3) problems with loading a scene in Unity5 !!!
Yes there is a
known issue
where it gets stuck on 0.9. Maybe this is the main problem at hand.
check out ... http://answers.unity3d.com/answers/1146173/view.html
and ... http://answers.unity3d.com/answers/1073667/view.html
Some basic working code example....
public void LaunchSoundboard()
{
StartCoroutine(_soundboard());
}
private IEnumerator _soundboard()
{
Grid.music.Duck();
yield return new WaitForSeconds(.2f);
AsyncOperation ao;
ao = UnityEngine.SceneManagement
.SceneManager.LoadSceneAsync("YourSceneName");
while (!ao.isDone)
{
yield return null;
}
// here, the new scene IS LOADED
SoundBoard soundBoard = Object.FindObjectOfType<SoundBoard>();
if(soundBoard==null) Debug.Log("WOE!");
soundBoard.SomeFunctionInSoundboardScript();
}
Note that you wait on ".isDone", rather than watch the float.
(4) #You actually have to have a scene called "preload", which ONLY preloads.
unfortunately the menu scene can not be your preload scene.
You have to actually have a separate preload scene which does nothing but that, "preload".
Note that any "game managers" you have must be on the preload scene. It's a bit annoying but that's how it is.
The purpose of the "preload" scene is to hold any game managers you have.
The only scene that you mark "don't destroy on load" must be only the "preload" scene. It's that simple.
It's a bit of a nuisance but very simple and reliable.

Categories