Search code examples
c#unity-game-enginegame-development

How do I stop a corner collision bug in Unity?


I am trying to replicate Atari Breakout on Unity to familiarize myself with the platform---I've made the functionality for the ball to destroy and bounce of blocks it collides with now. Theoretically, it should bounce off the collider, going in the opposite direction and retaining its velocity parallel to the plane of the collider it hit.

I have implemented four edge colliders on the block prefab to determine which side the ball hit the block on and therefore which way to bounce. This is where the problem arises: when the ball hits a block very close to its corner, it activates both colliders touching that corner and bounces twice in an instant, turning the ball around in the exact opposite direction while only destroying one block, which is not the desired effect.

Here is the code for the ball object. Colliding with an object tagged "Vertical" indicates a bounce off the side walls or sides of a block, which colliding with an object tagged "Horizontal" indicates a bounce off the top/bottom of a block, the top wall, or the user-controlled paddle.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Ball : MonoBehaviour
{
    public float speed = 5;
    float deltaX;
    float deltaY;
    double angleConst = 7 * System.Math.PI / 8 / 1.8;

    public List<GameObject> blocks = new List<GameObject>();
    public GameObject blockPrefab;

    // Start is called before the first frame update
    void Start()
    {
        transform.position = new Vector2(Random.Range(-6, 6), -1);
        deltaX = Random.Range(-1 * (float)System.Math.Sqrt(speed), (float)System.Math.Sqrt(speed));
        deltaY = (float)System.Math.Sqrt(speed - System.Math.Pow(deltaX, 2));

        for(int i = 0; i <= 4; i++)
        {
            for(int j = 0; j <= 9; j++)
            {
                blocks.Add(Instantiate(this.blockPrefab));
                blocks[j + 10 * i].transform.position = new Vector2(2 * j - 9, (float)0.3 + i);
            }
        }
    }

    void OnTriggerEnter2D(Collider2D col)
    {
        if(col.gameObject.tag == "Player")
        {
            // ball's horizontal position on the paddle
            double positionDiff = GameObject.Find("Paddle").transform.position.x - transform.position.x + 0.9;
            
            if(positionDiff >= 0.9)
            {
                deltaX = -1 * (float)System.Math.Sqrt(System.Math.Abs(speed * System.Math.Cos(positionDiff * angleConst)));
            }
            else if (positionDiff < 0.9)
            {
                deltaX = (float)System.Math.Sqrt(System.Math.Abs(speed * System.Math.Cos(positionDiff * angleConst)));
            }
            deltaY = -1 * (float)System.Math.Sqrt(System.Math.Abs(speed * System.Math.Sin(positionDiff * angleConst)));
        }
        else if(col.gameObject.tag == "Horizontal")
        {
            deltaY = -1 * deltaY;
            Debug.Log("horizontal");
        }
        else if(col.gameObject.tag == "Vertical")
        {
            deltaX = -1 * deltaX;
            Debug.Log("vertical");
        }
        else if(col.gameObject.tag == "Block")
        {
            Destroy(col.gameObject);
        }
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        transform.Translate(Vector2.right * deltaX * Time.fixedDeltaTime);
        transform.Translate(Vector2.down * deltaY * Time.fixedDeltaTime);
    }
}

I'm including the entire script in case other parts need referencing, but the thing to be focused on here is the OnTriggerEnter2D method.

I have tried to open up the edge colliders at the corners to give the ball room to register a single collider (but not so much that the ball could phase through the blocks), but to no avail. I have also attempted other movement methods than Transform.Translate, such as the AddForce method, but I don't know how to achieve a constant speed with a method like that rather than an unwanted acceleration effect. How can I avoid the ball activating two colliders simultaneously?

(Another small issue I've noticed: the ball hit the edge of a block right next to the outer wall and clipped right through, never to be seen again. This has only happened once and it seems to be such a rare occurence that I'm surprised it even happened once, but I'm including it in case it may be related to the worse bug highlighted in the rest of this question.)


Solution

  • I believe setting a Boolean flag on each collision would fix.

    ex: In the OnTriggerEnter2D function, integrate the below code.

    if (hasCollided) return; // If already collided in this frame, return.
    
    // btw in general rather use this for checking tags
    // == can silently fail if there is a typo or non-existing tag
    // and also CompareTag uses hashes and is slightly faster
    if (col.CompareTag("Player"))
    {
        ...
    
        hasCollided = true;
    }
    else if (colCompareTag("Horizontal"))
    {
        ...
    
        hasCollided = true;
    }
    

    and reset the flag on each frame

    private void FixedUpdate()
    {
        ...
    
        hasCollided = false;
    }