In this article I will be covering the Paddle class for the MGS: Pong game. I am not going to cover everything in great detail because it works very much like the Ball class. Let’s get started; below are the private members needed. New members are those that are used for the AI in the game,
#region Private Members
private SpriteBatch m_spriteBatch;
private ContentManager m_conManager;
private Texture2D m_paddleTexture;
private string m_texSource;
private Vector2 m_position = new Vector2();
private Color m_color = Color.White;
private bool m_isAI = false;
private Keys m_upKey;
private Keys m_dnKey;
private float m_speed = 5.0f;
private Ball m_ball = null;
private bool m_recenter = false;
#endregion
Next we need to handle the constructor as well as loading and unloading the content.
#region Constructor
public Paddle (Game game)
: base(game)
{
// Instantiates a new Content Manager. The CM class
// is a lightweight object that allows us to load and
// process XNA content.
m_conManager = new ContentManager ( game.Services );
}
#endregion
#region Load / Unload Content
protected override void LoadGraphicsContent (bool loadAllContent)
{
base.LoadGraphicsContent ( loadAllContent );
// Load All Content is true when the Device is created
// or goes through a hard reset. This happens when the
// window is created and sometimes when moved to
// another monitor.
if ( loadAllContent )
{
// Instantiates a new SpriteBatch object.
m_spriteBatch = new SpriteBatch ( GraphicsDevice );
// Checks if the texture file exists, and if so
// loads the texture.
if ( File.Exists ( m_texSource + ".xnb" ) )
m_paddleTexture = m_conManager.Load ( m_texSource );
}
}
protected override void UnloadGraphicsContent (bool unloadAllContent)
{
base.UnloadGraphicsContent ( unloadAllContent );
// Much like loadAllContent, the value of unloadAllContent
// is true when the device is created or goes through a hard
// reset. This occurs before a LoadGraphicsContent(...) in
// the latter cases.
if ( unloadAllContent )
{
// Unloads all the data.
m_conManager.Unload ();
// Dispose of unmanaged memory and
// set the paddle texture to null
if ( m_paddleTexture != null )
{
m_paddleTexture.Dispose ();
m_paddleTexture = null;
}
// Dispose of the spritebatch
// and set it to null.
if ( m_spriteBatch != null )
{
m_spriteBatch.Dispose ();
m_spriteBatch = null;
}
}
}
#endregion
Next we need to handle the paddle’s movement and the AI controlling the paddle. When the AI is handling the movement of the paddle the goal is simple: move the paddle until it is behind the ball. We only do this when the ball is moving towards the paddle because when it is moving away from the paddle it is better to move the paddle back to the center of the screen to minimize the distance it needs to travel next time around. This recentering only occurs on the harder difficulty of AI paddles because of how it works. The last thing to do in the Update method is to handle when a collision occurs. We offset the movement of the ball if the paddle is moving so that the game isn’t just a boring game of back and forth Ping Pong.
#region Updating
public override void Update (GameTime gameTime)
{
KeyboardState state = Keyboard.GetState ();
float yVel = 0f;
// Handle AI movement
if ( m_isAI )
{
// If the ball is moving towards the paddle,
// we need to move it for the computer!
if ( m_ball.VelocityX > 0 )
{
// If the ball is above the paddle, move upward!
if ( m_ball.Y + m_ball.Height < m_position.Y )
{
m_position.Y -= m_speed;
yVel = -1f;
}
// If the ball is below the paddle, move downward!
else if ( m_ball.Y > Destination.Bottom )
{
m_position.Y += m_speed;
yVel = 1f;
}
}
// If the ball is moving away, and recentering is on
// move the paddle back to the center slowly.
else if ( m_recenter )
{
// If the paddle is in the top half of the screen, move down
if ( Destination.Bottom - (Destination.Height / 2) < GraphicsDevice.Viewport.Height / 2 )
{
m_position.Y += m_speed / 4;
yVel = 1f;
}
// If the paddle is in the bottom half of the screen, move up
else if ( Destination.Bottom - (Destination.Height / 2) > GraphicsDevice.Viewport.Height / 2 )
{
m_position.Y -= m_speed / 4;
yVel = -1f;
}
}
}
// If the player is handling the paddle, let's
// see if there is any input
else
{
// If the up key is down, move up
if ( state.IsKeyDown ( m_upKey ) )
{
m_position.Y -= m_speed;
yVel = -1f;
}
// If the down key is down, move down.
else if ( state.IsKeyDown ( m_dnKey ) )
{
m_position.Y += m_speed;
yVel = 1f;
}
}
// Check the paddle against the bounds of the screen.
if ( m_position.Y + m_paddleTexture.Height > GraphicsDevice.Viewport.Height )
m_position.Y = GraphicsDevice.Viewport.Height - m_paddleTexture.Height;
else if ( m_position.Y < 0 )
m_position.Y = 0;
if ( m_ball != null )
{
// If there is a collision
if ( Destination.Intersects ( m_ball.Destination ) )
{
// Go back until the ball doesn't intersect the paddle.
do
{
m_ball.X -= m_ball.VelocityX * m_ball.Speed;
m_ball.Y -= m_ball.VelocityY * m_ball.Speed;
} while ( Destination.Intersects ( m_ball.Destination ) );
// Revert the direction of the ball's X movement
m_ball.VelocityX *= -1;
// If there is some speed to our paddle,
// give the ball some extra speed.
if(yVel > 0 || yVel < 0)
{
// If the movement is equal to the ball's travel,
// give it speed.
if ( yVel == m_ball.VelocityY )
m_ball.Speed += 1.0f;
// Set the direction of the ball's movement.
m_ball.VelocityY = yVel;
}
}
}
base.Update ( gameTime );
}
#endregion
The last couple of things are just like the Ball class. We provide a Draw method as well as a bunch of properties. In the next article I will cover drawing the background for our game!
#region Drawing
public override void Draw (GameTime gameTime)
{
base.Draw ( gameTime );
// If the texture doesn't exist, we can't draw!
if ( m_paddleTexture == null )
return;
// Start drawing.
m_spriteBatch.Begin ( SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState );
// Draw the paddle.
m_spriteBatch.Draw ( m_paddleTexture, m_position, m_color );
// End drawing.
m_spriteBatch.End ();
}
#endregion
#region Properties
///
/// Gets or Sets how fast the paddle can move.
///
public float Speed
{
get { return m_speed; }
set { m_speed = value; }
}
///
/// Gets or Sets whether or not the paddle is controlled
/// by a player or by the computer.
///
public bool IsAI
{
get { return m_isAI; }
set { m_isAI = value; }
}
///
/// Gets or Sets what key controls the down motion of the paddle.
///
public Keys DownKey
{
get { return m_dnKey; }
set { m_dnKey = value; }
}
///
/// Gets or Sets what key controls the up motion of the paddle.
///
public Keys UpKey
{
get { return m_upKey; }
set { m_upKey = value; }
}
///
/// Gets or Sets the X position of the paddle.
///
public float X
{
get { return m_position.X; }
set { m_position.X = value; }
}
///
/// Gets or Sets the Y position of the paddle.
///
public float Y
{
get { return m_position.Y; }
set { m_position.Y = value; }
}
///
/// Gets or Sets the source of the paddle's texture.
///
public string TextureSource
{
get { return m_texSource; }
set { m_texSource = value; }
}
///
/// Gets or Sets the color of the paddle.
///
public Color Color
{
get { return m_color; }
set { m_color = value; }
}
///
/// Gets or Sets what ball to interact with the paddle.
///
public Ball Ball
{
get { return m_ball; }
set { m_ball = value; }
}
///
/// Gets or Sets whether or not the paddle recenters.
///
public bool ReCenter
{
get { return m_recenter; }
set { m_recenter = value; }
}
///
/// Gets the width of the paddle's texture.
///
public int Width
{
get { return m_paddleTexture.Width; }
}
///
/// Gets the height of the paddle's texture.
///
public int Height
{
get { return m_paddleTexture.Height; }
}
///
/// Gets the rendered destination rectangle of the paddle.
///
public Rectangle Destination
{
get { return new Rectangle ( (int)m_position.X, (int)m_position.Y, Width, Height ); }
}
#endregion