Posts tagged ‘Pong’

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

Развивање 2Д Игри За Windows 8 Со MonoGame Четврт Дел–Дотерување

Во последниот дел од овој серијал ќе ја дотераме нашата игра. Прво ќе напишеме неколку линии за ‘вртење’ на топчето. Потоа, ќе додадеме SpriteFont кој што се користи за пишување текст за да ги нацртаме индикаторите за поени. Следно, ќе ставиме една линија на средина за да ја поделиме површината за играње на два дела, па ќе ставиме звуци за колизии и на крај ќе додадеме Splash Screen и логоа.

Вртење на Топчето

Отворете ја Game1.cs класата и додадете нова константа:

const float SPIN = 2.5f;

 

Следно, во Update методот веднаш после линиите за движење со Touch, додадете ги следните if-тестови:

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();
}

 

Во нашиот случај, доколку играчите користат touch, ќе забележите дека брзината на играчите не е дефинирана, туку во зависност од брзината на влечење, се мрда и играчот. Додека, со тастатура, има една и единствена брзина дефинирана во константата KEYBOARD_PADDLE_SPEED. За да не се дозволи играчите на touch да можат повеќе или помалку да го ‘вртат’ топчето, брзините ќе ги нормализираме за да се добие насоката на движење на играчот.

Значи прво проверуваме дали постои инпут со touch, доколку постои ја зачувуваме таа вредност и потоа ја нормализираме.

Во истиот Update метод, во if-тестовите кои што проверуваат дали има колизија помеѓу играчите и топчето, додадете ги линиите како што е покажано подолу:

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;
}

 

Откако ја менуваме насоката на топчето, на брзината ја додаваме насоката на играчот помножена со константата за ‘вртење’ на топчето.

Притиснете F5 или Debug –> Start Debugging и тестирајте. Ќе забележите дека во зависност од тоа како го удирате топчето, тоа или забрзува или забавува.

Слободно можете да ги менувате константите во зависност од тоа дали сакате топчето да има поголема почетна брзина, да има поголемо вртење и слично.

Индикатори за Поени

Најпрво превземете и инсталирајте го 8Bit Wonder фонтот од тука.

Сега ќе треба да се повторат чекорите за внесување надворешни фајлови од вториот дел за да ставиме SpriteFont во нашата игра.

Пуштете го Visual Studio 2012 Express for Windows Phone и отворете го PongContent проектот. Притиснете десен-клик на Content проектот па Add –> New Item.

 

Одберете лево Visual C#, потоа одберете SpriteFont, крстете го RetroFont и притиснете Add.

Откако ќе притиснете Add, ќе ви се отвори RetroFont.spritefont фајлот во Visual Studio каде што може да ги променувате неговите параметри како име на фонт, големина, стил итн.

Направете ги следните промени:

<FontName>8Bit Wonder</FontName>

<Size>32</Size>

<Style>Bold</Style>

Откако ќе ги промените вредностите, притиснете Build –> Build Solution. Откако ќе се комплетира компајлирањето, исклучете го Visual Studio 2012 Express for Windows Phone.

Вратете се на PongClone проектот, притиснете десен-клик на ‘Content’ фолдерот, Add –> Existing item.

Навигирајте во:

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

Одберете го RetroFont.xnb и притиснете Add.

Сега, селектирајте го RetroFont и променете го Build Action од None во Content.

Отворете ја Player.cs класата и додадете ја следната променлива:

public int Score;

Отворете ја Game1.cs класата, декларирајте нова SpriteFont променлива по декларацијата на играчите и топчето:

Player player1;
Player player2;
Ball ball;

SpriteFont retroFont;

Во LoadContent методот додадете ја следната линија за вчитување на SpriteFont-от:

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

Во Update методот, сменете ги if-тестовите за повторно лансирање на топчето во следното:

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++;
}

Значи, доколку топчето излезе од левата страна, вториот играч добива еден поен. Доколку топчето излезе од десната страна, првиот играч добива еден поен.

Во Draw методот, помеѓу _spriteBatch.Begin() и player1.Draw(_spriteBatch) додадете ја линијата за цртање на поените:

_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);

DrawString методот го прима SpriteFont објектот, текстот што сакаме да го напишеме, позицијата на текстот (во нашиот случај во зависност од должината на string-от позицијата ќе се променува, а должината на string-от ја мериме со MeasureString методот), и бојата на текстот.

Притиснете F5 или Debug –> Start Debugging за да тестирате. Ќе забележите на горната страна нацртани се индикаторите за поени на играчите.

Сега може да ја додадеме и линијата на средина за да ја поделиме површината на два дела.

Во Game1.cs веднаш после декларацијата на SpriteFont retroFont; додадете една текстура која што ќе ја нацртаме на средина:

SpriteFont retroFont;
Texture2D middleTexture;

Во LoadContent методот додадете ја следната линија:

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

Во Draw методот, после _spriteBatch.Begin() додадете ја линијата за цртање на текстурата:

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

Забележувате дека сега користиме друг overload на Draw методот. Бидејќи Middle текстурата ни е 4 пиксели широка и 1 пиксел висока (4×1), со помош на Rectangle ќе ја рашириме текстурата од горе до долу. Кога се користи Rectangle за цртање на објекти на екран, без разлика колкава е големината на текстурата, таа се раширува за да ја опфати големината на Rectangle објектот.

Притиснете F5 или Debug –> Start Debugging за да ја видите новата текстура.

Звучни Ефекти

Бидејќи звуците веќе ги ставивме во нашиот проект во вториот дел кога ги префрливме сите екстерни фајлови, сега нема потреба од повторно компајлирање.

За менаџирање на звучните ефекти ќе креираме нова статична класа, ‘SoundManager’.

Притиснете десен-клик на ‘Classes’, Add –> Class… , крстете ја класата ‘SoundManager’ (без наводници) и притиснете Add.

Преправете ја класата во следното:

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");
        }
    }
}

Забележувате дека имаме две променливи кои што ќе ги чуваат звуците за колизија помеѓу ‘ѕидовите’ и топчето, и помеѓу играчите и топчето. Потоа имаме еден статичен метод кој што прима ContentManager објект кој што се користи за вчитување на звуците.

Отворете ја Game1.cs класата и во LoadContent додадете ја следната линија:

SoundManager.LoadSounds(Content);

Потоа, во Update методот, преправете ги if-тестовите за колизија помеѓу играчите и топчето во следното:

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();
}

Значи, штом има колизија помеѓу некој од играчите и топчето, го активираме звукот.

Отворете ја Ball.cs класата и преправете го CheckWallCollision методот во следното:

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();
	}
}

Исто и тука забележувате дека штом се детектира колизија помеѓу топчето и горната или долната страна на екранот, го активираме звукот.

Притиснете F5 или Debug –> Start Debugging за да ги тестирате последните промени.

Splash Screen и Логоа

Превземете го .rar фајлот од тука и екстрактирајте ги сликите во следниот фолдер:

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

Кога ќе ве праша дали сакате да ги промените старите слики со новите, прифатете.

Во Visual Studio 2012, отворете го Package.appxmanifest фајлот (покажан на сликата)

Ке ви се отвори нов прозорец со опции за името на играта, можност за промена на логоата и слично. Притиснете на Splash Screen и во Background Color напишете black.

Притиснете F5 или Debug –> Start Debugging за да тестирате. Ќе видите дека сега имате нов Splash Screen.

Исто така на вашиот Start Screen ќе забележите ново иконче за играта.

Честито, ја комплетиравте вашата прва Windows 8 игра со MonoGame.

Мојот Solution може да го превземете од тука.

Напомена: Ќе треба да ги сетирате референците до MonoGame за да го тестирате мојот Solution.

Се надевам ви се допадна овој мини серијал. Доколку имате некакви прашања, оставете коментар подолу. Би сакал да им се заблагодарам на дечките од 3D Buzz, бидејќи пред повеќе од една година кога го учев XNA фрејмворкот, една од поголемите задачи беше креирање на Pong игра.

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.

Развивање 2Д Игри За Windows 8 Со MonoGame Трет Дел–Логика На Игра

Стигнавме до најинтересниот дел (барем за мене 🙂 ) од овој серијал. По цртањето на објектите кое што беше опфатено во вториот дел, во овој дел ќе додадеме движење, инпут (со тастатура и допир) и колизии.

Движење

Прво, ќе имплементираме движење на топчето бидејќи топчето е независно од надворешен инпут.

Отворете ја GameObject.cs класата и додадете го методот Move:

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

 

Движењето во 2Д игри е прилично едноставно. Се што се прави е, на вектор за позиција се додава друг вектор за да се добие нова позиција. Со повторување на овој метод 30 пати во секунда, ќе се добие некакво движење.

На првата линија забележувате искористен е зборот virtual. Овој метод ќе го искористиме во наследените класи Player и Ball со тоа што на двете класи ќе им додадеме уште неколку линии кои што не се заеднички меѓу нив.

Отворете ја Ball.cs класата и преправете ја во следното:

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();
		}
	}
}

 

Топчето ќе има променлива брзина, затоа ни треба еден вектор кој што ќе ја прати брзината (и насоката) на топчето. Random објектот ќе се користи за лансирање на топчето на левата или десната страна (50% веројатност на едната страна, 50% веројатност на другата страна). Конструкторот на линиите 15, 16, 17 и 18 се користи за креирање на Random објектот кој што ќе го користиме во Launch методот.

Додадете го следниот Launch метод во Ball.cs класата:

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;
}

 

Не плашете се од математиката во овој метод, многу е едноставна, ќе ви објаснам што се случува на секоја линија.

Како што може да видите на првата линија, Launch методот добива параметар за брзина (има разлика помеѓу speed и Velocity; speed е брзина скалар, velocity е брзина со насока, вектор). Оваа брзина (speed) ќе ни ја претставува почетната брзина на топчето.

Во третата линија, се сетира почетната позиција на топчето (во нашиот случај, на центарот на екранот).

Потоа, во линија 5 креираме променлива за вредност на ротација кој што потоа во линиите 7 и 8 се користи за да се добие аголот на кој што ќе се лансира топчето. Доколку ротацијата е 0, Sin(0) = 0, и Cos(0) = 1. Бидејќи вредноста која што произлегува од Sin функцијата ја ставаме во Velocity.X, а вредностa што произлегува од Cos функцијата ја ставаме во Velocity.Y , топчето ќе се лансира надолу. Како што кажав на претходниот пост, (0,0) на екранот се наоѓа на горниот лев агол, тоа значи дека позитивната страна на Y оди надолу.

Најпрво на вредноста за ротација му даваме вредност Math.PI / 2 со која што ќе добиеме лансирање на десната страна бидејки Pi / 2 = 90 степени, Sin(90 степени) = 1, Cos(90 степени) = 0. Потоа на таа вредност додаваме нова вредност со користење на Random објектот за да генерираме вредност помеѓу +60 и –60 степени (во нашиот случај рачунаме со радијани, поради едноставност, бројките во ова објаснување ги конвертирам во степени).

Како дојдовме до +60 и –60? random.NextDouble() генерира вредности помеѓу 0.0 и 1.0. Таа вредност ја множиме со Math.PI / 1.5f (120 степени). Сега добиваме вредности од 0 до 120 степени. Затоа, на крај имаме – Math.PI / 3 (60 степени) со кој што го добиваме посакуваниот интервал (-60,+60)

Сега топчето иако ќе се лансира по случаен агол измеѓу –60 и +60 степени, постојано ќе се лансира на десната страна. Во линија 11 генерираме цел број помеѓу 1 и 0. Шансите да се добие 1 или 0 се 50-50. Доколку се добие 1, вредноста на Velocity.X ја множиме со –1 со што ја променуваме насоката на лансирање од десно на лево.

Конечно вектор брзината (Velocity) ја множиме со скаларната брзина (speed) за да ја добиеме конечната почетна брзина на топчето.

Напомена: Доколку оваа математика не ви е јасна, откако ќе го напишеме кодот во Game1.cs, може да се вратите на овој метод и да ги промените вредностите кои што се во rotation. Почнете со давање на вредност 0 и зголемувајте/намалувајте од тука за да видите што се случува со аголот на лансирање. Ако повторно не ви е јасно, слободно оставете коментар подолу.

Отворете ја Game1.cs класата и најгоре каде што ја дефиниравме првата константа PADDLE_OFFSET = 70, додадете нова константа за почетна брзина на топчето:

const float BALL_START_SPEED = 8f;

Избришете ја следната линијата за сетирање на позицијата на топчето во LoadContent бидејќи истата ја имаме во Launch методот:

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

 

На тоа место ставете ја следната линија:

ball.Launch(BALL_START_SPEED);

 

Во Update методот, под линиите за ширина и висина на екранот, додадете ја линијата

ball.Move(ball.Velocity);

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

Оваа линија ќе го активира Move методот каде што се собираат векторите Position и Velocity, и резултатот се зачувува во Position.

Притиснете F5 или Debug –> Start Debugging за да ја пуштите апликацијата. Ќе забележите дека топчето се движи и после кратко време излегува од видното поле. Пред да преминеме на пишување логика за колизии, ќе го имплементираме инпутот за играчите.

Инпут

Најпрво ќе креираме нова класа која што ќе се користи за процесирање на инпутот од играчите. Притиснете десен клик на фолдерот ‘Classes’ Add –> Class… крстете ја новата класа ‘Input’ (без наводници).

Преправете ја Input.cs класата во следното:

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>();
        }

    }
}

 

Ќе забележите дека се додадени неколку библиотеки кои што ќе се потребни за имплементирање на инпутот. Потоа ќе забележите дека креираме статична класа (линија 12). Тоа значи дека самата класа ни претставува еден и единствен објект (не ни требаат повеќе објекти за да се процесира инпут). Потоа во линија 14 декларираме една листа Gestures која што ќе ги чува сите инпути на допир (Touch). Во конструкторот static Input(), ја иницијализираме листата за да можеме да ја користиме покасно.

Прво ќе го напишеме методот за инпут од тастатура. Додадете го следниот метод во класата:

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;
}

 

Oвој метод враќа Vector2 објект и прима еден параметар PlayerIndex со кој што се дефинира за кој играч станува збор (првиот, вториот итн.). Во третата линија иницијализираме еден вектор на (0,0) кој што ќе ја содржи насоката на движење. Во линија 4, декларираме KeyboardState променлива и ја земаме состојбата на тастатурата. Потоа во шестата линија проверуваме дали станува збор за првиот играч. Доколку одговорот е да, проверуваме дали буквата W е притисната. Ако е притисната, ја намалуваме вредноста на Y оската за 1 (Y се намалува ако се движиме нагоре). Истото се проверува и со буквата S за движење надолу. Доколку не станува збор за првиот играч, се проверуваат истите работи со различни копчиња (стрелките за горе и долу) за вториот играч. На крајот се враќа вредноста на векторот direction кој што ја содржи насоката. Насоката може да биде нагоре, надолу, или нема промена на местото ако нема никаков инпут.

Отворете ја Game1.cs класата и додадете уште една константа за брзината на играчите кога инпутот е преку тастатура:

const float KEYBOARD_PADDLE_SPEED = 10f;

 

Во Update методот, под линијата ball.Move(ball.Velocity); додадете ги следните линии:

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);

 

Прво декларираме и ги иницијализираме брзините на играчите во два Vector2 променливи. Бидејќи методот што претходно го напишавме во инпут класата враќа вектор со насока, го множиме резултатот со константата каде што е дефинирана брзината. Потоа ги повикуваме Move методите за движење на играчите.

Притиснете F5 или Debug –> Start Debugging и тестирајте го движењето на играчите. Ќе забележите дека играчите го напуштаат видното поле, во следниот дел за колизии ќе ги поставиме границите на движење.

Пред да преминеме на колизии, најпрво ќе го имплементираме инпутот преку допир (Touch). Отворете ја Input.cs класата и додадете го следниот метод:

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;
		}
	}
}

 

Овој метод враќа два вредности како што може да се види во првата линија. Во линија 3 ја бришиме цела листа и потоа во while циклус проверуваме дали има некаков инпут преку допир. Доколку има, сите инпути ги додаваме во листата Gestures. Потоа ги иницијализираме векторите на (0,0) и ги проверуваме сите инпути што ги имаме во листата Gestures. Доколку имаме инпут со влечење (FreeDrag), проверуваме на која позиција од екранот се случило влечењето. Доколку се случило во левата половина, брзината од Y оската на влечењето ја додаваме на брзината на играчот во Y оската. Доколку влечењето е во десната половина, истата математика ја правиме за брзината на вториот играч.

Отворете ја Game1.cs класата и најгоре, додадете ја следната библиотека:

using Microsoft.Xna.Framework.Input.Touch;

 

Потоа, сменете го конструкторот во следното:

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

Забележувате ја додадовме 5тата линија. Оваа линија ни дозволува инпут преку влечење на екранот.

Во Update методот, под повикувањето на методите за движење на играчите со тастатура, додадете ги следните линии:

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

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

 

Прво, во линија 4 декларираме два вектори каде што ќе ги чуваме вредностите за брзината на влечење. Откако ќе се повика методот за процесирање на Touch инпутот, повторно ги повикуваме методите за движење.

Како што е покажано на сликата подолу, сменете ја машината за дебагирање од Local Machine во Simulator за да може да го тестирате Touch инпутот.

Притиснете F5 или Debug –> Start Debugging за да го уклучите симулаторот и да го тестирате Touch инпутот.

Колизии

Имаме три работи кои што треба да ги средиме за да го завршиме овој дел. Колизија помеѓу горната и долната страна на екранот и топчето, ограничување на движењето на играчите и колизија помеѓу играчите и топчето.

Прво ќе го средиме топчето. Отворете ја Ball.cs класата и додадете го следниот метод:

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;
	}
}

 

Овој метод прво проверува дали топчето излегува од горната страна. Доколку излегува од горната страна, ја сетираме Y координатата на 0 и ја променуваме насоката на брзината (со ова добиваме одбивање од горната страна). Во вториот if-тест се проверува истото за долната страна меѓутоа приметувате дека треба да се искористи и висината на самата текстура бидејќи позицијата се наоѓа на горниот лев агол на самото топче.

Овој метод сега ќе го повикаме во Move методот на топчето. Додадете го следниот метод во Ball.cs класата:

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

 

Овој метод прво го повикува Move методот од GameObject (класата од кој што наследува) и потоа го повикува методот за проверка на колизии со горната и долната страна на екранот.

Пред да тестираме, ќе додадеме неколку линии за да се ресетира топчето доколку излезе од левата или десната страна на екранот.

Отворете ја Game1.cs класата и внесете го следниот код веднаш по процесирањето на Touch инпутот:

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);
}

 

Првиот if-тест проверува дали топчето излегло од левата страна и доколку да, повторно го лансира од центарот на екранот. Вториот if-тест проверува дали топчето излегло од десната страна.

Притиснете F5 или Debug –> Start Debugging за да ги тестирате последните промени. Ќе забележите дека сега топчето се одбива од горната и долната страна, а кога излегува од левата или десната страна се ресетира повторно на центарот на екранот.

Следниот чекор е да се ограничи движењето на играчите. Отворете ја Player.cs класата и додадете ја следната библиотека:

using Microsoft.Xna.Framework;

Потоа, додадете го следниот метод:

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;
}

 

Како и за топчето, најпрво се повикува Move методот од GameObject (класата од која што наследува) потоа се проверува дали играчот се наоѓа на горната граница  и се ограничува позицијата доколку играчот се обиде да излезе од екранот. Истото се случува и во вториот if-тест каде што се проверува дали играчот сака да излезе од долната страна.

Притиснете F5 или Debug –> Start Debugging и тестирајте ги последните промени. Ќе забележите дека сега играчот не смее да го напушти екранот.

Останува уште последниот дел од колизиите, колизија помеѓу топчето и играчите.

Отворете ја GameObject.cs класата и додадете го следното Property:

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

 

Ова Property враќа правоаголник со границите на објектите (играчите и топчето во нашиот случај).

Во истата класа (GameObject.cs) додадете го следниот метод:

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

 

Овој метод враќа булеан (true или false), а како параметри зема играч и топче. Со користење на Bounds Property-то за добивање на правоаголникот околу објектите се проверува дали правоаголникот на играчот се пресекува со правоаголникот на топчето. Доколку постои пресек, враќаме true. Во спротивост враќаме false.

Овој метод е статичен и може да се повика преку GameObject.CheckPaddleBallCollision(…);

Отворете ја Game1.cs класата и додадете ја следната библиотека:

using System;

 

Потоа во Update методот, веднаш после Touch движењето и пред проверката за повторно лансирање на топчето внесете ги линиите како што е покажано:

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);
}

 

Во четвртата линија забележувате се проверува колизија помеѓу првиот играч и топчето. Доколку постои колизија, брзината на топчето во X оската добива позитивна насока (позитивна насока во случајот значи надесно). Потоа се проверува колизија помеѓу вториот играч и топчето. Доколку постои колизија, се дава негативна вредност на брзината во X оската.

Притиснете F5 или Debug –> Start Debugging за да тестирате.

Со овој чекор го завршуваме третиот дел од овој серијал. Доколку нешто не ви е јасно, или заглавивте некаде, оставете коментар подолу. За да го превземете мојот Solution од овој дел, притиснете тука.

Напомена: Ќе треба да ги сетирате референците до MonoGame за да го тестирате мојот Solution.

Во наредниот, последен дел од овој серијал, ќе ја дотераме играта. Ќе додадеме ‘вртење’ на топчето кога играчот ќе го мава, ќе додадеме индикатори за поени, звуци и слично.

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.