Two 2D colliders conflicting in the same space

I’m trying to make for when “player” collides with the ball and presses space, it turns the destroy variable to true and destroys the ball, after that the variable turns to false. And when they try to press space against the background it loses hp and the variable turns to false.

When it doesn’t have the “destroy” bool, it works, but the problem is when it does, it doesn’t.

I made some testing and apparently the player is detecting the background game object before the ball game object, turning “destroy” into false. So when I try to destroy the ball at any point, it never will because it will always be false.

I’m new unity so I don’t even think this is the best implementation for 2D colliders so i’m open to other ones.

 private void Start()
    {
        gameManager = GameObject.Find("GameManager").GetComponent<GameManager>();
        destroy = false;
        lives = 3;
        //Debug.Log(lives);
    }
    
    private void Update()
    {
        Press();
    }

    //Ball get destroyed when player touches it
    private void OnTriggerStay2D(Collider2D other)
    {
        isTouching = true;

        //The problem lies in the order of operations; it is colliding with the background first, setting destroy to true, and then setting it to false.
        //So, no matter how many times you press space, this cycle will always happen. The issue is with the collision.
        
        if (destroy && other.gameObject.CompareTag("Ball"))
        {
            Debug.Log("Destroy");
            Destroy(other.gameObject);
            gameManager.ballCounter++;
            destroy = false;
        }

        if (destroy && other.gameObject.CompareTag("Background"))
        {
            Debug.Log("Lost a life");
            Debug.Log(lives);
            destroy = false;
        }
    }

  
    private void Press()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            destroy = true;
        }
    }
}

image

Just further context, the player is the black rectangle, and it moves around the circle collecting the orange balls.

you dont need to check if the player is colliding with the background, because if the player isn’t touching the ball when pressing space, he’ll lose hp no matter what

so, remove the second if statement and change the first statement

your algorithm will essentially be:

  1. Check if player has pressed the space key
    1a. If player isn’t pressing the space key, skip
  2. Check if player is touching a ball
    2a. If player is touching the ball, increase the points etc.
    2b. If player isn’t touching the ball, reduce life

Cheers

I came up with this and it almost works, the only problem now is when you press space on the ball, it destroys a random one. I am almost certain is because I used the FindWithTag function in line 38, which is just looking for any ball in the hierarchy with this tag and destroying it, but I don’t know any other way to do it.

public class DestroyBall : MonoBehaviour
{
    public bool isTouching = false;
    public bool destroy;
    private int lives;
    public GameManager gameManager;

    private void Start()
    {
        gameManager = GameObject.Find("GameManager").GetComponent<GameManager>();
        destroy = false;
        lives = 3;
    }

    private void Update()
    {
        CheckPress();
    }

    // Check for collision with the ball
    private void OnTriggerEnter2D(Collider2D other)
    {
        isTouching = other.gameObject.CompareTag("Ball");
    }

    private void OnTriggerExit2D(Collider2D other)
    {
        isTouching = false;
    }

    private void CheckPress()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            if (isTouching)
            {
                Debug.Log("Destroy");
                Destroy(GameObject.FindWithTag("Ball"));
                gameManager.ballCounter++;
            }
            else
            {
                lives--;
                Debug.Log("Lost a life");
                Debug.Log("Remaining lives: " + lives);
            }
        }
    }
}

Yeah

this is the reason for the new bug

you know the last hit ball already

if isTouching is true, then the other.gameObject is your last hit ball; save it to a field inside OnTriggerEnter2D

1 Like

Or instead of using OnTriggerEnter you can do this:

    void CheckPress()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Collider2D[] hits=new Collider2D[10];
            if (GetComponent<Collider2D>().GetContacts(hits)>0)
            {
                Debug.Log("Destroy");
                Destroy(hits[0].gameObject);
                gameManager.ballCounter++;
            }
            else
            {
                lives--;
                Debug.Log("Lost a life");
                Debug.Log("Remaining lives: " + lives);
            }
        }
    }
1 Like

Thank you both for the replies, they both worked :heart:

I was honestly expecting to have a “layer based” response using a unity function I didn’t know where it would prioritize objects foward but I guess it doesn’t exist. 90% of the effort I made trying to find the answer in that direction.

here is the code that venediklee sugested:

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class DestroyBall : MonoBehaviour
{
    public bool isTouching = false;
    public bool destroy;
    private int lives;
    public GameManager gameManager;
    GameObject lastBall;

    private void Start()
    {
        gameManager = GameObject.Find("GameManager").GetComponent<GameManager>();
        destroy = false;
        lives = 3;
    }

    private void Update()
    {
        CheckPress();
    }

    // Check for collision with the ball
    private void OnTriggerEnter2D(Collider2D other)
    {
        isTouching = other.gameObject.CompareTag("Ball");
        lastBall = other.gameObject;
    }

    private void OnTriggerExit2D(Collider2D other)
    {
        isTouching = false;
    }

    private void CheckPress()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            if (isTouching)
            {
                Debug.Log("Destroy");
                Destroy(lastBall);
                gameManager.ballCounter++;
            }
            else
            {
                lives--;
                Debug.Log("Lost a life");
                Debug.Log("Remaining lives: " + lives);
            }
        }
    }
}

according to docs, GetContacts won’t work on triggers; there is no reason to make the balls actual colliders rather than triggers

I actually just suggested algorithm fixes to your existing broken solution, I’d actually do this differently:

  1. I’d disable the collisions between the layer of the balls and every layer except the player’s box. This way when a component attached to the ball gets OnTrigger callbacks, I know for sure the “other” parameter is the player.
  2. In a component attached to the balls, I’d keep the count of balls actively touching the player(using ontriggerenter/exit) as a static int(having a bool would be problematic when a player is touching multiple balls at the same time). I’d also keep a static transform or collider in a field for the last touching ball.
  3. In a separate component attached to the player: when the player presses space, I check the count of balls actively touching the player; if it is 0 I reduce health, if it is not zero I destroy the last ball etc. I may also need to adjust the count of balls actively touching the player when I destroy a ball, because iirc OnTriggerExit2d isn’t called when that object is destroyed

Having a component on each ball may cause tiny performance issues if there is like 10k balls but at that point you have more issues to deal with and you’d most likely use math to represent the balls and the player rather than gameobjects

Cheers

PS: Am I cooked or does the circle in the original post look non-perfect?

If you require an array of contact points then GetContacts won’t work with triggers. But it can also return an array of colliders that will work with triggers.

If you use anything that returns multiple results, always use the overloads that accept a “List<T>” and reuse that list; this way you’ll keep performance higher by not slamming the GC.

If you want to check if a collider is currently touching anything (from the last simulation step) then simply use:

Collider2D.IsTouching(contactFilter) - Unity - Scripting API: ContactFilter2D

Place a public ContactFilter2D into your script and configure it in the inspector. Just make sure you select the useTriggers field. You can filter by anything and you’ll get a true/false return. It only checks existing contacts so is fast.