Coroutine only works the first time - c#

I assume that there is something fundamental about coroutines that I don't understand because I cannot get my head around why this is happening.
I have this coroutine that works perfectly as intended the first time but completely fails the second time I try to use it.
public IEnumerator CharacterDialogue()
{
inDialogue = true;
playerController.enabled = false;
mouselook.enabled = false;
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
dialogueMenu.SetActive(true);
yield return new WaitForEndOfFrame();
for (int i = 0; i < dialogueStrings.Length; i++)
{
while (!Input.GetKeyDown("e"))
{
yield return null;
}
yield return new WaitForEndOfFrame();
dialogueText.text = dialogueStrings[i];
}
yield return new WaitForEndOfFrame();
while (!Input.GetKeyDown("e"))
{
yield return null;
}
yield return new WaitForEndOfFrame();
QuestManager.Instance.SpawnDouxland();
inDialogue = false;
playerController.enabled = true;
mouselook.enabled = true;
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
dialogueMenu.SetActive(false);
}
It is pretty straightforward: I disable the controls of my player and make the dialogue window pop. I then wait for the input of the player between each string of text so that he may read at his own pace. Once he's done reading I reactivate the controls and close the dialogue window.
This code works exactly like I want the first time but the second time it just goes through the for loop without waiting for the input.
What am I doing wrong here and why does it work the first time?
Edit:
Comments pointed out that the issue must be in the way I call the coroutine. This now makes sense to me as I use the same key (e) to call the coroutine. So maybe it reads the key as pressed and runs through the for loop. Bu why would it only do so the second time?
Here's the snippet of code where I call my coroutine:
if (hit.collider.CompareTag("Character"))
{
raycastedObj = hit.collider.gameObject;
CrosshairActive();
interactionManager.InteractiveFeedbackTextCall("CharacterQuest");
if (Input.GetKeyDown("e"))
{
Debug.Log("I have interacted with: " + hit.collider.gameObject.name + ".");
StartCoroutine(canvasAnimManager.CharacterDialogue());
}
}

Thanks to some comments I figured out what the issue was.
if (Input.GetKeyDown("e"))
{
Debug.Log("I have interacted with: " + hit.collider.gameObject.name + ".");
StartCoroutine(canvasAnimManager.CharacterDialogue());
}
I thought the idea of GetKeyDown was that it was triggered only once... there must be something I'm not getting. Anyways the issue was that this was called multiple times and it make the coroutine go through the loop even after I was finished with my dialogue.
I just added a little bool check to fix the issue:
if (Input.GetKeyDown("e") && !canvasAnimManager.inDialogue)
{
Debug.Log("I have interacted with: " + hit.collider.gameObject.name + ".");
StartCoroutine(canvasAnimManager.FoukiDialogue());
}
Cheers and thanks for the help!

Related

Looped AudioSource stops looping after 5 or so seconds

For the life of me I can't understand why it stops looping. I have an engine acceleration script that works great, except for the fact that it stops looping after a certain amount of time. I'm only calling the audiosource to be stopped if the user presses a button, it should work just fine. It has worked before implementing this too, I've no idea what breaks it.
private void PlayAccelerateSound()
{
m_audioSource2.loop = true;
m_audioSource2.clip = m_engineSound;
if (!alreadyPlayed)
{
m_audioSource2.PlayOneShot(m_audioSource2.clip);
alreadyPlayed = true;
}
if (rb.velocity.x < minPitch)
{
m_audioSource2.pitch = minPitch;
}
else if (rb.velocity.x > maxPitch)
{
m_audioSource2.pitch = maxPitch;
}
else
{
m_audioSource2.pitch = rb.velocity.x;
}
}
Fixed it by using m_audioSource2.Play() instead of m_audioSource2.PlayOneShot(m_audioSource2.clip).

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.

Unity 'StartCoroutine' does not run twice when IEnumerator is passed as a variable?

So I've ran into something weird, when using a co-routine in Unity to simulate a NPC (walks towards a target, idles for x seconds, walks to a target --repeat--).
I've found that starting the co-routine with a variable that holds the IEnumerator will not run twice, while starting the co routine with the method passed in directly runs as expected, repeatable.
Why does this work this way?
Whats happening 'under the hood'? I cant wrap my head around why this is, and it bugs me.
Below my IEnumerator method that simulates idle time.
private IEnumerator sitIdle()
{
var timeToWait = GetIdleTime();
_isIdle = true;
yield return new WaitForSeconds(timeToWait);
_isIdle = false;
}
If this gets called a second time per Scenario #1 (below), it runs as expected when called multiple times. It just repeats the process over and over.
If however, if it gets called per Scenario #2 (below) as a variable, it will kick off once, but refuse to enter a second time and just plain 'skip' it in code.
void LateUpdate()
{
_idleRoutine = sitIdle; //this is not actually in the late update, just moved here for reference.
if (_agent.hasPath)
{
if (isTouchingTarget())
{
StartCoroutine(sitIdle2()); //Scenario #1
StartCoroutine(_idleRoutine); //Scenario #2
_currentTarget = null;
_agent.ResetPath();
}
}
Tl;dr: StartCoroutine(variable to IEnumerator) is not repeatable, while StartCoroutine(IEnumerator()) works fine, why cant I pass the IEnumerator as a variable?
The return value of SitIdle() is an IEnumerator. IEnumerators do not repeat once they are completed and have no iterations remaining, which is what StartCoroutine tells Unity to do. Each time the coroutine is resumed at a yield is another iteration Unity tells the coroutine to perform.
If you really would like to store the coroutine as a variable, so you can choose between one or another, you could store the method you're interested in as a delegate, then call that to get your fresh IEnumerator:
IEnumerator sitIdle()
{
var timeToWait = GetIdleTime();
_isIdle = true;
yield return new WaitForSeconds(timeToWait);
_isIdle = false;
}
IEnumerator sitIdleAlternative()
{
var timeToWait = GetIdleTime() + 2f;
_isIdle = true;
yield return new WaitForSeconds(timeToWait);
_isIdle = false;
}
delegate IEnumerator IdleDelegate ();
IdleDelegate _idleRoutine;
void LateUpdate()
{
_idleRoutine = new IdleDelegate(sitIdleAlternative); //this is not actually in the late update, just moved here for reference.
_idleRoutine = new IdleDelegate(sitIdle);
if (_agent.hasPath)
{
if (isTouchingTarget())
{
StartCoroutine(sitIdle2()); //Scenario #1
StartCoroutine(_idleRoutine()); //Scenario #2
_currentTarget = null;
_agent.ResetPath();
}
}
}

How to clear all user keystrokes in unity c#?

i'm writing some c# code within Unity game i'm developing.
and there's a problem i can't fix.
i'm using StartCoroutine in my code and inside i'm calling another StartCoroutine. i know that when doing that, there's two threads executing the code in those parts.
but not if i'm calling yield return to that StartCorotuine;
IEnumerator StartLoop()
{
yield return StartCorotuine(GetInputFromUser()); // 1
// some variables
yield return StartCorotuine(GetInputFromUser()); // 2
}
IEnumerator GetInputFromUser()
{
if (Input.GetKeyDown(KeyCode.Space))
{
print("IN");
}
else
{
yield return null;
}
}
the problem is, it prints twice.
the buffer isn't empty from the first time calling "GetInputFromUser()" and it keeps it until the second time and enters immediately to the if even when i'm not pressing Space.
things i've tried:
Console.Clear();
Console.ReadKey();
Console.ReadLine();
UnityEngine.Rendering.CommandBuffer.Clear();
while(Console.KeyAvailable)
Console.ReadKey();
and some more i can't even remember.
either the whole idea is wrong and somehow it worked till now. or i'm missing something.
any advise? i've googled everything i could think of.
[[[SOLVED]]]
I don't know how it works here, and if you're closing the post or something. but I've solved it.
The problem was, as someone suggested, that the calls happened in the same frame, so i've added another null in between to force skipping to the next frame:
IEnumerator StartLoop()
{
yield return StartCorotuine(GetInputFromUser()); // 1
// some variables
yield return null; // skipping frame
yield return StartCorotuine(GetInputFromUser()); // 2
}
IEnumerator GetInputFromUser()
{
if (Input.GetKeyDown(KeyCode.Space))
{
print("IN");
}
else
{
yield return null;
}
}
Thanks for the helpers!
First of all: Coroutines have nothing to do with threads! Every Coroutine is executed in the Unity main thread and get their MoveNext executed right after Update has finished for that behaviour.
Your problem should be that in
IEnumerator GetInputFromUser()
{
if (Input.GetKeyDown(KeyCode.Space))
{
print("IN");
}
else
{
yield return null;
}
}
in the case there was no input you do yield return null; which causes the Coroutine to wait at least one frame!
So the second one is executed in the next frame.
But in case there was an input you do not wait so the next Coroutine is directly started where again you don't wait!
Result: You get your print twice without waiting at all.
It's not fully clear what you are trying here but I assume you want to wait until Space is pressed so you should rather use e.g.
bool GetInputFromUser()
{
if (Input.GetKeyDown(KeyCode.Space))
{
print("IN");
return true
}
return false;
}
And use it like
// This now waits until the user presses Space
yield return new WaitUntil (GetInputFromUser);
or simply also yield for one frame in case there was the input like
IEnumerator GetInputFromUser()
{
if (Input.GetKeyDown(KeyCode.Space))
{
print("IN");
}
yield return null;
}
though
the goal is to check if the user presses the SpaceBar more than once.
It is very very unlikely that a user manages to press the key down twice in two consecutive frames.
StartLoop starts inside Start()
but at this moment the user can't provide any input yet anyway. So you check only once if the user presses a key within the first frames..
In Start()
I'm waiting for the first Space stroke.
once he has, I'm starting the StartCorotuine(StartLoop()); sorry for the mixup.
I'm using it like that, maybe it'll be more clear, once the user Starting the loop, I'm waiting for him to press Space. once he has a timer is set on and once the timer has timed the IEnumerator return without Input from the User and I'm getting to the next iteration, the only thing I want to do is to check if the user pressed twice on that Space.
{
while (timer < timeSetInStart)
{
If(Input.GetKeyDown(KeyCode.Space))
{
print("IN";
yield break;
}
else
{
yield return null;
}
}
}
I'm not sure if there's a specific reason why this needs to be in a coroutine but it might be easier to handle this all in Update():
bool spacePressed = false;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
if (spacePressed)
{
print("Space pressed twice");
spacePressed = false;
}
else
{
print("Space pressed once");
spacePressed = true;
}
}
}

Load Scene Unity

I use the function Application.LoadLevelAsync (s); in my game but it just stops at 89%. Help me. It's my code:
public void LoadScenes (string s)
{
if (!quit) {
SoundController.PlaySound (soundGame.ButtomClick);
progressBars.gameObject.SetActive (true);
background.SetActive (true);
text.gameObject.SetActive (true);
async = Application.LoadLevelAsync (s);
async.allowSceneActivation = false;
StartCoroutine (DisplayLoadingScreen ());
}
}
IEnumerator DisplayLoadingScreen ()
{
while (!async.isDone) {
loadProgress = (int)(async.progress * 100);
text.text = "Loading Progress " + loadProgress + "%";
progressBars.size = loadProgress;
yield return null;
}
if (async.isDone) {
Debug.Log ("Loading complete");
async.allowSceneActivation = true;
}
}
When you set async.allowSceneActivation = false, the loading stops at 90% or 0.9. This is because unity saves the last 10% to actually show you the scene that has been loaded. So 0 to 0.9 is when the actual loading process takes place and the last .1% is for displaying the loaded scene.
So in your case, you are checking for async.isDone which would become true only when progress is 100% but you have also set async.allowSceneActivation = false which would stop the progress at 90%. What you might want to check is async.progress >=0.9f and then set your async.allowSceneActivation = true.
Reference: https://docs.unity3d.com/ScriptReference/AsyncOperation-progress.html
From my experience, AsyncOperation.progress can cap out at about 89% as you've discovered. It may look like it's stalled at that message, when really it's still loading for however long it needs. It won't ever reach 100% even when it's done though, so it's rather useless if you want to implement a loading bar.
As such, async.isDone might never become true.
As a workaround, you could set async.allowSceneActivation = true; sooner than later, ignoring async.progress altogether:
async = Application.LoadLevelAsync (s);
async.allowSceneActivation = true;
I tend to use a progress-agnostic rotating loading icon instead of a loading bar because of this issue.
On a side note that I haven't tested, others have said that this is an Editor-only issue; async.progress and async.isDone might actually work on standalone builds.

Categories

Resources