+ Reply to Thread
Results 1 to 2 of 2

Thread: The XNA Tutorial - Part 2

  1. #1
    semprance is offline Programmer
    Join Date
    Jan 2010
    Posts
    126
    Rep Power
    0

    The XNA Tutorial - Part 2

    XNA Tutorial – Part 2
    Welcome back to The XNA Tutorial, sorry it’s been so long! More than 5000 words later, you’d think I was done. You’d think that was all that there is... but no! Our Pong clone is looking awesome (well, as awesome as three white oblongs on a black background can look) and it’s time to add some new features.

    Here’s a roundup of what we’ll be adding to our game in this tutorial:

    • A screen manager.
    • A system for keeping and displaying the score for each player.
    • The ability to play sounds.
    • A main-menu-style screen.

    We’ve got to start somewhere and I guess the first option is the best. A screen management system is an incredibly important element in most games and, yes, we probably should have dealt with this earlier but better to learn to walk before trying to run.

    Rather, than go in to great detail about screen management now, let’s get set up. You’re going to need some things before we can start. First, if you’re for some reason starting this tutorial without having completed Part 1, and you don’t have XNA or Visual Studio installed, then go back to Part 1 of the tutorial and follow the setup instructions at the beginning. Second, if you have XNA installed but didn’t complete Part 1 of the tutorial, or would just like an organised copy, then download the file PongTutorial.zip in Part 1 of the tutorial (I’ve also made this available at the bottom of this tutorial – it is named ‘Part1-PongTutorial.zip’).

    Finally, you need to open up the PongTutorial solution in Visual Studio. Now we’re ready to start coding again!

    Screen Management Is Boring

    Yes, screen management is boring. However, it’s a necessary evil and gives greater definition to the structure of our game. Let’s start by creating a new class. I’ll say now, I’m not going to reiterate how to perform some of the simple tasks that I went through in Part 1 so if you’re not sure how to do something, open up Part 1, hit Ctrl+F and search for how to do it. Now, create a new class in the normal manner and name it ‘ScreenManager.cs’. Naturally, you will have a file that looks like this:

    Code:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace PongTutorial
    {
     class ScreenManager
     {
    
     }
    }
    A quick, reminder – don’t forget to add in your ‘using’ statements! Moving on, this class is going to need to inherit from the DrawableGameComponent class, just like our GameScreen class did. So change the class definition to this:


    Code:
     class ScreenManager : DrawableGameComponent
    Simple enough. Now let’s create a constructor for the class. In the body of the class, insert the following:

    Code:
     public ScreenManager(Game1 g)
     : base(g)
     {
     }
    Now, although we need to continue building this class, there is another class we need to exist first. Create a new class and name it ‘Screen.cs’ then add in the normal ‘using’ statements. The Screen class and the ScreenManager class have a mutual dependence upon each other in that one is effectively useless without the other, but together they work in harmony.

    Unfortunately, this is where things start to get a bit tricky. In Part 1 of the tutorial, we made our GameScreen class the main platform on which our game was built. This will no longer be the case as the ScreenManager now needs to be the bottom layer of our game. Annoying? Yes, but revising and improving code is just as important as writing it in the first place.

    I like metaphors and analogies so here’s one to explain the ScreenManager: Imagine that you have a TV show with one filming set (GameScreen) and a static camera. Now imagine you introduce a few more film sets in to your show. Humbug! Now you need two more cameras! Well, the ScreenManager class is the equivalent of giving your camera wheels. The ScreenManager allows you to decide which set (or sets) are in action at any given time and gives you an easy way of changing the state of different screens.

    Right, let’s move on. Now we have our Screen class, we first need to make it abstract, so do so in the normal manner (as covered in Part 1). Next, we need to add the following members to the body of the class:
    Code:
     protected Game1 GAME_REF;
     public bool Active;
    The first member is likely to be familiar. As for the second member, the name makes it fairly self explanatory – it literally refers to whether the screen is currently active or inactive. By this, I mean whether the screen is being updated and drawn.

    Next we need to add some methods. Add the following underneath the members:
    Code:
     public abstract void Update(GameTime gameTime);
    
     public abstract void Draw(GameTime gameTime);
    As you’ve probably already noticed, the first member and both methods of this class are present in the GameScreen class. This means that we need to make some changes to our GameScreen class. First DELETE this line from the class:

    Code:
    private Game1 GAME_REF;
    Then change the class declaration so it looks like this:

    Code:
     class GameScreen : Screen
    As you can see we’ve removed our GAME_REF member and changed our class so it inherits from the Screen class instead of the DrawableGameComponent class.

    Now, you’ll also need to remove the call to the base constructor from the constructor declaration (it should be on the second line. You’ll also need to remove the calls to the base class methods in the Update and Draw method. Again, you should know how to do both these things, but if you don’t I can simplify it slightly more: go through the class and remove any line with the word base in it.
    Finally we need to make one less obvious change. Find the line in the Draw method that refers to the method Clear called from the Graphics Device object and change it so it appears as follows:

    Code:
     GAME_REF.GraphicsDevice.Clear(Color.Black);
    Those of you who’ve been paying close attention will realise that, now that we’re not inheriting from DrawableGameComponent, the handy class that gives us access to much of the Game1 class, we no longer have direct access to the GraphicsDevice. However, we can still access that through our GAME_REF so it’s not all that bad :-)

    Now we have one last change to make. Open up the Game1 class and find the line where we add our GameScreen as a component. As GameScreen no longer inherits from DrawableGameComponent, it can no longer legally be a member of the Components list. However, don’t delete the line just yet. Instead, change it to the following:

    Code:
     Components.Add(new ScreenManager(this));
    Awesome! With our revised code we now have a game that’s just as well built as before, but now we have the ability to insert more screens as we see fit. Sadly, if you press the debug button now, you’ll likely be presented with an empty screen. This is because our ScreenManager class is still virtually empty.

    The ScreenManager needs to be able to store screens in a way that allows us to manipulate them easily. An array seems like a good choice for this but it’s better to use a List. The List class is a collection type that is just **** awesome. Seriously, if you’ve had any experience with C# and haven’t used the List type then go and read up on it now because when it comes to games, it’s the type of choice for dealing with your game entities, plus it has endless applications and will probably be useful in nearly every program you every create.

    Right, now that you’re convinced, add the following lines above the constructor in the ScreenManager class:

    Code:
     List<Screen> screens;
     public List<Screen> Screens { get { return screens; } }
    ...and inside the constructor, add the following:

    Code:
     screens = new List<Screen>();
    
     screens.Add(new GameScreen(g));
    
     screens[0].Active = true;
    In this second piece of code, we initialise our List, we add a GameScreen, then, we make it the active screen. Currently, our GameScreen isn’t going to do anything. When it was a DrawableGameComponent, the Draw and Update methods were called implicitly by the Game1 class. Now, they need to be called explicitly within the ScreenManager. First, override the Update and Draw methods in the ScreenManager class. We did this in Part 1 so naturally you’re a pro at it now ;-) Second, add the following code to the top of the Update method:

    Code:
     foreach (Screen s in screens)
     if (s.Active)
     s.Update(gameTime);
    ...and third, add this (almost identical) piece of code to the top of the Draw method:

    Code:
     foreach (Screen s in screens)
     if(s.Active)
     s.Draw(gameTime);
    Great, now just hit the Debug button to make sure that it compiles and hey presto! We have the exact same game we had at the end of Part 1 of the tutorial! Admittedly, this is not very impressive but most of the progress we’ve made has been under the surface rather than having an effect on presentation or gameplay. Anyhow, it’s time to move on and introduce another screen!

    I’m a Compulsive Liar

    Yes, I tricked you again, it’s what I do; you should seriously be used to this by now. Instead of creating another screen, we are going to try adding some sound to our game. To start, we need some sort of class to manage the playing of sounds (you can totally see where this is going) so create a new class name SoundManager.cs and if necessary open it up from the Solution Explorer.

    Playing sounds is fairly simple but it’s best to have some measures in control. First, make the class abstract. Once more, you should be a pro at this by now. Add the usual ‘using’ statements and also add this one:

    Code:
    using Microsoft.Xna.Framework.Audio;
    This library’s purpose is given away by its name. Now we need to add some members to the class:

    Code:
     private static bool isBusy = false;
     private static SoundEffectInstance effectInstance;
    There’s a new type here, but I’ll explain in a minute. Next add the following two methods:

    Code:
     public static void PlaySound(SoundEffect effect)
     {
     if (!isBusy)
     {
     effectInstance = effect.CreateInstance();
     effectInstance.Play();
     }
     }
    
     public static void Update(GameTime gameTime)
     {
     if (effectInstance != null && effectInstance.State == SoundState.Playing)
     {
     isBusy = true;
     }
     else
     {
     isBusy = false;
     }
     }
    There we have it, our SoundManager class is complete. So let’s review: we’ve encountered two new types, an enum, and some methods and properties associated with them. Our new types are SoundEffect and SoundEffectInstance. SoundEffect is to audio what Texture2D is to graphics. We use the ContentManager in the same way as with Texture2D objects to load a sound into a SoundEffect object. SoundEffectInstance is a type that allows more control over a SoundEffect object, such as looping, applying various changes, and state-checking (which is what we’ve used it for). Our PlaySound method will allow us to play sounds effectively, and the overall setup of the class will help us to make sure that the individual sounds don’t clash when playing (as only one sound is able to play at a time). Anyhow, I’ll talk more on that later.

    Next, we need to make sure our SoundManager is updated, but first there’s another change we need to make because I forgot to do so earlier :-D Open up the GameScreen class and move the following line to the top of the Update method in the ScreenManager class:

    Code:
     InputManager.Update();
    We need to do this because we want our InputManager to update all the time, not just when the GameScreen is active. Next add the following line underneath the above code:

    Code:
     SoundManager.Update();
    Cool, now both our Manager classes update. Cool, now we can start adding the playing of sounds in to our game. First we need a sound asset though. At the bottom of this post you’ll find a zip file named Part2-Resources.zip that you’ll need to download (it also includes all the resources from Part 1). Save it somewhere, then open it and unpack a file named boop.wav. Next, add it to the Content folder of our project, in the Solution Explorer, just like we did with the ball and paddle graphics in Part 1 of the tutorial.

    Now, open up our GameBall class and add a ‘using’ statement for the audio library, as we did in our SoundManager class. Next, add the following member to the existing ones at the top of the class:

    Code:
     SoundEffect collisionSound;
    Next we need to make a change to the parameters of the constructor. Change it so it reads as follows:

    Code:
     public GameBall(Texture2D sprite, Vector2 position,
     GameComputer computer, GamePlayer player, SoundEffect effect)
    Of course, we need to assign our new member with this new parameter so, if you haven’t already done so intuitively, add the following to the bottom of the constructor:

    Code:
     this.collisionSound = effect;
    Now we need to go and make the appropriate changes in the GameScreen class. Open it up and add a ‘using’ statement for the audio library, then change the line where the GameBall is instantiated to this:

    Code:
     ball = new GameBall(
     ballTexture,
     new Vector2(
     (float)(container.X + container.Width / 2 - ballTexture.Width / 2),
     (float)(container.Y + container.Height / 2 - ballTexture.Height / 2)
     ),
     computer,
     player,
     GAME_REF.Content.Load<SoundEffect>("boop")
     );
    As you can see, all we’ve done is added the new parameter to the constructor so that we load in our sound correctly. Awesome, now we can start making the sound play!

    Go back in to the GameBall class and find the Collision method, we’re going to make a few changes:

    Code:
     public void Collision()
     {
     //Check if ball has hit the top or bottom of the screen.
     if (position.Y <= GameScreen.Container.Y)
     {
     directionUp = false;
     SoundManager.PlaySound(collisionSound);
     }
     if (position.Y >= GameScreen.Container.Y + GameScreen.Container.Height - sprite.Height)
     {
     directionUp = true;
     SoundManager.PlaySound(collisionSound);
     }
    
     //Update/create collision rectangles
     playerRect = new Rectangle((int)player.Position.X, (int)player.Position.Y, player.Sprite.Width, player.Sprite.Height);
     computerRect = new Rectangle((int)computer.Position.X, (int)computer.Position.Y, computer.Sprite.Width, computer.Sprite.Height);
     ballRect = new Rectangle((int)this.position.X, (int)this.position.Y, this.sprite.Width, this.sprite.Height);
    
     //Check for collision - If so, change direction.
     if (ballRect.Intersects(playerRect))
     {
     directionLeft = false;
     SoundManager.PlaySound(collisionSound);
     }
     if (ballRect.Intersects(computerRect))
     {
     directionLeft = true;
     SoundManager.PlaySound(collisionSound);
     }
    
     //Check for ball leaving screen.
     if (position.X - sprite.Width <= GameScreen.Container.X
     || position.X >= GameScreen.Container.X + GameScreen.Container.Width)
     this.Reset();
     }
    We’ve add one new line of code in four different places. This is so that, whenever a collision occurs, our ‘boop’ sound will play. Time to debug the code! Once again, if any errors appear, refer to the instructions in Part 1 for help. Now, hit the Debug button and check that our sounds play correctly. If all goes well it should play every time the ball bounces. Brilliant! Time to move on to adding a menu screen to our game :-)

    We Don’t Need A Main Menu...

    Yes, admittedly, we don’t need a main menu, but let’s just put one in anyway. After all, this is a learning exercise. Right then, first create a new class file named MenuScreen.cs in the usual manner. Second, add the usual ‘using’ statements. Third, change the class declaration so that the class inherits from Screen (as with the GameScreen class).

    First we’ll add a member so add the following to the body of the class:

    Code:
     Texture2D titleSprite;
    Second, open up the Resources zip file from earlier and add the file ‘mainmenu.png’ to the Content folder in the usual way. Third, add the following constructor to the class:

    Code:
     public MenuScreen(Game1 g)
     {
     GAME_REF = g;
     GAME_REF = g;
     titleSprite = GAME_REF.Content.Load<Texture2D>("mainmenu");
     titleContainer = new Rectangle(
     (int)(0 + (g.GraphicsDevice.Viewport.Width / 2) - (titleSprite.Width / 2)),
     (int)(0 + (g.GraphicsDevice.Viewport.Height / 2) - (titleSprite.Height / 2)),
     titleSprite.Width, titleSprite.Height
     ); }
    Cool, now override the Draw and Update method just as we did in the GameScreen class. Once you’ve done this add the following to the Draw method:

    Code:
     GAME_REF.sb.Begin(SpriteBlendMode.AlphaBlend);
     GAME_REF.GraphicsDevice.Clear(Color.Black);
     GAME_REF.sb.Draw(titleSprite, titleContainer, Color.White);
     GAME_REF.sb.End();
    This will make our graphic draw in the centre of the screen (we’ll load the graphic from the Content folder later). Now, we have a problem, we have no way of telling the ScreenManager to change screens. Now there are two ways we can tackle this: we can provide a reference to the ScreenManager to every screen (which seems a bit overkill), or we can provide access to it through our Game1 class (which all screens already have access to). Obviously we’re going with the second option. To do this, once again we must revise our code. Open up the Game1 class and add the following member and property to the top of the class:

    Code:
     ScreenManager sManager;
     public ScreenManager screenManager { get { return this.sManager; } } 
    Next change the contents of the Initialize method, to this:

    Code:
     sManager = new ScreenManager(this);
     Components.Add(sManager);
    But, oh no! Now we’ll have accessibility issues! These occur due to the class used in a property declaration being less accessible than the property itself. Open up the ScreenManager class and change the class declaration to the following:

    Code:
     public class ScreenManager : DrawableGameComponent
    Next, open up the Screen class and change its class declaration to the following:

    Code:
     public abstract class Screen
    Cool, now we have easy access to the screen manager from our screens. Now we can continue with our MenuScreen. We now need to add some logic to allow the game to start when the space bar has been pressed. Insert the following code in to the Update method of the MenuScreen class:

    Code:
     if (InputManager.KeySpace)
     {
     GAME_REF.screenManager.Screens[1].Active = true;
     this.Active = false;
     }
    Here we’re activating the GameScreen and deactivating the MenuScreen when the user presses the space bar. Now we need to make a quick change so that the menu screen appears before the game screen does. Open up the ScreenManager class and change the entire contents of the constructor to the following:

    Code:
     screens = new List<Screen>();
    
     screens.Add(new MenuScreen(g));
     screens.Add(new GameScreen(g));
    
     screens[0].Active = true;
     screens[1].Active = false;
    Cool, now we can see our new screen in action. Hit the Debug button and, if all goes well, you should be presented with the main menu screen. Hit space bar and the game should begin. Awesome! Our game now has a little more substance to it. Currently though, our game never ends and there is no way for users to return to the title screen. This is because our game has no scoring system and so there is no end point at which either the player or computer wins. I guess it’s time to ad one in.

    And The Winner Is...

    Well, I should hope this won’t take to long. After all, the revisions we’ve made throughout this tutorial have made our code much more robust. We’ll start by creating a class for managing the scores, which we will name – you guessed it! – ScoreManager.cs so create this file in the usual way. Next add the following properties:

    Code:
     public static int PlayerScore { get; set; }
     public static int ComputerScore { get; set; }
    Cool now let’s initialise these in the GameScreen class. Add the following to the GameScreen constructor:

    Code:
     ScoreManager.PlayerScore = 0;
     ScoreManager.ComputerScore = 0;
    Awesome now we need to make sure the score increases each time the ball goes behind a paddle. Open up the GameBall class and find the Reset method. Change the method so it appears as follows:

    Code:
     private void Reset(int p)
     {
     //Reset the position to the centre of the screen.
     position = new Vector2(
     (float)(GameScreen.Container.X + GameScreen.Container.Width / 2 - sprite.Width / 2),
     (float)(GameScreen.Container.Y + GameScreen.Container.Height / 2 - sprite.Height / 2)
     );
    
     //Flip directions.
     directionLeft = !directionLeft;
     directionUp = !directionUp;
    
     if (p == 1)
     ScoreManager.PlayerScore++;
     else if (p == 2)
     ScoreManager.ComputerScore++;
     }
    As you can see we’ve added a parameter to the method and added an if statement to the bottom that increases the score based on the value of the parameter. For this work we need to pass the correct parameter in calls to this method.

    To do this, find the ‘if’ statement that contains the call to the Reset method near the bottom of the Collision method and change it so it reads as follows:

    Code:
     if (position.X - sprite.Width <= GameScreen.Container.X)
     this.Reset(2);
     if (position.X >= GameScreen.Container.X + GameScreen.Container.Width)
     this.Reset(1);
    Now we’ve made it so that, when the ball passes off the screen, the correct player scores a point. Next we need to display the score on the screen. To do this we need to use a type we haven’t seen before – the SpriteFont. This class provides an interface between installed system fonts and the texture drawing components of our game. First we need to create a SpriteFont file:

    1. Right-click the Content folder in the Solution Explorer.
    2. Highlight ‘Add’, then select ‘New Item...’
    3. Select ‘Sprite Font’ and name the file ‘font.spritefont’.

    Our new file should open up automatically, and you should be presented with an XML-style layout. If you’re not familiar with XML, it’s basically just a way of representing data. The two items we are concerned with are ‘FontName’ and ‘FontSize’.

    Now, for those of you who are familiar with XML, spritefonts are just basic XML files which can be serialised in to SpriteFont objects. The loading of the font and conversion into Texture2D is performed by the Content Processor and the object itself so we don’t have to worry about it.

    Now, in our file you’ll see the name ‘Kootenay’ between the FontName tags. Change it so the line appears as follows:

    Code:
     <FontName>Courier New</FontName>
    Now change the line with the FontSize tags below, so that it appear as follows:

    Code:
     <Size>32</Size>
    That’s all! Now close the spritefont file and open up the GameScreen file. Add the following member to the existing ones in this class:

    Code:
     private SpriteFont font;
    ...then add the following to the bottom of the constructor:

    Code:
     font = g.Content.Load<SpriteFont>("font");
    Nice, now we have an easy way of printing the score to the screen. To do this we once again use the SpriteBatch, but instead of the Draw method, we call the DrawString method. Simple enough right? Of course it is, I’m not even kidding, the only complicated part of the next bit is getting the positioning spot on and even that’s not so hard. Add the following above the call to the End method, near the bottom of the Draw method:

    Code:
     GAME_REF.sb.DrawString(font, ScoreManager.PlayerScore.ToString(),
     new Vector2(
     0 + Container.Width / 2 - font.MeasureString(ScoreManager.PlayerScore.ToString()).X - 20,
     10),
     Color.White);
     GAME_REF.sb.DrawString(font, ScoreManager.ComputerScore.ToString(),
     new Vector2(
     0 + Container.Width / 2 + 20,
     10), 
     Color.White);
    Now these DrawString calls probably look scarier than they are. We’re just telling the SpriteBatch to draw the scores on the correct sides of the screen, and at the top, and to keep the scores centred at their positions.

    We’re almost at the end! One last thing to do, make sure that the game ends when one of our players hits 10 points. Add the following method to the bottom of the ScreenManager class:

    Code:
     public void CheckForWinner()
     {
     if (ScoreManager.ComputerScore == 10 || ScoreManager.PlayerScore == 10)
     {
     screens[1] = new GameScreen((Game1)Game);
    
     screens[0].Active = true;
     screens[1].Active = false;
     }
     }
    Last, add a call to the method in the Update method for the class. I’m pretty sure I don’t need to show you how to do this ;-)

    And that’s all for this time! Seriously, that’s all I’m going to show you. Sure we could add more stuff to the game but that’s your job. Now I’ll leave you with a list of things for you to try adding to the game yourself:

    • A Win/Lose screen that appears at the end of the game.
    • A line down the middle of the screen to separate the playing field.
    • A sound that plays when the player or computer scores a point.
    • A sound that plays when the game ends.
    • ADVANCED: An options screen that can be accessed from the Main Menu, in which the user can turn sounds On or Off.

    Enjoy yourselves, and I’ll be back soon with some additional material to help you build your own creations!

    So, until next time...
    Last edited by Roger; 01-28-2011 at 09:56 PM.

  2. CODECALL Circuit advertisement
    Join Date
    Always
    Posts
    Many

     
  3. #2
    Tesulakata's Avatar
    Tesulakata is offline Newbie
    Join Date
    Aug 2010
    Posts
    1
    Rep Power
    0

    Re: The XNA Tutorial - Part 2

    Thanks you so much!
    I try it

+ Reply to Thread

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Similar Threads

  1. The XNA Tutorial - Part 1
    By semprance in forum CSharp Tutorials
    Replies: 5
    Last Post: 09-25-2010, 06:22 AM
  2. Unity 3d tutorial - part 2
    By Blimp in forum Game Design Tutorials
    Replies: 4
    Last Post: 06-14-2010, 04:39 PM
  3. The XNA Tutorial - Part 2
    By semprance in forum Game Design Tutorials
    Replies: 0
    Last Post: 04-07-2010, 07:52 AM
  4. The XNA Tutorial - Part 1
    By semprance in forum Game Design Tutorials
    Replies: 0
    Last Post: 03-30-2010, 08:29 AM

Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts