- Home /
Coroutines and states
I have studied coroutines from wikipedia,Unity documentation and this unite 2013 video.
Now the problem is as Wikipedia explains Coroutines are able to save states(including local variables).In the video Devin Horsman shows that a state machine can be implemented using Coroutines at 7:54 in the video he shows implementation of a coroutine setup to work as a state machine.He explains that coroutines can "remember state" which is useful as it avoids having class level variables and avoid having cluttered code. So I tried implementing a very basic follower where the follows the enemy while in range and then goes to wait if out of range using coroutines as-
using UnityEngine;
using System.Collections;
public class Enemy : MonoBehaviour
{
private GameObject player;
private float DangerDistance = 8.0f;
public float EnemySpeed = 1.0f;
// Use this for initialization
void Awake()
{
player = GameObject.FindGameObjectWithTag("playerBall");
}
bool inDangerDistance()
{
Vector2 playerpos = new Vector2(player.transform.position.x,player.transform.position.z);
Vector2 mine = new Vector2(transform.position.x,transform.position.z);
Vector2 dotter = new Vector2(transform.forward.x,transform.forward.z);
//Debug.Log(Vector2.Dot((playerpos-mine).normalized,dotter.normalized));
return Vector2.Distance(playerpos,mine)<=DangerDistance && nearEquals(Vector2.Dot((playerpos-mine).normalized,dotter.normalized),1);
}
bool nearEquals(float first,float second)
{
return Mathf.Abs(first-second)<=0.2f;
}
IEnumerator search()
{
while(true)
{
if(inDangerDistance())
{
yield return StartCoroutine(follow());
}
transform.Rotate(0,90*Time.deltaTime,0);
yield return null;
}
}
IEnumerator follow()
{
Vector2 playerpos = new Vector2(player.transform.position.x,player.transform.position.z);
Vector2 mine = new Vector2(transform.position.x,transform.position.z);
while(true)
{
while(inDangerDistance())
{
playerpos.x = player.transform.position.x;
playerpos.y = player.transform.position.z;
mine.x = transform.position.x;
mine.y = transform.position.z;
Vector3 v1 = new Vector3(playerpos.x,0,playerpos.y)-new Vector3(mine.x,0,mine.y);
transform.forward = v1;
transform.Translate((playerpos.x-mine.x)*Time.deltaTime*EnemySpeed,0,(playerpos.y-mine.y)*Time.deltaTime*EnemySpeed,relativeTo:Space.World);
yield return null;
}
yield return StartCoroutine(search());
Debug.Log("yoohoo");
}
}
void Start()
{
StartCoroutine(search());
}
}
Now the problem I notice with this approach is there is no actual State preserved as I switch from search to follow and vice versa as StartCoroutine builds a new Coroutine object and defeats the purpose of preservation("yoohoo" is never printed in follow()).So what advantage really Coroutines have other than using WaitforSeconds() if they could not preserve state or there is something wrong in the video or I am completely wrong?Thanks for help.
Answer by Kiwasi · Oct 16, 2014 at 02:58 AM
You've essentially built an infinite chain of coroutines. Search will start Follow which starts Search which starts Follow... ad infinitum.
Take the while(true) condition out of Follow. Remove line 68 (the call to Search) and it should function closer to a FSM.
Another alternative is to have a master coroutine. Take the while (true) out of both and do something like this (call from Start). Remove the calls to other coroutines from Follow and Start. And have them only exit their loop when there transition condition is met.
IEnumerator FSM (){
while (true){
yield return StartCoroutine (Search());
yield return StartCoroutine (Follow());
}
}
the problem in the code u gave is still there as new Coroutine objects will be created by startCoroutine and I wont be able to save the state as such of search and follow.Please see the video I talk about.
@Ashish4241: You have to be careful with the definition of coroutines since there are huge differences. Unity (and C#) doesn't have true coroutines. C# / .NET provides generators, so called semicoroutines. They don't allow you to specify the re-entry point. You always come back where you left off.
Generators in .NET are actually implemented as a FS$$anonymous$$ with a switch statement. There used to be a great blog post with great examples how the generated IEnumerator class looks like. However the blog doesn't exist anymore, but there are others like this one.
You're using your coroutine just like subroutines. You're blocking the coroutine until the subroutine has finished. The point of using a coroutine for some kind of FS$$anonymous$$ is usually to have everything inside that coroutine. You can use nested while loops to stay in a certain state, break out if your done, ect... You can have temporary "update loops" inside the coroutine. That allows you to simply structure your code almost like pseude code.
Another way is to have for each state a seperate coroutine and run them all at once (you just start them all once in Start). Each coroutine simply idles by being trapped in a while loop with a yield return null until the while loop exits and the actual action / state this coroutine represents becomes active. However such an approach isn't very flexible.
Personally i never used coroutines to implement a complete FS$$anonymous$$. They come in handy when you have actions that should be executed in order but each action might take several frames to complete.
So whats shown in the video at 7:54 is just using coroutines for nothing more than subroutines which execute over frames?
Updated my code to show my original intent. Sorry for the mixup.
@Ashish4241 Yes. That is the whole point of a coroutine.
If you want a massive complex FS$$anonymous$$ then you should look at implementing a specific framework (UnityGems has one) or a graphical FS$$anonymous$$ (Playmaker)