- Home /
Start Coroutine after other has finished
Hi, I'm working on 2d ship game and I'm trying to get my ship to rotate and then move after having finished rotating. In order to make the rotation happen over time I'm using a while loop inside a coroutine and I'm also using another coroutine to make the ship move but I don't want the ship to start moving before its finished rotating. Here is my code:
void Update () {
//stuff
StartCoroutine(RotateToTarget());
//isRotatingToTarget = false; pointless since the coroutine wouldn't have finished rotating
//StartCoroutine (MoveToTarget()); If I add this here it doesn't wait for the rotation to finish
}
IEnumerator RotateToTarget(){
while(transform.rotation != rotationToTarget)
{
transform.rotation = Quaternion.Lerp(transform.rotation, rotationToTarget , Time.deltaTime);
this.isRotatingToTarget = (transform.rotation != rotationToTarget);
yield return null;
}
isRotatingToTarget = false;
print ("Can move to target");
StartCoroutine (MoveToTarget()); //this never gets excecuted
}
IEnumerator MoveToTarget(){
if (!isMovingToTarget && !isRotatingToTarget){
while(transform.position.x != target.x && transform.position.y != target.y)
{
isMovingToTarget = true;
transform.position = Vector2.MoveTowards(transform.position, target, Time.deltaTime*movementSpeed);
yield return null;
}
this.isMovingToTarget = false;
}
Answer by Yword · Oct 20, 2014 at 03:52 AM
I think it is because the comparison statement (transform.rotation != rotationToTarget) always returns true.
It would be better to compare 2 Quaternions using Quaternion.Angle(). You can check out the explanation at http://docs.unity3d.com/ScriptReference/Quaternion-operator_eq.html
So, you can use Quaternion.Angle() to get the angle between 2 quaternions, then you can check whether the angle is below some small threshold.
float rotationThreshold = 0.1f;
bool isClosedToTargetRotation = Quaternion.Angle(transform.rotation, rotationToTarget) < rotationThreshold;
Also, due to floating point imprecision, it is not recommended to compare floats using the equal operator. So, to check whether 2 positions are same, it would be better to compare the distance between them.
float distanceThreshold = 0.1f;
bool isClosedToTargetPosition = Vector3.Distance(transform.position, target.position) < distanceThreshold;
Answer by Pyrian · Oct 20, 2014 at 04:01 AM
I wouldn't use a coroutine for any of this. All you need is, in Update:
if (isRotatingToTarget) {
transform.rotation = Quaternion.Lerp(transform.rotation, rotationToTarget , Time.deltaTime);
isRotatingToTarget = (transform.rotation != rotationToTarget);
if (!isRotatingToTarget) isMovingToTarget = true;
} else if (isMovingToTarget) {
transform.position = Vector2.MoveTowards(transform.position, target, Time.deltaTime*movementSpeed);
if (transform.position.x == target.x && transform.position.y == target.y) isMovingToTarget = false;
}
Answer by robertbu · Oct 20, 2014 at 05:52 AM
The code in your question starts the RotateToTarget coroutine on every Update(). This stacks up Coroutine calls. So if your movement script would take 5 seconds to complete and your app is running at 60 fps, you have 300 coroutine running that may be accelerating the process or fighting with each other, or simply taking resources. Coroutines work well when you want independent piece of work to run without interference. If the target will be moving so this code as to reacquire it, then consider using Update() as @Pyrian suggests.
As for not completing the rotation, the issue is this line:
transform.rotation = Quaternion.Lerp(transform.rotation, rotationToTarget , Time.deltaTime);
The way this form of Lerp works is to walk the approximately the same fraction of the remaining distance to the goal each frame. Since the distance is shrinking, the same fraction represents smaller and smaller rotations. The result is an eased movement. But the problem is that it takes a very long time to complete...and only completes due to floating point imprecision and perhaps some slop built into rotation comparison. It like the old adage, "how many days will it take to reach my goal if I walk half the distance each day." The answer is infinite...you never reach your goal. A solution is the one @YWord suggests...to use an angle threshold.
Chaining coroutines or using flags will work for what you want here, but there is an alternate. You can use 'yield' with coroutines. Here a demonstration rewrite of your code. It may not be a perfect fit since I don't have all your code, nor the context where it will be used.
using UnityEngine;
using System.Collections;
public class Example : MonoBehaviour {
public Transform target;
public float rotationSpeed = 5.0f;
public float movementSpeed = 2.25f;
private bool isMoving = false;
void Update () {
if (Input.GetKeyDown (KeyCode.Space)) {
StartCoroutine(RotateThenMove());
}
}
IEnumerator RotateThenMove() {
if (isMoving) {
yield break;
}
isMoving = true;
yield return StartCoroutine(RotateToTarget());
yield return StartCoroutine(MoveToTarget());
isMoving = false;
}
IEnumerator RotateToTarget(){
Quaternion rotationToTarget = Quaternion.LookRotation(target.position - transform.position);
while(Quaternion.Angle(transform.rotation,rotationToTarget) > 0.5f) {
transform.rotation = Quaternion.Slerp(transform.rotation, rotationToTarget , Time.deltaTime * rotationSpeed);
yield return null;
}
}
IEnumerator MoveToTarget() {
while(transform.position != target.position) {
transform.position = Vector3.MoveTowards(transform.position, target.position, Time.deltaTime*movementSpeed);
yield return null;
}
}
}
Thanks, it works! The only problem I'm having is the ´is$$anonymous$$oving´ variable gets set to false long before the coroutines have finished so you can get the ship to move to another location by just clicking without the animations having finished. I've tried putting it inside ´$$anonymous$$oveToTarget()´ but there is no use. Also for some reason the ship takes forever to finish rotating (either that or there is some kind of pause inbetween rotating and movement).
Seriously, what's up with the coroutines? You are doing absolutely nothing that even suggests to me that "this is better with a coroutine" rather than just using the flags in the Update function.
Answer by LeftRight92 · Oct 20, 2014 at 04:30 AM
Although starting the second coroutine in the first coroutine should work (I think), you can use a 'flag'. Note this will only work if "Can move to target" prints successfully. If it doesn't, see the paragraph at the end of this solution.
Firstly, add a new boolean to your script:
private bool canMoveToTarget = false;
Add the following check to your update method:
if(canMoveToTarget) StartCoroutine(MoveToTarget());
Then, set the boolean to true on the last line of RotateToTarget() and set it false on the first line of MoveToTarget().
That all said, you may want to check your vector maths. Due to the nature of Lerp, unless it receives a '1' as the final parameter, you're always going to be moving closer to the target, never equal to it. I recommend you set transform.Rotation equal to rotationToTarget once the difference between the two is small enough.
Your answer
Follow this Question
Related Questions
Flip over an object (smooth transition) 3 Answers
Smoothed Rotation with 2D Top-Down View 1 Answer
How to Address Texture2D Elements from a Sprite with Sprite Mode: Multiple, in Code? 1 Answer
Getting the rotation of an object does not return correct rotation 1 Answer
void update working under conditions 1 Answer