- Home /
Randomly changing between two tags using coroutine
I'm trying to randomly change my enemy tags between "Safe" and "Deadly". when the enemy is safe I want the tag to be safe for 20 seconds and when the tag is deadly, I want it to be deadly for 10 seconds. I tried coroutine but its not properly working, the tags keep going on and off. Probably my code is the problem...any help?
void Update()
{
StartCoroutine(Wait());
index = Random.Range(0, 2);
if (index == 0)
{
isSafe = true;
}
else
{
isSafe = false;
}
}
IEnumerator Wait()
{
if (isSafe == false)
{
this.gameObject.tag = "Deadly";
yield return new WaitForSeconds(10f);
}
else if (isSafe == true)
{
this.gameObject.tag = "Safe";
yield return new WaitForSeconds(20f);
}
}
Answer by Casiell · Aug 19, 2021 at 02:24 PM
You are starting a new coroutine every frame and each coroutine will overwrite tags independently, so after a few frames the tags will switch pretty much constantly.
You want to move the StartCoroutine to Start instead of Update.
And I have no idea why you have the rest of the code in Update. Just pop those lines into your coroutine. When you set the Deadly tag, set isSafe to false and when you set the tag to Safe, set it to true.
Also your conditions in coroutine are the wrong way around.
Edit: And there is nothing random about this, so your title is incorrect, but that's a minor thing
I wanted to the state on tags to randomly change and not have a pattern, lets say like the tags going from Safe,Safe,Deadly,Safe instead of it being Safe,Deadly,Safe,Deadly. not sure if I'm making sense but that basically why I added the index using random range
Thank you for the response, I moved that line from Update to Start and added a while loop in the iEnumerator function and it works now
Safe,Safe,Deadly,Safe
What exactly makes this different from a variable wait time? ^^ If you get two "safe" in row it means nothing changes. So it's just a longer safe state. Ultimatively you are just switching between the two states, just with varying times.
Answer by Bunny83 · Aug 19, 2021 at 03:15 PM
I'm a bit confused by where or how the random plays in here when you have a clear vision that one state should last 10 seconds and the other 20. Do you mean at least 10 / 20 seconds? However what happens after that time? Roll a random number and then wait again that amount of time? That means if it's safe if could only change to deadly after 20, 40, 60, 80 ... seconds. Usually such mechanics work better when you instead roll a random wait time that is within a certain min / max range and just toggle between the two states. Anyways just like Casiell said, just move everything into a coroutine which you simply start at the beginning from inside Start.
IEnumerator SafeModeCycle()
{
while (true)
{
int index = Random.Range(0, 2);
isSafe = index == 0;
if (isSafe)
{
gameObject.tag = "Safe";
yield return new WaitForSeconds(20f);
}
else
{
gameObject.tag = "Deadly";
yield return new WaitForSeconds(10f);
}
}
}
void Start()
{
StartCoroutine(SafeModeCycle());
}
Though as I said it probably makes more sense to use a variable wait time. Rolling a random number between 0 and 1 has the chance to get stuck really really long on one outcome. Since we wait 10 / 20 seconds each time if you have "bad luck" you could get stuck in one state for minutes (and theoretically hours ^^). Instead you can do something like this:
IEnumerator SafeModeCycle()
{
while (true)
{
isSafe = true;
gameObject.tag = "Safe";
yield return new WaitForSeconds(Random.Range(20f, 20f * 3));
isSafe = false;
gameObject.tag = "Deadly";
yield return new WaitForSeconds(Random.Range(10f, 10f * 3));
}
}
Here we simply toggle between the two states but the time between the toggle is random. I arbitrarily have choosen a 3 times larget max value. So the enemy will stay safe for anything between 20 seconds and up to 60 seconds. So it will wait at least 20 seconds and max 60 seconds until it switches. Likewise the deadly state will last at least 10 seconds and max 30 seconds. Now you can easily adjust those values to balance your game.
Yea I don't know why I thought putting coroutine in the update was a good idea. It's working now in the Start function. Thank you, much appreciated
I recommend you to do this to avoid problems when disabling the object:
OnDisable()
{
StopCoroutine(YourCoroutine());
}
From the Unity docs: "Note: Coroutines are not stopped when a MonoBehaviour is disabled, but only when it is definitely destroyed. You can stop a Coroutine using MonoBehaviour.StopCoroutine and MonoBehaviour.StopAllCoroutines. Coroutines are also stopped when the MonoBehaviour is destroyed."
While it is true that disabling the component won't stop the coroutine, deactivating the gameobject does. Also your line:
StopCoroutine(YourCoroutine());
does absolutely nothing at all (beside allocating some garbage). When you call your IEnumerator method, it will generate a new IEnumerator object. You can not stop your coroutine that way since this particular IEnumerator instance has never been started. If you really want to be able to forcefully stop a coroutine from the outside, you have to remember the Coroutine object that was returned when you used StartCoroutine.
Though this coroutine won't really cause any issues since it just flips a flag. However if you want to implement a "pause" function and want the coroutine to be paused as well, you have to implement this in the coroutine as well and just keep it running. You can trap the coroutine in another loop like this:
while (isPaused)
yield return null;
This will be skipped when isPaused is false. However when it's set to true it will just get stuck in that loop and every frame it checks if isPaused is false again at which point it would continue your coroutine.
Your answer
Follow this Question
Related Questions
Problems with While Loops Only repeating Once 1 Answer
How do I get into the data returned from a UnityWebRequest ? 2 Answers
Coroutine error with result 1 Answer
RayCast not working 5 Answers
ANR details 0 Answers