Unity/Futile Pong Example (Part 6) – Adding collision detection to our Pong game

By the end of this tutorial, you should have something that looks like this. You can download the Assets folder for this tutorial here.

With our paddles and ball moving in the previous tutorial, the next step is to allow the two to interact with one another. We will do this with some very basic collision detection. We must first determine the moment when a paddle collides with a ball, or the ball collides with a wall, and then decide how to react when that happens.

First, let’s handle ball-and-wall collisions. The top and bottom edges of our stage are effectively going to be walls, despite the fact that we don’t actually see them. When the ball touches the top or bottom borders of our game, we want the ball to bounce.

In PDGame.cs, edit the Update function of the PDGame class to reflect the code below.

	public void Update(float dt) {
		float newPlayer1Y = player1.y;
		float newPlayer2Y = player2.y;
		
		// Handle Input
		if (Input.GetKey("w")) { newPlayer1Y += dt * player1.currentVelocity; }
		if (Input.GetKey("s")) { newPlayer1Y -= dt * player1.currentVelocity; }
		if (Input.GetKey("up")) { newPlayer2Y += dt * player2.currentVelocity; }
		if (Input.GetKey("down")) { newPlayer2Y -= dt * player2.currentVelocity; }
		
		// Integrate to find the new x and y values for the ball
		float newBallX = ball.x + dt * ball.xVelocity;
		float newBallY = ball.y + dt * ball.yVelocity;
		
		    // Check for ball-and-wall collisions
		    if (newBallY + (ball.height/2) >= Futile.screen.halfHeight) {
				newBallY = Futile.screen.halfHeight - (ball.height/2) - Mathf.Abs((newBallY - Futile.screen.halfHeight));
				ball.yVelocity = -ball.yVelocity;
			} else if (newBallY - ball.height/2 <= -Futile.screen.halfHeight) {
				newBallY = -Futile.screen.halfHeight + (ball.height/2) + Mathf.Abs((-Futile.screen.halfHeight - newBallY));
		        ball.yVelocity = -ball.yVelocity;
		    }
				
		// Render the ball at its new location
		ball.x = newBallX;
		ball.y = newBallY;
		player1.y = newPlayer1Y;
		player2.y = newPlayer2Y;
	}

You can find the code used to detect and handle wall collisions by finding the commented line for it. We take the new y value of our ball, add half the balls height, and determine if it is greater or equal to the y value of the top border of the screen. Why do we add half the height of the ball? The anchor and origin for our ball is, by default, set to its center. If we just used the y value of the ball, we would be checking to see if the center of the ball collided with the wall. What we really need to do is check if the top of the ball collides with the wall. In the same if statement, we check to see if the bottom of our ball has collided with the bottom wall. When a collision is detected, the y velocity will be reversed, effectively mirroring the angle at which the ball struck the wall.

UPDATE: In the ball-and-wall collision check, I changed this to prevent rapid collisions. This is not reflected in the webplayer demo or the assets file, but it is reflected now in the code above. I changed this during Part 8 of the tutorial series, so the next webplayer demo and assets file will not have the new collision code either.

Unfortunately, we can’t really test this collision test yet because our ball will never start at an angle that hits a wall. We will need to get the paddle collision detection working first.

Just beneath our wall collision code, add the code below.

		// Check for paddle-and-ball collisions
		Rect ballRect = ball.localRect.CloneAndOffset(newBallX, newBallY);
		Rect player1Rect = player1.localRect.CloneAndOffset(player1.x, newPlayer1Y);
		Rect player2Rect = player2.localRect.CloneAndOffset(player2.x, newPlayer2Y);
		
		if (ballRect.CheckIntersect(player1Rect)) {
			ball.xVelocity = -ball.xVelocity;
			
		}
		if (ballRect.CheckIntersect(player2Rect)) {
			ball.xVelocity = -ball.xVelocity;
		}

The first thing we do is create copies of the rectangles that define the bounds of our sprites at their current locations. In other words, for the purposes of collision detection, we create virtual rectangles (we don’t draw them) that represent the area occupied by our sprites. This is an incredibly basic aspect of collision detection that you should already be familiar with, since teaching this is not within the scope of these tutorials.

We then use the CheckIntersect method to determine if any point on the ball collides with any point on the paddle. The xVelocity is reversed, reflecting the angle at which the ball came in at. Go ahead and execute the code, and you should be able to see the ball bounce off the walls and paddles.

It shouldn’t take long for you to notice a few issues. First, the only thing you can achieve with the paddle is reflecting the ball’s angle. To be fun, you need to be able to ‘aim’ the ball depending upon where it strikes the paddle. Second, if the ball hits the top of a paddle, there is a good chance that the ball will react erratically, ping-ponging back and forth in collision hell. That is because multiple collisions are occurring with the same paddle over and over again.

The latter problem is easy to fix in a hackish manner by changing the if statements to the following.

		// Check for paddle-and-ball collisions
		Rect ballRect = ball.localRect.CloneAndOffset(newBallX, newBallY);
		Rect player1Rect = player1.localRect.CloneAndOffset(player1.x, newPlayer1Y);
		Rect player2Rect = player2.localRect.CloneAndOffset(player2.x, newPlayer2Y);
		
		if (ballRect.CheckIntersect(player1Rect) && ball.xVelocity < 0) {
			ball.xVelocity = -ball.xVelocity;
			
		}
		if (ballRect.CheckIntersect(player2Rect) && ball.xVelocity > 0) {
			ball.xVelocity = -ball.xVelocity;
		}

All this does is ensure that the paddle will only collide with the ball once per volley by checking the direction of the ball. There may be better ways to achieve this, mind you. For discussion purposes, if anyone would like to suggest a different method, post it in the comments for others to learn.

As for being able to direct the ball with the paddle, many Pong games disregard the incoming angle, and assign a new one depending on where the ball strikes the paddle. We’ll be doing the same. First, in your PDGame class, create a BallPaddleCollision function from the code below.

	public void BallPaddleCollision(PDPaddle player, float newBallY, float newPaddleY) {
		float localHitLoc = newBallY - newPaddleY; // Where did the ball hit, relative to the paddle's center?
		float angleMultiplier = Mathf.Abs(localHitLoc / (player.height/2)); // Express local hit loc as a percentage of half the paddle's height
		
		// Use the angle multiplier to determine the angle the ball should return at, from 0-65 degrees. Then use trig functions to determine new x/y velocities. Feel free to use a different angle limit if you think another one works better.
		float xVelocity = Mathf.Cos(65.0f * angleMultiplier * Mathf.Deg2Rad) * ball.currentVelocity; 
		float yVelocity = Mathf.Sin(65.0f * angleMultiplier * Mathf.Deg2Rad) * ball.currentVelocity;
		
		// If the ball hit the paddle below the center, the yVelocity should be flipped so that the ball is returned at a downward angle
		if (localHitLoc < 0) {
			yVelocity = -yVelocity;
		}
		
		// If the ball came in at an xVelocity of more than 0, we know the ball was travelling right when it hit the paddle. It should now start going left.		
		if (ball.xVelocity > 0) {
			xVelocity = -xVelocity;
		}
		
		// Set the ball's x and y velocities to the newly calculated values
		ball.xVelocity = xVelocity;
		ball.yVelocity = yVelocity;
	}

BallPaddleCollision takes the paddle/player that has collided with the ball, as well as the ball and paddle’s new y positions as parameters. The commentes in the code should explain what is happening, but in a nutshell, the further from the center of the paddle that the ball hits, the wider the angle, up to a maximum of 65 degrees. If hit its above the center of the paddle, the ball will be angled up, and if it hits below the center of the paddle, it will be angled down.

Finally, in order to put this function into play, we need to edit our collision checks to make a call to it.

		if (ballRect.CheckIntersect(player1Rect) && ball.xVelocity < 0) {
			BallPaddleCollision(player1, newBallY, newPlayer1Y);
		}
		if (ballRect.CheckIntersect(player2Rect) && ball.xVelocity > 0) {
			BallPaddleCollision(player2, newBallY, newPlayer2Y);
		}

Execute the code and give it a look. Our Pong game is starting to take the form of a game. I chose to change the ball’s velocity to 200.0f since it was a bit slow, and you can do the same if you’d like. Both players can interact with the ball, and even though there are no win conditions present, a player can still ‘win.’ The next tutorial will address this issue, as well as covering scoring and the user interface.

Take me to the next tutorial!

By the end of the tutorial you just read, you should have something that looks like this. You can download the Assets folder for this tutorial here.

About these ads

7 thoughts on “Unity/Futile Pong Example (Part 6) – Adding collision detection to our Pong game

  1. Pingback: Unity/Futile Tutorial Part 5 – Adding some ball movement and managing input for our Pong game in Futile | Game Development

  2. Your solution to the “collide once per volley” problem seems fine to me, but another common approach is to push the ball out of the paddle by the intersection amount (essentially making it stop intersecting). It’d look something like this, when the intersection happens:

    ball.x -= (ballRect.xMax – player2Rect.xMin);

    • This approach is definitely cleaner, and the concept can similarly be applied to the ball’s y value when striking a wall, since there is no sanity check there. If I find the time I’ll change the post to reflect it.

      Thanks for the reply!

  3. Pingback: Unity/Futile Tutorial Part 7 – Scoring conditions and the user interface | Game Development

  4. When I was going through this, I noticed that the paddles kept going off screen. It was driving me crazy, so I added a little extra code for Paddle-to-Wall collisions. Its a pretty simple solution, but I feel like it makes the game a bit more playable. Sorry if you intend to cover this later and I’m spoiling the surprise =D. Anyway, I pasted the code below. It just checks to make sure the new position isn’t out of the screen. If it is, it sets it to a max y postion (the top of the screen.)

    if (newPlayer1Y >= Futile.screen.halfHeight){
    newPlayer1Y = Futile.screen.halfHeight;
    } else if (newPlayer1Y = Futile.screen.halfHeight){
    newPlayer2Y = Futile.screen.halfHeight;
    } else if (newPlayer2Y <= -Futile.screen.halfHeight){
    newPlayer2Y = -Futile.screen.halfHeight;
    }

    Thanks for the guide, this has been great!

    • Hi Gary, you definitely didn’t spoil anything. This is one of those things that I sort of left to the imagination of the developer. I definitely should have taken a few seconds to throw it in there tough.

  5. Pingback: Planning Pong | skylergame101

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s