| XNA Game-Themed CS1 Examples (XGC1) | |
|
Release 2.0 (XNA V3.1) |
|
References:
Goals:
1. Obtain the example code
When the game starts, you'll see a screen that
looks similar to this:

This looks (and plays) almost identically to game presented in the prior tutorial. The only change that the user can observe is that when the soccer ball bounces off a Block of Paddle, a sound is played.
In this tutorial, we'll see another example of how to inheritance from a class that you did not create yourself - we will make the Block inherit from the XNACS1Rectangle class, much like the SoccerBall was made to inherit from the XNACS1Circle class in the prior tutorial. We will also see that the Paddle class, which inherits from the Block class, now also (indirectly) inherits from teh XNACS1Rectangle class, as well. In other words, we can have more than one level of inheritance - we can build up an inheritance hierarchy, if we want.
We will also see method overriding, which is a way to replace a method in a derived class with a similar, but more specialized method. (This should not to be confused with method overloading, which is something different)
1. SoccerBall.cs, Game1.cs
These are effectively identical to what was presented in the prior tutorial.
2. Block.cs
In this tutorial, we're changing how the SoccerBall is set up, by having it inherit from the XNACS1Circle class. Remember that XNACS1Circle is something that's provided to you, as part of the overall XNACS1Lib library. You've never seen the source code for it, but your programs make use of it. We'll make use of it here, by having the SoccerBall inherit from it.
|
public
class Block
:
XNACS1Rectangle
{ |
The most important change to the code in this tutorial is again the
smallest. We tell the C# compiler that we want the Block class to
inherit from the XNACS1Rectangle class by adding
:
XNACS1Rectangle
Because of this change, the Block class (and the Paddle class, which is derived from Block!) can be thought of as a more specialized type of XNACS1Rectangle. This means that any place that we could have used an XNACS1Rectangle, we can now also use the more specialized Block, or the even further specialized Paddle. For example, we can now use the the current Block/Paddle object directly when checking to see if a SoccerBall has collided with a it (instead of using the Block's m_Rec variable for it's rectangle).
Because of this, we remove the m_Rec instance variable from the Block class.
Throughout the remaining code, any place that we would have written (for example)
hit = m_Rec.Collided(theSoccer);
we can now remove the m_Rec part, and simply write:
hit = Collided(theSoccer);
Again, this works because the Block class is now a (more
specialized) XNACS1Rectangle class, and therefore it inherits
all the methods, instance variables, and properties of it's
XNACS1Rectangle base class.
We won't go over all of these details here, since you should be able to follow these small changes on your own.
|
///
<summary>
/// Create
the a block a xPos, and random Y position (between Min_Y and Max_Y)
///
</summary>
///
<param name="xPos">X-Position
of the block.</param>
public Block(float
xPos)
{
// initialize instance variables
m_HitCount = 0;
m_IsActive = true;
// base class accessor (from
XNACS1Rectangle)
CenterX = xPos;
CenterY = XNACS1Base.RandomFloat(BLOCK_POSITION_MIN_Y,
BLOCK_POSITION_MAX_Y);
Width = BLOCK_WIDTH;
Height = BLOCK_HEIGHT;
Label = "Hit=" +
m_HitCount;
Texture = "Block"; } |
As was mentioned above, you'll notice that the "m_Rec" has been removed from everywhere in the constructor. Notice also that there isn't even a line that creates the new XNACS1Rectangle object anymore. This is because the Block itself is an XNACS1Rectangle object, and so by the time the constructor is run, the Block (including all the "XNACS1Rectangle parts" of the Block object) will have already been created.
You'll notice that in the previous tutorial we were able to set the
starting center point, width, and height of the rectangle object when we
created it (by using the XNACS1Rectangle's constructor method).
Here, we're not able to do that, so instead we'll need to set those
fields inside the constructor:
// base class accessor (from
XNACS1Rectangle)
CenterX = xPos;
CenterY = XNACS1Base.RandomFloat(BLOCK_POSITION_MIN_Y,
BLOCK_POSITION_MAX_Y);
Width = BLOCK_WIDTH;
Height = BLOCK_HEIGHT;
|
///
<summary>
/// Play
audio cue for collision. This is a virtual method because each types
of Blocks will
///
implement a different collision audio cue.
///
</summary>
///
<returns></returns>
protected
virtual void PlayCollisionCue()
{
XNACS1Base.PlayACue("Bounce"); } |
This is a new, virtual method that we're declaring here in the Block class. The purpose of this method is to play a short sound that provides audio feedback to the user that something has happened (this is sometimes called an audio cue, or just a cue for short). You'll notice that we call this method from the CollideWithSoccer method, at the end of the region of logic that gets executed when an active Block is hit with a soccer ball.
We declare this method to be virtual because intend to replace this method with another, more specialized version in the Paddle subclass.
The following line causes the program to play the audio cue:
XNACS1Base.PlayACue("Bounce");
"Bounce" is the name
of an audio file that the program can play. It's found in the
folder Content → Resources → Audio, as pictured
below. You're free to add any .WAV file here that you want,
and the play it from your program using the above line of code
(after you've modified the code to use your file's name, of course!)

3. Paddle
This is very similar to what was presented in the prior tutorial. You'll notice that because Paddle is derived from Block, and Block is now derived from XNACS1Rectangle (and because Block no longer has an m_Rec instance variable), that we need to remove any occurrences of m_Rec from the Paddle class, as well. Additionally, we override the PlayCollisionCue method:
What's new is the PlayCollisionCue method, which now overrides the one in the base class
|
///
<summary>
///
Override the audio cue for our specialize cue.
///
</summary>
///
<returns></returns>
protected
override void
PlayCollisionCue()
{
XNACS1Base.PlayACue("Paddle");
} |
This is a new method that we declare to be override, meaning that this method is intended to replace the inherited PlayCollisionCue method with a more specialized version of that same method. In order to create a overridden method here in the derived class, C# requires that we BOTH mark the inherited method as being virtual in the base class, AND mark this method as being override here in the derived class. Leaving out either one will result in a compilation error.
You'll notice that we're playing a different sound ("Paddle", instead of "Block") here.
4. What happens when the soccer ball collides with a basic Block
Let's examine what happens when a soccer ball collides with the Block in the middle of the screen.
The framework (XNA, and the XNACS1Lib library) call the UpdateWorld method in the Game1.cs file.
The program executes all the statements sequentially, until it gets to:
m_TheBlocker.CollideWithSoccer(m_TheBall);
At this point it calls the CollideWithSoccer method on the
m_TheBlocker object, which is Block object.
I know this seems both obvious and trivial, but it'll be important
later.
The CollideWithSoccer method is executed sequentially from top to bottom,
until it gets to:
PlayCollisionCue();
Instead of just calling the method, C# notices that the method is declared to be virtual on the Block class. At that point, C# asks "Is there a more specialized version that could be used? In particular, is there a more specialized version of the Block class?" It examine the Block class because this method was called on m_TheBlocker back in UpdateWorld. C# finds the one-and-only version of the PlayCollisionCue on the Block class, and calls that version (thus playing the "Block" sound)
5. What happens when the soccer ball collides with a Paddle
Let's examine what happens when a soccer ball collides with, say, the left Paddle
The framework (XNA, and the XNACS1Lib library) call the UpdateWorld method in the Game1.cs file.
The program executes all the statements sequentially, until it gets to:
m_LeftPaddle.CollideWithSoccer(m_TheBall);
At this point it calls the CollideWithSoccer method on the
m_LeftPaddle object, which is Paddle
object. I know this seems both obvious and trivial,
but it'll be important later.
The CollideWithSoccer method (on the Block
class) is executed sequentially from top to bottom, until it gets to:
PlayCollisionCue();
Instead of just calling the method, C# notices that the method is declared to be virtual on the Block class. At that point, C# asks "Is there a more specialized version that could be used? In particular, is there a more specialized version of the Paddle class?" It examine the Paddle class because this method was called on m_LeftPaddle back in UpdateWorld. C# finds the more specialized version of the PlayCollisionCue on the Paddle class, and calls that version (thus playing the "Paddle" sound)
FURTHER EXERCISES::