[SOLVED] 3D Ball in Breakout clone acts unpredictably when colliding with corners


This problem is slowly driving me insane. As shown in the gif, the ball of my breakout game will start to act strange if it hits the corner between two blocks, and this happens frequently. I manually set for the blocks to not get destroyed here, but when they can be destroyed and the ball hits the corner it basically moves through the 2 blocks it hit and then hits another block above it, allowing the ball the take out 3 or more blocks before bouncing off as intended. Clearly NOT ideal.

To be clear: This is a 3D scene with an orthographic camera, here’s a view from the editor:

At the start (and when re-spawning) the ball runs this code: transform.position = Vector3.zero; direction = new Vector3(Random.Range(-1f, 1f), 1f, 0f).normalized; GetComponent<Rigidbody>().velocity = direction * speed;.

Then in FixedUpdate it constantly runs this: GetComponent<Rigidbody>().velocity = direction * speed * Time.deltaTime * 50f;.

If the ball collides with a block then it calculates a new direction using foreach (ContactPoint contact in collision.contacts) { direction = Vector3.Reflect(direction, contact.normal); return; }

Things I’ve Already Tried:

  • Changing the ball to isKinematic and moving it with translation instead of the Rigidbody.velocity
  • Expanding the edges of the box colliders on the blocks
  • Forcing the contact.normal value to be (0, -1, 0), even when the ball hits a corner and the normal would be different
  • Setting the Physics “Default Contact Offset” to 0.0001
  • Changing the ball’s collision detection to “Continuous”

I’m at a loss as for what to try next to resolve this, so hopefully there’s someone helpful out there who is willing to lend some insight!

No thanks to verarpaulson with the ad… I finally found a fix.

The issue was with the Contact Points and the Vector3.Reflect, and setting the velocity on FixedUpdate. I removed all of that code and added a ‘bouncy’ material to the ball (bounciness 1, friction 0), and removed the rigidbodies that were on my blocks and walls. Evidently, having rigidbodies on the non-ball objects in the scene caused the ball to slow down a bit on each bounce. So only the ball and paddle have rigidbodies now. The paddle would also slow the ball down, except I re-calculate the ball’s velocity each time it hits the paddle to have more control (ie: hitting on the left side of the paddle shoots the ball off to the left).

I’m sure I’ll be adding more to the ball script when I get to power-ups, but for basic ball movement, bouncing, and re-spawning here’s the code I used. I also put in some comments to explain what the different sections of the code is doing, so I hope this helps someone else in the future!
*Note that this code will not run without a GameManager and Lives script! You can remove or comment out those lines and replace them with whatever you want.

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

public class Ball : MonoBehaviour
private Vector3 direction;
private Rigidbody rb;

public float speed = 5f;
public GameObject paddleLeftEnd;
public GameObject paddleRightEnd;   

void Start()
    rb = GetComponent<Rigidbody>();

//Spawn the ball and set starting direction
public void Respawn()
    //Set ball position at (0,0,0) in scene
    transform.position = Vector3.zero; 

    //Set ball velocity to random upwards direction
    direction = new Vector3(Random.Range(-1f, 1f), 1f, 0f).normalized;
    rb.velocity = direction * speed;

//Check for trigger with ZoneOut and update Lives
public void OnTriggerEnter(Collider collider)
    GameObject objectTriggered = collider.gameObject;

    if (objectTriggered.tag == "ZoneOut")
        //Subtract a life and respawn ball

//Check which object was hit and act accordingly
void OnCollisionEnter(Collision collision)
    //Store game object hit
    GameObject objectHit = collision.gameObject;

    if (objectHit.tag == "Paddle")
        //Get y positions of ball and paddle
        float ballY = gameObject.transform.position.y;
        float paddleY = paddleLeftEnd.transform.position.y;
        //If ball is lower than the paddle, exit this method and
        //allow the ball to continue towards the screen bottom
        if (ballY < paddleY)
            Debug.Log("Ball Under Paddle");
        //Get x positions of ball and paddle end points
        float ballX = gameObject.transform.position.x;
        float p1 = paddleLeftEnd.transform.position.x;
        float p2 = paddleRightEnd.transform.position.x;           
        //Calculate the new value of x for the ball to be directed
        float PaddleLength = p2 - p1;
        float BallLocation = ballX - p1;
        float xDirection = ((BallLocation / PaddleLength) - 0.5f) * 5;
        //Set new ball direction
        direction = new Vector3(xDirection, 1f, 0f).normalized;
        rb.velocity = direction * speed;
    else if (objectHit.tag == "Block")
        //Destroy block hit and subtract block total           

    //Check if the ball is moving mostly horizontally. If the ball is 
    //too horizontal, increase or decrease the Y direction accordingly
    if (rb.velocity.y < 1.5f && rb.velocity.y > 0f)
        direction = new Vector3(direction.x, 0.2f, 0f);
        rb.velocity = direction * speed;
        Debug.Log("Horizontal Ball Averted");
    if (rb.velocity.y > -1.5f && rb.velocity.y <= 0f)
        direction = new Vector3(direction.x, -0.2f, 0f);
        rb.velocity = direction * speed;
        Debug.Log("Horizontal Ball Averted");