Posts tagged ‘Tutorial’

Developing 2D Games For Windows 8 Using MonoGame Part Four–Polishing

In the final part of this series we will polish our game. First we will type few lines to add ball spin. Then, we will add a SpriteFont which is used for drawing text on screen. We will use that to draw player scores. Next, we will add a line in the middle of the screen to separate player one’s field from player two’s field. Then we will add sounds for the collisions, and finish with adding a splash screen and tiles.

Ball Spin

Open the Game1.cs class and add a new constant:

const float SPIN = 2.5f;

Next, in the Update method, right after the lines for moving the players via Touch, add the following if-tests:

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

if (player1TouchVelocity.Y > 0f)
{
	player1Velocity = player1TouchVelocity;
}
if (player2TouchVelocity.Y > 0f)
{
	player2Velocity = player2TouchVelocity;
}

if (player1Velocity.Y != 0)
{
	player1Velocity.Normalize();
}
if(player2Velocity.Y != 0)
{
	player2Velocity.Normalize();
}

In our case, if the players use touch input, you will notice that the velocity of the players is not strictly limited. The paddles will move with the same speed as the players’ drag speed. With keyboard on the other hand, there is a strict speed value defined in the constant KEYBOARD_PADDLE_SPEED. We do not want to allow the players using touch to have more or less ball spin when they hit the ball. That is why we normalize their velocities, so we can only take their movement direction.

So, first we check whether there is touch input. If true, we save that value and then normalize it.

In the same Update method, in the if-tests that check whether there is collision between the paddles and the ball, add the lines like shown below:

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

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

 

After we change the direction of the ball, we add spin to the ball in the direction of player movement.

Press F5 or Debug –> Start Debugging to test. You will notice that when you hit the ball while you are moving a paddle, the ball’s velocity decreases or increases.

Feel free to experiment with the values given to the constants to alter gameplay. You can change the initial speed of the ball, add more or less spin etc.

Score Indicators

First, download and install the 8Bit Wonder font from here.

Now you will need to repeat the steps for compiling external assets as shown in part two so we can add a SpriteFont to our game.

Launch Visual Studio 2012 Express for Windows Phone and open the PongContent project. Right click on the Content project and Add –> New Item.

 

On the left side pick Visual C#, and then choose SpriteFont. Change the name to RetroFont and click Add.

After you click Add, the SpriteFont file will automatically open in Visual Studio. You can see different parameters that can be changed like font name, size, style etc.

Make the following changes:

<FontName>8Bit Wonder</FontName>

<Size>32</Size>

<Style>Bold</Style>

After you change the values, compile the project by pressing Build –> Build Solution. When the project successfully builds, close Visual Studio 2012 Express for Windows Phone.

Return to the PongClone project, right-click on the ‘Content’ folder and Add –> Existing Item…

Navigate to:

…\Documents\Visual Studio 2012\Projects\PongContent\PongContent\PongContent\bin\Windows Phone\Debug\Content

Select RetroFont.xnb and click Add.

Now select RetroFont and change its Build Action from None to Content.

Open the Player.cs class and add the following field:

public int Score;

Open the the Game1.cs class and declare a new SpriteFont variable after the declaration of the players and the ball:

Player player1;
Player player2;
Ball ball;

SpriteFont retroFont;

In the LoadContent method, add the following line to load the SpriteFont:

retroFont = Content.Load<SpriteFont>("RetroFont");

 

In the Update method, change the if-tests for launching the ball when it leaves the screen to the following:

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

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

So, if the ball leaves the screen from the left side, player 2 gets 1 point. If the ball leaves the screen from the right side, player 1 gets 1 point.

In the Draw method, between _spriteBatch.Begin() and player1.Draw(_spriteBatch), add a line for drawing the scores:

_spriteBatch.Begin();
_spriteBatch.DrawString(retroFont, player1.Score + "        " + player2.Score,new Vector2(ScreenWidth / 2 -retroFont.MeasureString(player1.Score + "        " + player2.Score).X / 2,0), Color.Cyan);
player1.Draw(_spriteBatch);

The DrawString method takes the SpriteFont object, the string we want to draw, position of the string (in our case the position depends on the length of the screen which is measured with the MeasureString method), and text color.

Press F5 or Debug –> Start Debugging to test. You will notice each player’s score on the top of the screen.

Now we can also add a line in the middle to divide the player fields.

In Game1.cs right after declaring SpriteFont retroFont; add a new texture:

SpriteFont retroFont;
Texture2D middleTexture;

In LoadContent, add the following line:

middleTexture = Content.Load<Texture2D>("Middle");

In the Draw method, after _spriteBatch.Begin() add the line for drawing the texture:

_spriteBatch.Draw(middleTexture,new Rectangle(ScreenWidth / 2 - middleTexture.Width / 2, 0, middleTexture.Width,ScreenHeight), null, Color.White);

You will notice that we are using a different overload for the Draw method. Because the Middle texture has a width of 4 pixels and a height of 1 pixel (4×1), with the help of a Rectangle we can stretch it however we want. When you use a Rectangle for drawing objects on screen, the texture size does not matter. The texture is stretched or shrinked to fit inside the defined Rectangle.

Press F5 or Debug –> Start Debugging to see the new texture.

Sound Effects

Because we already compiled the sound effects when adding our external assets in part two, we can immediately start using them.

For managing sound effects we will create a new static class, ‘SoundManager’.

Right-click on the ‘Classes’ folder, Add –> Class… Change the name to ‘SoundManager’ (without quotation marks) and click Add.

Change the code in SoundManager.cs to the following:

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

namespace PongClone
{
    public static class SoundManager
    {
        public static SoundEffect BallWallCollisionSoundEffect;
        public static SoundEffect PaddleBallCollisionSoundEffect;

        public static void LoadSounds(ContentManager Content)
        {
            BallWallCollisionSoundEffect = Content.Load<SoundEffect>("BallWallCollision");
            PaddleBallCollisionSoundEffect = Content.Load<SoundEffect>("PaddleBallCollision");
        }
    }
}

You will notice that we have two fields for holding the collision sounds between the ‘walls’ and ball, and between the players and the ball. Then we have a static method that takes a ContentManager object which is used for loading the sound effects.

Open the Game1.cs class and in the LoadContent method add the following line:

SoundManager.LoadSounds(Content);

Then in the Update method, change the if-tests for collision between the balls and the player to the following:

if (GameObject.CheckPaddleBallCollision(player1, ball))
{
	ball.Velocity.X = Math.Abs(ball.Velocity.X);
	ball.Velocity += player1Velocity * SPIN;
	SoundManager.PaddleBallCollisionSoundEffect.Play();
}

if (GameObject.CheckPaddleBallCollision(player2, ball))
{
	ball.Velocity.X = -Math.Abs(ball.Velocity.X);
	ball.Velocity += player2Velocity*SPIN;
	SoundManager.PaddleBallCollisionSoundEffect.Play();
}

So, as soon as the ball and a player collides, we play the sound effect.

Open the Ball.cs class and change the CheckWallCollision method to the following:

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

The same thing happens here. As soon as we detect collision between a ‘wall’ and the ball, we play the sound effect.

Press F5 or Debug –> Start Debugging to test the latest changes.

Splash Screen and Tiles

Download a .rar file containing the images from here. Extract the images to the following folder:

…\Documents\Visual Studio 2012\Projects\PongClone\PongClone\Assets

When you are asked, overwrite the existing files.

In Visual Studio 2012, open the Package.appxmanifest file (shown on the screen below).

A new window will open that contains options for the name of the game, ability to change logos and icons, etc. Click on the Splash Screen tab and type ‘blac’ (without quotation marks) in the Background Color field.

Press F5 or Debug –> Start Debugging to test. You will see that you now have a shiny new Splash Screen.

Also in your Windows 8 Start Screen you will notice a new small tile for your game.

Congratulations, you completed your first Windows 8 game using MonoGame!

You can download my Solution from here.

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

I hope you liked this mini series. If you have any questions, feel free to leave a comment below. I would like to thank the folks over at 3D Buzz, because more than a year ago when I was learning the XNA Framework, one of their assignments was to create a Pong game.

Advertisements

Developing 2D Games For Windows 8 Using MonoGame Part Three–Game Logic

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.

Developing 2D Games For Windows 8 Using MonoGame Part Two–Drawing Sprites

As I mentioned in the first part of this series, in this part I will start developing a Pong clone. First I will explain how to prepare your external assets (sprites, images, sounds etc.), then we will create the project where we will develop the game, we will copy the compiled .xnb assets, we will create few classes and finally we will draw few objects on the screen. Without further ado, let’s start with the first step.

Preparing the External Assets

In the first part I mentioned that the Content Pipeline for the Windows 8 version of MonoGame is still not ready. Therefore, we will need XNA to compile the external assets and then use the compiled files in our MonoGame project.

First of all, download the .rar compressed file ‘Pong Assets’ from here. After you finish downloading, extract the files to a folder of your choice (e.g. Desktop). Launch Visual Studio 2012 Express for Windows Phone and create a new XNA Windows Phone Game (4.0) project. Change the name to PongContent and press OK.

 

After the project is created, right-click on the Content project that you can find on the right side then Add –> Existing Item…

Navigate to the folder where you previously extracted the files, select all of the files and click Add to add them to the project.

Our next step is to compile the project. Click on Build –> Build Solution or press CTRL+Shift+B on your keyboard.

After the Build process is complete, close Visual Studio 2012 Express for Windows Phone and launch Visual Studio 2012 Express for Windows 8.

Create a New Project, pick MonoGame Game, change the name to PongClone and click OK.

If you followed my instructions on the first part to set up MonoGame by cloning the Git repository, now you will need to repeat the steps that I wrote under ‘Testing MonoGame’. They were: removing the existing references, adding the MonoGame Windows 8 project and adding a reference in your project to the MonoGame Windows 8 project. If you do not remember how this was done, read the instructions again here (look at the end ‘Testing MonoGame’)

If you installed MonoGame via the installer, you do not have to do anything else to set it up. With the creation of the project, all the required references will be added automatically.

Press F5 or Debug –> Start Debugging to test whether MonoGame is functional before we continue. If you see a blue screen, everything is fine.

Right-click on the PongClone project Add –> New Folder. Name the new folder ‘Content’ (without quotation marks).

Right-click on the folder Add –> Existing Item… and navigate to:

…\Documents\Visual Studio 2012\Projects\PongContent\PongContent\PongContent\bin\Windows Phone\Debug\Content

Select all the .xnb files and click Add.

 

 

 

 

Next, select all the added files as shown on the picture on the left, under Properties change the Build Action from None to Content. (Click on the picture for bigger view)

 

 

 

 

 

 

This step completes the preparation of the external assets. There are other methods that might be slightly faster (adding Windows Phone library and Content projects), but for that you need Visual Studio 2012 Professional or above and they are slightly more complex.

Game Loop

MonoGame has all the XNA namespace, class, method etc. names. Open the Game1.cs file where the ‘Main’ of the application is located. In this same file, besides the constructor, you will notice 5 more methods: Initialize, LoadContent, UnloadContent, Update and Draw.

The first two are used to set up the initial values and loading external assets. UnloadContent is used when you want to dispose the loaded assets (e.g. closing the game). Update and Draw are the methods where the magic happens! These two methods constantly run in a loop which is called the Game Loop. The speed of the loop is measured in frames per second (FPS) and can be manually set.

In Update we put the game logic (movement, input, collision detection, physics etc.). After all the calculations, we move to Draw which is used for drawing objects in a given position.

 

GameObject, Player and Ball

For our Pong clone, we will create few classes. In this part of the series, our goal is to draw our objects on the screen. Before we begin with typing the classes for our objects, we will first type a few lines where we will hold the screen dimensions at any given time.

Open Game1.cs if you did not open it before and under the existing lines for _graphics and _spriteBatch, add two static int variables where we will hold the width and height of the screen.

GraphicsDeviceManager _graphics;
SpriteBatch _spriteBatch;

public static int ScreenWidth;
public static int ScreenHeight;

Scroll down a bit, in the Initialize method, initialize the two variables.

ScreenWidth = GraphicsDevice.Viewport.Width;
ScreenHeight = GraphicsDevice.Viewport.Height;

base.Initialize();

Now, in the Update method, add the same two lines as above.

ScreenWidth = GraphicsDevice.Viewport.Width;
ScreenHeight = GraphicsDevice.Viewport.Height;

base.Update(gameTime);

With these lines, we will have the screen dimensions at any given time, whenever the dimensions change, they will be updated (e.g. Snap View on Windows 8).

Now we can create the classes for our in-game objects. Because the players and the ball share several attributes, we will create a GameObject superclass and then Player and Ball will inherit from that class.

Right-click on the PongClone project and add a new Folder ‘Classes’ (without quotation marks). Right click on the ‘Classes’ folder and Add –> Class…

Change the name to GameObject and click Add.

Repeat the same thing to add the Player and Ball classes.

Open the GameObject.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;
using Microsoft.Xna.Framework.Graphics;

namespace PongClone
{
    public class GameObject
    {
        public Vector2 Position;
        public Texture2D Texture;
    }
}

You will notice in lines 6 and 7, we added two libraries that containt the Vector2 and Texture2D classes that are used in lines 13 and 14. Also, in line 9 we removed .Classes in order to have access to the GameObject class without writing Classes.GameObject.

In line 11, we added the public keyword in order to have access to the class from other classes. In line 13, we declared a two-dimensional vector to store a position that contains X and Y coordinates. In line 14, we declared a variable that will hold the texture of the object.

Note: In our game I will avoid using other keywords like private, protected etc. for simplicity.

Next, we need to add a method for drawing. Add the following method in the GameObject class:

public void Draw(SpriteBatch spriteBatch)
{
	spriteBatch.Draw(Texture, Position, Color.White);
}

 

This method uses a SpriteBatch object to draw 2D objects on screen. spriteBatch.Draw(…) has several overloads, I will use the fourth overload that takes Texture2D, Vector2 and Color parameters. This line draws a given texture called Texture on a given position called Position. The last parameter is used for tinting the color of the texture. Color.White is used if you do not want to apply any tinting and use the original color of the texture.

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PongClone
{
    public class Player : GameObject
    {
    }
}

In line 7, we removed .Classes. In line 9, we added the public keyword and : GameObject to inherit from the GameObject class.

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PongClone
{
    public class Ball : GameObject
    {
    }
}

The same changes are also done to the Ball class.

Drawing the Players and the Ball

Open the Game1.cs class and create variables for the players and ball. Also, we will create an integer constant to set a player paddle offset used to offset the players from the left and right edges of the screen.

const int PADDLE_OFFSET = 70;

Player player1;
Player player2;
Ball ball;

The value 70 represents 70 pixels offset. In our case we will offset from the left and right edges of the screen.

Change the Initialize method to the following:

protected override void Initialize()
{
	// TODO: Add your initialization logic here

	ScreenWidth = GraphicsDevice.Viewport.Width;
	ScreenHeight = GraphicsDevice.Viewport.Height;

	player1 = new Player();
	player2 = new Player();
	ball = new Ball();
            
	base.Initialize();
}

In lines 8, 9 and 10 we created the objects for the players and the ball. Next, we will need to load the textures and set the initial positions of these three objects.

Change the LoadContent method to the following:

protected override void LoadContent()
{
	// Create a new SpriteBatch, which can be used to draw textures.
	_spriteBatch = new SpriteBatch(GraphicsDevice);

	// TODO: use this.Content to load your game content here
	player1.Texture = Content.Load<Texture2D>("Paddle");
	player2.Texture = Content.Load<Texture2D>("Paddle");

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

	ball.Texture = Content.Load<Texture2D>("Ball");
	ball.Position = new Vector2(ScreenWidth / 2 - ball.Texture.Width / 2, ScreenHeight / 2 - ball.Texture.Height / 2);
}

In lines 7 and 8 we load the same texture for each of the players. The texture i s located in the Content folder that we previously created. After that, we set the positions of the players. In line 10, for the X coordinate, we use the paddle offset of 70 pixels. For the Y coordinate, we use the screen height and the height of the player texture to correctly position it on the center of the Y axis.

In line 11, we take into account the complete screen width in order to position the second player on the right side.

In lines 13 and 14, we load the texture for the ball and set its initial position.

Note: (0,0) on the screen is the top-left corner. Look at the image here.

Finally we reached the last step, drawing the objects. Change the Draw method to the following:

protected override void Draw(GameTime gameTime)
{
	GraphicsDevice.Clear(Color.Black);

	// TODO: Add your drawing code here
	_spriteBatch.Begin();
	player1.Draw(_spriteBatch);
	player2.Draw(_spriteBatch);
	ball.Draw(_spriteBatch);
	_spriteBatch.End();
	base.Draw(gameTime);
}

 

In line 3, we changed the color of the background from CornflowerBlue to Black. Then to begin drawing, the method _spriteBatch.Begin() is fired. In lines 7, 8 and 9 we use the method that we previously typed in the GameObject class to draw the elements. After we finish drawing, we stop the SpriteBatch object by firing _spriteBatch.End().

Press F5 or Debug –> Start Debugging. If you do not have any syntax errors, you should see the following:

The complete solution until this point can be found here.

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

If you have any problems, leave a comment below.

In part three, we will implement input, movement and collision detection.