I have create a gameObject inside a destroy and have called the Destroy() method on this object with a delay duration of 2 second.
Destroy(gameObject,2.0f);
What I want is, if another gameObject with a collider collides with this gameObject, I want to cancel the destroy call for this particular gameObject.
I tried to call destroy again on the same gameobject with new duration, but it still gets destroyed based on the old duration.
void OnCollisionEnter2D(Collision2D coll)
{
//Debug.Log("Inside Enter");
if (coll.gameObject.tag == "Ball")
{
Destroy(gameObject, reInitializeLifeOfLine);
Debug.Log("Inside Enter");
}
}
Can someone please suggest how to achieve this
There is no built-in way to prevent destruction of a GameObject after you have called Destroy(). However, there are a variety of workarounds that others have explored which achieve exactly what you need.
Here's a simple one (adapted from Unity Answers) using Invoke(), which you can cancel manually:
void OnCollisionEnter2D(Collision2D coll)
{
if (coll.gameObject.tag == "Ball")
{
CancelInvoke("DestroySelf");
Invoke("DestroySelf", reInitializeLifeOfLine);
}
}
void DestroySelf () {
Destroy(gameObject);
}
Hope this helps! Let me know if you have any questions.
I resolved the issue using below code
float reInitializeLifeOfObject = 5.0f;
float lifeTimeOfObject = 3f;
private float startTime;
void Start()
{
startTime = Time.time;
}
void FixedUpdate()
{
// This will destroy the gameObject in 5 second
if((Time.time - startTime) > lifeTimeOfObject)
{
Destroy(gameObject.transform.parent.gameObject);
}
}
void OnCollisionEnter2D(Collision2D coll)
{
if (coll.gameObject.tag == "Ball")
{
// Re-initializing the startTime so that Object is not abruptly destroy while ball is still
// interacting with game object
startTime = Time.time;
}
}
I created a float variable startTime and initialized it in Start() method.
Now in FixedUpdate, I am checking whether 5 seconds have passed. If yes, then destroy the object.
Now for the collision part, I am checking in OnCollisionEnter2D(). If object is interacting with a collider, I am re-assigning value to startTime with current time.
This solved my problem
Related
In my game I have a shooting enemy that I want to attack the player only when he's in range, and to attack him once every 3 seconds. This is the code I need repeated
Whatever I try doing just results in the enemy shooting a metric ton of bullets at the player to the point when it sometimes crashes my project(tried for loops, while loops, played with booleans and timing with Time.deltaTime). Can someone smarter than me give me some pointers how I could do this?
You'd probably want to do the check not every frame but whenever the state changes. So you cache the player.
Transform target;
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player"))
target = other.transform;
}
private void OnTriggerExit2D(Collider2D other)
{
if (other.CompareTag("Player"))
target = null;
}
Whenever target is not null, you may shoot to it.
private void Update()
{
if (target != null)
ShootAt(target.position);
}
private void ShootAt(Vector3 position)
{
// Spawn your stuff here to shoot at the given location
}
Now how about the timing? I am not a fan of coroutines in Unity. They tend to get messy, are overused and the "WaitForSeconds" is not very precise. It just waits until the time has passed, but not exactly. Maybe you want more precision, who knows?
So let's extend the Update call to have a small timer.
private float shootCooldown;
private void Update()
{
if (target != null)
{
shootCooldown -= Time.deltaTime;
if (shootCooldown <= 0.0f)
{
shootCooldown += 3.0f;
ShootAt(target.position);
}
}
}
One last thing: If you want the timer to reset at some point, just set it to 0.0f. A good location for that would be when the player leaves the trigger. Also, maybe you want the timer to continue to go down, even if the target is null, so move the shootCooldown -= Time.deltaTime line out of the if-scope but make sure it does not count down forever, but rests at 0.
I would avoid using OnTriggerStay2D() and use OnTriggerEnter2D and OnTriggerExit2D in this situation. You need to call the 'yield return' statement within a loop as shown below. The below code should do what you are asking.
private void OnTriggerEnter2D(Collider2D other) {
if (other.tag == "Player") {
StartCoroutine(ShootAtPlayer());
}
}
private void OnTriggerExit2D(Collider2D other) {
if (other.tag == "Player") {
StopAllCoroutines();
}
}
private IEnumerator ShootAtPlayer() {
while (true) {
Instantiate(enemyBullet, enemy.transform.localPosition, transform.LocalRotation);
yield return new WaitForSeconds(3.0f);
}
}
I'm creating the platform falling effect by unchecking the isKinematic but I keep getting the error :
"MissingReferenceException: The object of type 'GameObject' has been destroyed but you are still trying to access it.
Here is my code:
// Update is called once per frame
void Update()
{
}
private void OnCollisionExit(Collision collision){
if(collision.gameObject.tag == "Player")
{
Fall();
Invoke("Fall", 0.2f); //delay 0.2 s chu y dau va viet thuong
}
}
void Fall(){
GetComponent<Rigidbody>().isKinematic = false;
Destroy(gameObject,1f);
}
}
Here is the error in unity
Can anyone know how to fix this problem? Thank you.
The flow of your current code is as following:
Called Fall() once (After 1 second from that call the gameObject will be destroyed).
You are using Invoke to call Fall() after 0.2 seconds while you have already a task to destroy the gameObject from the first call.
Update: I assumed that you want to set kinematic false immediately. I updated the piece of code :)
Code
using UnityEngine;
public class Q69205071 : MonoBehaviour
{
private void OnCollisionExit(Collision collision)
{
if (collision.gameObject.tag == "Player")
{
//Fall(1f);
/// since you want everything to be executed after 0.2 seconds
/// you could use Invoke or coroutines.
Invoke("Fall", 0.2f);
}
}
private void Fall()
{
GetComponent<Rigidbody>().isKinematic = false;
Destroy(gameObject, 1f); // 1 seconds delay before destroying the gameObject
}
}
Happy Coding :)
I've got a problem. I am creating a small game project where where a ball escapes from ghosts.
I am trying to write a script that plays "evil laughter" when the ball collides with a ghost.
When i added the audio upon collision code into the script, the other function which gets the player to the "Game over"-scene stops working
Does anyone know what the problem might be?
Thanks a lot in advance! (code below) <3
public class GhostScript : MonoBehaviour
public AudioSource ghostCollision; //this is the collision sound reference
public GameObject target; //this is the player or a reference for him
UnityEngine.AI.NavMeshAgent agent;
// Start is called before the first frame update
void Start()
{
ghostCollision = GetComponent<AudioSource>();
agent = GetComponent<UnityEngine.AI.NavMeshAgent>();
if (target == null) {
target = GameObject.FindGameObjectWithTag("Player");
}
}
// Update is called once per frame
void Update()
{
agent.destination = target.transform.position;
}
public void OnCollisionEnter(Collision collision){
if (collision.gameObject.tag == "Player"){
ghostCollision.Play();
SceneManager.LoadScene("menu");
}
}
That's because as soon as you call SceneManager.LoadScene the current scene (which contains your Player, AI and AudioSource) gets lost and replaced with the new Scene. Adding a delay to the scene load should do the trick, you can use Invoke for that (or Coroutines if you're more advanced).
private void OnCollisionEnter(Collision other)
{
if (other.gameObject.tag == "Player")
{
ghostCollision.Play();
// Invokes the function after the AudioClip is over
Invoke("LoadScene", ghostCollision.clip.length);
}
}
private void LoadScene()
{
SceneManager.LoadScene("your-scene-name");
}
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");
}
So I want to have two balls collide, destroy themselves, and then have another ball spawn at their place (preferably with a specific velocity). When I try to attach the script to the ball, however, both instances of the ball are destroyed on contact, then immediately spawns two prefabs of the ball, since they both have the code. This causes both balls to spawn and destroy each other over and over. I have this script attached to the balls:
private Vector3 ballPosition;
void OnTriggerEnter2D (Collider2D other) {
if (other.gameObject.tag == "Ball") {
ballPosition = new Vector3 ((transform.position.x + other.transform.position.x) / 2, (transform.position.y + other.transform.position.y) / 2, 0.0f);
StartCoroutine ("RespawnBall");
}
}
IEnumerator RespawnBall () {
Instantiate (gameObject, ballPosition, Quaternion.identity);
Destroy (gameObject);
yield return null;
}
How do I make this code destroy both balls, then spawn only one instance of the prefab?
You can also use a boolean on your script that is set so only the first ball having its OnTriggerEnter2D() method call will spawn a new ball :
private Vector3 ballPosition;
public bool spawnNewBall;
void Start() {
spawnNewBall = true;
}
void OnTriggerEnter2D (Collider2D other) {
if (other.gameObject.tag == "Ball") {
if (spawnNewBall) {
other.GetComponent</*YourScriptName*/>().spawnNewBall = false;
}
ballPosition = new Vector3 ((transform.position.x + other.transform.position.x) / 2, (transform.position.y + other.transform.position.y) / 2, 0.0f);
StartCoroutine ("RespawnBall");
}
}
IEnumerator RespawnBall () {
if (spawnNewBall) {
Instantiate (gameObject, ballPosition, Quaternion.identity);
}
Destroy (gameObject);
yield return null;
}
More simply, just do this:
public class TwoToOne : MonoBehaviour {
public bool doNothing;
void OnCollisionEnter (Collision col)
{
if (doNothing) return;
col.gameObject.GetComponent<TwoToOne>().doNothing = true;
Destroy(col.gameObject);
GameObject newCube = Instantiate(gameObject);
Destroy(gameObject);
}
}
That's a totally common script or pattern in Unity.
So, conceptually you just "destroy the other one"; since one of the scripts has to run first, it works out fine. In different systems from Unity you can do one of these things:
A, "destroy" the other game object in some way. That would be DestroyImmediate(col.gameObject) in Unity.
B, "destroy" or disable the other script in some way. that would be col.gameObject.GetComponent<TwoToOne>().enabled = false in Unity
C, "flag" (set a boolean) on the other game object in some way - as shown here.
As it happens, in Unity specifically you can't do A or B, so you just do C. B is the most elegant solution, but it doesn't work in Unity5, so for now do C!
You can solve this with the use of delegates OR as #JoeBlow suggested, the use of UnityEvents from an Event Manager.
In the following example I have opt'd to use delegates.
There are 2 scripts. One which sits on the spheres (which you already have) and the second, a static class which can be referenced from any other script in the application without instantiation.
EventManager.cs
using UnityEngine;
using System.Collections;
public static class EventManager{
// Create our delegate with expected params.
// NOTE params must match SphereScript.PostCollision declaration
public delegate void CollideEvent(string message);
// Create the delegate instance. This is the one we will invoke.
public static event CollideEvent PostCollision;
// Called whenever an object has collided with another
public static void Collision(GameObject obj1, GameObject obj2, Vector3 collisionPoint){
if (obj1.GetComponent<sphereScript>().isAlive && obj2.GetComponent<sphereScript>().isAlive) {
//Kill the 2 objects which haev collided.
obj1.GetComponent<sphereScript> ().Kill ();
obj2.GetComponent<sphereScript> ().Kill ();
//Create a cube.
GameObject cube = GameObject.CreatePrimitive (PrimitiveType.Cube);
cube.transform.position = collisionPoint;
// Invoke delegate invocation list
PostCollision("Something is dead");
}
}
}
SphereScript .cs
using UnityEngine;
using System.Collections;
public class sphereScript : MonoBehaviour {
// Am I alive?
public bool isAlive;
// Use this for initialization
void Start () {
// Add a function we want to be called when the EventManager invokes PostCollision
EventManager.PostCollision += PostCollision;
isAlive = true;
}
// Update is called once per frame
void Update () {
}
//Invoked from EventManager.PostCollision delegate
void PostCollision(string message){
if(isAlive)
Debug.Log (this.name + " message received: " + message);
}
// Called when it is time to destroy this gameobject
public void Kill(){
isAlive = false;
Destroy (this.gameObject);
}
//On collision with another object
void OnCollisionEnter2D(Collision2D collision){
if (collision.gameObject.GetComponent<sphereScript>()) {
EventManager.Collision (this.gameObject, collision.gameObject, collision.contacts [0].point);
}
}
// Called after this object has been destroyed
void OnDestroy(){
// cleanup events for performance.
EventManager.PostCollision -= PostCollision;
}
}
So why use this method?
This method is ideal for controlling the release of multiple 'happenings' from a single location. In this case, we trigger a function on each ball each of the colliding balls. The EventManager then creates a single cube in their wake and following this, notifies any remaining cubes that a collision has occurred elsewhere in the scene.
what you can do is create another script which only destroys the ball but not spawn and attach it to the 2nd ball. the first ball will have both destroy and respawn feature. so when both balls destroy each other, one will respawn and the other will not because you did not attach the spawn feature to the 2nd ball