📜 ⬆️ ⬇️

Microsoft XNA: Breakout Step by Step

A few days ago, surfing the great and mighty Internet, came across Microsoft XNA Studio. Not that I heard about this framework for the first time, but all the previous times somehow passed by, there was no time to understand it at all.
This time, something pulled me to dig deeper. Rightly judging that to get acquainted with the library there is no better method than to implement anything on it, and also having a free evening, I decided to write something simple, such as Arkanoid (Brick Out), which I have loved since childhood, not for personal gain, but for familiarization.

This is my first article on Habré, please do not kick with your feet

The meaning of this article is to show how easy it is to start creating your games using XNA and, in fact, give an impetus for further study of this platform. Therefore, the full-fledged game does not claim, the graphics - none, like the artist from me. Yes, and physics could be more realistic, but this is already beyond the scope of this article, I am sure that those interested in the development of games will find a lot of decent (and not very) materials in the network.
')
Sources can be downloaded here.



So what is Microsoft XNA?

Microsoft XNA is a toolkit and library for developing multi-platform 2D and 3D games in a managed Microsoft Managed Runtime Environment. Windows, Microsoft Xbox 360 and Microsoft Zune platforms are supported. Theoretically, you can write in any .Net language, in any IDE, but only C # and XNA Game Studio Express and all versions of Visual Studio 2005 and higher are officially supported. XNA also provides the ability to port games to supported platforms with minimal changes.

The latest version at the time of writing is Microsoft XNA Game Studio 3.1 (73.2 MB)

Creating a project



Create a new project - XNA Game Studio 3.1 - Windows Game (3.1)

Photobucket

Master will create a skeleton of the game:



The most interesting for us is the Game1.cs file, which defines the Game1 class, inherited from Microsoft.Xna.Framework.Game, where we will develop our game.
The Game1 class overrides the following Game methods:

void Initialize () - Called once to initialize resources before the game starts.
void LoadContent () - Called once, used to load content (sprites, etc.)
void UnloadContent () - Called once, used to upload content
void Update (GameTime gameTime) - This method implements the actual game logic, handling of collisions, handling keyboard or joystick events, playing audio, etc.
void Draw (GameTime gameTime) - Called to draw the playing field.

At the moment the compiled game looks like this



Add content



Add game resources, in this case, background pictures of bricks, rackets and balls - Content (Right Click) -> Add -> Existing Item ...



Pay attention to the Asset Name property, we use it to create the Texture2D object necessary for further animation.

Draw the background of the playing field



Download the image for the background of the playing field:

private Rectangle _viewPortRectangle; // private Texture2D _background; // protected override void LoadContent() { <... skip ...> // _viewPortRectangle = new Rectangle(0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height); _background = Content.Load<Texture2D>( @"background" ); <... skip ...> } * This source code was highlighted with Source Code Highlighter .
  1. private Rectangle _viewPortRectangle; // private Texture2D _background; // protected override void LoadContent() { <... skip ...> // _viewPortRectangle = new Rectangle(0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height); _background = Content.Load<Texture2D>( @"background" ); <... skip ...> } * This source code was highlighted with Source Code Highlighter .
  2. private Rectangle _viewPortRectangle; // private Texture2D _background; // protected override void LoadContent() { <... skip ...> // _viewPortRectangle = new Rectangle(0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height); _background = Content.Load<Texture2D>( @"background" ); <... skip ...> } * This source code was highlighted with Source Code Highlighter .
  3. private Rectangle _viewPortRectangle; // private Texture2D _background; // protected override void LoadContent() { <... skip ...> // _viewPortRectangle = new Rectangle(0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height); _background = Content.Load<Texture2D>( @"background" ); <... skip ...> } * This source code was highlighted with Source Code Highlighter .
  4. private Rectangle _viewPortRectangle; // private Texture2D _background; // protected override void LoadContent() { <... skip ...> // _viewPortRectangle = new Rectangle(0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height); _background = Content.Load<Texture2D>( @"background" ); <... skip ...> } * This source code was highlighted with Source Code Highlighter .
  5. private Rectangle _viewPortRectangle; // private Texture2D _background; // protected override void LoadContent() { <... skip ...> // _viewPortRectangle = new Rectangle(0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height); _background = Content.Load<Texture2D>( @"background" ); <... skip ...> } * This source code was highlighted with Source Code Highlighter .
  6. private Rectangle _viewPortRectangle; // private Texture2D _background; // protected override void LoadContent() { <... skip ...> // _viewPortRectangle = new Rectangle(0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height); _background = Content.Load<Texture2D>( @"background" ); <... skip ...> } * This source code was highlighted with Source Code Highlighter .
  7. private Rectangle _viewPortRectangle; // private Texture2D _background; // protected override void LoadContent() { <... skip ...> // _viewPortRectangle = new Rectangle(0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height); _background = Content.Load<Texture2D>( @"background" ); <... skip ...> } * This source code was highlighted with Source Code Highlighter .
  8. private Rectangle _viewPortRectangle; // private Texture2D _background; // protected override void LoadContent() { <... skip ...> // _viewPortRectangle = new Rectangle(0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height); _background = Content.Load<Texture2D>( @"background" ); <... skip ...> } * This source code was highlighted with Source Code Highlighter .
  9. private Rectangle _viewPortRectangle; // private Texture2D _background; // protected override void LoadContent() { <... skip ...> // _viewPortRectangle = new Rectangle(0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height); _background = Content.Load<Texture2D>( @"background" ); <... skip ...> } * This source code was highlighted with Source Code Highlighter .
  10. private Rectangle _viewPortRectangle; // private Texture2D _background; // protected override void LoadContent() { <... skip ...> // _viewPortRectangle = new Rectangle(0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height); _background = Content.Load<Texture2D>( @"background" ); <... skip ...> } * This source code was highlighted with Source Code Highlighter .
  11. private Rectangle _viewPortRectangle; // private Texture2D _background; // protected override void LoadContent() { <... skip ...> // _viewPortRectangle = new Rectangle(0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height); _background = Content.Load<Texture2D>( @"background" ); <... skip ...> } * This source code was highlighted with Source Code Highlighter .
  12. private Rectangle _viewPortRectangle; // private Texture2D _background; // protected override void LoadContent() { <... skip ...> // _viewPortRectangle = new Rectangle(0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height); _background = Content.Load<Texture2D>( @"background" ); <... skip ...> } * This source code was highlighted with Source Code Highlighter .
private Rectangle _viewPortRectangle; // private Texture2D _background; // protected override void LoadContent() { <... skip ...> // _viewPortRectangle = new Rectangle(0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height); _background = Content.Load<Texture2D>( @"background" ); <... skip ...> } * This source code was highlighted with Source Code Highlighter .


Background drawing

  1. protected override void Draw (GameTime gameTime)
  2. {
  3. GraphicsDevice.Clear (Color.CornflowerBlue);
  4. spriteBatch.Begin (SpriteBlendMode.AlphaBlend);
  5. // Draw the background
  6. spriteBatch.Draw (_background, _viewPortRectangle, Color.White);
  7. spriteBatch.End ();
  8. base .Draw (gameTime);
  9. }
* This source code was highlighted with Source Code Highlighter .


The SpriteBatch.Begin method prepares the graphics device for rendering sprites, SpriteBatch.End completes the drawing process and returns the device to its initial state. All SpriteBatch.Draw methods should be enclosed in SpriteBatch.Begin - SpriteBatch.End.

Creating a game object



Create a GameObject class that encapsulates any of our game objects:

  1. using Microsoft.Xna.Framework;
  2. using Microsoft.Xna.Framework. Graphics ;
  3. namespace Arkanoid
  4. {
  5. public class GameObject
  6. {
  7. public Texture2D Sprite { get ; set ; } // Sprite
  8. public Vector2 Position; // Position
  9. public Vector2 Velocity; // speed
  10. public int Width { get { return Sprite.Width; }} // Width
  11. public int Height { get { return Sprite.Height; }} // Height
  12. public bool IsAlive { get ; set ; } // object is alive
  13. public Rectangle Bounds // Object Borders
  14. {
  15. get
  16. {
  17. return new Rectangle (( int ) Position.X, ( int ) Position.Y, Width, Height);
  18. }
  19. }
  20. // Expand motion on the horizontal axis
  21. public void ReflectHorizontal ()
  22. {
  23. Velocity.Y = -Velocity.Y;
  24. }
  25. // Spreading on the vertical axis
  26. public void ReflectVertical ()
  27. {
  28. Velocity.X = -Velocity.X;
  29. }
  30. public GameObject (Texture2D sprite)
  31. {
  32. Sprite = sprite;
  33. IsAlive = true ;
  34. Position = Vector2.Zero;
  35. Velocity = Vector2.Zero;
  36. }
  37. }
  38. }
* This source code was highlighted with Source Code Highlighter .


Rendering and animation of the racket



First, create an object representing the racket and place it in the middle of the playing field a little higher from its bottom edge.

  1. private GameObject _paddle; // racket
  2. protected override void LoadContent ()
  3. {
  4. <... skip ...>
  5. // Create a racket, the starting position in the middle of the playing field, above the bottom edge
  6. _paddle = new GameObject (Content.Load <Texture2D> ( @ "paddle" ));
  7. _paddle.Position = new Vector2 ((_ viewPortRectangle.Width - _paddle.Width) / 2,
  8. _viewPortRectangle.Height - _paddle.Height - 20);
  9. <... skip ...>
  10. }
* This source code was highlighted with Source Code Highlighter .


Drawing the racket on the screen

  1. protected override void Draw (GameTime gameTime)
  2. {
  3. <... skip ...>
  4. spriteBatch.Draw (_paddle.Sprite, _paddle.Position, Color.White);
  5. <... skip ...>
  6. }
* This source code was highlighted with Source Code Highlighter .


At this stage, if you compile the application, we get something like this:



It would be Neplho to make the racket respond to keystrokes, for this we add the following code to the Update method

  1. protected override void Update (GameTime gameTime)
  2. {
  3. <... skip ...>
  4. KeyboardState keyboardState = Keyboard.GetState ();
  5. // Move the racket to the right
  6. if (keyboardState.IsKeyDown (Keys.Right))
  7. _paddle.Position.X + = 6f;
  8. // Move the racket to the left
  9. if (keyboardState.IsKeyDown (Keys.Left))
  10. _paddle.Position.X - = 6f;
  11. // Restrict the movement of the racket to the playing field
  12. _paddle.Position.X = MathHelper.Clamp (_paddle.Position.X, 0, _viewPortRectangle.Width - _paddle.Width);
  13. <... skip ...>
  14. }
* This source code was highlighted with Source Code Highlighter .


Bricks drawing



Create an array of GameObject representing the bricks that we actually break.

  1. private int _brickPaneWidth = 10; // How many Cypriots draw in width
  2. private int _brickPaneHeight = 5; // How many Cypriots draw in height
  3. private Texture2D _brickSprite; // Sprite bricks
  4. private GameObject [,] _bricks; // Array of bricks
* This source code was highlighted with Source Code Highlighter .


Add the following code to the LoadContent () method

  1. protected override void LoadContent ()
  2. {
  3. <... skip ...>
  4. // Create an array of bricks
  5. _brickSprite = Content.Load <Texture2D> ( @ "brick" );
  6. _bricks = new GameObject [_brickPaneWidth, _brickPaneHeight];
  7. for ( int i = 0; i <_brickPaneWidth; i ++)
  8. {
  9. for ( int j = 0; j <_brickPaneHeight; j ++)
  10. {
  11. _bricks [i, j] = new GameObject (_brickSprite)
  12. {
  13. Position = new Vector2 (i * 55 + 120, j * 25 + 100)
  14. };
  15. }
  16. }
  17. <... skip ...>
  18. }
* This source code was highlighted with Source Code Highlighter .


Drawing an array of bricks, the search is performed if the brick is “alive”, i.e. not broken by the ball

  1. protected override void Draw (GameTime gameTime)
  2. {
  3. <... skip ...>
  4. // Draw Bricks
  5. foreach ( var brick in _bricks)
  6. if (brick.IsAlive)
  7. spriteBatch.Draw (brick.Sprite, brick.Position, Color.White);
  8. <... skip ...>
  9. }
* This source code was highlighted with Source Code Highlighter .


At this stage, the playing field looks like this.



Ball drawing



Create a ball object

  1. private GameObject _ball; // Ball
  2. protected override void LoadContent ()
  3. {
  4. <... skip ...>
  5. // Making the ball, the starting position in the middle of the racket,
  6. // starting direction - right, up
  7. _ball = new GameObject (Content.Load <Texture2D> ( @ "ball" ));
  8. _ball.Position = new Vector2 ((_ viewPortRectangle.Width - _ball.Width) / 2,
  9. _viewPortRectangle.Height - _paddle.Height - _ball.Height - 20);
  10. _ball.Velocity = new Vector2 (3, -3);
  11. <... skip ...>
  12. }
* This source code was highlighted with Source Code Highlighter .


To animate the ball, add a new UpdateBall () method, and call it in the Update () method. We will need this method in the future to handle ball collisions with bricks and a racket.

  1. private void UpdateBall ()
  2. {
  3. _ball.Position + = _ball.Velocity;
  4. }
  5. protected override void Update (GameTime gameTime)
  6. {
  7. <... skip ...>
  8. // Move the ball
  9. UpdateBall ();
  10. <... skip ...>
  11. }
* This source code was highlighted with Source Code Highlighter .


To draw a ball, add the following code to the Draw () method

  1. protected override void Draw (GameTime gameTime)
  2. {
  3. <... skip ...>
  4. // Draw a ball
  5. spriteBatch.Draw (_ball.Sprite, _ball.Position, Color.White);
  6. <... skip ...>
  7. }
* This source code was highlighted with Source Code Highlighter .


At the moment, we have an almost completely finished playing field, but without collision handling the ball immediately flies out of the playing field. Add handling ball collisions with the playing field, bricks and racket

Collision handling



Let's create a new method that determines the place of collision of objects and changes the direction of the ball's flight.

  1. // Determination of the side of the collision and reflection of the ball's direction of flight
  2. public void Collide (GameObject gameObject, Rectangle rect2)
  3. {
  4. // Object collided from above or below, we reflect the direction of flight horizontally
  5. if (rect2.Left <= gameObject.Bounds.Center.X && gameObject.Bounds.Center.X <= rect2.Right)
  6. gameObject.ReflectHorizontal ();
  7. // Object collided to the left or to the right, reflect the direction of the flight vertically
  8. else if (rect2.Top <= gameObject.Bounds.Center.Y && gameObject.Bounds.Center.Y <= rect2.Bottom)
  9. gameObject.ReflectVertical ();
  10. }
* This source code was highlighted with Source Code Highlighter .


Add the following code to the UpdateBall () method

  1. private void UpdateBall ()
  2. {
  3. // The future position of the ball is needed to prevent the ball from sticking on the object's surface
  4. Rectangle nextRect = new Rectangle (( int ) (_ ball.Position.X + _ball.Velocity.X),
  5. ( int ) (_ ball.Position.Y + _ball.Velocity.Y),
  6. _ball.Width, _ball.Height);
  7. // Facing the top edge of the playing field
  8. if (nextRect.Y <= 0)
  9. _ball.ReflectHorizontal ();
  10. // When pushing the ball against the bottom edge of the playing field, the ball "dies"
  11. if (nextRect.Y> = _viewPortRectangle.Height - nextRect.Height)
  12. {
  13. _ball.IsAlive = false ;
  14. }
  15. // Collision of the ball with the left or right edge of the playing field
  16. if ((nextRect.X> = _viewPortRectangle.Width - nextRect.Width) || nextRect.X <= 0)
  17. {
  18. _ball.ReflectVertical ();
  19. }
  20. // Collision of the ball with the racket
  21. if (nextRect.Intersects (_paddle.Bounds))
  22. Collide (_ball, _paddle.Bounds);
  23. // Collision of a ball with bricks
  24. foreach ( var brick in _bricks)
  25. {
  26. if (nextRect.Intersects (brick.Bounds) && brick.IsAlive)
  27. {
  28. brick.IsAlive = false ;
  29. Collide (_ball, brick.Bounds);
  30. }
  31. }
  32. _ball.Position + = _ball.Velocity;
  33. }
* This source code was highlighted with Source Code Highlighter .


So what happened



Of course, the physics in the game, to put it mildly, no, sometimes the ball sticks when colliding with a moving racket. But, again, the meaning of this article is familiarity with the environment of Microsoft XNA Game Studio, and, I must say, it copes well with the routine, freeing up developer time to focus on the logic of the game.

Sources can be downloaded here.

Additional resources:
creators.xna.com
XNAWiki

Ps. Many thanks to ivv for the invitation.
Pps. Thanks for the karma, moved to XNA.

The text was prepared in Habra Editor

Source: https://habr.com/ru/post/77864/


All Articles