Lesson 7 deals with creating your own classes. By creating your own classes, you can make it a lot easier to reuse your code and simplify your program.
This lesson picks up where Lesson 6 left off.
After this lesson, your code should look something like this: http://www.bluerosegames.com/brg/XnaLesson7.zip
Great, so there's a ball bouncing around the screen. Now what if we wanted 2 balls bouncing around the screen? Well we could duplicate all of the code we did for the one ball, declaring new variables spriteLocation2 and spriteVelocity2, and duplicating all of the conditional logic. Then what happens if we want to have 3 bouncing balls? Or 10? That would be a lot of copying and pasting, and there's a good chance you could make a mistake somewhere along the line.
If that isn't bad enough, then let's say we find a problem with the bouncing ball code, or we want the balls to behave differently. We would have to change that code in all 10 copies of the code.
Luckily, there is a good answer to this problem. Using object oriented design, we can encapsulate all of the data and logic related to the bouncing ball in a class definition. We can then create as many of them as we want and just refer to the methods and properties of the objects created from that class definition to do the work.
Naming Your Classes
Give your classes descriptive names, and always capitalize the first letter of the class name. Also captialize the first letter of each word in the class name, and if you have an acronym of 3 or more letters in the name, only captialize the first letter of the acronym.
Since this class is going to have the data and methods which control a bouncing ball, let's go out on a limb and call the class BouncingBall (I know, so original).
Defining the BouncingBall Class
It is good programming style to put each of your class definitions in a separate file. Do I sometimes put multiple class definitions in the same file? Yes, I admit it. Usually when I'm in a hurry and want to get something done quickly, but if things get too hairy I'll usually go back and split it up later.
Fortunately for us, Microsoft Visual C# Express has a bult in wizard to help us define a class.
In the Solution Explorer, right click on MyFirstGame and select Add->New Item. You should see a dialog similar to the following:
Under Visual Studio installed templates, select Class. Enter BouncingBall.cs for the Name (all C# class files require the .cs file extension). Similar to how a new project and corresponding files were automatically generated for us earlier, in this case the BouncingBall class file is generated giving us a good starting point for coding our BouncingBall class. Since the class wizard doesn't really know about XNA, we need to add a couple of "using" statements to the beginning of the BouncingBall.cs file:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
As stated previously, classes are made up of Methods, Properties, and Fields. For now let's concentrate on Fields and Methods. Our Fields store the data assocaited with a particular class. Each instance of the class, or each object created from the class template, stores its own copy of the data. So what data do we have that should be associated with the ball object? In this case it's fairly straightforward. We have the vector for the current x and y position of the ball, and the vector for the current x and y velocities.
Inside the BouncingBall class's block (inside the curly braces just below the "class BouncingBall" statement in the BouncingBall.cs file) add the following:
public Vector2 Position;
public Vector2 Velocity;
The keyword public before each of the Field declarations just tells C# that this data can be accessed by other objects in your program. If the keyword private was used instead, only methods of the object itself would be able to access the data. When Fields or methods are public, try to follow the standard C# naming conventions of capitalizing the first letter and then the first letter of each word.
Now as discussed earlier, there is a special method called a constructor. It has the same name as the class, and is used to initialize the object, or assign the initial values to the data. Notice above that we didn't assign values to the Fields when we declared them.
So again inside the BouncingBall class's block add the following:
public BouncingBall()
{
Position = new Vector2(0f, 0f);
Velocity = new Vector2(1f, 1f);
}
Now any time a new BouncingBall object is created, the initial values of its data will be 0 for the x and y position, and 1 for the x and y velocity.
Now to change the Game1 class to use this new class.
Remove the definitions in the Game1 class for spriteLocation and spriteVelocity. We will be using the Fields inside the BouncingBall object instead. Where those definitions were, add the following line declaring a BouncingBall object:
BouncingBall ball1;
In the Game1.Initialize() method, before the
base.Initialize();
statement, add the following line:
ball1 = new BouncingBall();
which creates a new BouncingBall object named ball1. When this statement executes, the BouncingBall constructor we defined earlier will execute, and so after this line of code, the data inside our ball1 object will be initialized.
Now to change our Game1.Update() and Game1.Draw() methods to use the ball1 object.
In the Game1.Update() method, change all "spriteLocation"s to ball1.Location and all "spriteVelocity"s to ball1.Velocity so that it looks as follows:
protected override void Update(GameTime gameTime)
{
// Allows the default game to exit on Xbox 360 and Windows
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// TODO: Add your update logic here
ball1.Position = ball1.Position + ball1.Velocity;
if (ball1.Position.X < 0 || ball1.Position.X > graphics.GraphicsDevice.Viewport.Width - spriteTexture.Width)
{
// If we get in here, we've hit a vertical wall
ball1.Velocity.X = -ball1.Velocity.X;
ball1.Position.X = ball1.Position.X + ball1.Velocity.X;
}
if (ball1.Position.Y < 0 || ball1.Position.Y > graphics.GraphicsDevice.Viewport.Height - spriteTexture.Height)
{
// If we get in here, we've hit a horizontal wall
ball1.Velocity.Y = -ball1.Velocity.Y;
ball1.Position.Y = ball1.Position.Y + ball1.Velocity.Y;
}
base.Update(gameTime);
}
Now in the Game1.Draw() method, change the x and y to ball1.X and ball1.Y so that the spriteBatch.Draw statement looks as follows:
spriteBatch.Draw(spriteTexture, ball1.Position, Color.White);
If you run the program again you'll see that it behaves just like it did before these changes. Not that exciting yet, but in part 2 of "Double Trouble", we'll tackle moving the Update logic into the BouncingBall class, and then show how this makes drawing 2 or more balls much easier.