1
\$\begingroup\$

The behavior I want is that when all the bricks are destroyed, the last brick's OnCollisionEnter2D handler calls SimulateWin(), triggering the next level.

For some reason SimulateWin() is never called.

using UnityEngine;
using System.Collections;

public class Brick : MonoBehaviour {

    public static int brickCount = 0;
    public int maxHits;
    public int timesHit;
    private LevelManager levelmanager;

    void Start ()
    {
        timesHit = 0;
        levelmanager = GameObject.FindObjectOfType<LevelManager>();
    }

    void OnCollisionEnter2D(Collision2D col)
    {
        timesHit++;
        Debug.Log("Collision happened with " + col.gameObject.name);

        if(timesHit >= maxHits)
            Destroy(gameObject);

        if(GameObject.FindObjectOfType<Brick>() == null)//Not entering this if
            SimulateWin();//this is never called
    }

    void SimulateWin()//This is the function that is supposed to be called when all the bricks are destroyed.
    {
        levelmanager.LoadNextLevel();
    }

}
\$\endgroup\$
5
  • \$\begingroup\$ An interesting thing about programming is the vast number of things you can do with it. So, when you say "It doesn't work", there's a whole lot of things it can not be doing correctly. Maybe you can narrow the scope of the question a bit? What are you expecting it to do? What have you tried to do already to fix it? \$\endgroup\$
    – House
    Sep 20, 2017 at 21:37
  • \$\begingroup\$ If you are not familiar with programming in general, just type the things that other people in tutorials type. Don't try to invent anything by yourself in the beginning. After going through some tutorials because it's more fun, take some on programming in general. Understand data types, inheritance, polymorphism, encapsulation. Then take a few courses on algorithms, math, and programming. That is your roadmap but for now, these questions look silly. This code looks ok, if you want people to help you post error message along with code. \$\endgroup\$ Sep 20, 2017 at 21:41
  • \$\begingroup\$ Start by inspecting the return value of FindGameObjectOfType after you've destroyed your last brick. The result may surprise you. ;) \$\endgroup\$
    – DMGregory
    Sep 20, 2017 at 22:29
  • \$\begingroup\$ If it's solved, don't edit the question, write and answer and put it in there. \$\endgroup\$
    – Engineer
    Sep 21, 2017 at 4:34
  • \$\begingroup\$ Probably not worth it. \$\endgroup\$
    – Engineer
    Sep 21, 2017 at 11:10

1 Answer 1

1
\$\begingroup\$

Two things to know about Unity's various Find methods:

  1. As others have pointed out, Unity's Find methods have to search through potentially many objects in your scene to find the one(s) meeting your criteria, and that can be needlessly slow - especially in large complex scenes.

    • Switching to FindObjectsOfType as suggested in another answer can technically be even worse, because instead of stopping the search after finding the first one, Unity has to keep looking to find more, and has to allocate memory for an array to return them in.

    • Of course, for a small game prototype like it sounds like you're making, this is unlikely to be a big deal. You'll find experienced Unity devs like us get hung up about it more as a matter of maintaining good code hygiene and avoiding creeping problems in the future, rather than any immediate crisis. ;)

  2. Unity's Find methods will find even an object that was Destroy()ed this frame, because Destroy takes effect at the end of the frame, just before rendering.

    • So your FindObjectOfType<Brick>() call still finds that last brick, because we're checking in the same frame that we destroyed it.

    • Calling the same Find method from another object, as suggested in another answer, won't get around this if you're still checking in the same frame.

    • Trying to hack around this gets weird and ugly. That's a good sign there's a better solution available.


What I'd recommend here is a simple counter.

  • When we add a Brick to our scene, we'll increment the counter.
  • When it's destroyed, we'll decrement it.
  • Then we can query how many are left at any time just by checking the value of a variable - no searching required!

In fact, you even have the counter variable already waiting there in your code - you're just not using it yet. :)

public class Brick : MonoBehaviour {

    // Here's our counter variable that was there all along.
    // The "static" means it's shared between all Bricks.
    public static int brickCount = 0;
    //...
    void Start ()
    {
        // Add one to our brick count for this new brick.
        brickCount++;

        timesHit = 0;    

        // Most bricks never actually use the LevelManager, 
        // so let's skip doing that here. Just find it when you win.
        //levelmanager = GameObject.FindObjectOfType<LevelManager>();
    }

    void OnCollisionEnter2D(Collision2D col)
    {
        timesHit++;

        if(timesHit >= maxHits) {
            brickCount--;
            Destroy(gameObject);
        }

        if(brickCount == 0)
            SimulateWin();
    }

    // ...
}

The counter has some side benefits too. Now any other script in your scene can query Brick.brickCount to find out how many bricks are left, and do things like...

  • Display a remaining count in the game's UI

  • Play different sounds to build up to a climax as you close in on the final few bricks

  • Add a "Time Attack" game mode where you're scored on how many bricks you're able to break in a limited time

  • Change the spawning rate of bonuses/power-ups, or always spawn a particular item when you cross the halfway mark...

And all of this is practically free, because you're just reading the value of an integer. :)

(Though I would recommend making the variable private and exposing a Getter for it, so outside scripts don't unintentionally mess with our count...)

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .