MonoGame Tutorial: Building a 2D Game Using C#

Part 8 – Moving and Rotating Images

In Part 7, we added code to move the paddle, but we still don’t have a ball to hit! So, it is time to add a ball to our game. Right-click on the project Bricks in the solution explorer and select Add and Class from the drop-down menus:
Add new class
In the Add New Item Dialog, enter “Ball.cs” for the file name, then click Add:
Add Ball.cs File
Enter the following code in “Ball.cs”, and then we’ll describe the new code we’ve added:
[code language=”csharp” highlight=”6,7,8,9,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207″]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Bricks
{
class Ball
{
public float X { get; set; }
public float Y { get; set; }
public float XVelocity { get; set; }
public float YVelocity { get; set; }
public float Height { get; set; }
public float Width { get; set; }
public float Rotation { get; set; }
public bool UseRotation { get; set; }
public float ScreenWidth { get; set; } //width of game screen
public float ScreenHeight { get; set; } //height of game screen
public bool Visible { get; set; } //is ball visible on screen
public int Score { get; set; }
public int bricksCleared { get; set; } //number of bricks cleared this level

private Texture2D imgBall { get; set; }
private SpriteBatch spriteBatch; //allows us to write on backbuffer when we need to draw self
private GameContent gameContent;

public Ball(float screenWidth, float screenHeight, SpriteBatch spriteBatch, GameContent gameContent)
{
X = 0;
Y = 0;
XVelocity = 0;
YVelocity = 0;
Rotation = 0;
imgBall = gameContent.imgBall;
Width = imgBall.Width;
Height = imgBall.Height;
this.spriteBatch = spriteBatch;
this.gameContent = gameContent;
ScreenWidth = screenWidth;
ScreenHeight = screenHeight;
Visible = false;
Score = 0;
bricksCleared = 0;
UseRotation = true;
}

public void Draw()
{

if (Visible == false)
{
return;
}
if (UseRotation)
{
Rotation += .1f;
if (Rotation > 3 * Math.PI)
{
Rotation = 0;
}
}
spriteBatch.Draw(imgBall, new Vector2(X, Y), null, Color.White, Rotation, new Vector2(Width / 2, Height / 2), 1.0f, SpriteEffects.None, 0);
}

public void Launch(float x, float y, float xVelocity, float yVelocity)
{
if (Visible == true)
{
return; //ball already exists, ignore
}
Visible = true;
X = x;
Y = y;
XVelocity = xVelocity;
YVelocity = yVelocity;
}

public bool Move(Wall wall, Paddle paddle)
{
if (Visible == false)
{
return false;
}
X = X + XVelocity;
Y = Y + YVelocity;

//check for wall hits
if (X < 1)
{
X = 1;
XVelocity = XVelocity * -1;
}
if (X > ScreenWidth – Width + 5)
{
X = ScreenWidth – Width + 5;
XVelocity = XVelocity * -1;
}
if (Y < 1)
{
Y = 1;
YVelocity = YVelocity * -1;
}
if (Y + Height > ScreenHeight)
{
Visible = false;
Y = 0;
return false;
}
//check for paddle hit
//paddle is 70 pixels. we’ll logically divide it into segments that will determine the angle of the bounce

Rectangle paddleRect = new Rectangle((int)paddle.X, (int)paddle.Y, (int)paddle.Width, (int)paddle.Height);
Rectangle ballRect = new Rectangle((int)X, (int)Y, (int)Width, (int)Height);
if (HitTest(paddleRect, ballRect))
{
int offset = Convert.ToInt32((paddle.Width – (paddle.X + paddle.Width – X + Width / 2)));
offset = offset / 5;
if (offset < 0)
{
offset = 0;
}
switch (offset)
{
case 0:
XVelocity = -6;
break;
case 1:
XVelocity = -5;
break;
case 2:
XVelocity = -4;
break;
case 3:
XVelocity = -3;
break;
case 4:
XVelocity = -2;
break;
case 5:
XVelocity = -1;
break;
case 6:
XVelocity = 1;
break;
case 7:
XVelocity = 2;
break;
case 8:
XVelocity = 3;
break;
case 9:
XVelocity = 4;
break;
case 10:
XVelocity = 5;
break;
default:
XVelocity = 6;
break;
}
YVelocity = YVelocity * -1;
Y = paddle.Y – Height + 1;
return true;
}
bool hitBrick = false;
for (int i = 0; i < 7; i++)
{
if (hitBrick == false)
{
for (int j = 0; j < 10; j++)
{
Brick brick = wall.BrickWall[i, j];
if (brick.Visible)
{
Rectangle brickRect = new Rectangle((int)brick.X, (int)brick.Y, (int)brick.Width, (int)brick.Height);
if (HitTest(ballRect, brickRect))
{
brick.Visible = false;
Score = Score + 7 – i;
YVelocity = YVelocity * -1;
bricksCleared++;
hitBrick = true;
break;
}
}
}
}
}
return true;
}
public static bool HitTest(Rectangle r1, Rectangle r2)
{
if (Rectangle.Intersect(r1, r2) != Rectangle.Empty)
{
return true;
}
else
{
return false;
}
}
}
}
[/code]
This class has much of the logic for our game. Some of its properties are similar to our other classes. The X and Y properties are the ball’s coordinates on the screen. Height and Width are the dimensions of the ball. Like the other classes, we are passing spriteBatch which will be used when we need to Draw the ball.

We are getting a reference to the ball image from the gameContent.imgBall field, and saving it in imgBall. The Visible property is used to determine if the ball needs to be drawn on the screen. The XVelocity and YVelocity fields are used to store how many X and Y pixels the ball will move every time the frame is updated. They are used to update the ball position on the screen.

Our ball will have the ability to rotate as it moves, so we have a Rotation property to specify how much our ball image should rotated when drawing it on the screen. We will update the rotation whenever we draw to the screen so the ball appears to spin as it moves. We’ve added a property called UseRotation to determine if the ball should rotate. In the game, we will be drawing a ball next to the counter showing the number of balls remaining in the game, and we don’t want that ball to spin like the regular game ball does. We can set this property to control the Rotation.

Since this class will be detecting brick hits, it is a handy place to store a counter for bricks that we have cleared on a game level, and also to keep a running game score. That’s what the bricksCleared and Score fields are used for.

All of our fields are initialized on the constructor call via passed arguments.

Our Draw method is similar to our other Draw methods, with a couple of exceptions. First, we are checking the Visible property to determine if the ball needs to be drawn. Also, we are checking the UseRotation property to determine whether to rotate the ball image before drawing, and if this property is true, we increase the rotation by a small amount. This is the first time that we are passing a non-zero value for the Rotation argument on the spriteBatch.Draw call. MonoGame actually does the rotation for us. We just have to provide the rotation angle, which we have stored in the Rotation property. The field after the Rotation argument on the Draw call specifies the origin for the rotation as an X, Y coordinate. We’ll set this to the mid-point of the ball image.

The next method is called Launch. It will be called by the Game1 class whenever we need to serve a new ball. It just initializes the ball’s position and velocity and makes it visible.

The Move method is responsible for computing the ball’s new position on the screen. It is called from the Game1 Draw method. In addition to updating the ball’s coordinates, Move is responsible for detecting collisions with other objects in our game field. If the ball hits a side wall, we reverse the XVelocity by multiplying by “-1”. If the ball hits the top wall we reverse the YVelocity by multiplying by “-1”.

We use the HitTest method to determine if the ball hits the paddle or a brick. It just checks if the rectangle formed by the ball intersects the rectangle formed by the paddle or one of the bricks. If we hit the paddle, we reverse the YVelocity and the ball bounces back toward the top of the screen. The X position on the paddle will determine the direction and angle of deflection of the ball. If it hits the left half of the paddle, the ball will be deflected to the left, and the closer the ball hits to the left edge of the paddle, the sharper the angle of deflection will be. Similarly, if the ball hits the right half of the paddle, the ball will be deflected to the right, and the closer the ball hits to the right edge of the paddle, the sharper the angle of deflection will be.

For brick hit detection, we just iterate through all of the bricks in our Wall object. If the brick Visible property is false, it has already been destroyed, so no collision with it can occur. If a hit is detected, we set that brick’s Visible property to false, increment our bricksCleared field, and increment the users score. We give higher scores for the bricks higher on the screen. So, bricks in the first row are worth 1 point. Bricks in the second row are worth 2 points, and so on. If we do hit a brick, we reverse the YVelocity.

If the ball’s Y coordinate is greater than the ScreenHeight Property, the ball has fallen out of play, so we just set the Visible property to false.

Okay, that’s it for the Ball class for now, but we need to connect it up to the game. So it’s time to head back to the “Game1.cs” file. First, we need to add a field for our Ball object. In “Game1.cs”, add the following line:
[code language=”csharp” highlight=”10″]
public class Game1 : Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
GameContent gameContent;

private Paddle paddle;
private Wall wall;
private GameBorder gameBorder;
private Ball ball;
//rest of file not shown for brevity
[/code]
We’ll initialize the ball object in our LoadContent method. Add the indicated line as shown below:
[code language=”csharp” highlight=”29″]
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
gameContent = new GameContent(Content);
screenWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
screenHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
//set game to 502×700 or screen max if smaller
if (screenWidth >= 502)
{
screenWidth = 502;
}
if (screenHeight >= 700)
{
screenHeight = 700;
}
graphics.PreferredBackBufferWidth = screenWidth;
graphics.PreferredBackBufferHeight = screenHeight;
graphics.ApplyChanges();

//create game objects
int paddleX = (screenWidth – gameContent.imgPaddle.Width) / 2; //we’ll center the paddle on the screen to start
int paddleY = screenHeight – 100; //paddle will be 100 pixels from the bottom of the screen
paddle = new Paddle(paddleX, paddleY, screenWidth, spriteBatch, gameContent); // create the game paddle
wall = new Wall(1, 50, spriteBatch, gameContent);
gameBorder = new GameBorder(screenWidth, screenHeight, spriteBatch, gameContent);
ball = new Ball(screenWidth, screenHeight, spriteBatch, gameContent);
}
[/code]
We need to add a couple of new fields. The ballsRemaining field will indicate how many balls the user still has available to play in the game. And the readyToServeBall will be used to determine if the game state permits the user to launch a new ball (i.e. there isn’t a ball already in play). Add the indicated lines to the “Game1.cs” file as shown below:
[code language=”csharp” highlight=”16,17″]
public class Game1 : Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
GameContent gameContent;

private Paddle paddle;
private Wall wall;
private GameBorder gameBorder;
private Ball ball;

private int screenWidth = 0;
private int screenHeight = 0;
private MouseState oldMouseState;
private KeyboardState oldKeyboardState;
private bool readyToServeBall = true;
private int ballsRemaining = 3;
//rest of class not shown for brevity
[/code]

We also need to add logic to serve the ball. Add a new method called ServeBall to your Game1.cs file:
[code language=”csharp” highlight=”1,2,3,4,5,6,7,8,9,10,11,12,13″]
private void ServeBall()
{
if (ballsRemaining < 1)
{
ballsRemaining = 3;
ball.Score = 0;
wall = new Wall(1, 50, spriteBatch, gameContent);
}
readyToServeBall = false;
float ballX = paddle.X + (paddle.Width) / 2;
float ballY = paddle.Y – ball.Height;
ball.Launch(ballX, ballY, -3, -3);
}
[/code]
The ServeBall method will check to see if we have any balls remaining. If we don’t, it will reset the game screen for a new game. If we do, it will call the ball.Launch method to launch the ball. The ball is always launched from the coordinates of the middle of the paddle.

In our update method, we have to add code to Launch the ball when the user clicks the left mouse button, or when the user hits the space bar on the keyboard. Add the following lines to the Update method in “Game1.cs” as shown below:
[code language=”csharp” highlight=”24,25,26,27,28,39,40,41,42″]
protected override void Update(GameTime gameTime)
{
if (IsActive==false)
{
return; //our window is not active don’t update
}

if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();

// TODO: Add your update logic here
KeyboardState newKeyboardState = Keyboard.GetState();
MouseState newMouseState = Mouse.GetState();

//process mouse move
if (oldMouseState.X != newMouseState.X)
{
if (newMouseState.X >= 0 && newMouseState.X < screenWidth && newMouseState.Y >=0 && newMouseState.Y < screenHeight)
{
paddle.MoveTo(newMouseState.X);
}
}

//process left-click
if (newMouseState.LeftButton == ButtonState.Released && oldMouseState.LeftButton == ButtonState.Pressed && oldMouseState.X == newMouseState.X && oldMouseState.Y == newMouseState.Y && readyToServeBall)
{
ServeBall();
}

//process keyboard events
if (newKeyboardState.IsKeyDown(Keys.Left))
{
paddle.MoveLeft();
}
if (newKeyboardState.IsKeyDown(Keys.Right))
{
paddle.MoveRight();
}
if (oldKeyboardState.IsKeyUp(Keys.Space) && newKeyboardState.IsKeyDown(Keys.Space) && readyToServeBall)
{
ServeBall();
}

oldMouseState = newMouseState; // this saves the old state
oldKeyboardState = newKeyboardState;

base.Update(gameTime);
}
[/code]

Finally, in our “Game1.cs” file Draw method, we need to add logic to move and draw the ball. Add the indicated lines to “Game1.cs” as shown below:
[code language=”csharp” highlight=”10,11,12,13,14,15,16,17,18,19,20,21,22″]
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);

// TODO: Add your drawing code here
spriteBatch.Begin();
paddle.Draw();
wall.Draw();
gameBorder.Draw();
if (ball.Visible)
{
bool inPlay = ball.Move(wall, paddle);
if (inPlay)
{
ball.Draw();
}
else
{
ballsRemaining–;
readyToServeBall = true;
}
}
spriteBatch.End();
base.Draw(gameTime);

}
[/code]
We’ve made quite a few updates, so now would be a good time to run the game by pressing F5. You should be able to press Space or click the mouse to launch the ball. You should see the ball moving and spinning. It should bounce off the walls, and destroy bricks when it hits them. The paddle should deflect the ball, and the ball should fall out of play if you miss it with the paddle. It’s starting to look like a real breakout-type game, but some sounds would sure spice up game play! We’ll tackle that next, in Part 9.

13 thoughts on “MonoGame Tutorial: Building a 2D Game Using C#”

  1. I was looking for a quick tutorial to dive into MonoGame. Thank you! This one is perfect! It’s very easy to understand basic concepts of game making.

  2. Excellent tutorial . Although XNA is no longer supported by MicroSoft, it’s a good framework for 2D game development.

  3. Exactly what I was looking for! I heard that Axiom Verge was built with Monogame and instantly wanted to figure out what I could do with it as well. Thanks for setting me on the right track with this quick tutorial 🙂

  4. What a great tutorial! Very too the point with great code examples! Thoughts on doing another one on another game type?

  5. Great tutorial! This really helped me understand the basics of XNA/MonoGame; which in turn will help me get past a hurdle with something else I was attempting.

    Also, being the audio nerd that I am I couldn’t help but play around with the panning option. Using what I had already learned in the tutorial, is was simple enough to make the brick breaking sound pan more left or right as you get farther from the center. Fun stuff!

  6. The best XNA/MonoGame tutorial so far!!
    I`m so hyped about the game, that I would love to see how to make more levels with more difficulties.

    Great Job!

Leave a Reply

Your email address will not be published. Required fields are marked *