I have a BonusController script as a component of a Bonus gameObject. This Bonus must be destroyed on collision but has to "act" for a few seconds. I have started a coroutine for this, but the script stops its execution (I think it's because I set the gameObject inactive). Here is the code of BonusController:
void OnTriggerEnter2D(Collider2D col)
{
StartCoroutine(speedUp(1));
gameObject.SetActive(false);
}
IEnumerator speedUp(float seconds)
{
Debug.Log("before");
yield return new WaitForSeconds(seconds);
Debug.Log("after"); // <--- This is never called
}
How can I remove the object and don't stop the coroutine script execution?
Can't you just disable the mesh renderer and collider? This way the gameobject will still exists, but the user won't be able to see it.
You cannot pull the ground on which you are standing. :)
Just disable the SpriteRenderer as you are using 2D methods. And keep the object alive and enable.
{
StartCoroutine(speedUp(1));
//gameObject.SetActive (false);
GetComponent<SpriteRenderer> ().enabled = false;
GetComponent<Collider2D> ().enabled = false;
// Above line will deactivate the first collider it will find.
}
IEnumerator speedUp(float seconds)
{
Debug.Log("before");
yield return new WaitForSeconds(seconds);
Debug.Log("after"); // <--- This is never called
}
In order to destroy the object after some time, call destroy inside the coroutine after the yield.
yield return WaitForSeconds(second);
Destroy(this);
Now it is properly destroyed and will free up memory opposed to still being there, invisible, but taking up resources.
Related
void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.layer == layer)
{
StopAllCoroutines();
Destroy(gameObject, 1f);
}
}
//Coroutine is called from another script
public IEnumerator MoveRb(Vector3 destination)
{
yield return new WaitUntil(() => rb.velocity == Vector3.zero);
destination.y = rb.position.y;
rb.velocity = transform.right;
//this is where i get an error
yield return new WaitUntil(() => Vector3.Distance(rb.position, destination) < 0.1f);
rb.velocity = Vector3.zero;
}
Basically, getting "MissingReferenceException" when trying to destroy an object while running coroutine. Delay of 1 second, nor replacing "WaitUntil" with while loop and "yield return null" doesn't fix this issue. The only place where object gets destroyed is inside of "OnCollisionEnter" inside of the same gameObject script. What am I missing here?
Full exception message:
MissingReferenceException: The object of type 'Rigidbody' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
As you posted on the other answer you actually run this routine from another script
public class ConveyourBelt : MonoBehaviour
{
void OnCollisionEnter(Collision other)
{
if (other.gameObject.TryGetComponent(out MovingPart movingPart))
{
// runs this Coroutine on THIS component
StartCoroutine(movingPart.MoveRb(transform.position + transform.right));
}
}
}
The issue here is that this ConveyourBelt component is running the Coroutine, not the MovingPart component attached to the other object
=> The call to
StopAllCoroutines();
in the MovingPart component has no effect at all since it is never running that routine!
So when you destroy the object after 1 second the routine could still be running on the ConveyourBelt component.
As a solution you should rather make the routine running on the other component like
public class ConveyourBelt : MonoBehaviour
{
void OnCollisionEnter(Collision other)
{
if (other.gameObject.TryGetComponent<MovingPart>(out var movingPart))
{
// rather runs the Coroutine on the movingPart component
movingPart.StartCoroutine(movingPart.MoveRb(transform.position + transform.right));
}
}
}
The code should work in theory. I think the problem is you are trying to access the coroutine again during this 1 second destroy delay. This results in the coroutine running while the gameobject is destroyed.
If you want this 1s delay then either make sure that the coroutine can't get called again after you have collided with something or the easier way is to make the coroutine error proof. Means you check if the rigidbody is null before you use it.
I am new to Unity and C#, I am trying to add a 'wait time' to my death script. So when my Player dies it shows a particle animation and then resets the level, however, at the moment my particles are playing but the level doesn't reset.
public GameObject particles;
public Player_Movement player;
void OnCollisionEnter2D(Collision2D Col)
{
if (Col.collider.tag == "Enemy")
{
Instantiate(particles, transform.position, Quaternion.identity);
Destroy(gameObject);
StartCoroutine("RestartGameCo");
}
}
public IEnumerator RestartGameCo()
{
yield return new WaitForSeconds(0.5f);
SceneManager.LoadScene("Level1");
}
Destroy(gameObject);
StartCoroutine("RestartGameCo");
Your code is fine, but you destroy the gameobject that has this script on it. Which also destroys the script, and stops all the coroutines. So it will never be called.
A solution is to make the object invisible in some way, like disabling the mesh renderer and collider instead of destroying it.
You are actually destroying the gameobject itself before even running the co-routine. It means that the co-routine "RestartGameCo" won't even run. One way to debug these kind of things is using Debug.log messages.
Code:
void OnCollisionEnter2D(Collision2D Col)
{
if (Col.collider.tag == "Enemy")
{
Instantiate(particles, transform.position, Quaternion.identity);
StartCoroutine("RestartGameCo");
}
}
public IEnumerator RestartGameCo()
{
yield return new WaitForSeconds(0.5f);
SceneManager.LoadScene("Level1");
}
Let me know if it helps.
I know a couple options about that. Here's one.
By using the Thread, it will pause your application for the amount of time of your desire. To do so, you need to add using System.Threading; to your the current cs file you are editing. Now simply change your code to that:
void OnCollisionEnter2D(Collision2D Col)
{
Thread.Sleep(5000) // 5000 in milliseconds
if (Col.collider.tag == "Enemy")
{
Instantiate(particles, transform.position, Quaternion.identity);
Destroy(gameObject);
StartCoroutine("RestartGameCo");
}
}
public IEnumerator RestartGameCo()
{
Thread.Sleep(5000)
yield return new WaitForSeconds(0.5f);
SceneManager.LoadScene("Level1");
}
Already 2 days I'm trying to solve this problem but I can't.I'm trying to delete the unlimited spawning barriers consistently when they are collide with the boundary.The barriers are made from 5 cubes.Any ideas?
public List<GameObject> spawning=new List<GameObject>();
public Vector3[] positions = new Vector3[5];
public GameObject barrier;
public GameObject boundary;
void Start()
{
StartCoroutine (SpawnBarrier());
}
void Update()
{
if(true)
{
foreach (GameObject move in spawning)
move.transform.Translate (0f, 0f, -0.1f);
}
}
IEnumerator SpawnBarrier(){
yield return new WaitForSeconds (3f);
while (true) {
for(int i=0;i<=4;i++)
{
spawning.Add (Instantiate (barrier, positions [i], Quaternion.identity)as GameObject);
}
yield return new WaitForSeconds (3f);
}
}
Your barriers are marked as isTrigger on their colliders therefore you can use OnTriggerEnter to detect when any of the barriers collides with the boundary.
You need to create new script, lets call that BoundaryDetector and attach it to the barrier prefab so that every instance of the barrier will have this script attached to it.
When OnTriggerEnter is called, check if the trigger is made by the boundary. This can be done by checking for the "Player" tag since boundary is tagged as Player in your screenshot. If the detected trigger tag is "Player", first remove the spawning from the spawning List then Destroy it.
The BoundaryDetector script is as below (Must be attached to the barrier prefab):
ScriptFromYourQuestion yourInstancingSript;
void Start()
{
GameObject obj = GameObject.Find("NameOfObjectScriptInYourQuestionIsAttachedTo");
yourInstancingSript = obj.GetComponent<ScriptFromYourQuestion>();
}
void OnTriggerEnter(Collider other)
{
//Detect if we collided with the boundary
if (other.CompareTag("Player"))
{
//Remove Self/barrier from the List
yourInstancingSript.spawning.Remove(this.gameObject);
//Delete Self/barrier
Destroy(this.gameObject);
}
}
Note: The ScriptFromYourQuestion should be replaced with the name of the script in your question.
MUST DO:
To get the code above working, the following changes must be made in the Update function of the script from your question:
1.You must attach Rigidbody to at-least one of the Objects (boundary or barrier). In this case, I suggest you do so to the barrier prefab.
2.You must remove move.transform.Translate (0f, 0f, -0.1f); and replace it with Rigidbody.MovePosition and use that to move your barries because you have now attached Rigidbody to your barries and this is the proper way to move a Rigidbody Object.
Maybe something like this:
Your Update function in the code form your quesion should looks something like this:
public float speed = 100.0f;
void Update()
{
if (true)
{
foreach (GameObject move in spawning)
{
//Get Rigidbody component
Rigidbody rb = move.GetComponent<Rigidbody>();
//Calculate Z-axis pos to move to
Vector3 pos = new Vector3(0, 0, 1);
pos = pos.normalized * speed * Time.deltaTime;
//Move with Rigidbody
rb.MovePosition(rb.transform.position + pos);
}
}
}
Your problem is that you have an infinite loop while true that you're not breaking out of. Instead you should add a counter. You haven't explained what you want to do but my example is that it will breakout once it completes 10 instantiations of 5 blocks.
IEnumerator SpawnBarrier(){
spawnCount=0
yield return new WaitForSeconds (3f);
while (true) {
for(int i=0;i<=4;i++)
{
spawning.Add (Instantiate (barrier, positions [i], Quaternion.identity)as GameObject);
}
if (++spawnCount==10)
{
break;
}
yield return new WaitForSeconds (3f);
}
}
If I understood corrently, you want to destroy barriers that collided with some kind of boundary?
If the 'boundary' is the visible screen space
In this case, we can assume that, once the object is invisible to MainCamera it can be counted as out of the boundary. Keeping that in mind, we can use OnBecameInvisible() method, which is method (or message, as stated in Unity Scripting API) called by MonoBehaviour class:
// A piece of barrier that gets destroyed once out of screen
public sealed class BarrierPiece : MonoBehaviour
{
// Called once the object is no longer visible to any (scene editor included) camera
private void OnBecameInvisible()
{
Destroy(gameObject);
}
}
NOTE: if you have more than one Camera in your scene, the object must be invisible to all of the cameras in order for the OnBecameInvisible() to be called.
If the 'boundary' is other object
In this case, there are many approaches based on your game:
Create GameObject with Rigidbody component and add a script which would have OnTriggerEnter(...) method and destroy colliding barriers in it.
(Inefficient) have two static Vector2 variables, one for pivotPosition, other for maximumOffset (this one should be squared for faster calculations). Then, have script on each of the barriers and in Update() checks whether the object's squared distance to the pivotPosition is greater than maximumOffset and call Destroy(gameObject) if the condition is true.
(others) ...
Those are two that came up quickly in my mind.
Hope this helps!
EDIT
Didn't look at the images...
So for your solution, you would need to assign Rigidbody component to the Boundary and add BoxCollider component on all of the barriers. Now, create a tag, called Barrier and tag all the barriers with it. Create a script:
// Destroys barriers on collision
public sealed class BarrierBoundary : MonoBehaviour
{
// Called once the script is created
// Checks if the object has Rigidbody component attached
private void Awake()
{
Debug.Assert(GetComponent<Rigidbody>() != null);
}
private void OnCollisionEnter(Collision collision)
{
if(collision.collider.tag == "Barrier")
Destroy(collision.collider.gameObject);
}
}
(The code wasn't tested, so it can have some typos.)
Now, assign the script to the Boundary. And voila! Barriers are destroyed once hitting the boundary.
Hope (again) this helps!
I kinda stuck with my script right now. When a gameObject, with my script attached to it, has a trigger event with a specific gameObject, I want to destroy the specific gameObject after an amount of time.
So i came to this:
void OnTriggerEnter ( Collider other) {
if (other.gameObject.tag == "leaf1"){
StartCoroutine (LeafDestruction ());
}
}
IEnumerator LeafDestruction(){
yield return new WaitForSeconds (5);
Destroy (gameObject);
}
I know it's a noob mistake but i think i miss something, because when i run this script, it destroys the gameObject with the script attached to it, and not the specific gameObject(with tags).
How can i fix that?
A simpler solution is to use the optional 2nd parameter of the Destroy method:
The object obj is destroyed immediately after the current Update loop, or t seconds from now if a time is specified.
Given the official parameters:
Parameters
obj
The object to destroy.
t
The optional amount of time to delay before destroying the object.
You can adjust your if statement to:
if (other.gameObject.tag == "leaf1")
Destroy(other.gameObject, 5.0f);
Basically you need to tell your coroutine that it should destroy the other.gameObject and not the gameObject that is running this script.
So what you could do is add a parameter to your coroutine, passing in the gameObject that it should really be destroyed:
void OnTriggerEnter ( Collider other) {
if (other.gameObject.tag == "leaf1")
{
IEnumerator coroutine = LeafDestruction(other.gameObject);
StartCoroutine (coroutine);
}
}
IEnumerator LeafDestruction(GameObject toDestroy){
yield return new WaitForSeconds (5);
Destroy (toDestroy);
}
You destroyed the object instead of the leaf. The gameObject is an alias of this.gameObject, which is the game object this script component is attached to. Note MonoBehaviour inherits from Behaviour and Behaviour inherits from Component.
GameObject leafObject;
void OnTriggerEnter ( Collider other) {
if (other.gameObject.tag == "leaf1"){
leafObject = other.gameObject;
StartCoroutine (LeafDestruction ());
}
}
IEnumerator LeafDestruction(){
yield return new WaitForSeconds (5);
Destroy (leafObject);
}
I used OnMouseDown() to deactivate an object but i want the object to activate again in a few seconds. I have used WaitForSeconds() for other things but this time it just doesn't work
this is what i could gather by researching (the deactivating part works fine):
void Start()
{
StartCoroutine(wait());
}
void Update(){}
void OnMouseDown()
{
gameObject.SetActive(false);
}
IEnumarator wait()
{
yield return new WaitForSeconds(3);
gameObject.SetActive(true);
}
There are too many reasons your code isn't work right. You are doing it backwards. Your coroutine starts immediately when your program starts because wait() is called from the Start() function. When it starts, it pauses for 3 seconds and set your GameObject to SetActive(true);
If your GameObject is already visible to the screen, your code wont do anything because SetActive(true) will be called even when it is visible. If you fail to press/click on the screen before that 3 seconds, you wont be able to see SetActive(true); because your coroutine code would have finished running by that time.
Also, if you disable a GameObject, the coroutine attached to it will stop. The solution is to create a reference of the GameObject you want to disable then use that reference to disable and enable it from another script without problems.
Since provided a code, I fixed/re-wrote it for you. I replaced the OnMouseDown fucntion with something more robust.
All you have to do is create an empty GameObject. Attach this script to that empty GameObject. Then drag and drop that GameObject you want to disable and enable to the this "Game Object To Disable" slot in this script, from the Editor.
Do NOT attach this script to that GameObject you want to disable and enable.
Tested with cube and it worked.
using UnityEngine;
using System.Collections;
public class ALITEST: MonoBehaviour
{
//Reference to the GameObject you want to Disable/Enable
//Drag the Object you want to disable here(From the Editor)
public GameObject gameObjectToDisable;
void Start()
{
}
void Update()
{
//Keep checking if mouse is pressed
checkMouseClick();
}
//Code that checks when the mouse is pressed down(Replaces OnMouseDown function)
void checkMouseClick()
{
//Check if mouse button is pressed
if (Input.GetMouseButtonDown(0))
{
RaycastHit hitInfo = new RaycastHit();
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hitInfo))
{
//Check if the object clicked is that object
if (hitInfo.collider.gameObject == gameObjectToDisable)
{
Debug.Log("Cube hit");
StartCoroutine(wait()); //Call the function to Enable/Disable stuff
}
}
}
}
//This value is used to make sure that the coroutine is not called again while is it already running(fixes many bugs too)
private bool isRunning = false;
IEnumerator wait(float secondsToWait = 3)
{
//Exit coroutine while it is already running
if (isRunning)
{
yield break; //Exit
}
isRunning = true;
//Exit coroutine if gameObjectToDisable is not assigned/null
if (gameObjectToDisable == null)
{
Debug.Log("GAME OBJECT NOT ATTACHED");
isRunning = false;
yield break; //Exit
}
gameObjectToDisable.SetActive(false);
//Wait for x amount of Seconds
yield return new WaitForSeconds(secondsToWait);
//Exit coroutine if gameObjectToDisable is not assigned/null
if (gameObjectToDisable == null)
{
Debug.Log("GAME OBJECT NOT ATTACHED");
isRunning = false;
yield break; //Exit
}
gameObjectToDisable.SetActive(true);
isRunning = false;
}
}
Because you're calling StartCoroutine() in Start(), your coroutine will resume 3 seconds after the component is started. You want to call StartCoroutine(wait()) in OnMouseDown() so the GameObject will become active after that.
You cannot deactivate GameObject and continue Coroutine on it. If you deactivate GameObject that has runing Coroutine it will be stoped.
So if you want to do it right, you need Coroutine runing on other GameObject and from there actvating this GameObject.
If you need more help, ask.