3

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?

YojaX
  • 45
  • 4
  • this is an absolute basic in Unity / game programming. You simply "destroy the other one", which will stop it "destroying that one". In Unity, as it happens you can't destroy in the physics loop, so you simply set a boolean. In different systems you either destroy, disable the script, or just set a boolean. Kardux gave the correct basic answer. – Fattie Nov 30 '16 at 12:48
  • Another issue arising Yoj is that, as a general rule, you don't use the "tag" things in Unity. Really at this stage you need to get with the **Layer** system for physics. You can't do any non-trivial Unity projects without it. (There's a few situations like that in Unity: they try to make a "hello world" system that will get new learners by their very first couple days in Unity; but it really doesn't help. You just can't do physics without a layers concept; you might as well use it from the start.) – Fattie Nov 30 '16 at 13:25
  • @JoeBlow, so far this is my first project, and just following tutorials I learned to do this with tags, which is do-able with how simple my project is. Could you recommend good tutorials dealing with layers? – YojaX Dec 01 '16 at 03:23
  • Sure, https://unity3d.com/learn/tutorials/topics/interface-essentials/layers good luck @YokaX ! – Fattie Dec 01 '16 at 03:35

3 Answers3

2

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!

Fattie
  • 27,874
  • 70
  • 431
  • 719
Kardux
  • 2,117
  • 1
  • 18
  • 20
  • 1
    HI Kardux, I just added a typical "Two To One" script; it's commonplace. (FWIW one must be using layers in physics anyway, you rarely examine the tags.) – Fattie Nov 30 '16 at 12:49
  • 1
    @JoeBlow while I do agree that what you added is simpler, @YojaX stated he wanted to destroy both the colliding balls and instantiate a new one; plus I wanted to keep the spirit of what he created so far :) But yeah i'd do it your way too : avoiding instantiating and simply using one of the two existing balls as the new one is far better (I'd simply reset the `doNothing` to `false` before returning so it would work with upcoming collisions) – Kardux Nov 30 '16 at 12:52
  • 1
    HI @Kardux. Yes by all means, your answer is the right one. (It's common on this site to add more info to a good answer, rather than making a new answer.) Awesome and have a great day. – Fattie Nov 30 '16 at 13:21
0

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.

Community
  • 1
  • 1
Zze
  • 18,229
  • 13
  • 85
  • 118
  • 1
    Hey Zzzzze! There's a couple of issues: (1) a "destroy the other one first" script is absolutely standard in games, it's a very basic thing when bouncing things around (2) interestingly the problem you describe in the last para doesn't exist: logically they all get "erased" (3) to some extent you "just don't use delegates like that" in Unit:; the unity tutorial you linked is notoriously one of the worst pieces of Unity documentation (***and that's saying something!!***) also it is way, way, way out of date, (4) you must use UnityEvent these days http://stackoverflow.com/a/36249404/294884 – Fattie Nov 30 '16 at 12:56
  • Hello again @JoeBlow.. Do you just look at my activity feed and follow me around? haha - I have updated my answer to accomodate your comment. I am yet to change the example over to use UnityEvents however, I will do this when I have time. While I have your attention, could you please also comment on my answer for another question - if you have time... Would like to hear your thoughts - http://stackoverflow.com/questions/40564137/weird-lines-3d-unity/40599689#40599689 Cheers! – Zze Dec 01 '16 at 00:32
-2

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

Faizan Ali
  • 13
  • 7