Start Coroutine step-after-step in loop - c#

I have a list (called path) with GameObjects. The current GameObject (with the script attached) should move from one position of the GameObjects in the path to the next stepwise. My current code makes it move to the last position of the path immediately. I already tried with WaitForSeconds at the end of the Coroutine but as they are all started at once, it has no effect.
What can I do to get the step-after-step effect?
Here my code so far:
public List<GameObject> path;
private Vector3 start;
private Vector3 target;
private float lungeSpeed = .8f;
private float lungeDistance = 5;
private IEnumerator coroutine;
public void StartPath() {
foreach (GameObject field in path) {
start = transform.position;
target = new Vector3(field.transform.position.x + lungeDistance, field.transform.position.y, field.transform.position.z);
coroutine = MoveObject(start, target, lungeSpeed);
StartCoroutine(coroutine);
}
}
IEnumerator MoveObject(Vector3 start, Vector3 target, float speed)
{
float t = 0.0f;
float rate = 1.0f / speed;
while (t < 1.0)
{
t += Time.deltaTime * rate;
transform.position = Vector3.Lerp(start, target, t);
yield return null;
}
yield return new WaitForSeconds(1);
}

Right now your code in StartPath isn't waiting on MoveObject to finish. You could solve this by running StartPath in a coroutine and have use yield return MoveObject(start, target, lungespeed).
Still will stop the execution of the foreach loop in startPath untill MoveObject has finished with the yield return new WaitForSeconds
public IEnumerator StartPath() {
foreach (GameObject field in path) {
start = transform.position;
target = new Vector3(field.transform.position.x + lungeDistance, field.transform.position.y, field.transform.position.z);
coroutine = MoveObject(start, target, lungeSpeed);
yield return StartCoroutine(coroutine);//this will keep the foreach loop from iterating untill the coroutine has finished
}
}
also a little side note:
(because the coroutine is executed in a new parallel thread)
Is incorrect. Coroutines do not run on a seperate thread. A coroutines runs on the main thread as all the rest of your code, it just does a little trick where it pauses and resumes execution based on your yield statements, but still on the main thread.
If you want to run something on a seperate thread you need to call new Thread(). However this is a whole different piece of cake as thread cannnot inherit from Monobehaviour

Related

Where should I start a coroutine which should be called every time collision is detected

I am currently working on a new project MOBA like game and I came a cross problem which I just can not find a solution for. I am pretty sure the problem is where I call the coroutine. Right now I am calling the function in Update that checks for collision and starts the coroutine if it detects something. The coroutine starts but it never ends. I tried to find a solution in events, functions, stopping the coroutine manually but I just can not find solve the problem. All feedback is welcome, thanks in advance.
Here is the code:
Where I call the function
private void Update()
{
CheckForCollision();
}
What is in the function
private void CheckForCollision()
{
Collider[] colliders = Physics.OverlapSphere(position, scale, layerMask);
foreach (Collider c in colliders)
{
c.TryGetComponent<PlayerController>(out PlayerController player);
StartCoroutine (player.ApplyDebuff(player.speed, 30, 5, debuffValue =>
player.speed = debuffValue, originalValue => player.speed = originalValue));
player.TakeDamage(50);
Destroy(gameObject);
}
}
What is in the Coroutine
public IEnumerator ApplyDebuff(float stat, float percentage, float duration,
System.Action<float> OnWhileAction, System.Action<float> AfterWaitAction)
{
float originalValue = stat;
float timeStamp = Time.time;
while (Time.time < timeStamp + duration)
{
float debuffValue = originalValue - (percentage / 100 * originalValue);
OnWhileAction(debuffValue);
yield return null;
}
AfterWaitAction(originalValue);
}

Unity WaitForSeconds Delay not Functioning

I'm making a rhythm game and and working on a script that spawns an object on every beat of a song. Thus, it spawns a single object, waits for the length of the beat, and then spawns the next one. It's supposed to do this until the song finishes. The issue is that the script seems to be spawning everyone of the objects at once, rather than waiting for the specified amount of time. The script compiles fine and I can't figure out what the issue is. Here's my script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class spawnState : MonoBehaviour
{
public float waitTime = 0.483870968f;
public bool running = true;
public int beats = 504;
private int count = 0;
public GameObject spawn;
// Update is called once per frame
void Update()
{
//Generate object
while (running == true)
{
//Wait
StartCoroutine(Wait());
//Count & Stop
count += 1;
if (count >= beats)
{
running = false;
}
}
}
//Wait function
IEnumerator Wait()
{
//Wait
yield return new WaitForSeconds(waitTime);
//Spawn
Instantiate(spawn, transform.position, spawn.transform.rotation);
}
}
Thank you!
For starters you don't want any possibly blocking while loop within Update!
Update is called once every frame.
Do you really want to instantiate 504 objects within one frame?
And then StartCoroutine does not delay the method which calls it. It rather registers a Coroutine to be running and continues immediately with the rest of the code. So you are starting 504 "parallel" running Coroutines and after 0.48.. seconds you suddenly get all 504 objects instantiated at the same time.
What you rather want to do would e.g. be either directly using a timer field without a Coroutine
private float timer;
void Update()
{
if(!running) return;
timer += Time.deltaTime;
if(timer >= waitTime)
{
timer = 0;
Instantiate(spawn, transform.position, spawn.transform.rotation);
count += 1;
if (count >= beats)
{
running = false;
}
}
}
Or if you want to use a Coroutine you could e.g. simply do
private void Start ()
{
StartCorouine (SpawnRoutine());
}
private IEnumerator SpawnRoutine ()
{
for(var count = 0; count < beats; count++)
{
yield return new WaitForSeconds (waitTime);
Instantiate(spawn, transform.position, spawn.transform.rotation);
}
}
or if Start is anyway the only place where you trigger this it can be that routine itself
private IEnumerator Start ()
{
for(var count = 0; count < beats; count++)
{
yield return new WaitForSeconds (waitTime);
Instantiate(spawn, transform.position, spawn.transform.rotation);
}
}

Error with Fader during Scene Transitions

I successfully used a fader in a practice game I made a few months back. I am now trying to implement a similar approach for my mobile game.
My goal: when a user goes to the play screen, the screen fades out, loads the scene async, and fades back in once that method is finished.
MY CODE
These are my variables:
CanvasGroup canvasGroup;
Coroutine currentActiveFade = null;
int sceneIndexToLoad = -1;
float fadeInTime = 2f;
float fadeOutTime = 1f;
float fadeWaitTime = 0.5f;
These are my secondary methods:
public void FadeOutImmediately()
{
canvasGroup.alpha = 1;
}
public Coroutine FadeOut(float time)
{
return Fade(1, time);
}
public Coroutine Fade(float target, float time)
{
if (currentActiveFade != null)
{
StopCoroutine(currentActiveFade);
}
currentActiveFade = StartCoroutine(FadeRoutine(target, time));
return currentActiveFade;
}
private IEnumerator FadeRoutine(float target, float time)
{
while (!Mathf.Approximately(canvasGroup.alpha, target))
{
canvasGroup.alpha = Mathf.MoveTowards(canvasGroup.alpha, target, Time.deltaTime / time);
yield return null;
}
}
public Coroutine FadeIn(float time)
{
return Fade(0, time);
}
This is my primary function, where I get my error:
public IEnumerator TransitionToNewScene()
{
if(sceneIndexToLoad < 0)
{
Debug.LogError("Scene to load is not set");
yield break;
}
DontDestroyOnLoad(gameObject);
//GETS STUCK ON THIS LINE
yield return FadeOut(fadeOutTime);
yield return SceneManager.LoadSceneAsync(sceneIndexToLoad); //this still executes
yield return new WaitForSeconds(fadeWaitTime);
FadeIn(fadeInTime);
Destroy(gameObject);
}
My code reaches and starts the FadeOut coroutine, and also loads the next scene. When I put a debug statement underneath my FadeOut call, it doesn't reach it, but seems to reach the next yield return.
MY ISSUE: The canvasGroup alpha reaches 1 in the next scene, but that's where it stops. The FadeIn code is not reached, and my gameObject is not destroyed. Why is this happening?
Please note: this code is almost exactly like in my previous project, which works perfectly.
Don't do this Destroy(gameObject); until the fadein finishes, all coroutines will stop when gameobject is destroyed.
I might put it in the end of FadeRoutine:
private IEnumerator FadeRoutine(float target, float time)
{
while (!Mathf.Approximately(canvasGroup.alpha, target))
{
canvasGroup.alpha = Mathf.MoveTowards(canvasGroup.alpha, target, Time.deltaTime / time);
yield return null;
}
if(target == 0)
Destroy(gameObject);
}
GameObject with the transition script on it has to be a top level in the hierarchy.
Basically, it cannot be a child of any GameObject as DontDestroyOnLoad doesn't work for child objects.

Waiting for coroutine to finish before moving forward

So I have this coroutine that moves an object to a place, and I do it for a list of objects, but I want it to move them one by one (aka wait until previous coroutine is done before starting a new one) but adding any yields just stops the whole thing... im a bit lost to why.
Ive tried adding "yield return new WaitUnitl()" or "WaitForSeconds" but wherever i try to place it it either makes it wait before moving everything at once or they just stop moving all at once
Moving code:
public IEnumerator MoveObject(Vector3 source, Vector3 target, float overTime)
{
float startTime = Time.time;
while (Time.time < startTime + overTime)
{
transform.position = Vector3.Lerp(source, target, (Time.time - startTime) / overTime);
yield return null;
}
transform.position = target;
}
called in this for loop:
for (int i = 0; i < CardsInHand.Count; i++)
{
Card c = CardsInHand[i];
Vector3 target = new Vector3(startt + (1.5f * i), transform.position.y);
StartCoroutine(c.MoveObject(c.transform.position, target, 1));
c.GetComponent<SpriteRenderer>().sortingOrder = i;
}
Expect them to move one at a time, not all at once
Edit: well I had the biggest fart ever.... i forgot to use StartCoroutine() after making the method a coroutine... and i kept wondering why it wont move
To await a Coroutine you want to change the method you're currently in to be in a Coroutine, and then yield the new coroutine like this:
IEnumerator MyMethod()
{
for (int i = 0; i < CardsInHand.Count; i++)
{
Card c = CardsInHand[i];
Vector3 target = new Vector3(startt + (1.5f * i), transform.position.y);
yield return StartCoroutine(c.MoveObject(c.transform.position, target, 1));
c.GetComponent<SpriteRenderer>().sortingOrder = i;
}
}
From this answer by #Everts:
When creating a coroutine, Unity attaches it to a MonoBehaviour object. It will run first on call fo the StartCoroutine until a yield is hit. Then it will return from the coroutine and place it onto a stack based on the yield.

How to continuously move a GameObject between two positions?

I'm coding my games boss behaviour and in the final stage of the battle the boss is supposed to charge towards the player and then move back to its original position. Wait for 5 seconds and then do the same.
I tried to achieve this using coroutines and Vector2.MoveTowards() but am not getting the desired effect, first off the boss does not "Move Towards" the player but instantly appears at the targetPosition and then just stays there, does not move back.
Below is my code:
private Vector2 chargeTarget;
private Vector2 tankStartPosition;
void Start()
{
chargeTarget = new Vector2(-5.0f, transform.position.y);
tankStartPosition = transform.position;
}
void Update()
{
if (Time.time > nextCharge)
{
StartCoroutine(TankCharge());
nextCharge = Time.time + chargeRate;
}
}
IEnumerator TankCharge()
{
transform.position = Vector2.MoveTowards(tankStartPosition, chargeTarget, Time.deltaTime * chargeSpeed);
transform.position = Vector2.MoveTowards(chargeTarget, tankStartPosition, Time.deltaTime * returnSpeed);
}
Any idea what I am doing wrong here? And how to get my desired action?
Thank you
Calling MoveTowards once only moves the game object once during that iteration of the game loop. Calling MoveTowards once doesn't move the game object all the way to its target (unless the maxDistanceDelta parameter is big enough to move the game object to its target in one iteration).
If the boss is instantly appearing at the target, I'm guessing your chargeSpeed is too big.
What you want to do is call MoveTowards once per Update cycle. However, the way you're doing your coroutine, the coroutine will only move the game object once and then exit. Normally coroutines will have a loop within them (otherwise the coroutine will exit after running once). Something like this:
IEnumerator TankCharge()
{
while (Vector3.Distance(transform.position, chargeTarget.position) > Mathf.Epsilon)
{
// Adjust this so this game object doesn't move the entire
// distance in one iteration
float distanceToMove = Time.deltaTime * chargeSpeed;
transform.position = Vector3.MoveTowards(transform.position, chargeTarget.position, distanceToMove)
yield return null;
}
}
However, for your situation, you don't really need a coroutine. You can just do this directly in Update()
private bool returnToStart = false;
private float timer;
void Update
{
float distanceToMove = Time.deltaTime * chargeSpeed;
if (timer <= 0)
{
if (!returnToStart)
{
transform.position = Vector3.MoveTowards(transform.position, chargeTarget.position, distanceToMove)
// Target reached? If so, start moving back to the original position
if (Vector3.Distance(transform.position, chargeTarget.position) <= Mathf.Epsilon)
{
returnToStart = true;
this.timer = this.chargeRate;
}
}
else
{
transform.position = Vector3.MoveTowards(transform.position, tankStartPosition.position, distanceToMove)
// Original position reached? If so, start moving to the target
if (Vector3.Distance(transform.position, tankStartPosition.position) <= Mathf.Epsilon)
{
returnToStart = false;
this.timer = this.chargeRate;
}
}
}
else
{
this.timer -= Time.time;
}
}

Categories

Resources