Second Coroutine isn't working Unity - c#

I am making 2D game and want to cause delay of about 3 sec after player's all lives ran out. I tried to implement Coroutine method before the scene start all over again but it doesn't work.
I have already implemented Coroutine method for each time my player falls of a cliff and respawn back to its position. And it works like a charm.
public void Respawner()
{
StartCoroutine("RespawnCoroutine");
}
// Coroutine Delay of 2 sec for each time player Respawn
public IEnumerator RespawnCoroutine()
{
classobj.gameObject.SetActive(false);
yield return new WaitForSeconds(respawnDelaySec);
classobj.transform.position = classobj.respawnPoint;
classobj.gameObject.SetActive(true);
}
public void ReduceLives()
{
if (lives <= 3 && lives >= 2)
{
lives--;
live_text.text = "Remaining Live " + lives;
}
else
{
StartCoroutine("RestartScene1");
}
}
public IEnumerable RestartScene1()
{
yield return new WaitForSeconds(RestartSceneDelaySec);
SceneManager.LoadScene("demo2");
}
here is no error on console window but SceneManager.LoadScene("demo2"); is never called and the player is respawning each time after i die and after 1 life remaining

The issue with your second coroutine is..
You have mistakenly used "IEnumerable" instead "IEnumerator", make it change to "IEnumerator" and it will work..

You should not call SceneManager.LoadScene("demo2"); after StartCoroutine("RestartScene1");.
StartCoroutine("RestartScene1"); this code you can say is an asynchronous code. It is called, and the execution of program keeps going forward (the execution does not await here). You should call the code you want to delay inside that Coroutine after yielding.
Small exampel:
public void SomeFunction()
{
StartCoroutine("RestartScene1");
// The code here will **not** be delayed
}
public IEnumerable RestartScene1()
{
yield return new WaitForSeconds(RestartSceneDelaySec);
// The code here will be delayed
}

Related

Unity - Problem with while loop on embedded coroutines

Sorry if this is a newbie question, but I looked around and can't find any clue to fix my problem yet.
I am currently encountering a freeze when I try to run two coroutines together.
My end goal is:
to have one long coroutine representing the duration of a day.
one other smaller coroutine that will activate repeatedly while the day is not finished.
The two cotourines works fine independantly or when the second coroutine is directly embedded into the first one without the check on the endDay bool.
But when I tried to include the while loop, unity freeze at play (it reaches the 'DayStart.SetActive(true)' of the code below but does not go further).
Thinking the endDay bool being at the end of the first coroutine was probably the reason to the problem, I tried to build two completely independant ones (non-embedded coroutines with only the endDay bool as link between the two) but it didn't go better.
Does someone has an idea on how I could make it work?
For information, here is the code I initially used and that reverted the error. I spare you the detail of the second coroutine as it is quite long and probably not the problem here.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Transaction : MonoBehaviour
{
public GameObject DayStart;
public GameObject DayTracker;
private bool endDay;
private void Awake()
{
instance = this;
}
public void MainDayCycle()
{
StartCoroutine(MainDayCycleCO());
}
IEnumerator MainDayCycleCO()
{
endDay = false;
// Beginning of day picture
DayStart.SetActive(true);
yield return new WaitForSeconds(3);
DayStart.SetActive(false);
// [To be completed] Day Tracker bar
DayTracker.SetActive(true);
TradeCycle();
// Lancement de délai de journée et enregistrement de la fin
yield return new WaitForSeconds(20);
endDay = true;
}
public void TradeCycle()
{
while (endDay == false)
{
StartCoroutine(TradeCycleCO());
}
}
As I said this one
public void TradeCycle()
{
while (endDay == false)
{
StartCoroutine(TradeCycleCO());
}
}
Doesn't have any yield and this loop will freeze because nowhere inside the endDay is changed!
It sounds like what you rather want to do is (still don't have your TradeCycleCO code)
// Does this even need to be public?
public void TradeCycle()
{
StartCoroutine(TradeCycleCO());
}
IEnumerator TradeCycleCO()
{
while(!endDay)
{
// Your code here
// Make sure something yield returns in here
// If needed you could also interrupt at certain points via checking again
// Use that to do e.g. some cleaning up after the loop
if(endDay) break;
// or use that to immediately leave the coroutine overall
if(endDay) yield break;
}
// Code that may be executed after the day has ended e.g. for cleaning up stuff
}

How to wait a certain amount of time without freezing code in C#/Unity?

I'm making a practice game to get used to coding where you have to shoot a bird. When you run out of bullets, you need to press the 'r' key to reload your bullets. I want there to be a delay between when the button is pressed and when the bullets reload, but so far what I found is code that freezes everything (shown below). Is there a way to keep the code from freezing everything?
Summary: The code below freezes everything (the whole game) when pressing the 'r' button. Is there code I can use that won't freeze everything and will only wait 2 seconds before running the next action?
IEnumerator TimerRoutine()
{
if (Input.GetKeyDown(KeyCode.R))
{
yield return new WaitForSeconds(2); //Fix this, freezes everything
activeBullets = 0;
}
}
Use Coroutines To set This delay
if (Input.GetKeyDown(KeyCode.R) && isDelayDone) // defined isDelayDone as private bool = true;
{
// When you press the Key
isDelayDone = false;
StartCoroutine(Delay());
IEnumerator Delay()
{
yield return new WaitForSeconds(2);
isDelayDone = true;
activeBullets = 0;
}
}
Your problem is that you are waiting 2 seconds after the key press but not waiting for the actual key event it.
Here is a modified version of your method doing what you want.
IEnumerator TimerRoutine()
{
while(activeBullets == 0) // Use the bullets value to check if its been reloaded
{
if (Input.GetKeyDown(KeyCode.R)) // Key event check each frame
{
// Key event fired so wait 2 seconds before reloading the bullets and exiting the Coroutine
yield return new WaitForSeconds(2);
activeBullets = reloadBulletsAmount;
break;
}
yield return null; // Use null for the time value so it waits each frame independant of how long it is
}
}
(I know this has an accepted answer I just feel this method would be better)

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.

(Unity3D) in Block Breaker game I'm trying to load levels when all bricks destroyed

Good Morning Developers!
so here is what i'm trying to do: i created a block breaker game, and i wrote some code so that when all bricks in the scene are destroyed the next level is loaded.
It works fine, but there is a bug! when i lose before destroying all the bricks, and then i press "play again", the static variable who is responsible of counting bricks on scene does not reset to 0! it keeps the number of brick before i lost and add to it the number of bricks in the new scene!, so instead of returning 24 for ex (which is the correct number of bricks in scene) it returns 35 (11 + 24)
how can i fix that please?
here is the code i'm using: first the brick script :
public int maxHits;
public int timesHit;
public Sprite[] hitSprites;
public static int breakableCount = 0;
private bool isBreakable;
private LevelManager levelManager;
// Use this for initialization
void Start () {
isBreakable = (this.tag == "Breakable");
if(isBreakable){
breakableCount++;
}
print (breakableCount);
timesHit = 0;
levelManager = GameObject.FindObjectOfType<LevelManager> ();
}
void OnCollisionEnter2D(Collision2D collision) {
if (isBreakable) {
HandleHits ();
}
}
void HandleHits(){
//TODO remove the print!!
print ("collison");
timesHit++;
if (timesHit >= maxHits) {
breakableCount--;
print (breakableCount);
levelManager.BrickDestroyed ();
Destroy (gameObject);
} else {
LoadSprite ();
}
}
// Update is called once per frame
void Update () {
}
//TODO Remove this when player can WIN
void NextLevel(){
levelManager.LoadNextLevel ();
}
void LoadSprite(){
int spriteIndex = timesHit - 1;
this.GetComponent<SpriteRenderer> ().sprite = hitSprites [spriteIndex];
}
and here is the LevelManager script I'm using to manage levels :
public void LoadLevel (string name) {
Debug.Log ("level change requested for : " + name);
Application.LoadLevel (name);
}
public void ExitRequest() {
Debug.Log ("Exit game requested");
Application.Quit ();
}
public void LoadNextLevel () {
Application.LoadLevel (Application.loadedLevel + 1);
}
public void BrickDestroyed () {
if(Brick.breakableCount <= 0) {
LoadNextLevel ();
}
}
hope i explained correctly, and sorry if i made some English errors i'm not native speaker lol, Thank you have a nice day ^^
-Edited due to misunderstanding-
I didn't realize that was your BRICK script. The reset should be inside our LevelManager.
Your first line in your function to load a new level in LevelManager should be:
breakableCount = 0;
This will make it so that when the level is initialized that the counter is reset.
Also, you could reset the same way as soon as you've decided that a person has beat the current level.
Also, I recognize this from Ben Tristram's Unity Dev Course. You should try using the tools built into his class for questions, there is a lot of support there for these specific exercises!
Stack Overflow is great though, and it's a great source for when that stuff falls through. Another place to check is https://gamedev.stackexchange.com/
private static int breakableCount = 0;
public static int BreakableCount
{
get{ return breakableCount; }
set{
breakableCount = value;
if(breakableCount <= 0){ EndOfLevel() }
}
}
Turning your variable into property (or you can use a method if you prefer), you can now add some logic when it gets modified.
EndOfLevel is just a method you call to load the next level, save some data and reset some static values before leaving.
I Want to update this post because i find a solution and i have another question in the same subject!
First i'll tell you how i fixed it:
as #JorgeSantos suggested i created a ResetGame fonction in my loadlevel script :
void ResetGame(){
Brick.breakableCount = 0;
print ("breakableCount set to 0 ");
}
then i called that fontion in my LoadLevel fonction :
public void LoadLevel (string name) {
ResetGame ();
Debug.Log ("level change requested for : " + name);
Application.LoadLevel (name);
now the variable is resetting just fine
the only problem (it's not really a problem because the game runs fine, it's just that i want to know why it's happening) is that for ex let's say that i run the game, destroy 4 bricks, and then i lose, (keep in mind that there are 24 bricks in the scene) so i left 20 bricks non destroyed!
when i press play again, in the console, i notice that when i destroy a brick the breakableCount variable is not taking new values, then when i destroy 4 brick, (which means i'm in the same number of bricks left as i were before losing), then the breakableCount variable takes the value 20 (which is the right value) and continue decreasing when i destroy bricks normally!, You can see now the the game continue to work fine, but i dont understand why that variable is not reset to the right number of brick after i click on play again, and only takes effect when i reach the same number of destroyed brick as in my fist try?!
hope i made my point clear looking forward for your answers, and thank you all ^^

Cannot use InvokeRepeating with method parameters. How do I work around this?

I'm trying to implement a damage over time system, but Unity keeps saying "Trying to Invoke method...Couldn't be Called." The method I want to call uses the parameters "Collider coll", but from my research you can't invoke if the method has said paremters.
Here is my code:
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
public class DamageOverTime : MonoBehaviour
{
public int PHP; //PHP = Player Health from PlayerHealth.cs script.
public int Damage; //Amount of damage.
public int DamageOverTime; //Damage over time.
public float DamageInterval_DOT = .25f; //Damage interval for damage over time.
public string Level;
PlayerHealth player;
void Start()
{
player = GameObject.Find("Player").GetComponent<PlayerHealth>();
InvokeRepeating("OnTriggerEnter", DamageInterval_DOT, DamageInterval_DOT);
}
void Update()
{
PHP = GameObject.Find("Player").GetComponent<PlayerHealth>().PlayerHP;
if (PHP <= 0)
{
SceneManager.LoadScene(Level);
}
}
void OnTriggerEnter(Collider coll)
{
if (coll.gameObject.tag == "Player")
{
GameObject.Find("Player").GetComponent<PlayerHealth>().PlayerHP = PHP - Damage;
}
if (coll.gameObject.tag == "Ball")
{
gameObject.SetActive(false);
SceneManager.LoadScene(Level);
}
}
}
My goal is to get the OnTriggerEnter function to loop ever 1/4 of a second (or lower possibly). Current upon entering a collider my health is drained by 60% in about a second which is far too fast. How should I work around this?
You can't use InvokeRepeating with OnTriggerEnter, because it's a trigger, which means it will trigger once when entrance of its holder occured.
Also InvokeRepeating means that you want to keep repeating an action continously which is not the case here. You want your trigger to occur once and then remove health points over time.
Solution - Coroutine
Unity3D makes custom usage of IEnumerable and yield keyword called Coroutine that always returns an IEnumerator. How it works? It will return control on every yield there is in our Coroutine and then will go back to exact point where it gave back control instead of starting function execution from scratch.
Code:
void OnTriggerEnter(Collider coll)
{
if (coll.gameObject.tag == "Player")
{
StartCoroutine("DamageOverTimeCoroutine");
}
if (coll.gameObject.tag == "Ball")
{
gameObject.SetActive(false);
SceneManager.LoadScene(Level);
}
}
public IEnumerator DamageOverTimeCoroutine()
{
var dotHits = 0;
while (dotHits < 4)
{
//Will remove 1/4 of Damage per tick
GameObject.Find("Player").GetComponent<PlayerHealth>().PlayerHP -= Damage / 4;
dotHits++;
//Will return control over here
yield return new WaitForSeconds(DamageInterval_DOT);
//And then control is returned back here once 0.25s passes
}
}
There's of course room for improvement in this Coroutine. You can pass parameters to it, same as you can to any other method in C#. Also you can implement other invervals that are not hardcoded. Code above is just a simple example on how to deal with such scenarios.
For continous damage over time
public IEnumerator DamageOverTimeCoroutine()
{
var dotHits = 0;
var player = GameObject.Find("Player").GetComponent<PlayerHealth>();
while (true)
{
//Stop removing damage, player is dead already
if (player.PlayerHP <= 0)
yield break;
//Will remove 5 Damage per tick
player.PlayerHP -= 5;
dotHits++;
//Will return control over here
yield return new WaitForSeconds(DamageInterval_DOT);
//And then control is returned back here once 0.25s passes
}
}
To stop Coroutine somewhere else from code use StopCoroutine("DamageOverTimeCoroutine") to stop certain coroutine type or StopAllCoroutines() to stop all coroutines that are active now.

Categories

Resources