I am programming a task in VR where the user hears an audio file with a certain amount of numbers and needs to press a key after that. If he did it right then he should press "r" and an audio file that is one number longer will be played. If he did it wrong he should press "w" and if it was his first wrong answer after a right answer an audio file will be played that has the same amount of numbers than before. If he did it wrong and already did it wrong right before that the amount of numbers is reduced by one.
In total there will be 10 audio files which is why I decided to use a for loop.
But now I don't know how I can make the for loop wait for the user input. I've also read that it is in general not god to use a for loop when you have to wait for a user input but I don't know what else I can do.
Here is my code so far:
IEnumerator playDSBtask()
{
bool secondWrong = false;
fillList();
for (int i = 0; i < 10; i = i + 1)
{
List<AudioSource> x = chooseList(score);
AudioSource myAudio = x[0];
float duration = myAudio.clip.length;
myAudio.GetComponent<AudioSource>();
myAudio.Play();
yield return new WaitForSeconds(duration + 5);
if (Input.GetKeyDown("r"))
{
score++;
secondWrong = false;
}
else if (Input.GetKeyDown("f") && secondWrong == false)
{
secondWrong = true;
}
else if (Input.GetKeyDown("f") && secondWrong == true)
{
score--;
}
x.Remove(myAudio);
}
}
But this won't work since the for loop will just continue if none of the if or else if statements are true and therefor executed.
Using a for loop in your case is totally fine since it is in a Coroutine where you yield inside of it so there will be no endless loop and therefore freeze of the main thread.
You could probably use WaitUntil something like
...
yield return new WaitForSeconds(duration + 5);
// Wait until any o the expected inputs is pressed
yield return WaitUntil(() => Input.GetKeyDown("r") || Input.GetKeyDown("f"));
// then check which one exactly
if (Input.GetKeyDown("r"))
{
...
alternatively this could also be done in a more generic way but also more error prone
...
yield return new WaitForSeconds(duration + 5);
// wait until something breaks out of the loop
while(true)
{
// check if any key was pressed
if(Input.anyKeyDown)
{
// handle r key
if (Input.GetKeyDown("r"))
{
score++;
secondWrong = false;
// important!
break;
}
else if (Input.GetKeyDown("f"))
{
if(!secondWrong)
{
secondWrong = true;
}
else
{
score--;
}
// Important!
break;
}
}
// if nothing called break wait a frame and check again
yield return null;
}
x.Remove(myAudio);
...
In general you might want to use KeyCode.F and KeyCode.R instead of strings.
From a UX perpective though you would either want to reduce the 5 seconds wait or/and show some kind of UI feedback for when user input is handled/awaited.
Related
I have written a function on a script that activates when the player loses all his lives. That calls a CoRoutine in a script attached to my main character that makes a simple death animation and then moves to the game over screen. Debug.Log shows that the function calls, and when I use non-CoRoutine functions attached to the main character, those functions call to. However, the CoRoutine itself never calls, not even showing the log of it ever activating? Does anyone know what is up?
Code included below:
if (GameObject.Find("Heart 1") == null)
{
Debug.Log("Naw");
player.DeathAnimation(20);
Debug.Log("still not working");
}
public IEnumerator DeathAnimation(int i)
{
int k = i;
Debug.Log("Numerator works");
transform.Rotate(Vector3.forward * 9);
yield return new WaitForSeconds(.08f);
k--;
Debug.Log(k);
if (k <= 0)
{
SceneManager.LoadScene("Game Over");
yield break;
}
StartCoroutine(DeathAnimation(k));
}
There doesn’t seem to be a reason to make this a recursive coroutine. I’d suggest removing the recursion which might also solve the issue you’re having or at least make it simpler to identify.
if (GameObject.Find("Heart 1") == null)
{
Debug.Log("Hmm");
StartCoroutine( player.DeathAnimation(20) );
Debug.Log("maybe working");
}
…
public IEnumerator DeathAnimation(int i)
{
Debug.Log("Numerator works");
for( ; i>0; i-- ) {
transform.Rotate(Vector3.forward * 9);
yield return new WaitForSeconds(.08f);
Debug.Log(i);
}
SceneManager.LoadScene("Game Over");
}
I have a game I am developing in Unity where AI is doing large calculations when it is its turn. It searches the position to depth 1, then 2, then 3 etc. Between each depth I want to instantiate a Gameobject with info about the depth to UI. The problem is that nothing happens until the AI is completely finished, then all items are added at once. Here is some code to explain better:
private void AIMakeMove()
{
for (int currentDepth = 1; currentDepth < maxDepth + 1; currentDepth++)
{
SearchPosition(currentDepth);
}
}
private void SearchPosition(int _currentDepth)
{
// Search the position to the given depth
score = Negamax(_currentDepth);
// Print things PROBLEM HERE
GameObject printItem = Instantiate(printItemPrefab, printItemParent.transform);
Debug.Log(_currentDepth);
}
I also tried just a simple Debug.Log instead of Instantiate but same thing happens then, all prints to console happens after the AI is done with its thinking process.
Why is my UI not updating with information? I tell it to create some things after it run the first iteration with depth 0 but it skips this step and goes on depth 2 instead. Can someone please let me know how to get information out between each depth?
The problem is that nothing happens until the AI is completely finished
well the UI is only updated if the Unity main-thread is allowed to finish a frame.
You, however, block the main thread until all iterations are finished.
If it is okey for you to block between each instantiation then you could simply use a Coroutine and do something like
private void AIMakeMove()
{
StartCoroutine(AIMakeMoveRoutine());
}
private IEnuemrator AIMakeMoveRoutine()
{
for (int currentDepth = 1; currentDepth < maxDepth + 1; currentDepth++)
{
SearchPosition(currentDepth);
// This now tells Unity to "interrupt" this routine here
// render the current frame and continue from here in the next frame
yield return null;
}
}
private void SearchPosition(int _currentDepth)
{
score = Negamax(_currentDepth);
GameObject printItem = Instantiate(printItemPrefab, printItemParent.transform);
Debug.Log(_currentDepth);
}
This will finish a frame and start a new one (thus refresh the UI) after each finished iteration.
However, if this still blocks the rest of your application too much you should additionally actually run the calculation async e.g. using a Task like
private void AIMakeMove()
{
StartCoroutine(AIMakeMoveRoutine());
}
private IEnuemrator AIMakeMoveRoutine()
{
for (int currentDepth = 1; currentDepth < maxDepth + 1; currentDepth++)
{
// you can yield another IEnuemrator -> executes this and waits for it to finish
yield return SearchPosition(currentDepth);
// This now tells Unity to "interrupt" this routine here
// render the current frame and continue from here in the next frame
yield return null;
}
}
private IEnumerator SearchPosition(int _currentDepth)
{
// run the NegamaxTask asynchronously in the background
var task = Task.Run(() => Negamax(_currentDepth));
// wait for the task to finish
while(!task.IsCompleted)
{
// do nothing but skip frames to allow the rest of the application to run smoothly
yield return null;
}
// If you do nothing else inside the loop this could also be written as
//yield return new WaitWhile(() => !task.IsComoleted);
// or
//yield return new WaitUntil(() => task.IsCompleted);
// since the task is already finished it is save / non-blocking to access the result now
score = task.Result;
var printItem = Instantiate(printItemPrefab, printItemParent.transform);
Debug.Log(_currentDepth);
}
Now this allows your application to continue with a normal frame-rate while in the background you do the heavy calculations and once in a while get a result back when an iteration is finished.
Try using a thread:
private void AIMakeMove()
{
new System.Threading.Thread(() =>
{
for (int currentDepth = 1; currentDepth < maxDepth + 1; currentDepth++)
{
SearchPosition(currentDepth);
}
}).Start();
}
private void SearchPosition(int _currentDepth)
{
// Search the position to the given depth
score = Negamax(_currentDepth);
// Print things PROBLEM HERE
GameObject printItem = Instantiate(printItemPrefab, printItemParent.transform);
Debug.Log(_currentDepth);
}
I want to be able to flash stuff at a certain frequency. For an example, let's say 2Hz. I also want to be able to specify a ratio, where I can have the thing displayed for let's say 2/3 of the cycle and have it hidden for 1/3, so the ratio would be 2:1. It's a wild bunch of flashing, so I Need to stay flexible in the way I do it. There might be some flashing with a ratio of 3:5 and a frequency of 2Hz, and some other flashing at 4Hz with ratio 1:1, and so on.
Also, I need to be able to flash in sync. So if one object is flashing already and I start flashing another one, they need to be in sync (or rather their cycles need to be in sync, the flashing may vary as the ratio may be different). But if at the same frequency, they need to "turn on" at the same time, even if their ratios are different. Also, they all need to turn on at the same time the slowest turns on.
My current approach: I have a GameObject FlashCycle, that essentially in it's update method calculates a progress for the 3 frequency's I have (2Hz, 4Hz and 8Hz).
float time = Time.time;
twoHerzProgress = (time % twoHerzInSeconds) / twoHerzInSeconds;
fourHerzProgress = (time % fourHerzInSeconds) / fourHerzInSeconds;
eightHerzProgress = (time % eightHerzInSeconds) / eightHerzInSeconds;
I have tried different times, but that didn't really matter so let's just stick to that one if you don't think it's a bad idea!
Now, whenever I want to flash an object, in it's own Update() I do this:
switch (flashRate.herz)
{
case FlashRateInterval.twoHerz:
show = flashCycle.oneHerzProgress <= onTimePercentage;
case FlashRateInterval.fourHerz:
show =flashCycle.twoHerzProgress <= onTimePercentage;
case FlashRateInterval.eightHerz:
show =flashCycle.fourHerzProgress <= onTimePercentage;
default:
show =true;
}
and then just continue and have the object displayed if show == true.
Unfortunately this doesn't flash the objects at a nice smooth and regular interval. I measured the 2Hz interval and got differences in the ratio of up to 48ms, and though it seems like not much it really makes a difference on the screen.
So the question boils down to: How can I get quick, reqular flashes while maintaining the flexibility (ratio and frequency wise) and have a syncronized flash?
Thanks for your help!
You could use Coroutines and WaitForSeconds to achieve that
// onRatio and offRatio are "optional" parameters
// If not provided, they will simply have their default value 1
IEnumerator Flash(float frequency ,float onRatio = 1, float offRatio = 1)
{
float cycleDuration = 1.0f / frequency;
float onDuration = (onRatio/ (onRatio + offRatio)) * cycleDuration;
float offDuration = (offRatio/ (onRatio + offRatio)) * cycleDuration;
while(true)
{
show = true;
yield return new WatForSeconds(onDuration);
show = false;
yield return new WatForSeconds(offDuration);
}
}
so you can call it either with a frequency e.g. 8Hz
StartCoroutine(Flash(8.0f));
this is actually equal to any call where you set onRatio = offRatio e.g.
StartCoroutine(Flash(8.0f, onRatio = 1, offRatio = 1));
StartCoroutine(Flash(8.0f, onRatio = 2, offRatio = 2));
....
or with a frequency and ratios e.g. 1(on):2(off) with 8Hz
StartCoroutine(Flash(8.0f, onRatio = 1, offRatio = 2));
With this setup the Coroutine runs "forever" in the while(true)-loop. So, don't forget before you start a new Coroutine with different parameters to first stop all routines with
StopAllCoroutines();
Now if you want to change that dynamically in an Update method, you would have to add some controll flags and additional variables in roder to make sure a new Coroutine is only called when something changed:
FlashRateInterval currentInterval;
float currentOnRatio = -1;
float currentOffRatio = -1;
void Update()
{
// if nothing changed do nothing
if(flashRate.herz == currentInterval
//todo && Mathf.Approximately(<yourOnRatio>, currentOnRatio)
//todo && Mathf.Approximately(<yourOffRatio>, currentOffRatio)
) return;
StopAllCoroutines();
currentInterval = flashRate.herz;
//todo currentOnRatio = <yourOnRatio>;
//todo currentOffRatio = <yourOffRatio>;
switch (flashRate.herz)
{
case FlashRateInterval.twoHerz:
StartCoroutine(2.0f);
//todo StartCoroutine(2.0f, onRatio = <yournRatio>, offRatio = <yourOffRatio>);
case FlashRateInterval.fourHerz:
StartCoroutine(4.0f);
//todo StartCoroutine(4.0f, onRatio = <yournRatio>, offRatio = <yourOffRatio>);
case FlashRateInterval.eightHerz:
StartCoroutine(8.0f);
//todo StartCoroutine(8.0f, onRatio = <yournRatio>, offRatio = <yourOffRatio>);
default:
show =true;
}
}
Notes:
I dont know your FlashRateInterval but if you need to use it for some reason you could make it like
public enum FlashRateInterval
{
AllwaysOn,
twoHerz = 2,
fourHerz = 4,
eightHerz = 8
}
in order to directly use the correct values.
I would call a frequency variable flashRate.herz. You also wouldn't call a size value cube.meters. I'ld recommend to rename it to flashRate.frequency.
To archieve that syncing you would somehow need access to all Behaviours and compare their values (so I'ld say some static List<YourBehavior>) and than e.g. in the Coroutine wait until all bools are e.g. set to true before continuing with your own one. For that you would need an additional bool since it is possible that show is true permanently on one component.
public bool isBlinking;
IEnumerator Flash(float frequency ,float onRatio = 1, float offRatio = 1)
{
//todo: You'll have to set this false when not blinking -> in Update
isBlinking = true;
float cycleDuration = 1.0f / frequency;
float onDuration = (onRatio/ (onRatio + offRatio)) * cycleDuration;
float offDuration = (offRatio/ (onRatio + offRatio)) * cycleDuration;
// SYNC AT START
show = false;
// wait until all show get false
foreach(var component in FindObjectsOfType<YOUR_COMPONENT>())
{
// skip checking this component
if(component == this) continue;
// if the component is not running a coroutine skip
if(!component.isBlinking) continue;
// Now wait until show gets false
while(component.show)
{
// WaitUntilEndOfFrame makes it possible
// for us to check the value again already before
// the next frame
yield return new WaitForEndOfFrame;
}
}
// => this line is reached when all show are false
// Now lets just do the same but this time wating for true
// wait until all show get false
foreach(var component in FindObjectsOfType<YOUR_COMPONENT>())
{
// skip checking this component
if(component == this) continue;
// if the component is not running a coroutine skip
if(!component.isBlinking) continue;
// Now wait until show gets false
while(!component.show)
{
// WaitUntilEndOfFrame makes it possible
// for us to check the value again already before
// the next frame
yield return new WaitForEndOfFrame;
}
}
// this line is reached when all show are getting true again => begin of loop
while(true)
{
.........
Instead of using FindObjectsOfType<YOUR_COMPONENT>() which is kind of slow you could also do something like
public static List<YOUR_COMPONENT> Components = new List<YOUR_COMPONENT>();
private void Awake()
{
if(!Components.Contains(this)){
Components.Add(this);
}
}
so you also get currently disabled components and objects
You got some diferences because you are doing everything in an Update() cycle with <= condition. On slower/faster machines you will have more/less differences because the frame's duration will never be equal to your frequency.
Try doing everything in a Corotine: unity coroutine docs
//bad code below but i think its more understandable like this
IEnumerator Flash()
{
while(true)
{
BlinkOn();
Sync();//sync here another cicle if you want to sync when on starts
yield return new WaitForSeconds(yourDuration);// yourDuration*multiplier/something+0.5f....ecc
BlinkOff()
Sync();//sync here another cicle if you want to sync when of starts
yield return new WaitForSeconds(yourDuration);
}
}
I am trying to use two yields within a coroutine loop (because I need to iterate out arrays with pauses between each loop).
The first loop works correctly, with all the yields working for the right amount of time. By the second loop, the yield return new WaitForSeconds() begins counting down right away, not waiting for the yield and code before it to complete (it seems). By the time of the third loop, the timing is all off.
I tried using a while loop instead of a for but got the same result.
TLDR: I need to loop out my arrays with pauses between each one. How can I use more than one yield past the first loop through in a coroutine?
public IEnumerator doPathfinding()
{
for (int i = 0; i < waypoint.Length; i++)
{
// get first waypoint from array
var _waypoint = waypoint[i];
// get node on A* of cloest waypoint
closestPointOnNavmesh = AstarPath.active.GetNearest(_waypoint.transform.position, NNConstraint.Default).clampedPosition;
// Move towards destination
ai.destination = closestPointOnNavmesh;
// Wait until within X range of waypoint
yield return new WaitUntil(() => distanceReached == true);
// Agent is now at waypoint and automatically stops. Wait 5 seconds before looping to next waypoint.
yield return new WaitForSeconds(5f);
}
Debug.Log("Loop End");
}
public override void OnUpdate()
{
// Get current distance to the target. If distance is less than offset, then sent event.
currentDistance = Vector3.Distance(go.transform.position, closestPointOnNavmesh);
if(currentDistance <= arrivalOffset.Value)
{
distanceReached = true;
}
else
{
distanceReached = false;
}
}
The code inside the couroutine is fine, it works as intended.
Most probably, based on the issue you reported, you're calling more than once the coroutine at the same time.
Use a bool to check if the coroutine has already started or not, like this:
bool isDoPathFindingRunning = false;
IEnumerator = doPathFinding();
private void Awake() {
pathFinding = doPathfinding();
}
private void WhereeverYouStartCoroutine() {
if (!isDoPathFindingRunning)
StartCoroutine(pathFinding);
}
public IEnumerator doPathfinding() {
isDoPathFindingRunning = true;
// Do your stuff
isDoPathFindingRunning = false;
}
I've written code to reset the x position of the ufo when it moves off the screen. I have taken into consideration everything that could happen with the ufo.
Below is the code which says if the ufo travels off the screen, then the x position of the ufo is set back to 0, and it is killed off by means of false. I don't know what more i could add. The ufo travels off the screen, not to be seen ever again (poor thing) :( Any help?
if (ufo.alive == false)
{
Random random = new Random();
int randomNumber = random.Next(0, 100);
{
if (randomNumber == 1)
{
ufo.alive = true;
}
}
{
if (ufo.XPos > 1000)
{
// kill the ufo if it goes off th
ufo.alive = false;
ufo.XPos = 0;
}
}
//make a new one
// here you want to do it randomly .
// so
//int random = random number (you have to do some code to make a random number google it.
//if (random number == 1)
// ufo = new ufo();
// so if you tell it to make a random number between 1 and 1000, then every now and then, 1 will be the number it makes
// fo when it amkes one, and randomnumber is equal to 1, it will make a new ufo.
// i will let you figure out how to do the random bit.
// i guess haha
}
//if ufo is alive
// check for collision
if (ufo.alive == true)
{
// also, we need to make it move
ufo.XPos = ufo.XPos + 1;
if (MissileFired != null)
{
// if you miss, and the ufo carries on, it will go forever.
//so
Rectangle rectMissile = new Rectangle((int)MissileFired.GetPosition().X, (int)MissileFired.GetPosition().Y, MissileImg.Width, MissileImg.Height);
Rectangle rectUFO = new Rectangle(ufo.XPos, 30, UFOImage.Width, UFOImage.Height);
if (rectMissile.Intersects(rectUFO))
{
PlayerScore = PlayerScore + 1000;
// we needed to kill the missile, other wise it gives you a point for every time it goes through.
MissileFired = null;
//now only 1000 points for winning
ExplosionSoundInstance.Play();
ufo.alive = false;
}
}
}
}
EDITED CODE: above
the error you have is easy to debug and fix, you should learn to use a debugger, it is very useful.
here is a debugging tutorial
Basically the check to see if the position of the UFO > 1000 is never being run when the UFO is alive because it's inside the scope of the first IF statement.
if (ufo.alive == false)
{
if (ufo.XPos > 1000)
{
}
}
if the UFO is moving shouldn't you be checking for the position while it's alive?
I'm not sure that your brackets are well-formed. The open bracket { on line 5 doesn't make any sense there. Plus you dont' have a closing bracket at the very end.
Maybe the code is dying not the UFO?