Lesson 8: Double Trouble (Part 2)
In this lesson we continue creating the BouncingBall class by moving the BouncingBall related logic into the class.
This lesson picks up right where lesson 7 left off.
Source code after completion of lesson 8: http://www.bluerosegames.com/xna101/lessons/XnaLesson8.zip
In Part 1, we cleaned up our code a bit by creating a BouncingBall class and moving the BouncingBall related data into that class. The fancy term for this is encapsulating the data in a class. However, it usually isn't enough to just encapsulate the data, we also need to move the BouncingBall related logic into methods of the BouncingBall class. If you notice from Part 1, we cleaned up the declarations in the Game1 class (reducing 4 declarations to 1) but the Game1.Update() method actually got uglier.
So in Part 2 we'll tackle the Update() method and clean it up.
Following the model that XNA has implemented where XNA calls the Update method of the Game1 class to update positions, etc. we can do the same thing for the BouncingBall class. We will tell the BouncingBall objects to Update themselves.
First define a new Update() method in the BouncingBall class. Inside the BouncingBall class block, add the following lines of code:
public void Update()
{
// TODO: Add the BouncingBall update logic here
}
Then replace the // TODO line with all of the ball1 related code from the Game1.Update() method (so everything in the method except the first and last statements) and then remove the "ball1." prefix from everything in the new code. We won't need the "ball1." references any more because this code is now part of our BouncingBall class and it knows which copy of the data to access.
Now the BouncingBall.Update() method should look like this:
public void Update()
{
// TODO: Add the BouncingBall update logic here
Position = Position + Velocity;
if (Position.X < 0 || Position.X > graphics.GraphicsDevice.Viewport.Width - spriteTexture.Width)
{
// If we get in here, we've hit a vertical wall
Velocity.X = -Velocity.X;
Position.X = Position.X + Velocity.X;
}
if (Position.Y < 0 || Position.Y > graphics.GraphicsDevice.Viewport.Height - spriteTexture.Height)
{
// If we get in here, we've hit a horizontal wall
Velocity.Y = -Velocity.Y;
Position.Y = Position.Y + Velocity.Y;
}
}
Uh oh, there's a couple of problems. The spriteTexture object is declared in the Game1 object, and we can't access it from inside the BouncingBall class directly. There are a few ways to handle this, but in this case let's create static Fields in the BouncingBall class to store the sprite texture and the game Viewport. Static Fields are Fields that can be accessed by all objects of that type and can be accessed from outside the class by specifying the class name instead of an instance of the class. So after the "public Vector2 Velocity;" declaration at the top of the BouncingBall class definition, add the following:
public static Texture2D Texture;
public static Viewport GraphicsViewport;
In the BouncingBall.Update() method, change all references to spriteTexture to Texture, and graphics.GraphicsDevice.Viewport to GraphicsViewport. So now the BouncingBall.Update() method should look like this:
public void Update()
{
// TODO: Add the BouncingBall update logic here
Position = Position + Velocity;
if (Position.X < 0 || Position.X > GraphicsViewport.Width - Texture.Width)
{
// If we get in here, we've hit a vertical wall
Velocity.X = -Velocity.X;
Position.X = Position.X + Velocity.X;
}
if (Position.Y < 0 || Position.Y > GraphicsViewport.Height - Texture.Height)
{
// If we get in here, we've hit a horizontal wall
Velocity.Y = -Velocity.Y;
Position.Y = Position.Y + Velocity.Y;
}
}
Now in the Game1 class, remove the spriteTexture definition from the class, since it will be stored in the static Texture Field in the BouncingBall class. Change the Game1.LoadContent() method to look like this:
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
BouncingBall.Texture = Content.Load<Texture2D>("red_ball");
BouncingBall.GraphicsViewport = graphics.GraphicsDevice.Viewport;
}
Note the use of the BouncingBall class name to set the static fields.
Inside the Game1.Update() method, we need to replace the code to update the ball's position with a call to the ball1 Update() method. So Game1.Update() should now look like this:
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();
ball1.Update();
base.Update(gameTime);
}
Now add a BouncingBall.Draw() method as follows:
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(Texture, Position, Color.White);
}
and in the Game1.Draw() method, change the spriteBatch.Draw() statement to this:
This is MUCH cleaner. Notice how little code there is now in the Game1 class. It makes the code easier to understand, and it will also be easier to manitain your code and to make changes later. Run the program and see that it still behaves the way it was behaving before.
Now for the second ball. EVERYWHERE in the Game1.cs file where there is a reference to ball1, copy the line and paste another line directly below the first, changing all "ball1"s to "ball2"s.
Run the program again. What? No difference? This is because both balls are being drawn in exactly the same locations. They have the same x and y positions and velocities. So after we create the ball2 object, let's change some of its data. At the bottom of the Game1.Initialize() method, after the line
ball2 = new BouncingBall();
add the following:
ball2.Velocity = new Vector2(10f, 5f);
Now the initial x velocity of ball2 will be 10, and the initial y velocity will be 5. Run the program again and you should see 2 balls bouncing around at different speeds.
I hope that now you can see the power of creating and using classes in your program. Basically any time you find yourself copying the same code you should ask yourself if there is some way you can create a class to simplify things.