How to clear all user keystrokes in unity c#? - 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;
}
}
}

Related

How do I enable a function when my game is done doing an asynchronous function?

This has been fustrating me as a beginner/learning coder. I've been trying to make it so when my game finishes loading using an async method, it loads a button so you can continue. Instead, it ignores the function altogether, causing the button to still be there ingame and glitch out when you press it too early. I have my code right here; how can I fix this?
IEnumerator LoadSceneAsynchronously(int levelIndex)
{
AsyncOperation operation = SceneManager.LoadSceneAsync(levelIndex);
while (!operation.isDone)
{
Debug.Log(operation.progress);
loading.GetComponent<Animator>().Play("loading");
text.GetComponent<Animator>().Play("idle");
yield return null;
}
if (!operation.isDone)
{
yield return new WaitForSeconds(5);
loading.GetComponent<Animator>().Play("loading disappear");
text.GetComponent<Animator>().Play("appear text");
text.GetComponent<Animator>().Play("blink text")
yield return null;
}
}
public void DestroyLoading()
{
gameObject.SetActive(false);
GameIsDoneLoading = true;
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space) && !GameIsDoneLoading == true)
{
Debug.Log("Space key was pressed.");
text.GetComponent<Animator>().Play("disappear text");
logo.GetComponent<Animator>().Play("fpsa logo disappear");
}
}
When you 'yield' in a Coroutine, code execution returns to the exact same place in the next frame, so in the while loop above it just checks that condition every frame, then yields to the next frame. I believe what you want to do is...
IEnumerator LoadSceneAsynchronously(int levelIndex)
{
// start the async loading
AsyncOperation operation = SceneManager.LoadSceneAsync(levelIndex);
// start playback of your animations in the scene
loading.GetComponent<Animator>().Play("loading");
text.GetComponent<Animator>().Play("idle");
// now wait for the scene to fully load
while (!operation.isDone)
{
Debug.Log(operation.progress);
yield return null;
// after yielding, we return here and repeat the while
// loop on the next frame
}
// we have broken free of the while loop so the level has now fully loaded
// and operation.isDone is now true
// play some other animations to fade out the loading system?
loading.GetComponent<Animator>().Play("loading disappear");
text.GetComponent<Animator>().Play("appear text");
text.GetComponent<Animator>().Play("blink text")
// I'm assuming these animations take about 5 seconds to complete?
// so just wait in here for 5
yield return new WaitForSeconds(5);
// loading level and animations all done...proceed to games
DestroyLoading();
}

How to wait a certain amount of time without freezing code in C#/Unity?

I'm making a practice game to get used to coding where you have to shoot a bird. When you run out of bullets, you need to press the 'r' key to reload your bullets. I want there to be a delay between when the button is pressed and when the bullets reload, but so far what I found is code that freezes everything (shown below). Is there a way to keep the code from freezing everything?
Summary: The code below freezes everything (the whole game) when pressing the 'r' button. Is there code I can use that won't freeze everything and will only wait 2 seconds before running the next action?
IEnumerator TimerRoutine()
{
if (Input.GetKeyDown(KeyCode.R))
{
yield return new WaitForSeconds(2); //Fix this, freezes everything
activeBullets = 0;
}
}
Use Coroutines To set This delay
if (Input.GetKeyDown(KeyCode.R) && isDelayDone) // defined isDelayDone as private bool = true;
{
// When you press the Key
isDelayDone = false;
StartCoroutine(Delay());
IEnumerator Delay()
{
yield return new WaitForSeconds(2);
isDelayDone = true;
activeBullets = 0;
}
}
Your problem is that you are waiting 2 seconds after the key press but not waiting for the actual key event it.
Here is a modified version of your method doing what you want.
IEnumerator TimerRoutine()
{
while(activeBullets == 0) // Use the bullets value to check if its been reloaded
{
if (Input.GetKeyDown(KeyCode.R)) // Key event check each frame
{
// Key event fired so wait 2 seconds before reloading the bullets and exiting the Coroutine
yield return new WaitForSeconds(2);
activeBullets = reloadBulletsAmount;
break;
}
yield return null; // Use null for the time value so it waits each frame independant of how long it is
}
}
(I know this has an accepted answer I just feel this method would be better)

Is there a way to stay in a coroutine until a condition is met?

I am trying to program a very simple dialogue system. When a player gets a dialogue box, the box remains until they press a key. Then a second dialogue box appears. Again, they press a key, and so on.
I am realizing, however, that my method of using coroutines doesn't work, because every dialogue box displays at once. Using a simplified version:
IEnumerator Test() {
//Code that displays a message
yield return StartCoroutine(WaitForKey(KeyCode.Space));
}
IEnumerator WaitForKey(KeyCode keyCode)
{
while (!Input.GetKeyDown(keyCode))
yield return null;
}
void start(){
StartCoroutine(Test());
StartCoroutine(Test());
}
The results of the above code is that two messages are displayed. Plus, they display immediately--once a condition is met, the process jumps from that courtine to the initiating, runs the next line of code, and periodically returns to the first coroutine to see if it has finished.
How do I get one coroutine to finish before it continues with the rest of the code following it?
The easy way to go about this is to creates arrays of Canvas as dialog. Disable all of them in the Editor. With the code below, you can change the array size of the dialogueGameObjectCanvas to how many dialogues you have then assign the parent of each dialogues in order to the dialogueGameObjectCanvas array in the Editor. Cancas must be the parent of each dialogue. Run it.
Pressing the space bar will go to the next dialogue. This is the easiest way.
Now, for the pro way. If the dialogue is generated during run time, you can use List to add or remove dialog(GameObjectUICanvas)instead of using array to do it. You can easily convert array to List and that should work for dynamically generated dialogues.
startDisplayDialogue() starts the dialogue.
isDialogueDisplaying() tells you if the dialogue is currently displayed on the screen.
getDialogueCurrentNumberDisplaying() gets the current array number of dialogue disaplying
stopDisplayDialogue() kills the whole dialogue and closes them.
Remember that startDisplayDialogue() is coroutine function and must be started with StartCoroutine (startDisplayDialogue ());
public GameObject[]dialogueGameObjectCanvas;
bool isRunning = false;
int dialogNumberDisplaying = 0;
// Use this for initialization
void Start ()
{
//animationSprite = new GameObject[5]; Uncomment if you want to assign through code, otherwise assign the dialogues from the Editor
StartCoroutine (startDisplayDialogue ());
}
//Display the first dialogue then wait for the space to be pressed then display the next dialue
IEnumerator startDisplayDialogue ()
{
//Make sure there is only one instance of the dialuge functionr running. Break if there is alread one running
if (isRunning) {
yield break;
} else {
isRunning = true;
}
int dialogueNumber = 0; //Starts from 0 to the size of the animationSprite array
dialogNumberDisplaying = dialogueNumber;
while (isRunning) {
//Stop if the dialogNumber is >= number of dialogues to dispaly(dialogue arrays)
if (dialogueNumber >= dialogueGameObjectCanvas.Length) {
isRunning = false;
//dialogueGameObjectCanvas [dialogueNumber].SetActive (false);
Debug.Log ("End of dialogue");
yield break;
}
//Dispaly the current Dialogue based on the array number
dialogueGameObjectCanvas [dialogueNumber].SetActive (true);
//Wait until the Space bar is pressed
while (!Input.GetKeyDown(KeyCode.Space)) {
yield return null; //Must wait here or else Unity would freeze
}
//Space bar has been pressed. Disable the current Dialogue then increement dialogueNumber so that then the next one in the array will display
dialogueGameObjectCanvas [dialogueNumber].SetActive (false);
dialogueNumber++;
dialogNumberDisplaying = dialogueNumber; //For the getDialogueCurrentNumberDisplaying function
//Check if stopDisplayDialogue is called then exit
if (!isRunning) {
dialogueGameObjectCanvas [dialogueNumber].SetActive (false);
yield break;
}
yield return null;
}
}
//Check if a dialogue is currently displaying
bool isDialogueDisplaying ()
{
return isRunning;
}
//Get the current array number of dialogue currently displaying
int getDialogueCurrentNumberDisplaying ()
{
return dialogNumberDisplaying;
}
//Stops the dialogue from displaying
void stopDisplayDialogue ()
{
isRunning = false;
}
Your issue is that you are calling StartCoroutine(Test()) twice in the same frame, only when you use
yield return StartCouroutine(Test());
will the next line wait until the coroutine finishes to be executed.
So right now your start function will run the Test() functions until it gets it first yield return X, and then it will go back to the second line of the Start function and do the same thing. Then in the next frame (or whenever your yield return X finishes) it will continue executing those routines.
To solve your problem you could do a while(true) in your Test function if i understand what you are doing. However, if you post more detailed code I can give you a better suggestion as to why your approach isnt working the way you expect!

How to stop co-routine?

When two co-routines are running, how do you stop the first co-routine?
GLOBALS.stableTime = 5;
IEnumerator StableWaittingTime ()
{
yield return new WaitForSeconds (1f);
if (GLOBALS.stableTime == 0) {
GameManager.instance.LevelFaildMethod ();
} else {
GameManager.instance.stableWaittingTime.text = GLOBALS.stableTime.ToString ();
GLOBALS.stableTime--;
StartCoroutine ("StableWaittingTime");
}
}
There are three ways to stop coroutines.
The first is to call StopAllCoroutines(), which will obviously stop all running coroutines.
The second is to call StopCoroutine(coroutine), where coroutine is a variable name given to your IEnumerator.
And the third is to do a yield break from within the coroutine.
Worth noting is that both StopAllCoroutines and StopCoroutine can only stop a coroutine when the coroutine reaches a yield return *.
So if you have two coroutines with the same name and you want to stop the one you are executing in you do yield break.
Interestingly, if you want to stop every other coroutine besides the one you are executing in, you call StopCoroutines() from within that coroutine.
#Imapler answer is almost all you need. I would just add that StopCoroutine method of MonoBehaviour is overloaded and has 3 types of parameters, so it is possible to stop many coroutines of same name.
For your need here, just use yield break; like this:
void Start ()
{
StartCoroutine (StableWaittingTime ());
}
IEnumerator StableWaittingTime ()
{
yield return new WaitForSeconds (1f);
if (false)
{
// do something
}
else
{
// do something
StartCoroutine (StableWaittingTime ());
yield break;
}
}

No effects with WaitForSeconds

I want my character to stop when I press a button on my gamepad or keyboard. The character must do a specific animation when I press the button and nothing else, so no movement at all, just the animation.
I'm trying to figure out how WaitForSeconds works, but when I try to use it, it doesn't work. Here the code of the function that calls WaitForSeconds
public IEnumerator Wait()
{
yield return new WaitForSeconds (6);
}
When the bool variable animationTest is true I want the program to wait for 6 seconds
if (animationTest)
{
UnityEngine.Debug.Log ("check1");
StartCoroutine (Wait ());
UnityEngine.Debug.Log ("check2");
animationTest = false;
}
but this doesn't work! check1 and check2 are printed at the same time. I'm missing something. This runs in FixedUpdate().
The Coroutine does not work like this. It starts a new (parallel) execution.
In order to achieve the wait you'd have to do it in the IEnumerator.
public IEnumerator SomethingElse() {
animationTest = false;
Debug.Log("check1");
yield return new WaidForSeconds(6f);
Debug.Log("check2");
yield return true;
}
void FixedUpdate() {
if (animationTest) {
StartCoroutine(SomethingElse());
}
}
Now when you set the animationTest at some point, you should see the two logs with a time gap of 6 seconds in between.

Categories

Resources