| 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:

The game behaves in a manner that is identical to the what was described for the previous tutorial, with a couple of new features:
Let's examine the source code, feature by feature
CheckWorldBound():
|
///
<summary> /// Check the center position of the soccer ball, if it is outside /// of the world bound, flip the ball velocity accordingly /// </summary> private void CheckWorldBound() { XNACS1Lib. BoundCollideStatus status = CollideWorldBound(m_TheSoccer);switch (status) { case BoundCollideStatus.CollideTop: case BoundCollideStatus.CollideBottom: m_TheSoccer.VelocityY = -m_TheSoccer.VelocityY; PlayACue( "Wall");break; case BoundCollideStatus.CollideRight: case BoundCollideStatus.CollideLeft: m_TheSoccer.Center = new Vector2(BALL_INIT_X, BALL_INIT_Y); m_TheSoccer.VelocityX = XNACS1Base.RandomFloat(BALL_VELOCITY_MIN_X, BALL_VELOCITY_MAX_X); m_TheSoccer.VelocityY = XNACS1Base.RandomFloat(BALL_VELOCITY_MIN_Y, BALL_VELOCITY_MAX_Y); m_BallsMissed++; m_NumBounces = 0; PlayACue("BallDie"); break; case BoundCollideStatus.InsideBound: break; } } |
calls the function CollideWorldBound(m_TheSoccer), which determines if the argument/parameter is currently overlapping the bounds of the world. In other words, is the soccer ball overlapping the edges of the screen? You'll notice that there are a limited number of possible responses, which is why the CollideWorldBound function uses the BoundCollideStatus enum to report it's results. Here's how to interpret the value returned from CollideWorldBound:
switch (status)
{
case BoundCollideStatus.CollideTop:
case BoundCollideStatus.CollideBottom:
//POINT #1
break;
case BoundCollideStatus.CollideRight:
case BoundCollideStatus.CollideLeft:
//POINT #2
break;
case BoundCollideStatus.InsideBound:
//POINT #3
break;
}
//POINT #3
break;
Conceptually, what's happening is that the program at the
'switch' line, the program asks "What is the current value of the variable
status?", and then the program jumps directly the case that has that
value. Here is a pictorial representation:

You'll notice that each of the cases are mutually exclusive - if the current value of status is BoundCollideStatus.InsideBound, then the program will jump to the third case, and do nothing, after which it will continue on to the rest of the program. In C#, one cannot execute multiple cases - the program executes exactly one case, then proceeds onwards to the rest of the program.
You'll notice that while you can't execute multiple,
separate cases, you can have a case that's triggered my
any one of several values. For example, if the ball has bounced off
the top or bottom of the screen, then we want to do the same thing: reverse
the Y axis velocity, play the "Wall" sound, and then continue. We can
express this by writing listing out multiple cases, like so:
case
BoundCollideStatus.CollideTop:
m_TheSoccer.VelocityY = -m_TheSoccer.VelocityY;
PlayACue("Wall");
break;
You can list as many values as you want to, but they must all be listed individually (using the case <value>: format)
We'll leave a detailed explanation of the CollideRight and CollideLeft cases for the next section.
3. Putting The Ball Back In The Middle When The Player Misses
|
///
<summary> /// Check the center position of the soccer ball, if it is outside /// of the world bound, flip the ball velocity accordingly /// </summary> private void CheckWorldBound() { XNACS1Lib. BoundCollideStatus status = CollideWorldBound(m_TheSoccer);switch (status) { case BoundCollideStatus.CollideTop: case BoundCollideStatus.CollideBottom: m_TheSoccer.VelocityY = -m_TheSoccer.VelocityY; PlayACue( "Wall");break; case BoundCollideStatus.CollideRight: case BoundCollideStatus.CollideLeft: m_TheSoccer.Center = new Vector2(BALL_INIT_X, BALL_INIT_Y); m_TheSoccer.VelocityX = XNACS1Base.RandomFloat(BALL_VELOCITY_MIN_X, BALL_VELOCITY_MAX_X); m_TheSoccer.VelocityY = XNACS1Base.RandomFloat(BALL_VELOCITY_MIN_Y, BALL_VELOCITY_MAX_Y); if (m_MaxBounces > EXPERT_LEVEL_BOUNCES) { m_TheSoccer.VelocityX = m_TheSoccer.VelocityX + BALL_VELOCITY_EXPERT_BOOST_X; m_TheSoccer.VelocityY = m_TheSoccer.VelocityY + BALL_VELOCITY_EXPERT_BOOST_Y; } m_BallsMissed++; m_NumBounces = 0; PlayACue("BallDie"); break; case BoundCollideStatus.InsideBound: break; } } |
case BoundCollideStatus.CollideLeft:
m_TheSoccer.VelocityY =
XNACS1Base.RandomFloat(BALL_VELOCITY_MIN_Y, BALL_VELOCITY_MAX_Y);{
m_TheSoccer.VelocityX = m_TheSoccer.VelocityX + BALL_VELOCITY_EXPERT_BOOST_X;
m_TheSoccer.VelocityY = m_TheSoccer.VelocityY + BALL_VELOCITY_EXPERT_BOOST_Y;
}
and we will play a sound to provide audio
feedback to the player that the ball was missed
PlayACue("BallDie");
4. Tracking the Maximum Number Of Bounces
Let's examine this new distinction between 'current' and 'maximum' number of bounces. In a nutshell, the instance variable m_NumBounces tracks the number of times that the ball has bounced off either paddle since the last time the ball was missed. m_MaxBounces tracks the highest number of bounces in row that the player has ever reached. m_MaxBounces is used to determine if the game is in Novice or Expert mode, and m_NumBounces is used to determine if m_MaxBounces has increased.
|
<code above this point
omitted for clarity> // collect statistics private int m_NumBounces; private String m_SkillLevel; private int m_MaxBounces; <code below this point omitted for clarity> |
|
///
<summary> /// Initialize the statistics information /// </summary> private void InitializeStats() { m_BallsMissed = 0; m_NumBounces = 0; m_MaxBounces = 0; m_SkillLevel = "Novice";} |
|
///
<summary> /// Check the center position of the soccer ball, if it is outside /// of the world bound, flip the ball velocity accordingly /// </summary> private void CheckWorldBound() { XNACS1Lib. BoundCollideStatus status = CollideWorldBound(m_TheSoccer);switch (status) { case BoundCollideStatus.CollideTop: case BoundCollideStatus.CollideBottom: m_TheSoccer.VelocityY = -m_TheSoccer.VelocityY; PlayACue( "Wall");break; case BoundCollideStatus.CollideRight: case BoundCollideStatus.CollideLeft: m_TheSoccer.Center = new Vector2(BALL_INIT_X, BALL_INIT_Y); m_TheSoccer.VelocityX = XNACS1Base.RandomFloat(BALL_VELOCITY_MIN_X, BALL_VELOCITY_MAX_X); m_TheSoccer.VelocityY = XNACS1Base.RandomFloat(BALL_VELOCITY_MIN_Y, BALL_VELOCITY_MAX_Y); if (m_MaxBounces > EXPERT_LEVEL_BOUNCES) { m_TheSoccer.VelocityX = m_TheSoccer.VelocityX + BALL_VELOCITY_EXPERT_BOOST_X; m_TheSoccer.VelocityY = m_TheSoccer.VelocityY + BALL_VELOCITY_EXPERT_BOOST_Y; } m_BallsMissed++; m_NumBounces = 0; PlayACue("BallDie"); break; case BoundCollideStatus.InsideBound: break; } } |
|
<code above this point
omitted for clarity> // paddle colliding with the soccer BounceOffPaddles(); if (m_NumBounces > m_MaxBounces) m_MaxBounces = m_NumBounces; if (m_MaxBounces > EXPERT_LEVEL_BOUNCES) { // String equality test if (m_SkillLevel == "Novice") { PlayACue("Victory"); m_TheSoccer.VelocityX = m_TheSoccer.VelocityX + Math.Sign(m_TheSoccer.VelocityX) * BALL_VELOCITY_EXPERT_BOOST_X; m_TheSoccer.VelocityY = m_TheSoccer.VelocityY + Math.Sign(m_TheSoccer.VelocityY) * BALL_VELOCITY_EXPERT_BOOST_Y; } m_SkillLevel = "Expert"; } EchoToBottomStatus("[" + m_SkillLevel + "] " + "CurrentBounces:" + m_NumBounces + " MaxBounces:" + m_MaxBounces + " Total Balls Missed:" + m_BallsMissed); } // end of UpdateWorld |
m_MaxBounces = m_NumBounces;
Which says to update m_MaxBounces if (and only if) m_NumBounces is higher than the previous maximum, no matter when it occurred.
Since being in Novice or Expert mode is dependent on the maximum number of non-wall bounces, not the current number, we need to change this if statement accordingly. The nested if statement (which figures out if this is the first time we've advanced to Expert mode) remains valid, and therefore unchanged.
"CurrentBounces:" + m_NumBounces +
" MaxBounces:" + m_MaxBounces +
" Total Balls Missed:" + m_BallsMissed);
FURTHER EXERCISES::
For this exercise, you should use the same project that was explained in the
above tutorial.
Examine the BounceOffBlocks routine. First, without trying to compile
such a solution, answer the following question: Does it make sense to refactor
this code using a Case structure (i.e., using a C# switch statement?)?
Why, or why not?
For this exercise, you should use the same project that was explained in the
above tutorial.
Let's say that you want to add a couple of levels to the game, so that
instead of just "Novice", and "Expert", there are the following levels:
| Number of Uninterrupted Bounces | Level Name |
| 0 - 5 | Novice |
| 6 - 10 | Expert |
| 11-15 | Super Expert |
| 16 - 20 | Ultra Super Expert |
| 21+ | Mega Ultra Super Expert |
Refactor the code in the UpdateWorld method so that as the player reaches
the appropriate number of (uninterrupted) paddle bounces, each of the above
levels are displayed in bottom status bar, using a Case statement.