1

I am creating a project for a university assignment, and I have hit a bit of a snag. The problem is minor and can most likely be passed off as unnoticed, but it still bothers me and, being a procedural person in terms of completing work, I can't move on to anything else until it is solved.

I have the paddle and ball code working fine, as well as a simple angular velocity based on where the ball hits the paddle, however my issue is when the ball hits the bricks. The code mostly works and it seems flawless, however this changes when the ball does one of these two things:

  • Hitting a brick on one of its corners may incorrectly reverse the wrong axis of the ball's velocity
  • Hitting two bricks at the same time (in their corners) will cause the ball to pass through them and remove them; even if they have full health.

The code I am using is here (Ball.cs):

EDIT (Updated Code):

public bool Touching(Brick brick)
{
    var position = Position + Velocity;
    return position.Y + Size.Y >= brick.Position.Y &&
              position.Y <= brick.Position.Y + brick.Size.Y &&
              position.X + Size.X >= brick.Position.X &&
              position.X <= brick.Position.X + brick.Size.X;
}

public bool Collide(Brick brick)
{
    //Secondary precaution check
    if (!Touching(brick)) return false;

    //Find out where the ball will be
    var position = Position + Velocity;

    //Get the bounds of the ball based on where it will be
    var bounds = new Rectangle((int)position.X, (int)position.Y, Texture.Width, Texture.Height);

    //Stop the ball from moving here, so that changes to velocity will occur afterwards.
    Position = Position - Velocity;

    //If the ball hits the top or bottom of the brick
    if (bounds.Intersects(brick.Top) || bounds.Intersects(brick.Bottom))
    {
        Velocity = new Vector2(Velocity.X, -Velocity.Y); //Reverse the Y axe of the Velocity
    }

    //If the ball hits the left or right of the brick
    if (bounds.Intersects(brick.Left) || bounds.Intersects(brick.Right))
    {
        Velocity = new Vector2(-Velocity.X, Velocity.Y); //Reverse the X axe of the Velocity
    }

    return true;
}

These methods are being called from Level.cs' Update method:

public override void Update(GameTime gameTime)
{
    player.Update(gameTime);
    balls.ForEach(ball => ball.Update(gameTime));
    foreach (var brick in bricks)
    {
        foreach (var ball in balls)
        {
            if (ball.Touching(brick))
            {
                if (!collidingBricks.Contains(brick)) collidingBricks.Add(brick);
            }
        }
        brick.Update(gameTime);
    }

    if (collidingBricks.Count > 0)
    {
        foreach (var ball in balls)
        {
            Brick closestBrick = null;
            foreach (var brick in collidingBricks)
            {
                if (closestBrick == null)
                {
                    closestBrick = brick;
                    continue;
                }
                if (Vector2.Distance(ball.GetCenterpoint(brick), ball.GetCenterpoint()) < Vector2.Distance(ball.GetCenterpoint(closestBrick), ball.GetCenterpoint()))
                {
                    closestBrick = brick;
                }else
                {
                    brick.Health--;
                    if (brick.Health > 0) brick.Texture = Assets.GetBrick(brick.TextureName, brick.Health);
                }
            }

            if (ball.Collide(closestBrick))
            {
                closestBrick.Health--;
                if (closestBrick.Health > 0) closestBrick.Texture = Assets.GetBrick(closestBrick.TextureName, closestBrick.Health);
            }
        }
        collidingBricks = new List<Brick>();
    }

    bricks.RemoveAll(brick => brick.Health <= 0);
    base.Update(gameTime);
}

The ball's Collide method is what determines the new velocity values of the ball. As you can see, it is entirely based on where the ball will be, not where the ball is. Additionally, I run the collision check for the closest brick that the ball is colliding with, but I reduce the health of all bricks that the ball is touching (so, the velocity will only update based on just the closest brick, not all colliding bricks).

I understand why it is causing me problems, and that it may be because of the fact that the ball is hitting two sides at the same time (as it hits the corner.)

Like I said, it's a minor issue, but can even still impact on the game experience, especially when you expect the ball to logically go in one direction only for it to travel in the opposite.

Dragonphase
  • 169
  • 2
  • 4
  • 14
  • If you `return true;` after the Y collision check, aren't you favoring that direction in the event that it collides on both axises? – Cyral Nov 20 '13 at 01:25
  • You are right in that it still does somewhat favor the direction, and I could simply return true after both if statements rather than return false. However, this still raises issues when determining the velocity of the ball in regards to its current trajectory. – Dragonphase Nov 20 '13 at 01:50

2 Answers2

1

I'm assuming that each call to your Update method you move the ball to a new position by adding the velocity vector scaled by the frame time, then test the new position. Is this correct?

In this model the ball is occupying a series of point locations without passing through the intervening space. This type of motion causes a variety of problems due to an effect called 'Tunneling'. This happens when an object moves fast enough that it appears to pass through objects partially or completely.

Imagine that your ball is moving vertically at 3 pixels per frame and is currently 1 pixel away from the edge of a block. Next frame, the ball will move to a position that is 2 pixels inside the block. Since the ball and the block can't overlap, this is an error. Perhaps not a huge error, but still not right.

As you have discovered, the error really bites you when you get to the corners. If the ball's new position is overlapping the corner of the block it becomes more difficult to determine the correct action since the closest edge of the block may not be the one that you passed through (conceptually) to get to where you ended up.

The solution to this is call Continuous Collision Detection. Each time you move the ball, you have to check the entire path of motion to determine if a collision occurs.

The simplest method - although certainly not the fastest - might be to use something like Bresenham's Algorithm to plot the positions your ball occupies along the line of motion. Bresenham's will give you a reasonable line of motion with integer steps - one step per pixel. These steps translate to coordinates that you can use to detect collisions.

You can speed things up slightly by pre-calculating whether it is possible for a collision to occur, and if so with what. If you have 100 blocks on the screen you don't want to check every one of the for every step. You can narrow the range by calculating a rectangular bounding box for the total movement (current position to end position) and build a list of all blocks and other objects that fall within the box. No results means no collisions, so this is a good way to short-circuit the expensive operations while also making them less expensive in the end.

There's a lot of work to do to get it perfect, and a reasonably large amount to get it close enough. Google continuous collision detection and game physics tunneling for more information on the topics.

If you can get away with it, something like [Box2D] or Farseer will give you some interesting options for handling this. Of course you'll probably spend about as much time re-tooling your game around a physics engine as you would have solving the original problem :P

Corey
  • 13,462
  • 1
  • 29
  • 60
  • Hi Corey. Thanks for your answer. Whilst it is in-depth and somewhat understandable, I am still struggling with the same issue even after applying continuous collision detection. based on CCD, what I have done is create a new Vector2 within the collision method, which is the current position + the current velocity. The problem still persists. I'll update my question with my most recent code. – Dragonphase Nov 30 '13 at 00:03
0

I think that both problems can be solved by resolving the collision along its shallow axis. Consider this situation:

In your current code, this collision would get resolved along the Y axis, because there's a preference for it, though collision has clearly happened on the X axis.

The general approach to collision is to first get the collision depth (this can be easily accomplished by making a new rectangle with Rectangle.Intersect static method and using its width and height). Then you have to check how deep the intersection is for both axes and, in your case, reverse shallow axis component of ball's velocity vector. The last step is to check relative positions of two intersecting objects and move one of them along the shallow axis - in a given example, ball is to the right of brick, so it has to be moved to the right by the intersection depth.

rot13
  • 96
  • 1
  • 3
  • Sorry for the late reply. I have used this answer to help me calculate which axe of the ball's velocity to reverse, and it works perfectly. But I am still running into the issue where my ball still ploughs through a long line of bricks if it hits two on both of their corners. I have thought about subtracting the velocity of the brick from its position after it has detected a hit but before the velocity has been reversed, however, this sometimes causes the ball to freeze when hitting a corner, and does not always work. – Dragonphase Nov 27 '13 at 18:08
  • @Dragonphase Did you try moving the ball out of the intersection using its depth? This might solve the problem, because it seems that collision gets detected for both bricks so velocity is reversed twice. – rot13 Nov 27 '13 at 21:26
  • That actually makes perfect sense, I just can't believe I haven't thought about that previously... – Dragonphase Nov 29 '13 at 23:23