Unity - Lines after yield not getting executed [duplicate] - c#

I have a 3 second countdown timer which is activated when game is unpaused.
I had it working correctly a couple of days ago but now it doesn't work anymore. It gets blocked on the number 3. This is the code:
IEnumerator Timer() {
Time.timeScale = 0;
objectWithGSScript.scoreText.fontSize = 300;
objectWithGSScript.scoreText.text = "" + 3;
yield return WaitOneSecond();
objectWithGSScript.scoreText.text = "" + 2;
yield return WaitOneSecond();
objectWithGSScript.scoreText.text = "" + 1;
yield return WaitOneSecond();
objectWithGSScript.scoreText.text = "Go!";
yield return WaitOneSecond();
Time.timeScale = 1f;
objectWithGSScript.scoreText.text = objectWithGSScript.score.ToString();
}
IEnumerator WaitOneSecond() {
float start = Time.realtimeSinceStartup;
while (Time.realtimeSinceStartup < start + 1f) {
print("entered");
yield return null;
}
}
It prints "entered" only once, it seems it exit from the coroutine, like it's returning null forever.
What could be the problem?
Any help would be appreciated.

The function in your code is perfectly fine. No, it does not and should not stop at the number 3.
These are the possible reasons why it is behaving like it is getting stopped at the number 3.
1.You are calling StopCoroutine or or StopAllCoroutines. Please check that you are not stopping the coroutines. If you are when it is running, then it will behave this way.
2.You are destroying the script or GameObject this script is attached to. Check where you call the Destroy(gameObject);, Destroy(this); or something similar. If the script is destroyed, the coroutine should stop running.
Remember that you can destroy a script from another script so check all scripts.
3.You disabled the GameObject that his script is attached to. When you disable a GameObject, the coroutine stops working. Check that you don't have gameObject.SetActive(false); or anything with SetActive(false); that disables that GameObject.
4.If you have a coroutine function in ScriptA and then starts that coroutine from ScriptB, if you destroy ScriptB, the coroutine from ScriptA will freeze at the yield return statement. It is important that you know this.
5. Null problem...
Maybe the objectWithGSScript.scoreText.text is not null. You must check each variable and make sure that they are not null. The if stametement is fine but a good shortcut is this:
UnityEngine.Debug.Log(objectWithGSScript);
UnityEngine.Debug.Log(objectWithGSScript.scoreText);
UnityEngine.Debug.Log(objectWithGSScript.scoreText.text);
then you can do:
objectWithGSScript.scoreText.fontSize = 300;
objectWithGSScript.scoreText.text = "" + 3;
I can't think of any other possible reason why this is happening but check all those five things mentioned above.

Related

Unity Coroutine Yield seems to close coroutine every time

Hey so I've been working on this damage over time code for quite awhile now, I've used MANY online resources, and I have tons of iterations of trying this, and it hasn't worked, so I come here.
DISCLAIMER: My object does not get destroyed when yielding, it just hits the yield, and nothing happens.
if(CurrentStatus == Status.Burning && burning==false){
burning=true;
StartCoroutine (DamageOverTimeCoroutine(100, 5));
Debug.Log("what");
burning = false;
//CurrentStatus = Status.Empty;
}
public IEnumerator DamageOverTimeCoroutine(float damageAmount, float duration){
float amountDamaged = 0;
float damagePerLoop = damageAmount / duration;
while (amountDamaged < damageAmount) {
int numInt = (int)Math.Ceiling(damagePerLoop);
enemyCurrentHealth -= numInt;
var clone = (GameObject) Instantiate(damageNumber, this.gameObject.transform.position, Quaternion.Euler(Vector3.zero));
clone.GetComponent<FloatingNumbers> ().damageNumber = numInt;
amountDamaged += damagePerLoop;
yield return null;
Debug.Log(amountDamaged);
}
if(amountDamaged>damageAmount){
//StopCoroutine(DamageOverTimeCoroutine());
}
CurrentStatus = Status.Empty;
burning=false;
}
To reiterate, it does the damage, and then quits on the yield return, I've changed it from WaitSeconds just to see if it would work any other way, it does not. I've never had the Debug after the yield fire. Why is this happening?
Would be much appreciated,
Thanks
Figured it out, the
var clone = (GameObject) Instantiate(damageNumber, this.gameObject.transform.position, Quaternion.Euler(Vector3.zero));
clone.GetComponent ().damageNumber = numInt;
lines short circuted it without throwing a error. Odd, but it works now. Thanks for the input guys

Coroutine only works the first time

I assume that there is something fundamental about coroutines that I don't understand because I cannot get my head around why this is happening.
I have this coroutine that works perfectly as intended the first time but completely fails the second time I try to use it.
public IEnumerator CharacterDialogue()
{
inDialogue = true;
playerController.enabled = false;
mouselook.enabled = false;
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
dialogueMenu.SetActive(true);
yield return new WaitForEndOfFrame();
for (int i = 0; i < dialogueStrings.Length; i++)
{
while (!Input.GetKeyDown("e"))
{
yield return null;
}
yield return new WaitForEndOfFrame();
dialogueText.text = dialogueStrings[i];
}
yield return new WaitForEndOfFrame();
while (!Input.GetKeyDown("e"))
{
yield return null;
}
yield return new WaitForEndOfFrame();
QuestManager.Instance.SpawnDouxland();
inDialogue = false;
playerController.enabled = true;
mouselook.enabled = true;
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
dialogueMenu.SetActive(false);
}
It is pretty straightforward: I disable the controls of my player and make the dialogue window pop. I then wait for the input of the player between each string of text so that he may read at his own pace. Once he's done reading I reactivate the controls and close the dialogue window.
This code works exactly like I want the first time but the second time it just goes through the for loop without waiting for the input.
What am I doing wrong here and why does it work the first time?
Edit:
Comments pointed out that the issue must be in the way I call the coroutine. This now makes sense to me as I use the same key (e) to call the coroutine. So maybe it reads the key as pressed and runs through the for loop. Bu why would it only do so the second time?
Here's the snippet of code where I call my coroutine:
if (hit.collider.CompareTag("Character"))
{
raycastedObj = hit.collider.gameObject;
CrosshairActive();
interactionManager.InteractiveFeedbackTextCall("CharacterQuest");
if (Input.GetKeyDown("e"))
{
Debug.Log("I have interacted with: " + hit.collider.gameObject.name + ".");
StartCoroutine(canvasAnimManager.CharacterDialogue());
}
}
Thanks to some comments I figured out what the issue was.
if (Input.GetKeyDown("e"))
{
Debug.Log("I have interacted with: " + hit.collider.gameObject.name + ".");
StartCoroutine(canvasAnimManager.CharacterDialogue());
}
I thought the idea of GetKeyDown was that it was triggered only once... there must be something I'm not getting. Anyways the issue was that this was called multiple times and it make the coroutine go through the loop even after I was finished with my dialogue.
I just added a little bool check to fix the issue:
if (Input.GetKeyDown("e") && !canvasAnimManager.inDialogue)
{
Debug.Log("I have interacted with: " + hit.collider.gameObject.name + ".");
StartCoroutine(canvasAnimManager.FoukiDialogue());
}
Cheers and thanks for the help!

Trouble understanding Delays and Coroutines

void start()
StartCoroutine(Text());
IEnumerator Text()
{
Debug.Log("Hello")
yield return new WaitForSeconds(3)
Debug.Log("ByeBye")
}
I understand the basic concept that this does but I don't get what anything means such as yield return new WaitforSeconds(3) and what StartCoroutine is and What an IEnumerator is.
Can anyone explain to me what they mean?
When you call a function, it runs to completion before returning. This effectively means that any action taking place in a function must happen within a single frame update; a function call can’t be used to contain a procedural animation or a sequence of events over time. As an example, consider the task of gradually reducing an object’s alpha (opacity) value until it becomes completely invisible.
void Fade()
{
for (float ft = 1f; ft >= 0; ft -= 0.1f)
{
Color c = renderer.material.color;
c.a = ft;
renderer.material.color = c;
}
}
As it stands, the Fade function will not have the effect you might expect. In order for the fading to be visible, the alpha must be reduced over a sequence of frames to show the intermediate values being rendered. However, the function will execute in its entirety within a single frame update. The intermediate values will never be seen and the object will disappear instantly.
It is possible to handle situations like this by adding code to the Update function that executes the fade on a frame-by-frame basis. However, it is often more convenient to use a coroutine for this kind of task.
A coroutine is like a function that has the ability to pause execution and return control to Unity but then to continue where it left off on the following frame. In C#, a coroutine is declared like this:
IEnumerator Fade()
{
for (float ft = 1f; ft >= 0; ft -= 0.1f)
{
Color c = renderer.material.color;
c.a = ft;
renderer.material.color = c;
yield return null;
}
}
It is essentially a function declared with a return type of IEnumerator and with the yield return statement included somewhere in the body. The yield return null line is the point at which execution will pause and be resumed the following frame. To set a coroutine running, you need to use the StartCoroutine function:
void Update()
{
if (Input.GetKeyDown("f"))
{
StartCoroutine("Fade");
}
}
You will notice that the loop counter in the Fade function maintains its correct value over the lifetime of the coroutine. In fact any variable or parameter will be correctly preserved between yields.
By default, a coroutine is resumed on the frame after it yields but it is also possible to introduce a time delay using WaitForSeconds:
IEnumerator Fade()
{
for (float ft = 1f; ft >= 0; ft -= 0.1f)
{
Color c = renderer.material.color;
c.a = ft;
renderer.material.color = c;
yield return new WaitForSeconds(.1f);
}
}
This can be used as a way to spread an effect over a period of time, but it is also a useful optimization. Many tasks in a game need to be carried out periodically and the most obvious way to do this is to include them in the Update function. However, this function will typically be called many times per second. When a task doesn’t need to be repeated quite so frequently, you can put it in a coroutine to get an update regularly but not every single frame. An example of this might be an alarm that warns the player if an enemy is nearby. The code might look something like this:
bool ProximityCheck()
{
for (int i = 0; i < enemies.Length; i++)
{
if (Vector3.Distance(transform.position, enemies[i].transform.position) < dangerDistance) {
return true;
}
}
return false;
}
If there are a lot of enemies then calling this function every frame might introduce a significant overhead. However, you could use a coroutine to call it every tenth of a second:
IEnumerator DoCheck()
{
for(;;)
{
ProximityCheck();
yield return new WaitForSeconds(.1f);
}
}
This would greatly reduce the number of checks carried out without any noticeable effect on gameplay.
Note: You can stop a Coroutine with StopCoroutine and StopAllCoroutines. A coroutines also stops when the GameObject it is attached to is disabled with SetActive(false). Calling Destroy(example) (where example is a MonoBehaviour instance) immediately triggers OnDisable and the coroutine is processed, effectively stopping it. Finally, OnDestroy is invoked at the end of the frame.
Coroutines are not stopped when disabling a MonoBehaviour by setting enabled to false on a MonoBehaviour instance.
Reference: https://docs.unity3d.com/Manual/Coroutines.html
Unity (ab)uses enumerators to build C# CoRoutines, because async / await didn't exist. When you write;
IEnumerator Text()
{
Debug.Log("Hello")
yield return new WaitForSeconds(3)
Debug.Log("ByeBye")
}
The compiler turns that into something like;
IEnumerator Text() => new StateMachine();
public class StateMachine : IEnumerable{
private int state = 0;
// plus any local variables moved to fields.
StateMachine(){}
public object Current { get; set; }
public bool MoveNext(){
switch(state){
case 0:
Debug.Log("Hello");
Current = new WaitForSeconds(3);
state = 1;
return true;
case 1:
Debug.Log("ByeBye");
return false;
}
}
}
Since the state of your function is now stored in fields on an object, your method can pause before it finishes. Unity will then look at the object you yield to decide when to call MoveNext().
Now that C# has async methods, which also cause your methods to be translated into state machines. It would be possible for a new version of unity to support them instead, like;
async Task Text()
{
Debug.Log("Hello")
await Something.WaitForSeconds(3)
Debug.Log("ByeBye")
}
But they would still have to support the old way of building CoRoutines.

Why coroutine stops working/executing

I have a 3 second countdown timer which is activated when game is unpaused.
I had it working correctly a couple of days ago but now it doesn't work anymore. It gets blocked on the number 3. This is the code:
IEnumerator Timer() {
Time.timeScale = 0;
objectWithGSScript.scoreText.fontSize = 300;
objectWithGSScript.scoreText.text = "" + 3;
yield return WaitOneSecond();
objectWithGSScript.scoreText.text = "" + 2;
yield return WaitOneSecond();
objectWithGSScript.scoreText.text = "" + 1;
yield return WaitOneSecond();
objectWithGSScript.scoreText.text = "Go!";
yield return WaitOneSecond();
Time.timeScale = 1f;
objectWithGSScript.scoreText.text = objectWithGSScript.score.ToString();
}
IEnumerator WaitOneSecond() {
float start = Time.realtimeSinceStartup;
while (Time.realtimeSinceStartup < start + 1f) {
print("entered");
yield return null;
}
}
It prints "entered" only once, it seems it exit from the coroutine, like it's returning null forever.
What could be the problem?
Any help would be appreciated.
The function in your code is perfectly fine. No, it does not and should not stop at the number 3.
These are the possible reasons why it is behaving like it is getting stopped at the number 3.
1.You are calling StopCoroutine or or StopAllCoroutines. Please check that you are not stopping the coroutines. If you are when it is running, then it will behave this way.
2.You are destroying the script or GameObject this script is attached to. Check where you call the Destroy(gameObject);, Destroy(this); or something similar. If the script is destroyed, the coroutine should stop running.
Remember that you can destroy a script from another script so check all scripts.
3.You disabled the GameObject that his script is attached to. When you disable a GameObject, the coroutine stops working. Check that you don't have gameObject.SetActive(false); or anything with SetActive(false); that disables that GameObject.
4.If you have a coroutine function in ScriptA and then starts that coroutine from ScriptB, if you destroy ScriptB, the coroutine from ScriptA will freeze at the yield return statement. It is important that you know this.
5. Null problem...
Maybe the objectWithGSScript.scoreText.text is not null. You must check each variable and make sure that they are not null. The if stametement is fine but a good shortcut is this:
UnityEngine.Debug.Log(objectWithGSScript);
UnityEngine.Debug.Log(objectWithGSScript.scoreText);
UnityEngine.Debug.Log(objectWithGSScript.scoreText.text);
then you can do:
objectWithGSScript.scoreText.fontSize = 300;
objectWithGSScript.scoreText.text = "" + 3;
I can't think of any other possible reason why this is happening but check all those five things mentioned above.

Make loop wait for action to complete

Im working in Unity3d (this is more a C# question, so I doubt that is an issue). Im working on a movement system like you would find in Civilization. I have a loop setup so that you can move 2 squares per turn. This works fine. I click on a square 10 blocks away and it takes 5 turns to get there. Now im trying to make the pawn lerp between blocks. I have got lerp to work, problem is, it jumps from the current tile to the 1st tile, then transitions to the 2nd tile where its supposed to be. I used a coroutine to make this work instead of the update function (as the update would cause it to just lerp to the final destination instead of from current, to first, to second). So what im running into is the loop that goes through each move the pawn has, isnt waiting for the coroutine to complete before continuing its own loop. Here is the code
public void MoveNextTile()
{
float remainingMoves = moveSpeed;
while (remainingMoves > 0)
{
if (currentPath == null)
return;
//Get the cost from current tile to next tile
remainingMoves -= map.CostToEnterTile(currentPath[0].x, currentPath[0].y, currentPath[1].x, currentPath[1].y);
//Move us to the next tile in the sequence
toVector = map.TileCoordToWorldCoord(currentPath[1].x, currentPath[1].y);
Vector3 fromVec = transform.position;
StartCoroutine(MoveObject(fromVec, toVector, 1.0f));
//transform.position = map.TileCoordToWorldCoord(currentPath[1].x, currentPath[1].y);
//Remove the old current tile
this.tileX = currentPath[0].x;
this.tileY = currentPath[0].y;
currentPath.RemoveAt(0);
if (currentPath.Count == 1)
{
this.tileX = currentPath[0].x;
this.tileY = currentPath[0].y;
currentPath = null;
}
}
}
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;
}
I know this is a noob question. I just never have needed to do this in C# before. Thank in advance for all the help
I suggest you research into how coroutines work.
Your coroutine doesn't execute fully and then return to complete the rest of your MoveNextTile function. It actually executes up until the first yield statement and then continues execution of MoveNextTile. Each subsequent frame will continue to run one 'step' of the coroutine until the next yield statement in an attempt to replicate asynchronous methods.
What you want to do is tell your program to explicitly wait for your coroutine to finish. To do so;
yield return StartCoroutine(MoveObject(fromVec, toVector, 1.0f));
Of course, you can only use this statement inside of an IEnumerator. So you would have to change your void MoveNextTile function to IEnumerator MoveNextTile. You end up with something as follows;
public IEnumerator MoveNextTile() {
// Ommited
while (remainingMoves > 0) {
//Ommited
yield return StartCoroutine(MoveObject(fromVec, toVector, 1.0f));
// Now MoveNextTile will continue after your MoveObject coroutine finishes.
}
}

Categories

Resources