XMLpitstop.com   |  VBnetexpert.com   |  Community Credit  
 
 
Pitstop Search:  
in
 
Sign in | Join | Help
 
 
  Blog
    Home  
 
  Entries By Date
 
<September 2008>
SunMonTueWedThuFriSat
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011
 
 
  Blog Categories
   
 
  Archives
    November 2008 (2)  
    September 2008 (3)  
    August 2008 (3)  
    June 2008 (4)  
    May 2008 (2)  
    April 2008 (3)  
    March 2008 (3)  
    February 2008 (5)  
    December 2007 (4)  
    November 2007 (1)  
    October 2007 (3)  
 
  Syndication
    RSS  
    Atom  
    Comments RSS  
  .NET Flea Market  
 

Zune XNA Example 2

The last post was really long and I think I tried to do too much in one post.  So, here's something smaller.

In the last example, we created a custom class for a stack of cards.  One thing I want to point out about that class is that it had a shuffle method.  Said another way, the class was in charge of shuffling the cards inside it.  The XNA game that used the card stack didn't contain the logic for shuffling the cards.  That's one of the keys of creating objects - they take care of themselves.  To reinforce that concept, here's a game that has no playability.  Here's the game description:

The game will display a dot on the screen.  The dot will be a random color and random size, and will appear in a random location.

Objects?  I only read "dot". "Screen" is part of the game, so we already have that object built in. Properties?  Dot seems to have a color, size and location.  The term "random" refers to the value of those properties, so we don't need to concern ourselves with using that as a property.  We will need to remember it when we write the code though.  Methods? None that are mentioned.  But the way we are going to write this, we are going to have the dot draw itself, without any involvement from the game.  So the dot knows where it is, what size it is, and what color it is.  It seems perfectly capable of drawing itself.

Into the code.  This is our RandomDot class:

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

namespace DrawRandomDots
{
    class RandomDot:DrawableGameComponent
    {
        Random _rndm;
        Game _parent;
        SpriteBatch _spriteBatch;
        List<Color> _availableColors;
        Texture2D _dot;
                
        public RandomDot(Game game):base(game)
        {
            _rndm = new Random();
            _parent = game;
            _availableColors=new List<Color>();
        }

        protected override void LoadContent()
        {
            _spriteBatch = new SpriteBatch(_parent.GraphicsDevice);
            _dot=_parent.Content.Load<Texture2D>("Dot");
            _availableColors.AddRange(new Color[]{Color.Blue,Color.DarkBlue,Color.DarkGreen,
                Color.DarkRed,Color.Green,Color.LightBlue,Color.Magenta,Color.Navy,
                Color.Orange,Color.Purple,Color.Red,Color.White,Color.Yellow});
              
            base.LoadContent();
        }

        public override void Draw(GameTime gameTime)
        {
            Rectangle size;
            Color color;
                        
            size = new Rectangle(_rndm.Next(0, 240), _rndm.Next(0, 320), _rndm.Next(1, 10), _rndm.Next(1, 10));
            color = _availableColors[_rndm.Next(0, _availableColors.Count)];

            _spriteBatch.Begin();
            _spriteBatch.Draw(_dot, size, color);
            _spriteBatch.End();
            
            base.Draw(gameTime);
        }
    }
}

It's not a lot of code.  Here's the line-by-line:

Line 1-5 (using...): These are namespaces we are going to be using in our class.  Because we put them in using statements, we don't need to type all that out when we include the type name.  Notice I brought in some XNA namespaces, which is something we didn't need in the CardStack class.  Because this class is actually going to be drawing, we will be using classes in these namespaces.

Line 7 (namespace...): This is the namespace for our custom class.  Like I mentioned before, this is the name of the project I'm working in.  If you create a project under a different name, you need to update this to match.

Line 9 (class...): This is the beginning of our class definition.  It's called RandomDot.  The colon indicates we are inheriting from DrawableGameComponent, so we're going to get a lot of code for free.  DrawableGameComponent is part of the Microsoft.XNA.Framework namespace, which is included in the using statements above.

Line 11 (random...): This is the variable for the random number generator we'll use for the color, location, and size.  Our game description specifically says random.

Line 12 (Game...): This variable will hold the game that the dot is being included in.  I used the name "parent" to emphasize a point.  The dot class cannot exist on its own.  It is dependant on a game.  Without a game to draw the dot in, the dot class cannot be drawn.  Similar to how a person cannot be created without a parent, the dot class cannot be created without a game. 

Line 13 (spritebatch...): This variable will hold the spritebatch that we draw to.  Because this class is going to handle drawing itself, we need a spritebatch to draw to.

Line 14 (List...): This variable will hold the colors that the dot can be.  We will select one color at random from this list.

Line 15 (Texture2D...): This variable will hold the graphic that we will draw to the spritebatch.

Line 17 (public...): This is our constructor for the class.  Unlike the cardstack class, this class has only one constructor and it requires that a game object be passed into it.  By doing this, we enforce the rule that this class cannot exist without a game.  The colon and "base(game)" code show that the base class, which is DrawableGameComponent, needs to receive the game object as well.  If you do not include this on the constructor, you'll get a "...does not contain a constructor that takes '0' arguments" error.

Line 19 (_rndm...): We instantiate our random generator.  If we don't, we get "object reference not set" errors.  This is the most common error you will probably experience, so it's always important to ensure your objects get instantiated.

Line 20 (_parent...): We are going to hold on to a reference to the parent game, because other methods in our class will need it.  So we store it in a variable.

Line 21 (_availablecolors...): We instantiate our list so we can start adding colors into it.

Line 24 (public...): This is a method that is provided by DrawableGameComponent, but we want to add additional code into it.  To do that, we use the keyword "override".  That tells .NET: "when using this class, run this code instead of the usual code."  In Visual Studio it's easy to do overrides, you type in "override" and Intellisense gives you a list of methods you can override.  Pick one and the proper code is written for you.

Line 26 (_spritebatch...): We're in the LoadContent method, which only gets run once.  So we are going to create the objects that we need to draw the dot.  We need a spritebatch to draw to.  We can get this from the game that the dot belongs to, if we had passed it in from the game, or we can create a brand new one from the GraphicsDevice of the game.  We're going to to the latter, but there are drawbacks to doing it this way.  For such a simple game, it's not a concern.  Notice we use the _parent variable to reference the game that the dot belongs to.

Line 27 (_dot...): Here we load the image that will be our dot.  Again, we are using the functionality the game provides us.  This is a single pixel image called dot.png.

Line 28-30 (_availablecolors...): Here we are assigning the colors to the list.  This is a shorthand way of putting in a bunch of items into a list.  The code "new Color[]{... ... ...}" creates an array of colors.  That array isn't assigned to any variable.  That code is within the statement ".AddRange()", so the array is sent into the AddRange method which adds all the array items into the list.  This is another case where you need to read the line from inside out.

Line 32 (base...): In line 24, I said that we are replacing the usual code with our own using "override".  But DrawableGameComponent has a lot of functionality in it for this method already.  We don't want to have to rewrite it all; we want to use it.  In this line we are calling the LoadContent method in DrawableGameComponent, so we're not losing anything.  So what we've done it not so much replace the functionality of LoadContent, but more enhance it with additional code.

Line 35 (public...): Here's another case where we want to inject additional code into the generic DrawableGameComponent.  So we override the Draw method also.  The Draw method has a parameter for the current game time.  When we override it, we have to include that parameter also.  When we override, the parameters must be just like the base class.

Line 37 (Rectangle...): We need a variable to hold the size and location of the dot.  The rectangle class will do both for us together.  If we just wanted location, we could use Vector2

Line 38 (Color...): This variable will hold the color of the dot.  Sometimes in the code, you'll see some shorthand versions used and others we'll assign the value to a variable when we use it.  If we're going to assign it to a variable, it's usually because we need to use that value more than once, or by using an extra variable, we add clarity to the code.  In this case, we are doing the latter.

line 40 (size=...): Here we create the rectangle to hold the location and size of the dot.  Notice the X and Y coordinates are the width and height of the screen and the width and height of the rectangle are 1 through 10.  Each value is randomly selected.

Line 41 (color=...): Here we pick a color at random from the list of available colors.  Notice the upper limit of the random.Next statement is the count of the list.  This means we can add or remove colors from the list without having to change this line.  If we hard-coded 10 in there (because we put 10 colors in the list), we'd have to change it if we altered the size of the available colors list.  Is this hypocritical that the width and height used in the screen size in line 40 are hard-coded values yet I'm saying hard-coded values are bad?  Absolutely.  You can get the current screen size from XNA, and maybe we'll do that later.

Line 43 (spritebatch...): Before writing to a spritebatch, we need to call Begin on it.

Line 44 (spritebatch...):  Here we draw the dot texture at the specific location in the specific color.  Notice in line 38 how I mentioned clarity?  Imagine replacing the variables with all the code from line 40 and 41.  The extra variables are worth it.  Also, notice we are specifying what color to draw the dot?  But our dot is an image and it has a color already, right?  Yes, but if your image is just white, the color parameter tints the image in the color you specify.  So you can have a solid color image and draw it in any color as long as the original texture is white.  So you don't need a green dot, a red dot, a blue dot.  You just make a white dot and draw it in the color you want.

Line 45 (spritebatch...): When we are done drawing to a spritebatch, we need to call End.

Line 47 (base...): Like line 32, we want the regular code for the Draw method of DrawableGameComponent to run as well, so we call that here.  You don't have to always have this line at the end.  You may want the base code to run first or somewhere in the middle of your additional code.  In these cases, we run it last.

So now you have a class that draws itself.  You'll see that in the game code, there is actually very little we have to write now.  Here's the code for the game:

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;

namespace DrawRandomDots
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        RandomDot dot;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            dot = new RandomDot(this);
            this.Components.Add(dot);
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(Color.Black);

            base.Draw(gameTime);
        }
    }
}

Once again, I'll only discuss the additions I made to the Zune game template.  I added a dot.png to the Content folder of the project.  The image is just a single white pixel.

Line 15 (RandomDot...): Here is the variable for our custom class.  We're only going to have one for right now.

Line 21 (dot=...): We need to instantiate our class so we can use it.  We pass in the current game in the constructor by using "this".

Line 22 (this...): This is something new.  We are going to add the random dot as a component of the game.  This means that it will get drawn automatically for us.  If you think of any normal application, like Microsoft Word, can you imagine how hard it would be to write the main program if you had to maintain all the little pieces like menus, toolbars, rulers, status bars, popup messages?  Instead, these individual pieces are built independently and they manage themselves.  That's what you have done.  You have built a component that manages itself.

That's it.  Only three changes.  Well, I changed the background color to black in line 40, too.  But the point is, because the RandomDot class handles drawing itself, the main program does not need to.  As you make games that have lots of different parts, you will be crushed under the weight of the code in the Draw method is you try to do it all in the main game class.  So when you're writing a card game, the cards should draw themselves.  But wait, how will the cards know where to be drawn?  If there's five cards, how will they know about each other so they can choose a location that doesn't overlap with each other?  We'll do that in a later post, but the quick answer is: another class that has a list of cards inside it.  That class knows about the five cards.  The cards don't know and will never know about each other.

Comments

No Comments

About anachostic

That's me. Seek and ye shall find.
 
 
Copyright © . All Rights Reserved.
Powered by Community Server (Commercial Edition), by Telligent Systems