
Development of (football) games using MonoGame

To make life easier for game developers, a variety of frameworks are created, not only for C and C ++, but also for C # and even JavaScript. One of these frameworks is Microsoft XNA, which uses Microsoft DirectX technology and allows you to create games for the Xbox 360, Windows, and Windows Phone. Microsoft XNA is no longer developing now, but at the same time, the Open Source community has proposed another option - MonoGame. We’ll get to know this framework more closely by the example of a simple football (why?) Game.
What is MonoGame?
MonoGame is an open source implementation of the XNA API not only for Windows, but also for Mac OS X, Apple iOS, Google Android, Linux, and Windows Phone. This means that you can create games immediately for all these platforms with minimal changes. Ideal for those who make plans to take over the world!You don't even need Windows to develop with MonoGame. You can use the MonoDevelop (open source cross-platform IDE for Microsoft .NET languages) or the cross-platform IDE Xamarin Studio to run on Linux and Mac.
If you are a Microsoft .NET developer and use Microsoft Visual Studio daily, MonoGame can also be installed there. At the time of this writing, the latest stable version of MonoGame was 3.2; it is installed in Visual Studio 2012 and 2013.
Create the first game
To create the first game, select MonoGame Windows Project from the template menu. Visual Studio will create a new project with all the necessary files and links. If you run the project, you get something like this:Boring, isn't it? Nothing, this is just the beginning. You can start developing your game in this project, but there is one caveat. You will not be able to add any objects (drawings, sprites, fonts, etc.) without converting them to a format compatible with MonoGame. To do this, you need one of the following:
- Install XNA Game Studio 4.0
- Install Windows Phone 8 SDK
- Use an external program like XNA content compiler
So, in Program.cs you have the Main function. She initializes and launches the game.
static void Main()
{
using (var game = new Game1())
game.Run();
}
Game1.cs is the core of the game. You have two methods that are called 60 times per second: Update and Draw. With Update, you recalculate data for all elements of the game; Draw, respectively, involves drawing these elements. Note that there is very little time for loop iteration - only 16.7 ms. If there is not enough time to complete the cycle, the program will skip several Draw methods, which, of course, will be noticeable in the picture.
For example, we will create a soccer game, “score a penalty.” The touch will be controlled by our touch, and the computer “goalkeeper” will try to catch the ball. The computer selects the random location and speed of the goalkeeper. Points are considered the way we are familiar.
Add content to the game.
The first step in creating a game is to add content. Let's start with the background image of the field and the ball. Let's create two PNG images: the field (bottom) and the ball (on the KDPV).
To use these drawings in the game, you need to compile them. If you are using the XNA Game Studio or the Windows Phone 8 SDK, you need to create an XNA content project. Add drawings to this project and assemble it. Then go to the project directory with the resulting .xnb files in your project. XNA Content Compiler does not require a new project, objects in it can be compiled as needed.
When the .xnb files are ready, add them to the Content folder of your game. Create two fields in which we will store the texture of the ball and the field:
private Texture2D _backgroundTexture;
private Texture2D _ballTexture;
These fields are loaded in the LoadContent method:
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
_backgroundTexture = Content.Load("SoccerField");
_ballTexture = Content.Load("SoccerBall");
}
Now draw the textures in the Draw method:
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Green);
// Set the position for the background
var screenWidth = Window.ClientBounds.Width;
var screenHeight = Window.ClientBounds.Height;
var rectangle = new Rectangle(0, 0, screenWidth, screenHeight);
// Begin a sprite batch
_spriteBatch.Begin();
// Draw the background
_spriteBatch.Draw(_backgroundTexture, rectangle, Color.White);
// Draw the ball
var initialBallPositionX = screenWidth / 2;
var ínitialBallPositionY = (int)(screenHeight * 0.8);
var ballDimension = (screenWidth > screenHeight) ?
(int)(screenWidth * 0.02) :
(int)(screenHeight * 0.035);
var ballRectangle = new Rectangle(initialBallPositionX, ínitialBallPositionY,
ballDimension, ballDimension);
_spriteBatch.Draw(_ballTexture, ballRectangle, Color.White);
// End the sprite batch
_spriteBatch.End();
base.Draw(gameTime);
}
This method fills the screen with green, and then draws the background and the ball on the penalty spot. The first method spriteBatch Draw draws a background adjusted to the size of the window, the second method draws the ball at the penalty spot. There is no movement here yet - we must add it.
Ball movement
To move the ball, you need to recount its location in each iteration of the cycle and draw it in a new place. We calculate the new position in the Update method:protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
// TODO: Add your update logic here
_ballPosition -= 3;
_ballRectangle.Y = _ballPosition;
base.Update(gameTime);
}
The ball position is updated in each cycle by subtracting 3 pixels. The variables _screenWidth, _screenHeight, _backgroundRectangle, _ballRectangle and _ballPosition are initialized in the ResetWindowSize method:
private void ResetWindowSize()
{
_screenWidth = Window.ClientBounds.Width;
_screenHeight = Window.ClientBounds.Height;
_backgroundRectangle = new Rectangle(0, 0, _screenWidth, _screenHeight);
_initialBallPosition = new Vector2(_screenWidth / 2.0f, _screenHeight * 0.8f);
var ballDimension = (_screenWidth > _screenHeight) ?
(int)(_screenWidth * 0.02) :
(int)(_screenHeight * 0.035);
_ballPosition = (int)_initialBallPosition.Y;
_ballRectangle = new Rectangle((int)_initialBallPosition.X, (int)_initialBallPosition.Y,
ballDimension, ballDimension);
}
This method resets all variables depending on the size of the window. It is called in the Initialize method.
protected override void Initialize()
{
// TODO: Add your initialization logic here
ResetWindowSize();
Window.ClientSizeChanged += (s, e) => ResetWindowSize();
base.Initialize();
}
This method is called in two places: at the beginning and every time the window is resized. If you run the program, you will notice that the ball moves straight, but does not stop with the end of the field. You need to move the ball when it flies into the goal using the following code:
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
// TODO: Add your update logic here
_ballPosition -= 3;
if (_ballPosition < _goalLinePosition)
_ballPosition = (int)_initialBallPosition.Y;
_ballRectangle.Y = _ballPosition;
base.Update(gameTime);
}
The variable _goalLinePosition is also initialized in the ResetWindowSize method.
_goalLinePosition = _screenHeight * 0.05;
In the Draw method, you still need to remove all calculations:
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Green);
var rectangle = new Rectangle(0, 0, _screenWidth, _screenHeight);
// Begin a sprite batch
_spriteBatch.Begin();
// Draw the background
_spriteBatch.Draw(_backgroundTexture, rectangle, Color.White);
// Draw the ball
_spriteBatch.Draw(_ballTexture, _ballRectangle, Color.White);
// End the sprite batch
_spriteBatch.End();
base.Draw(gameTime);
}
The ball moves perpendicular to the goal line. If you want to move the ball at an angle, create the _ballPositionX variable and increase it (to move to the right) or decrease it (to move to the left). An even better option is to use Vector2 to position the ball:
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
// TODO: Add your update logic here
_ballPosition.X -= 0.5f;
_ballPosition.Y -= 3;
if (_ballPosition.Y < _goalLinePosition)
_ballPosition = new Vector2(_initialBallPosition.X,_initialBallPosition.Y);
_ballRectangle.X = (int)_ballPosition.X;
_ballRectangle.Y = (int)_ballPosition.Y;
base.Update(gameTime);
}
If you run the program now, you will see that the ball flies at an angle. The next task is to attach finger control.

We implement management
In our game, control is done with a finger. The movement of the finger sets the direction and force of the blow.In MonoGame, touch data is obtained using the TouchScreen class. You can use raw data or the Gestures API. Raw data provides greater flexibility, since you get access to all the information, the Gestures API transforms raw data into gestures, and you can filter only those that you require.
In our game we only need a click and, since the Gestures API supports this movement, we will use it. First of all, let’s indicate what gesture we will use:
TouchPanel.EnabledGestures = GestureType.Flick | GestureType.FreeDrag;
Only clicks and drags will be handled. Next, in the Update method, we process the gestures:
if (TouchPanel.IsGestureAvailable)
{
// Read the next gesture
GestureSample gesture = TouchPanel.ReadGesture();
if (gesture.GestureType == GestureType.Flick)
{
…
}
}
Turn on the click in the Initialize method:
protected override void Initialize()
{
// TODO: Add your initialization logic here
ResetWindowSize();
Window.ClientSizeChanged += (s, e) => ResetWindowSize();
TouchPanel.EnabledGestures = GestureType.Flick;
base.Initialize();
}
Until now, the ball has rolled all the time while the game was running. Use the _isBallMoving variable to tell the game when the ball is moving. In the Update method, when a click is detected, set _isBallMoving to True and the movement will begin. When the ball recounts the goal line, set _isBallMoving to False and return the ball to its original position:
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
// TODO: Add your update logic here
if (!_isBallMoving && TouchPanel.IsGestureAvailable)
{
// Read the next gesture
GestureSample gesture = TouchPanel.ReadGesture();
if (gesture.GestureType == GestureType.Flick)
{
_isBallMoving = true;
_ballVelocity = gesture.Delta * (float)TargetElapsedTime.TotalSeconds / 5.0f;
}
}
if (_isBallMoving)
{
_ballPosition += _ballVelocity;
// reached goal line
if (_ballPosition.Y < _goalLinePosition)
{
_ballPosition = new Vector2(_initialBallPosition.X, _initialBallPosition.Y);
_isBallMoving = false;
while (TouchPanel.IsGestureAvailable)
TouchPanel.ReadGesture();
}
_ballRectangle.X = (int) _ballPosition.X;
_ballRectangle.Y = (int) _ballPosition.Y;
}
base.Update(gameTime);
}
The ball speed is no longer constant, the program uses the _ballVelocity variable to set the speed along the x and y axes. Gesture.Delta returns the movement change since the last update. To calculate the click speed, multiply this vector by TargetElapsedTime.
If the ball moves, the _ballPosition vector changes based on the speed (in pixels per frame) until the ball reaches the goal line. The following code stops the ball and removes all gestures from the input queue:
_isBallMoving = false;
while (TouchPanel.IsGestureAvailable)
TouchPanel.ReadGesture();
Now if you run the ball, it will fly in the direction of the click and at its speed. However, there is one problem: the program does not look at where the click occurred on the screen. You can click anywhere and the ball will begin to move. The solution is to use the raw data, get the touch point and see if it is near the ball. If so, the gesture sets the _isBallHit variable:
TouchCollection touches = TouchPanel.GetState();
if (touches.Count > 0 && touches[0].State == TouchLocationState.Pressed)
{
var touchPoint = new Point((int)touches[0].Position.X, (int)touches[0].Position.Y);
var hitRectangle = new Rectangle((int)_ballPositionX, (int)_ballPositionY, _ballTexture.Width,
_ballTexture.Height);
hitRectangle.Inflate(20,20);
_isBallHit = hitRectangle.Contains(touchPoint);
}
Then the movement starts only if _isBallHit is True:
if (TouchPanel.IsGestureAvailable && _isBallHit)
There is one more problem. If you hit the ball too slowly or in the wrong direction, the game will end because the ball will not cross the goal line and will not return to its original position. It is necessary to establish a time limit for the ball to move. When the timeout is reached, the game starts again:
if (gesture.GestureType == GestureType.Flick)
{
_isBallMoving = true;
_isBallHit = false;
_startMovement = gameTime.TotalGameTime;
_ballVelocity = gesture.Delta*(float) TargetElapsedTime.TotalSeconds/5.0f;
}
...
var timeInMovement = (gameTime.TotalGameTime - _startMovement).TotalSeconds;
// reached goal line or timeout
if (_ballPosition.Y <' _goalLinePosition || timeInMovement > 5.0)
{
_ballPosition = new Vector2(_initialBallPosition.X, _initialBallPosition.Y);
_isBallMoving = false;
_isBallHit = false;
while (TouchPanel.IsGestureAvailable)
TouchPanel.ReadGesture();
}
Adding a goalkeeper
Our game works - we’ll make it more difficult now by adding a goalkeeper who will move after we hit the ball. The goalkeeper is a picture in PNG format, we will compile it first.
The goalkeeper is loaded in the LoadContent method:
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
_backgroundTexture = Content.Load("SoccerField");
_ballTexture = Content.Load("SoccerBall");
_goalkeeperTexture = Content.Load("Goalkeeper");
}
Draw it in the Draw method
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Green);
// Begin a sprite batch
_spriteBatch.Begin();
// Draw the background
_spriteBatch.Draw(_backgroundTexture, _backgroundRectangle, Color.White);
// Draw the ball
_spriteBatch.Draw(_ballTexture, _ballRectangle, Color.White);
// Draw the goalkeeper
_spriteBatch.Draw(_goalkeeperTexture, _goalkeeperRectangle, Color.White);
// End the sprite batch
_spriteBatch.End();
base.Draw(gameTime);
}
_goalkeeperRectangle - goalkeeper rectangle in the window. It changes in the Update method:
protected override void Update(GameTime gameTime)
{
…
_ballRectangle.X = (int) _ballPosition.X;
_ballRectangle.Y = (int) _ballPosition.Y;
_goalkeeperRectangle = new Rectangle(_goalkeeperPositionX, _goalkeeperPositionY,
_goalKeeperWidth, _goalKeeperHeight);
base.Update(gameTime);
}
The variables _goalkeeperPositionY, _goalKeeperWidth and _goalKeeperHeight fields are updated in the ResetWindowSize method:
private void ResetWindowSize()
{
…
_goalkeeperPositionY = (int) (_screenHeight*0.12);
_goalKeeperWidth = (int)(_screenWidth * 0.05);
_goalKeeperHeight = (int)(_screenWidth * 0.005);
}
The initial position of the goalkeeper is in the center of the window in front of the goal line:
_goalkeeperPositionX = (_screenWidth - _goalKeeperWidth)/2;
The goalkeeper begins to move with the ball. He performs harmonic oscillations from side to side:
X = A * sin (at + δ),
where A is the amplitude of the oscillations (gate width), t is the period of oscillations, and δ is a random variable so that the player cannot predict the goalkeeper's movement.
Odds are calculated at the time of hitting the ball:
if (gesture.GestureType == GestureType.Flick)
{
_isBallMoving = true;
_isBallHit = false;
_startMovement = gameTime.TotalGameTime;
_ballVelocity = gesture.Delta * (float)TargetElapsedTime.TotalSeconds / 5.0f;
var rnd = new Random();
_aCoef = rnd.NextDouble() * 0.005;
_deltaCoef = rnd.NextDouble() * Math.PI / 2;
}
Coefficient a - goalkeeper speed, a number between 0 and 0.005, representing a speed between 0 and 0.3 pixels per second. The delta coefficient is a number between 0 and π / 2. When the ball moves, the goalkeeper position is updated:
if (_isBallMoving)
{
_ballPositionX += _ballVelocity.X;
_ballPositionY += _ballVelocity.Y;
_goalkeeperPositionX = (int)((_screenWidth * 0.11) *
Math.Sin(_aCoef * gameTime.TotalGameTime.TotalMilliseconds +
_deltaCoef) + (_screenWidth * 0.75) / 2.0 + _screenWidth * 0.11);
…
}
The amplitude of movement is _screenWidth * 0.11 (gate width). Add (_screenWidth * 0.75) / 2.0 + _screenWidth * 0.11 to the result so that the goalkeeper moves in front of the goal.
Checking if the ball is caught and adding a score
To check whether the ball is caught or not, you need to see if the rectangles of the goalkeeper and the ball intersect. We do this in the Update method after calculating the positions:_ballRectangle.X = (int)_ballPosition.X;
_ballRectangle.Y = (int)_ballPosition.Y;
_goalkeeperRectangle = new Rectangle(_goalkeeperPositionX, _goalkeeperPositionY,
_goalKeeperWidth, _goalKeeperHeight);
if (_goalkeeperRectangle.Intersects(_ballRectangle))
{
ResetGame();
}
ResetGame returns the game to its original state:
private void ResetGame()
{
_ballPosition = new Vector2(_initialBallPosition.X, _initialBallPosition.Y);
_goalkeeperPositionX = (_screenWidth - _goalKeeperWidth) / 2;
_isBallMoving = false;
_isBallHit = false;
while (TouchPanel.IsGestureAvailable)
TouchPanel.ReadGesture();
}
Now you need to check if the ball hit the goal. Let's do it when the ball crosses the line:
var isTimeout = timeInMovement > 5.0;
if (_ballPosition.Y < _goalLinePosition || isTimeout)
{
bool isGoal = !isTimeout &&
(_ballPosition.X > _screenWidth * 0.375) &&
(_ballPosition.X < _screenWidth * 0.623);
ResetGame();
}
To add account management, you need to add a new object to the game - a font that will be used to write numbers. A font is an XML file that describes a font: appearance, size, style, etc. In the game we will use this font:
Segoe UI24 0 false
You must compile this font and add the resulting XNB file to the Content folder of your project:
_soccerFont = Content.Load("SoccerFont");
In ResetWindowSize, reset the account position:
var scoreSize = _soccerFont.MeasureString(_scoreText);
_scorePosition = (int)((_screenWidth - scoreSize.X) / 2.0);
To store the score, we define two variables: _userScore and _computerScore. _userScore increases when the player scores, _computerScore - when the player misses, the time limit expires or the goalkeeper catches the ball.
if (_ballPosition.Y < _goalLinePosition || isTimeout)
{
bool isGoal = !isTimeout &&
(_ballPosition.X > _screenWidth * 0.375) &&
(_ballPosition.X < _screenWidth * 0.623);
if (isGoal)
_userScore++;
else
_computerScore++;
ResetGame();
}
…
if (_goalkeeperRectangle.Intersects(_ballRectangle))
{
_computerScore++;
ResetGame();
}
ResetGame re-creates the invoice text and sets its position:
private void ResetGame()
{
_ballPosition = new Vector2(_initialBallPosition.X, _initialBallPosition.Y);
_goalkeeperPositionX = (_screenWidth - _goalKeeperWidth) / 2;
_isBallMoving = false;
_isBallHit = false;
_scoreText = string.Format("{0} x {1}", _userScore, _computerScore);
var scoreSize = _soccerFont.MeasureString(_scoreText);
_scorePosition = (int)((_screenWidth - scoreSize.X) / 2.0);
while (TouchPanel.IsGestureAvailable)
TouchPanel.ReadGesture();
}
_soccerFont.MeasureString measures the length of the count line, this value is used to calculate the position. The score is drawn in the Draw method:
protected override void Draw(GameTime gameTime)
{
…
// Draw the score
_spriteBatch.DrawString(_soccerFont, _scoreText,
new Vector2(_scorePosition, _screenHeight * 0.9f), Color.White);
// End the sprite batch
_spriteBatch.End();
base.Draw(gameTime);
}
Turning on the lights of the stadium
As a final touch, we’ll add to the game the inclusion of stadium lights when it becomes dark in the room. We will use for this the light sensor, which is now in many ultrabooks and transformers. To use the sensor, you can use the Windows API Code Pack for the Microsoft .NET Framework, but we will go the other way: use the WinRT Sensor API. Although this API is written for Windows 8, it can also be used in desktop applications.Highlight your project in Solution Explorer, right-click and select Unload Project. Then press the right button again - Edit project. In the first PropertyGroup, add the TargetPlatFormVersion tag:
Debug
…
512
8.0
Press the right button again and select Reload Project. Visual Studio will reload the project. When you add a new link to the project, you will see the Windows tab in the Reference Manager, as shown.

Add a link to Windows in the project. This will also require adding a reference to System.Runtime.WindowsRuntime.dll.
Now write the code for detecting the light sensor:
LightSensor light = LightSensor.GetDefault();
if (light != null)
{
If the sensor is present, you will get a non-zero value that can be used to determine the illumination:
LightSensor light = LightSensor.GetDefault();
if (light != null)
{
light.ReportInterval = 0;
light.ReadingChanged += (s,e) => _lightsOn = e.Reading.IlluminanceInLux < 10;
}
If the reading is less than 10, the variable _lightsOn is True and the background will be drawn a little differently. If you look at spriteBatch in the Draw method, you will see that its third parameter is color. Previously, we always used white, while the background color does not change. If you use any other color, the background color will change. Choose green when the lights are off and white when they are on. Make changes to the Draw method:
_spriteBatch.Draw(_backgroundTexture, rectangle, _lightsOn ? Color.White : Color.Green);
Now our field will be dark green when the lights are off and light green when on.

The development of our game is completed. Of course, there is something to be done in it: come up with an animation when the ball is hammered, add a rebound to the ball from the bar, and so on. Let it be your homework.
Original article on Intel Developer Zone