Collision Correction Too Large

I’m programming a game that’s a cross between Arkanoid and Space Invaders. You have a bat which you use to hit a ball. The ball destroys bricks, except that in this case the bricks are germs. The germs move all the way left (or right) and then switch direction. Whenever they’re in the middle, they drop down a little bit.

The problem is when the ball hits the germs. Sometimes, the ball shifts farther away than it should when it bounces off. This is better than the bug I used to have (the ball going right through the germs - which only occurs very rarely now) but still annoying.

In Developing Games in Java, it explains how to slide a sprite off of a tile. I basically did the same thing except sliding a sprite (the ball) of another sprite (a germ). I’ve tried several other methods, but all caused the ball to go right through the germs at times.

Although my code is excessively complicated, I see no way to obtain help but to post it here. Here’s my code for moving the balls:

//if the ball is moving horizontally, move it
if(horizontalDirection != Direction.NONE) {
	//determine its horizontal velocity
	double xVelocity = ((horizontalDirection == Direction.NEGATIVE) ? -1 : 1) *
		horizontalSpeed;
			
	double oldX = ball.getX();
	double newX = oldX + xVelocity * elapsedSeconds;
			
	ball.setPosition(newX, ball.getY());

	SelfIterableBag<Sprite> bGermHitByBall =
		GamePlayGermUtil.getGermsCollidingWithSprite(ball);
	if(bGermHitByBall.size() > 0) {
		hitGermsWithBall(ball, bGermHitByBall);
		Direction newDirection =
			ballMover.getHorizontalMovementDirection().getReverse();
		Sprite germ = bGermHitByBall.getAny();
		horizontalCorrection =
			correctBallGermCollisionHelper(ball, germ, true, newDirection);
		changeBallDirection(ball, true, newDirection);
	} //end if there were germ to ball collisions
} //end if the ball is moving horizontally

//if the ball is moving vertically, move it
if(verticalDirection != Direction.NONE) {
	//determine its vertical velocity
	double yVelocity = ((verticalDirection == Direction.NEGATIVE) ? -1 : 1) *
		verticalSpeed;
			
	double oldY = ball.getY();
	double newY = oldY + yVelocity * elapsedSeconds;
		
	ball.setPosition(ball.getX(), newY);

	SelfIterableBag<Sprite> bGermHitByBall =
		GamePlayGermUtil.getGermsCollidingWithSprite(ball);
	if(bGermHitByBall.size() > 0) {
		hitGermsWithBall(ball, bGermHitByBall);
		Direction newDirection =
			ballMover.getVerticalMovementDirection().getReverse();
		Sprite germ = bGermHitByBall.getAny();
		verticalCorrection =
			correctBallGermCollisionHelper(ball, germ, false, newDirection);
		changeBallDirection(ball, false, newDirection);
	} //end if there were germ to ball collisions
} //end if the ball is moving vertically

The SelfIterableBag class is my version of the Bag class discussed elsewhere on this forum. It’s basically an array. The Direction class is the direction for one axis only - it’s either NEGATIVE, NONE, or POSITIVE to specify which direction the Sprite is moving in that axis.

The “getGermsCollidingWithSprite” method is not a problem. Here is the code of the 2 main methods used here:

/**Corrects the specified ball's direction.
 * @param ball the ball Sprite
 * @param shouldChangeHorizontally whether to change the ball's direction horizontally
 * @param correctionDirection the direction to move the ball in for the correction*/
private static void changeBallDirection(final Sprite ball,
	final boolean shouldChangeHorizontally, final Direction correctionDirection)
{
	SpriteMover ballMover = ball.getMover();
	if(shouldChangeHorizontally)
		ballMover.setHorizontalMovementDirection(correctionDirection);
	else
		ballMover.setVerticalMovementDirection(correctionDirection);
} //end changeBallDirection

/**Corrects a collision where the germ collided with the ball.
 * @param ball the ball colliding
 * @param germ the germ that was collided with
 * @param horizontalGermMovementDirection the Direction the germ moved in horizontally
 * @param didGermMoveDown whether the germ moved down*/
private static void correctGermToBallCollision(final Sprite ball,
	final Sprite germ, final Direction horizontalGermMovementDirection,
	final boolean didGermMoveDown)
{
	//determine how to fix ball Sprite's coordinates based upon how the germ is moving
	boolean shouldFixHorizontally = true;
	if(didGermMoveDown) {
		//get the intersection between the 2 Sprites
		Rectangle ballGermIntersection = ball.getIntersection(germ);
			
		//fix the smallest distance possible
		shouldFixHorizontally = ballGermIntersection.width < ballGermIntersection.height;
	}
	
	//correct the ball's position
	Direction newDirection = (shouldFixHorizontally ? horizontalGermMovementDirection :
		Direction.POSITIVE);
	correctBallGermCollisionHelper(ball, germ, shouldFixHorizontally, newDirection);
} //end correctGermToBallCollision

And here’s the helper method:

/**Corrects the position of the ball after a collision with a germ.
 * @param ball the ball Sprite
 * @param germ the germ Sprite
 * @param shouldFixHorizontally whether to fix the collision horizontally
 * @param correctionDirection the direction to move the ball in for the correction
 * @return the amount of the correction.  This amount is always positive, giving no
 * information about direction.*/
private static double correctBallGermCollisionHelper(final Sprite ball,
	final Sprite germ, final boolean shouldFixHorizontally,
	final Direction correctionDirection)
{
	double correction = 0.0;
	
	//determine the position to move to horizontally
	if(shouldFixHorizontally) {
		double xNew = ball.getX();

		switch(correctionDirection) {
		case NEGATIVE:
			//shove the germ left
			if(ball.getX2() >= germ.getX())
				xNew = germ.getX() - ball.getWidth();
			break;
		case POSITIVE:
			//shove the germ right
			if(ball.getX() <= germ.getX2())
				xNew = germ.getX2() + 1;
			break;
		//ignore the approaching vertically case
		} //end switch movement direction

		correction = xNew - ball.getX();
		ball.setPosition(xNew, ball.getY());
	} else { //else determine the position to move to vertically
		double yNew = ball.getY();

		switch(correctionDirection) {
		case NEGATIVE:
			//shove the germ up
			if(ball.getY2() >= germ.getY())
				yNew = germ.getY() - ball.getHeight();
			break;
		case POSITIVE:
			//shove the germ down
			if(ball.getY() <= germ.getY2())
				yNew = germ.getY2() + 1;
			break;
		//ignore the approaching horizontally case
		} //end switch movement direction

		correction = yNew - ball.getY();
		ball.setPosition(ball.getX(), yNew);
	} //end else determine the position to move to vertically
	
	return Math.abs(correction);
} //end correctBallGermCollisionHelper

Minor changes, such as using the horizontal/vertical correction values to only change the ball’s direction along one axis, cause the ball to go through the germs again, which is unacceptable. I considered sliding the ball once after the germs move and then again after the ball moves, but that would just make the correction even greater.

Isn’t it easier to represent the germs as circles? Colliding moving circles in 2D is pretty easy. If a circle doesn’t accurately describe the germ’s shape, you could make it a set of circles, and collide against each of them. Use a bounding circle to prevent doing too much collision-checks.

HTH

Checking for collisions isn’t the problem. It’s where to move the ball to after the collision occurs. Using circles, I would be completely lost on collision correction.

Me: “Colliding moving circles in 2D is pretty easy”

So not only detecting, but also the response. There are numerous articles on circle-circle collision.

I will look it up, but I think it’s too late in the development cycle to make that much of a change.

I reduced the magnitude of the problem by setting a maximum correction amount for the horizontal correction. This seems to work, though still has slight overcorrections.

I figured out the problem. Or, at least, I figured out how to fix it. It had nothing to do with the shape used by collision detection (rectangle versus circle).

I just had to add an if statement for each axis preventing the correction for that axis from exceeding the number of pixels the ball actually moved. It seems like a lame solution, but it works perfectly so far as I can tell.