IEnumerator OnCollisionEnter2D not waiting - c#

I am trying to set a gameobject to active on collision, wait a second and then set it to inactive again, but after the WaitForSeconds() line the execution seems to stop. I have no experience with C# and Unity so this may be a beginner mistake. Any idea why this could be happening?
private IEnumerator OnCollisionEnter2D(Collision2D collidedWith) {
if (collidedWith.gameObject.tag == "Shape") {
collidedWith.transform.GetChild(0).gameObject.SetActive(true);
Object.Destroy(this.gameObject);
yield return new WaitForSeconds(1);
Debug.Log("Should have waited for 1 second");
collidedWith.transform.GetChild(0).gameObject.SetActive(false);
}
}

You do
Object.Destroy(this.gameObject);
on this object which is running the Coroutine -> The routine is interrupted in that very same moment (when it reaches the first yield statement) -> it never actually starts to wait ;)
You should rather have a component on the object you collide with and make sure the Coroutine is run on that one instead.
E.g. like
// Have this on your Shape objects
public class Shape : MonoBehaviour
{
private bool isCollided;
public void Collided()
{
if(!isCollided) StartCoroutine(Routine());
}
private IEnumerator Routine()
{
if(isCollided) yield break;
isCollided = true;
var child = transform.GetChild(0).gameObject;
child .SetActive(true);
yield return new WaitForSeconds(1);
child.SetActive(false);
isCollided = false;
}
}
and then rather do e.g.
private void OnCollisionEnter2D(Collision2D collidedWith)
{
if (collidedWith.gameObject.TryGetComponent<Shape>(out var shape))
{
shape.Collided();
Destroy(gameObject);
}
}

Related

How do I add a cooldown to my Unity teleport

I'm sorry for any messy code, I'm relatively new to this. I made a working teleport in Unity but whenever I teleport from one of the teleports to the other, I wanna make it so there's a 5 second cooldown before you can use the teleporter again. So I used IEnumerator, added 5 seconds before "justTeleported" became false again, but when I teleported, I instantly got teleported back, and had to wait 5 seconds before I could try again. So my though was maybe I'm touching the trigger too quickly, before it can become false, that's why I added the two seconds. But now, whenever I get on the teleporter, it goes from true to false to true a couple times, and then I eventually get teleported back to where I came from. If anyone could help, I would be very thankful. Thank you.
{
public Transform Destination;
bool justTeleported;
public GameObject Player = GameObject.FindGameObjectWithTag("Player");
// Start is called before the first frame update
void Start()
{
justTeleported = false;
}
private void Update()
{
print(justTeleported)
}
private void OnTriggerEnter2D(Collider2D coll)
{
if (coll.gameObject.tag == "Player" && justTeleported == false)
{
StartCoroutine("Cooldown");
}
}
IEnumerator Cooldown()
{
justTeleported = true;
yield return new WaitForSeconds(2f);
Player.transform.position = Destination.transform.position;
yield return new WaitForSecondsRealtime(5f);
justTeleported = false;
}
Because each of the teleports has its own bool justTeleported, setting the first true doesn't automatically set the other true, so you need some way to tell the Destination teleport script to start it's own cooldown coroutine. Here's my test script, it works for me.
using System.Collections;
using UnityEngine;
public class Teleport : MonoBehaviour
{
public Teleport Destination = null;
public float CooldownTime = 5f;
private bool justTeleported = false;
public void StartCoolDown()
{
StartCoroutine(Cooldown());
}
private void OnTriggerEnter2D(Collider2D coll)
{
if (coll.gameObject.CompareTag("Player") && !justTeleported) //CompareTag("tag") is better than .tag == "tag"
{
TeleportPlayer(coll.gameObject);
}
}
private void TeleportPlayer(GameObject player)
{
if (Destination) //if Destination is not null
{
StartCoolDown();
Destination.StartCoolDown(); //tell Destination to cooldown
player.transform.position = Destination.transform.position;
}
}
private IEnumerator Cooldown()
{
justTeleported = true;
yield return new WaitForSeconds(CooldownTime);
justTeleported = false;
}
}
Teleport1 (Destination: Teleport2)
Teleport2 (Destination: Teleport1)

How can I make the "do" loop continue, but not infinitely in my code?

Basically, whenever I walk into the trigger it repeats the loop infinitely. I know the reason for this but I don't know the way to fix it so that it works properly. It's meant to do it until you leave the trigger.
public void OnTriggerEnter(Collider other)
{
InDamageRange = true;
StartCoroutine(waiter());
}
public void OnTriggerExit(Collider other)
{
StopCoroutine(waiter());
InDamageRange = false;
}
public IEnumerator waiter()
{
do
{
yield return new WaitForSeconds(1.5f);
player.Health = player.Health - damage;
} while (InDamageRange != false);
}
What can I do to make this work?
Edit: Turns out the issue was that I was trying to stop the coroutine before setting the bool to false. I swapped the two lines in OnTriggerExit and that fixed the issue, thanks for the help! :)
Every time you call waiter() you are creating a new instance of your IEnumerator. You do need to keep a reference to the original one if you want to stop it. Try this:
private IEnumerator _waiter;
public void OnTriggerEnter(Collider other)
{
InDamageRange = true;
_waiter = waiter();
StartCoroutine(_waiter);
}
public void OnTriggerExit(Collider other)
{
StopCoroutine(_waiter);
InDamageRange = false;
}
public IEnumerator waiter()
{
do
{
yield return new WaitForSeconds(1.5f);
player.Health = player.Health - damage;
} while (InDamageRange != false);
}
But given your code setting InDamageRange = true should have also stopped it. Is it your actual code?

UNITY SCRIPTS:spawn in same location after getting destroyed(after 3 seconds)

hey does anyone knows how to make a C# script for object to re spawn after getting destroyed by a bullet
There are multible ways to archive this.
Note that you can also work completely without Coroutine but this usually sucks
private float timer;
private bool isRespawning;
private GameObject clone;
private void DestroyObject(GameObject obj)
{
isRespawning = true;
timer = 3f;
// first make a clone of the current Object
var clone = Instantiate(obj, obj.transform.position, obj.transform.rotation, objtransform.parent);
// disable the clone
clone.SetActive(false);
// Destroy the original
Destroy(obj);
}
private void Update()
{
if(Mathf.Approximately(timer,0f)) return;
timer += Time.deltaTime;
if(timer > 0) return;
// Set the clone active
clone.SetActive(true);
timer = 0;
}
Simplier would be a Coroutine:
private void DestroyAndRespawn(GameObject obj)
{
StartCoroutine(DestroyAndRespawnRoutine(obj));
}
private IEnumerator DestroyAndRespawnRoutine(GameObject obj)
{
// first make a clone of the current Object
var clone = Instantiate(obj, obj.transform.position, obj.transform.rotation, objtransform.parent);
// disable the clone
clone.SetActive(false);
// Destroy the original
Destroy(obj);
// Wait 3 seconds
yield return new WaitForSeconds(3f);
// Set the clone active
clone.SetActive(true);
}
But why should you destroy the object and respawn it? You can simply only disable and later re enable it
private void DestroyAndRespawn(GameObject obj)
{
StartCoroutine(DestroyAndRespawnRoutine(obj));
}
private IEnumerator DestroyAndRespawnRoutine(GameObject obj)
{
// disable the obj
obj.SetActive(false);
// Wait 3 seconds
yield return new WaitForSeconds(3f);
// Set the objactive
obj.SetActive(true);
}
Update
Since you want to call this on collision:
You will need another GameObject that works as a Manager for the respawning / enabling, disabling since you can not just attach that script to the object you disable -> would also disable the script and never re-enable the object.
Create a new empty GameObject e.g. called "RespawnManager"
Create a new Script RespawnManager.cs:
public class RespawnManager : MonoBehaviour
{
public static RespawnManager Singleton;
private void Awake()
{
if(Singleton)
{
enabled = false;
return;
}
Singleton = this;
}
private void DestroyAndRespawn(GameObject obj)
{
StartCoroutine(DestroyAndRespawnRoutine(obj));
}
private IEnumerator DestroyAndRespawnRoutine(GameObject obj)
{
// disable the obj
obj.SetActive(false);
// Wait 3 seconds
yield return new WaitForSeconds(3f);
// Set the objactive
obj.SetActive(true);
}
}
Than you can call it on collision like
OnCollisionEnter(Collision obj)
{
if (obj.gameObject.name != "bullet") return;
// pass this GameObject to the manager
RespawnManager.Singleton.DestroyAndRespawn(gameObject);
}
Set object active to False. using
gameobject.SetActive(false);
And you can use Coroutine to do this.
If you use Destroy(yourObject);, the object does get removed and you loose access.
You could make your Object into a prefab and spawn it where you want by using GameObject obj = Instantiate(...);
Obj contains the reference to the new spawned object and not to the prefab, which means you can later again use Destroy(obj);

Unity3d coroutine stops after while-loop

I have a singleton LevelManager loading a level, waiting for a script from the newly-loaded level to assign a GameObject to the LevelManager to then do stuff with it.
I have the following code:
// some GameObject calls the loadLevel coroutine
void somefunction(sceneToLoad){
StartCoroutine(LevelManager.Instance.loadLevel (sceneToLoad));
}
// snippet of LevelManager.cs
public GameObject levelPrepper = null;
public IEnumerator loadLevel(string levelName){
Application.LoadLevel (levelName);
while (!levelPrepper)
yield return null;
yield return StartCoroutine (waitForLevelPrepper());
print("yay");
//do stuff
}
//snippet of the levelPrep.cs:
void Awake(){
LevelManager.Instance.levelPrepper = gameobject;
}
The problem is that "yay" never gets printed.
I've done some reading and found that this might happen when the GameObject carrying the coroutine is destroyed. However, LevelManager is definitely never destroyed during the process, so I'm at a loss.
The issue is that you start the Coroutine not on the LevelManager, but on "some gameObject", that most likely will be destroyed and its coroutine will stop being executed.
You could fix that by moving the call StartCoroutine into a new method, like this :
void somefunction(sceneToLoad)
{
LevelManager.Instance.LoadLevel(sceneToLoad));
}
public class LevelManager
{
public void LoadLevel(string levelName)
{
StartCoroutine(LoadLevelCoroutine);
}
private GameObject levelPrepper = null;
private IEnumerator LoadLevelCoroutine(string levelName){
Application.LoadLevel (levelName);
while (!levelPrepper)
yield return null;
yield return StartCoroutine (waitForLevelPrepper());
print("yay");
//do stuff
}
}
or calling the StartCoroutine of LevelManager directly
void somefunction(sceneToLoad){
LevelManager.Instance.StartCoroutine(LevelManager.Instance.loadLevel(sceneToLoad));
}

WaitForSeconds on C#

Look at this simple code:
function Start()
{
yield WaitForSeconds (4);
Application.LoadLevel(1);
}
It works! I'm trying to make something similiar using C#, but the processor just ignore the WaitForSeconds. This is my code in C#:
using UnityEngine;
using System.Collections;
public class openingC : MonoBehaviour
{
void Start()
{
executeWait(5);
Application.LoadLevel(1);
}
void executeWait(float aux)
{
StartCoroutine(Wait(aux));
}
IEnumerator Wait(float seconds)
{
yield return new WaitForSeconds(seconds);
}
}
Can someone explain to me why it's not working? Thanks for your time.
public class openingC : MonoBehaviour
{
void Start()
{
executeWait(5);
Application.LoadLevel(1);
}
void executeWait(float aux)
{
StartCoroutine(Wait(aux));
}
IEnumerator Wait(float seconds)
{
yield return new WaitForSeconds(seconds);
}
}
First, the Start method runs, executeWait is called, the program jumps to the method. It finds the coroutine and starts running it until a yield is found or the end of the method. Yield returns to the program, the pointer goes back up to executeWait and finishes the method. The pointer goes back up to Start and calls for Application.LoadLevel.
You want to hang the LoadLevel call.
public class openingC : MonoBehaviour
{
void Start()
{
StartCoroutine(Wait(5));
}
//You don't need executeWait
IEnumerator Wait(float seconds)
{
yield return new WaitForSeconds(seconds);
Application.LoadLevel(1);
}
}
Try this:
Thread.Sleep(500); //500 millisecond waiting...
Reference
This should also be okay.
IEnumerator Start()
{
yield return new WaitForSeconds(4);
Application.LoadLevel (1);
}

Categories

Resources