Lerp in Coroutine not running - unity3d

I'm using the coroutine below to try to scale a transform from Vector3.zero to Vector3.one over one second (scalingTime). I've determined that the coroutine is definitely being run, but the object is not scaling. Am I using the "yield return null" in the while loop correctly?
IEnumerator ScaleLaser()
{
float elapsedTime = 0;
float scalingTime = 1;
Vector3 currentScale = laser.localScale;
while (elapsedTime < scalingTime)
{
transform.localScale = Vector3.Lerp(currentScale, Vector3.one, elapsedTime / scalingTime);
elapsedTime += Time.deltaTime;
yield return null;
}
}

This should work.
IEnumerator ScaleLaser()
{
float scalingTime = 1;
float time = 0;
while (time < 1)
{
time += Time.deltaTime / scalingTime;
laser.localScale = Vector3.Lerp(laser.localScale, Vector3.one, time);
yield return null;
}
}

Related

Write IEnumerator for Movement

I want to write an IEnumerator to move at the desire distance at a specified time. I have tried to write the code for this but this is running a different way.
float moveDistance=1f;
float moveSpeed=5f;
float elapsedDistance = 0f;
while (elapsedDistance <= moveDistance)
{
elapsedDistance += Time.deltaTime * moveSpeed;
Vector3 cubeLocalPosition = transform.localPosition;
cubeLocalPosition.y += Time.deltaTime * moveDistance;
transform.localPosition = cubeLocalPosition;
yield return null;
}
Through this code, Object can't able to travel 1 unit distance. How can I correct this code?
Your while loop condition uses elapsedDistance which is increasing with moveSpeed. That latter is 5 so it will be 1 in 1/5 of a second. Your object is likely only moving 0.2unit.
you should use Mathf.Lerp or MoveTowards
float distance = 1f;
float time = 0f;
float period = 1f; // how long in second to do the whole movement
yield return new WaitUntil(()=>
{
time += Time.deltaTime / period;
float movement = Mathf.Lerp(0f, distance, time);
Vector3 cubeLocalPosition = transform.localPosition;
cubeLocalPosition.y += movement;
transform.localPosition = cubeLocalPosition;
return time >= 1f;
});
Following your own rotation, you calculate the finalpoint to go
and after,
you could use Vector3.Lerp or Vector.Slerp to move in the specified time..So the moving speed adapt itself following the time desired
var endpoint = transform.position + transform.forward.normalized * distance;
StartCoroutine(MoveToPosition(transform, endpoint, 3f)
:
:
public IEnumerator MoveToPosition(Transform transform, Vector3 positionToGO, float timeToMove)
{
var currentPos = transform.position;
var t = 0f;
while (t < 1f)
{
t += Time.deltaTime / timeToMove;
transform.position = Vector3.Lerp(currentPos, positionToGO, t);
yield return null;
}
transform.position = positionToGO;
}

Face other object using lerp in a coroutine

I want to have a coroutine to use the lerp function and so far I managed to do so partially. The characters will turn their position to face the target object. However, I want the characters to move their rotation starting from their own while what is currently happening is their rotation is set to some abstract value like X=0 and then it will start rotating them from there.
What I want to do is simply turn the character to face another transform smoothly. Here's the code:
public IEnumerator LookSlerpAt(Transform target, int seconds)
{
IsTurningHead = true;
Vector3 relativePos = target.position - this.transform.position;
Quaternion lookRotation = Quaternion.LookRotation(relativePos);
float elapsedTime = 0f;
float fraction = elapsedTime / seconds;
while (elapsedTime <= seconds)
{
this.transform.rotation = Quaternion.Lerp(target.rotation, lookRotation, fraction);
print($"rotation:{this.transform.rotation}");
elapsedTime += Time.deltaTime;
fraction = elapsedTime / seconds;
yield return Time.deltaTime;
}
IsTurningHead = false;
}
Thanks for your time
if you want to rotate smoothly to direction you could use that as coroutine: You specify the specified time to rotate and the velocity is automatically adjusted
public IEnumerator RotateToDirection(Transform transform, Vector3 positionToLook, float timeToRotate)
{
var startRotation = transform.rotation;
var direction = positionToLook - transform.position;
var finalRotation = Quaternion.LookRotation(direction);
var t = 0f;
while (t <= 1f)
{
t += Time.deltaTime / timeToRotate;
transform.rotation = Quaternion.Lerp(startRotation, finalRotation, t);
yield return null;
}
transform.rotation = finalRotation;
}

How to ensure that one coroutine completes before another is run?

I have two coroutines. One has several coroutines with it. The first coroutine does not wait for the second to complete before the second one runs within the Start() method. Please see the code below.
void Start() {
StartCoroutine(DrawLineWithDuration (3.0f));
StartCoroutine (changeLinePos (2.0f));
}
IEnumerator DrawLineWithDuration (float duration) {
lr.positionCount = 1;
lr.SetPosition(0, GetPoint(0));
float waitDur = duration / numPoints;
for (int i = 1; i < numPoints + 1; i++) {
float t = i / (float)numPoints;
lr.positionCount = i+1;
lr.SetPosition(i, GetPoint(t));
yield return new WaitForSeconds(waitDur);
}
}
IEnumerator moveToPosition1(Vector3 toPosition, float duration)
{
float counter = 0;
Vector3 startPos = points[1];
while (counter < duration)
{
counter += Time.deltaTime;
points[1] = Vector3.Lerp(startPos, toPosition, counter / duration);
yield return null;
}
}
IEnumerator moveToPosition2(Vector3 toPosition, float duration)
{
float counter = 0;
Vector3 startPos = points[2];
while (counter < duration)
{
counter += Time.deltaTime;
points[2] = Vector3.Lerp(startPos, toPosition, counter / duration);
yield return null;
}
}
IEnumerator moveToOriginal1(Vector3 toPosition, float duration)
{
float counter = 0;
Vector3 startPos = points[1];
while (counter < duration)
{
counter += Time.deltaTime;
points[1] = Vector3.Lerp(startPos, toPosition, counter / duration);
yield return null;
}
}
IEnumerator moveToOriginal2(Vector3 toPosition, float duration)
{
float counter = 0;
Vector3 startPos = points[2];
while (counter < duration)
{
counter += Time.deltaTime;
points[2] = Vector3.Lerp(startPos, toPosition, counter / duration);
yield return null;
}
}
IEnumerator changeLinePos(float waitTime) {
yield return new WaitForSeconds(waitTime);
while (isRepeating) {
StartCoroutine (moveToPosition1 (newPos1, moveDuration));
StartCoroutine (moveToPosition2 (newPos2, moveDuration));
yield return new WaitForSeconds (betweenPosMoveDelay);
StartCoroutine (moveToOriginal1 (oriPos1, moveDuration));
StartCoroutine (moveToOriginal2 (oriPos2, moveDuration));
yield return new WaitForSeconds (betweenPosMoveDelay);
}
}
Within Start() how can I make sure that StartCoroutine(DrawLineWithDuration (3.0f)); completes with its given duration before StartCoroutine (changeLinePos (2.0f)); begins. Also within changeLinePos(float waitTime) how can I make sure that moveToPosition1 and moveToPosition2 complete before moveToOriginal1 and moveToOriginal1 and moveToOriginal2?
It's actually pretty simple, you can yield return StartCoroutine(DrawLineWithDuration (3.0f)); as the first line in changeLinePos, like this:
void Start() {
StartCoroutine (ChangeLinePos (2.0f));
}
IEnumerator ChangeLinePos(float waitTime) {
yield return StartCoroutine(DrawLineWithDuration (3.0f));
while (isRepeating) {
StartCoroutine (MoveToOriginal1 (oriPos1, moveDuration));
StartCoroutine (MoveToOriginal2 (oriPos2, moveDuration));
yield return new WaitForSeconds (betweenPosMoveDelay);
}
}
IEnumerator MoveToOriginal1(Vector3 toPosition, float duration) {
yield return StartCoroutine (MoveToPosition1 (newPos1, moveDuration));
//Rest of MoveToOriginal1 code
}
IEnumerator MoveToOriginal2(Vector3 toPosition, float duration) {
yield return StartCoroutine (MoveToPosition2 (newPos2, moveDuration));
//Rest of MoveToOriginal1 code
}
With this structure, the timing is the following:
ChangeLinePos starts in Start()
The first line starts DrawLineWithDuration and stops the execution of ChangeLinePos until the former is finished
When DrawLineWithDuration is finished, and if isRepeating==true, the code starts MoveToOriginal1 and MoveToOriginal2 synchronously
MoveToOriginal1 and MoveToOriginal2 both start MoveToPosition1 and MoveToPosition2 and wait until the last two are finished.
I left the while because it seems to be affected by something outside the coroutines, so be sure that isRepeating changes to false when needed, if not the MoveToOriginal1 and MoveToOriginal2 coroutines will be started always until the application is stopped.

how to change from one coroutine to another when while condition is false

I have created two coroutines, one for moving my badGuy game objects right and another for moving them left (please see code below).
IEnumerator moveBadGuyLeft (Transform fromPosition, Vector3 toPosition, float duration, int newIndex) {
while (emptyPos.Contains(badGuyPos[newIndex])) { //how to switch to moveBadGuyRight when this condition is false
emptyPos.Add(badGuyPos[newIndex + 1]);
filledPos.Remove(badGuyPos[newIndex + 1]);
float counter = 0;
Vector3 startPos = fromPosition.position;
while (counter < duration)
{
counter += Time.deltaTime;
fromPosition.position = Vector3.Lerp(startPos, toPosition, counter / duration);
yield return null;
}
emptyPos.Remove(badGuyPos[newIndex]);
filledPos.Add(badGuyPos[newIndex]);
if (newIndex > 0) {
newIndex--;
}
startPos = toPosition;
toPosition = new Vector3 (badGuyPos[newIndex], startPos.y, startPos.z);
int waitInterval = Random.Range(3, 5);
yield return new WaitForSeconds(waitInterval);
}
}
IEnumerator moveBadGuyRight (Transform fromPosition, Vector3 toPosition, float duration, int newIndex) {
while (emptyPos.Contains(badGuyPos[newIndex])) { //how to switch to moveBadGuyLeft when this condition is false
emptyPos.Add(badGuyPos[newIndex - 1]);
filledPos.Remove(badGuyPos[newIndex - 1]);
float counter = 0;
Vector3 startPos = fromPosition.position;
while (counter < duration)
{
counter += Time.deltaTime;
fromPosition.position = Vector3.Lerp(startPos, toPosition, counter / duration);
yield return null;
}
emptyPos.Remove(badGuyPos[newIndex]);
filledPos.Add(badGuyPos[newIndex]);
if (newIndex > 0) {
newIndex++;
}
startPos = toPosition;
toPosition = new Vector3 (badGuyPos[newIndex], startPos.y, startPos.z);
int waitInterval = Random.Range(3, 5);
yield return new WaitForSeconds(waitInterval);
}
}
I am trying to move my badGuy objects left till they meet a filled position then move them right till they meet a filled position and so on, toggling between left and right. I know that if my condition
while (emptyPos.Contains(badGuyPos[newIndex]))
is false then I should change from one coroutine to the other or vice versa. How can I implement this changing between coroutines? Please see how I am calling the coroutine in the Start method below:
for (int i = 0; i < badGuys.Count; i++) {
if (badGuys [i].getBlockType () == BadGuySetup.BadGuyType.moving) {
int indexInBadGuyPos = badGuyPos.IndexOf(badGuys[i].getBadGuyGameObject().transform.position.x);
Vector3 targetPos = new Vector3(badGuyPos[indexInBadGuyPos - 1], badGuys[i]. getBadGuyGameObject().transform.position.y, 0.0f);
StartCoroutine(moveBadGuyLeft(badGuys [i]. getBadGuyGameObject().transform, targetPos, 1.0f, indexInBadGuyPos - 1));
}
}
UPDATE
I added the following to the if (newIndex) > 0 condition in moveBadGuyLeft and moveBadGuyRight:
in moveBadGuyLeft:
if (newIndex > 0) {
newIndex--;
if (!emptyPos.Contains(badGuyPos[newIndex])) {
isMovingLeft = false;
badGuyDestPos = new Vector3(badGuyPos[newIndex + 2], startPos.y, startPos.z );
badGuyNewIndex = newIndex + 2;
break;
}
}
in moveBadGuyRight:
if (newIndex > 0) {
newIndex++;
if (!emptyPos.Contains(badGuyPos[newIndex])) {
isMovingLeft = false;
badGuyDestPos = new Vector3(badGuyPos[newIndex - 2], startPos.y, startPos.z );
badGuyNewIndex = newIndex - 2;
break;
}
}
Then I created another coroutine which is suppose to change between the two coroutines:
IEnumerator movingBadGuys(Transform fromPosition, Vector3 toPosition, float duration, int newIndex) {
if(isMovingLeft){
yield return StartCoroutine (moveBadGuyLeft(fromPosition, toPosition, duration, newIndex));
}
else if(!isMovingLeft){
yield return StartCoroutine (moveBadGuyRight(fromPosition, toPosition, duration, newIndex));
}
}
Finally I updated looping through the badguys and moving them:
for (int i = 0; i < badGuys.Count; i++) {
if (badGuys [i].getBlockType () == BadGuySetup.BadGuyType.moving) {
int indexInBadGuyPos = badGuyPos.IndexOf(badGuys[i].getBadGuyGameObject().transform.position.x);
Vector3 targetPos = new Vector3(badGuyPos[indexInBadGuyPos - 1], badGuys[i]. getBadGuyGameObject().transform.position.y, 0.0f);
badGuyDestPos = targetPos;
badGuyMoveDuration = 1.0f;
badGuyNewIndex = indexInBadGuyPos - 1;
StartCoroutine(movingBadGuys(badGuys [i]. getBadGuyGameObject().transform, badGuyDestPos, badGuyMoveDuration, badGuyNewIndex));
}
}
But this is changes are not working. I am testing it with only two badGuys which always start moving left and they only move left once and stop though there are other empty positions for them to continue moving left.
Unfortunately, I think this is not a good way to use coroutines. A better approach would be to have a single coroutine "moveBadGuy" then have the BadGuy object have a move method, and some internal state variable. For example.
class BadGuy{
bool moveLeft = false;
public void move(){
if(moveLeft){
//move the guy left
}else{
//move right
}
}
}
Then your coroutine can do
public IEnumerator moveBadGuy(BadGuy guyToMove){
guyToMove.move();
}
Another solution involving a single coroutine would be to have a moveLeft function and a moveRight function. For example,
public IEnumerator moveBadGuy(){
if(shouldmoveleft){
moveLeft();
}else{
moveRight();
}
}
public void moveLeft(){
//logic for moving left
}
public void moveRight(){
//logic for moving right
}
I recommend that you dont depend on while , what you can do is use the ability of courotuines to yield another coroutine like this yield return StartCoroutine(myFunc());
I personally wouldn't give the responsibility to the Move[Right|Left] coroutine to stop themselves.
Since coroutines are just iterator blocks you can even abstract further, and let the parent coroutine drive their execution.
Here's an abstract example:
IEnumerator AlternateOnCondition(Func<bool> evaluateCondition,
IEnumerator firstAction,
IEnumerator secondAction)
{
while(true)
{
if(evaluateCondition())
{
if (firstAction.MoveNext())
yield return firstAction.Current;
}
else
{
if (firstAction.MoveNext())
yield return secondAction.Current;
}
yield return null;
}
}
This is just an example of course, but it should give you the idea how coroutine can be nested.

Unity3D bird flying back and forth

I'm trying to create a bird moving across screen along x axis.
bird.transform.position = Vector3.Lerp (pos1, pos2, (Mathf.Abs(speed * Time.time) + 1.0f) / 2.0f);
Using this in Update() the bird flies only once. I want that after it flies to the right, it should wait 2-3 seconds and then fly back with a different sprite.
transform.translate doesn't work like this. Any help will be appreciated.
you would need to put in another LERP for going the other direction and have a variable for which way the bird is flying so roughly:
bool goleft;
if(goleft)
{
if(transform.position != pos2)
{
transform.position = Vector3.Lerp (pos1, pos2, (Mathf.Abs(speed * Time.time) + 1.0f) / 2.0f);
}
else
{
goleft = false;
//change the direction the bird is facing here
}
}
else
{
if(transform.position != pos1)
{
transform.position = Vector3.Lerp (pos2, pos1, (Mathf.Abs(speed * Time.time) + 1.0f) / 2.0f);
}
else
{
goleft = true;
//change the direction the bird is facing here
}
}
Hope that helps
Not tested but i would start here:
Vector3[] posts;
int current = 0;
float speed = 5.0f;
float threshold = Single.Epsilon;
float delay = 2.0f;
public void Update() {
if(!waiting && posts.Length > 0)
{
if(!Mathf.Approximately((posts[current].position - transform.position).sqrMagnitude, threshold)
{
float step = speed * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, posts[current].position, step);
}
else StartCoroutine("Next");
}
}
IEnumerator Next() {
waiting = true;
yield return new WaitForSeconds(delay);
current = (current + 1) % posts.Length;
waiting = false;
}
This will also allow you to have as many posts as you want to have, and all your movement dynamics can be handled in Next(), whereas, if you want him to go from post 0...1...2...3...0...1.. or 0...1...2...3...2...1...
If you want the latter you just change current = (current + 1) % posts.Length; to Mathf.PingPong(current + 1, posts.Length);
I'd go a bit like this:
float flipCooldown
float defaultFlipCooldown = 2.0f;
bool isGoingRight;
Vector2 pos1;
Vector2 pos2;
void Start() {
flipCooldown = defaultFlipCooldown;
isGoingRight = true;
pos1 = new Vector2(0, 0);
pos2 = new Vector2(5, 0); // whatever floats your boat
}
void Update()
{
Vector2 initialPosition = null;
Vector2 finalPosition = null;
if (flipCooldown <= 0) {
isGoingRight = !isGoingRight
flipCooldown = defaultFlipCooldown;
ChangeSprite();
}
if (isGoingRight) {
initialPos = pos1;
finalPos = pos2;
} else {
initialPos = pos2;
finalPos = pos1;
}
bird.transform.position = Vector3.Lerp (initialPos, finalPos, (Mathf.Abs(speed * Time.time) + 1.0f) / 2.0f);
flipCooldown -= Time.deltaTime;
}
What you want to get is that the Time.deltaTime is decreasing the cooldown for the bird to turn. You can easily change the cooldown in the defaultFlipCooldown variable. When it's done going one way, it'll just flip the position and the Lerp function will do the rest of the work. The ChangeSprite function will be just a GetComponent<SpriteRenderer>().sprite change.
If you don't want fix positions, you can calculate how much it'll fly, define the final position and just change the pos1 and pos2.
It's also important to note that WaitForSeconds will just work with Coroutines that is a concept for working with threads, never in a method like Update. You can learn more about Coroutines in Unity's manual: http://docs.unity3d.com/Manual/Coroutines.html