We reached the most interesting part (at least for me 🙂 ) of this series. After drawing our game objects which was covered in part two, in this part we will add movement, input (keyboard and touch) and collision detection.

Movement

First, we will implement movement logic for the ball because the ball is independent from external input.

Open the GameObject.cs class and add the Move method:

public virtual void Move(Vector2 amount)
{
	Position += amount;
}

Movement in 2D games is quite simple. All that you have to do is, add some 2D vector to a position vector to have a new position. Repeating this, let’s say 30 times per second, will result in movement.

In the first line you will notice we use the ‘virtual’ keyword. We will override this method in the classes that inherit from GameObject (in our case Player and Ball).

Open the Ball.cs class and change it to the following:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;

namespace PongClone
{
	public class Ball : GameObject
	{
		public Vector2 Velocity;
		public Random random;

		public Ball()
		{
			random = new Random();
		}
	}
}

 

The velocity of the ball will vary, therefore we need a vector to hold the velocity. The Random object will be used to launch the ball from the center of the screen to the left or right side (50% probability to the left, 50% probability to the right). The constructor in lines 15, 16, 17 and 18 is used to create the Random object that we will use in the Launch method.

Add the following Launch method to the Ball.cs class:

public void Launch(float speed)
{
	Position = new Vector2(Game1.ScreenWidth / 2 - Texture.Width / 2, Game1.ScreenHeight / 2 - Texture.Height / 2);
	// get a random + or - 60 degrees angle to the right
	float rotation = (float) ( Math.PI/2 + (random.NextDouble() * (Math.PI/1.5f) - Math.PI/3));

	Velocity.X = (float) Math.Sin(rotation);
	Velocity.Y = (float)Math.Cos(rotation);

	// 50% chance whether it launches left or right
	if (random.Next(2) == 1)
	{
		Velocity.X *= -1; //launch to the left
	}

	Velocity *= speed;
}

 

Don’t get immediately scared from the maths in this method. It is quite simple, I will explain everything in detail.

As you can see in the first line, the Launch method takes a parameter for speed. This speed will represent the initial speed of the ball when it launches.

In line 3, we set the initial position of the ball to the center of the screen.

After that, in line 5 we create a variable to hold a value for the rotation that later in lines 7 and 8 will be used to calculate a launch angle. If the value of rotation is 0, Sin(0) = 0 and Cos(0) = 1. Because the values calculated from the Sin function are stored in Velocity.X and the values calculated from the Cos function are stored in Velocity.Y, the ball will launch straight down. As I mentioned in the previous part, (0,0) on the screen is on the top left, that means the positive direction of Y is downwards.

First to the rotation value we give a value of Math.PI / 2 which will result in launching the ball to the right side, because PI / 2 = 90 degrees, Sin(90 degrees) = 1, Cos(90 degrees) = 0. Then, to that value we use the Random object to generate a value between +60 and –60 (if you think in degrees and not radians). Note that the calculations are done using radians, I am converting the values to degrees in this explanation to make it more clear.

How did we get to +60 and –60? random.NextDouble generates values between 0.0 and 1.0. That value is multiplied by Math.PI/1.5f (120 degrees). Now we have values from 0 do 120 degrees. Then, we subtract Math.PI / 3 (60 degrees) which results in the desired interval of random values (-60, +60).

Now our ball will launch at a random angle between –60 and +60 degrees to the right. In line 11 we generate an integer between 1 and 0. The chances to get a 1 or 0 are 50-50. If we get a 1, the X component of Velocity (Velocity.X) is multiplied by –1 which changes the launching direction from right to left.

Finally we multiply the Velocity vector by the scalar speed to get the initial velocity of the ball.

Note: If this maths does not make sense to you, after we type the code in Game1.cs, you can come back to this method and change the rotation values. Start from giving the value 0 to rotation, and increase/decrease from there to see how the values affect the launch angle. If you still do not understand how it works, please leave a comment below.

Open the Game1.cs class and on top, where we defined our first constant in the previous part, PADDLE_OFFSET = 70, add a new constant for the initial speed of the ball:

const float BALL_START_SPEED = 8f;

Remove the following line for setting the position of the ball in LoadContent because we already have the same line in our Launch method:

ball.Position = new Vector2(ScreenWidth / 2 - ball.Texture.Width / 2, ScreenHeight / 2 - ball.Texture.Height / 2);

 

On its place, put the following line:

ball.Launch(BALL_START_SPEED);

 

In the Update method, under the lines for screen width and screen height, add the line ball.Move(ball.Velocity):

ScreenWidth = GraphicsDevice.Viewport.Width;
ScreenHeight = GraphicsDevice.Viewport.Height;
ball.Move(ball.Velocity);
base.Update(gameTime);

This line will fire the Move method where the Velocity vector is added to the Position vector.

Press F5 or Debug –> Start Debugging to start the game. You will notice that the ball is moving and after a short while it leaves the screen. Before typing collision and reset logic, we will first implement player input.

Input

First we will create a new class which will be used to process player input. Right-click on the ‘Classes’ folder Add –> Class… change the name to ‘Input’ (without quotation marks) and click Add.

Change the Input.cs class to the following:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;

namespace PongClone
{
    public static class Input
    {
        public static List<GestureSample> Gestures;

        static Input()
        {
            Gestures = new List<GestureSample>();
        }

    }
}

 

You will first notice the new libraries that are added so we can implement the input logic. Then you will notice, in line 12, we are creating a static class. That means that the class itself is the object and only one Input object exists in the program. In line 14 we declare a list, Gestures, that will store all the touch gestures. The static constructor, static Input(), initializes the list so we can later use it.

First we will type the keyboard input method. Add the following method to the class:

public static Vector2 GetKeyboardInputDirection(PlayerIndex playerIndex)
{
	Vector2 direction = Vector2.Zero;
	KeyboardState keyboardState = Keyboard.GetState(playerIndex);

	if (playerIndex == PlayerIndex.One)
	{
		if (keyboardState.IsKeyDown(Keys.W))
			direction.Y += -1;
		if (keyboardState.IsKeyDown(Keys.S))
			direction.Y += 1;
	}

	if (playerIndex == PlayerIndex.Two)
	{
		if (keyboardState.IsKeyDown(Keys.Up))
			direction.Y += -1;
		if (keyboardState.IsKeyDown(Keys.Down))
			direction.Y += 1;
	}
	return direction;
}

 

This method returns a Vector2 object and takes a PlayerIndex parameter which indicates player 1, player 2, player 3, etc. In line 3, we initialize a vector to (0,0) which will store the direction of movement. In line 4, we declare a KeyboardState variable that takes the state of the keyboard. Then in line 6 we check whether the input is for the first player. If true, we check whether the ‘W’ key is pressed. If yes, we decrease the Y component of the direction vector by 1 (Y decreases if we move upwards). The same thing is checked for the ‘S’ key for moving down. Then we check the same things with different keys (Up arrow and Down arrow) for the second player. In the end, we return the direction vector that has the direction of movement. The direction can be up, down or it can stay (0,0) which represents no movement.

Open the Game1.cs class and add another constant for the paddle speed when keyboard is used:

const float KEYBOARD_PADDLE_SPEED = 10f;

 

In the Update method, under the line ball.Move(ball.Velocity); add the following lines:

ball.Move(ball.Velocity);

Vector2 player1Velocity = Input.GetKeyboardInputDirection(PlayerIndex.One) * KEYBOARD_PADDLE_SPEED;
Vector2 player2Velocity = Input.GetKeyboardInputDirection(PlayerIndex.Two) * KEYBOARD_PADDLE_SPEED;

player1.Move(player1Velocity);
player2.Move(player2Velocity);

 

First we declare and initialize the velocities of each player. Because the method that we implemented in the Input class returns a direction, we multiply it with a scalar speed (the constant that we defined). Then we call the Move methods for moving the players.

Press F5 or Debug –> Start Debugging to test the player input. You will notice that the players can leave the screen. In the next part, collisions, we will limit the player movement to stay within the screen.

Before we implement collision detection, first we will implement touch input. Open the Input.cs class and add the following method:

public static void ProcessTouchInput(out Vector2 player1Velocity, out Vector2 player2Velocity)
{
	Gestures.Clear();
	while (TouchPanel.IsGestureAvailable)
	{
		Gestures.Add(TouchPanel.ReadGesture());
	}
	player1Velocity = Vector2.Zero;
	player2Velocity = Vector2.Zero;

	foreach (GestureSample gestureSample in Gestures)
	{
		if (gestureSample.GestureType == GestureType.FreeDrag)
		{
			if (gestureSample.Position.X >= 0 && gestureSample.Position.X <= Game1.ScreenWidth / 2)
                        player1Velocity.Y += gestureSample.Delta.Y;
			if (gestureSample.Position.X >= Game1.ScreenWidth/2 && gestureSample.Position.X <= Game1.ScreenWidth)
                        player2Velocity.Y += gestureSample.Delta.Y;
		}
	}
}

 

This method returns two values as you can see from the first line. In line 3 we clear the Gestures list. Then, in the while loop we check whether touch input has occurred. If yes, we add all the gestures to our list. Then we initialize the vectors to (0,0) and we check all the gestures in our list. If we have FreeDrag input, we check the position where such an input has happened. If the FreeDrag has happened on the left half, we add the Y component of the drag to the velocity vector for player 1. If the FreeDrag occurred on the right half, the same thing is done for player 2.

Open the Game1.cs class and on top add the following library:

using Microsoft.Xna.Framework.Input.Touch;

 

Next, change the constructor to the following:

public Game1()
{
	_graphics = new GraphicsDeviceManager(this);
	Content.RootDirectory = "Content";
	TouchPanel.EnabledGestures = GestureType.FreeDrag;
}

You will notice we added the 5th line. This line enables FreeDrag gestures on the screen.

In the Update method, after calling the Move methods for player1 and player2 add the following:

player1.Move(player1Velocity);
player2.Move(player2Velocity);

Vector2 player1TouchVelocity, player2TouchVelocity;
Input.ProcessTouchInput(out player1TouchVelocity, out player2TouchVelocity);
player1.Move(player1TouchVelocity);
player2.Move(player2TouchVelocity);

First, in line 4 we declare two vectors where we will store the velocities for touch input. After we call the method to process touch input, we fire the Move methods again with touch velocities.

As shown on the image below, change the debugging machine from Local Machine to Simulator so you can test the Touch input.

Press F5 or Debug –> Start Debugging to launch the simulator and test touch input.

Collision Detection

We have three things that we need to do in order to complete this part: Collision between upper and lower walls and the ball, limiting the player movement to stay within the screen, and collision between the players and the ball.

We will first start with the ball. Open the Ball.cs class and add the following method:

public void CheckWallCollision()
{
	if (Position.Y < 0)
	{
		Position.Y = 0;
		Velocity.Y *= -1;
	}
	if (Position.Y + Texture.Height > Game1.ScreenHeight)
	{
		Position.Y = Game1.ScreenHeight - Texture.Height;
		Velocity.Y *= -1;
	}
}

This method first checks whether the ball leaves the screen from the top of the screen. If true, we set the Y component to 0 and then change the direction of the velocity (we get a bouncing effect with this). In the second if-test we check the same thing but for the bottom of the screen. You notice that we also need to use the height of the ball itself because the position of the ball indicates the position of the top-left corner.

We will call this method in the overridden Move method in the Ball.cs class. Add the following method to the Ball.cs class:

public override void Move(Vector2 amount)
{            
	base.Move(amount);
	CheckWallCollision();
}

This method first calls the GameObject Move method (the class that it inherits from) and then calls the method to check the collision with the top and bottom ‘walls’ of the screen.

Before testing, we will also add a few lines to reset the position of the ball when it leaves the screen from the left or right side.

Open the Game1.cs class and add the following lines right after processing touch input:

player1.Move(player1TouchVelocity);
player2.Move(player2TouchVelocity);

if (ball.Position.X + ball.Texture.Width < 0)
{
	ball.Launch(BALL_START_SPEED);
}

if (ball.Position.X > ScreenWidth)
{
	ball.Launch(BALL_START_SPEED);
}

 

The first if-test checks whether the ball leaves the screen from the left side. If true, it launches the ball from the center of the screen. The second if-test checks whether the ball leaves the screen from the right side.

Press F5 or Debug –> Start Debugging to test the latest changes. You will notice that the ball now bounces from the top and bottom ‘walls’ and resets to the center of the screen when it leaves the screen from the left or right side.

Our next step is to limit the player movement to stay within the screen. Open the Player.cs class and add the following library:

using Microsoft.Xna.Framework;

Then add the following method:

public override void Move(Vector2 amount)
{
	base.Move(amount);
	if (Position.Y <= 0)
		Position.Y = 0;
	if (Position.Y + Texture.Height >= Game1.ScreenHeight)
		Position.Y = Game1.ScreenHeight - Texture.Height;
}

 

Like in the Ball.cs class, we first call the GameObject Move method (the class which we inherit from) and then check whether the player tries to leave the screen from the top. If yes, we set the Y component to 0 which limits the player from moving off-screen. The same thing happens in the second if-test where we check whether the player tries to leave the screen from the bottom.

Press F5 or Debug –> Start Debugging to test the latest changes. You will notice that now the players cannot move off-screen.

The last thing we need to do is to add collision between the players and the ball.

Open the GameObject.cs class and add the following property:

public Rectangle Bounds
{
	get { return new Rectangle((int)Position.X, (int)Position.Y, Texture.Width, Texture.Height); }
}

This property returns a rectangle with the bounds of our objects (players and ball in our case).

In the same class (GameObject.cs) add the following method:

public static bool CheckPaddleBallCollision(Player player, Ball ball)
{
	if (player.Bounds.Intersects(ball.Bounds))
		return true;
	return false;
}

This method returns a boolean value (true or false). As an input parameter it takes a Player and Ball object. By using the Bounds property we get the bounds of the players and ball, and then check whether those rectangles intersect. If they intersect, we return true. If not, we return false.

This method is static and can be called by typing GameObject.CheckPaddleBallCollision(…);

Open the Game1.cs class and add the following library:

using System;

Then in the Update method, right after the touch Move methods and before checking whether the ball leaves the screen, add the following lines:

player1.Move(player1TouchVelocity);
player2.Move(player2TouchVelocity);

if (GameObject.CheckPaddleBallCollision(player1, ball))
{
	ball.Velocity.X = Math.Abs(ball.Velocity.X);
}

if (GameObject.CheckPaddleBallCollision(player2, ball))
{
	ball.Velocity.X = -Math.Abs(ball.Velocity.X);
}

if (ball.Position.X + ball.Texture.Width < 0)
{
	ball.Launch(BALL_START_SPEED);
}

 

In line 4 you will notice we check collision between the first player and the ball. If a collision occurs, the X component of the ball’s velocity is reversed (gets a positive direction, positive is to the right). Then we check for collision between player 2 and the ball. If collision occurs, we set a negative direction to the X component of the ball’s velocity.

Press F5 or Debug –> Start Debugging to test.

With this step we are done with the third part of this series. If you are having trouble understanding something, please leave a comment below. To download my Solution, click here.

Note: You will need to set up the correct references to the MonoGame project in order to test my Solution.

In the final part of this series, we will polish the game by adding ball spin, sounds, point indicators etc.

Advertisements