JSEDLAK » Blog Archive » MGS: Pong (Pages 4-6)

MGS: Pong (Pages 4-6)

Now that we have balls, paddles, and a background, we need a menu system that will allow us to pull it all together. This menu system is rather simple and straight forward which ends in code that isn’t quite reusable, but it gets the job done. As an exercise, I suggest you abstract this class to a reusable component. First and as always, let’s start the class off with some members. Note some new ones that you have not seen: two booleans that remember whether or not the up and down key were pressed the last frame. This allows us to check current values against these to know if we need to handle a new key press.

?View Code CSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MainMenu : Microsoft.Xna.Framework.DrawableGameComponent
{
    #region Private Members
    private SpriteBatch m_spriteBatch;
    private ContentManager m_conManager;
 
    private SpriteFont m_largeFont;
    private SpriteFont m_medFont;
 
    private int m_curIndex = 0;
 
    private Texture2D m_selector;
 
    private bool m_upKey = false;
    private bool m_dnKey = false;
    #endregion

Now we need to have some events so that anyone using the class knows when a selection has been made.

?View Code CSHARP
1
2
3
4
5
6
7
#region Events
public event EventHandler EasySelected;
public event EventHandler MediumSelected;
public event EventHandler HardSelected;
public event EventHandler QuitSelected;
public event EventHandler TwoPlayerSelected;
#endregion

Now, as usual, we have to load and unload some content for our menu.

?View Code CSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#region Constructor
public MainMenu (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 );
 
        m_largeFont = m_conManager.Load<SpriteFont> ( @"Content\Fonts\LargeFont" );
        m_medFont = m_conManager.Load<SpriteFont> ( @"Content\Fonts\MediumFont" );
 
        m_selector = m_conManager.Load<Texture2D> ( @"Content\Textures\Ball" );
    }
}
 
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 the spritebatch
        // and set it to null.
        if ( m_spriteBatch != null )
        {
            m_spriteBatch.Dispose ();
            m_spriteBatch = null;
        }
 
        m_largeFont = null;
        m_medFont = null;
 
        if ( m_selector != null )
        {
            m_selector.Dispose ();
            m_selector = null;
        }
    }
}
#endregion

This is the fancy bit of code; code that handles moving up and down within the menu. The idea is rather simple however: if the user has a key pressed (Up or Down) and these were not pressed the last frame, we know it is a new press. If they are the Up and Down keys, we simply move the option up or down the menu options. If it gets to the max on either end, we move it to the other end. However, if the user pressed the select key, we fire the event for the currently selected option.

?View Code CSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#region Updating
public override void Update (GameTime gameTime)
{
    KeyboardState state = Keyboard.GetState ();
 
    // If the up arrow is down, and it wasn't down last update,
    // we know that it is a new press. Let's process it!
    if ( state.IsKeyDown ( Keys.Up ) && !m_upKey )
    {
        // Set the "last update" variable to true.
        // This prevents users holding the key down.
        m_upKey = true;
 
        // Decrement the index, and if needed
        // go to the bottom.
        m_curIndex--;
        if ( m_curIndex < 0 )
            m_curIndex = 4;
    }
    // Otherwise, reset the "last update" variable
    else
        m_upKey = state.IsKeyDown ( Keys.Up );
 
    // If the down key is down, and it wasn't down last update,
    // do the same thing as with the up key, but increment the
    // menu index.
    if ( state.IsKeyDown ( Keys.Down ) && !m_dnKey )
    {
        m_dnKey = true;
        m_curIndex++;
        if ( m_curIndex > 4 )
            m_curIndex = 0;
    }
    else
        m_dnKey = state.IsKeyDown ( Keys.Down );
 
    // If the enter key was pressed, lets throw an event so the listener
    // can do something.
    if ( state.IsKeyDown ( Keys.Enter ) )
    {
        switch ( m_curIndex )
        {
            case 0:
                if ( EasySelected != null )
                    EasySelected.Invoke ( this, EventArgs.Empty );
                break;
            case 1:
                if(MediumSelected != null)
                    MediumSelected.Invoke ( this, EventArgs.Empty );
                break;
            case 2:
                if(HardSelected != null)
                    HardSelected.Invoke ( this, EventArgs.Empty );
                break;
            case 3:
                if(TwoPlayerSelected != null)
                    TwoPlayerSelected.Invoke ( this, EventArgs.Empty );
                break;
            case 4:
                if(QuitSelected != null)
                    QuitSelected.Invoke ( this, EventArgs.Empty );
                break;
        }
    }
 
    base.Update ( gameTime );
}
#endregion

Lastly, we need to handle drawing the menu options. This code is very much specific to our game as well as the resolution being run. Much like other classes, we draw items using SpriteBatch. In this case, though, we draw specific items in specific places to show the options. In the next article, I will cover how to tie all these classes together to finish the game!

?View Code CSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#region Drawing
public override void Draw (GameTime gameTime)
{
    base.Draw ( gameTime );
 
    // Start drawing.
    m_spriteBatch.Begin ( SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState );
 
    // Draws the title in the center.
    Vector2 measure = m_largeFont.MeasureString ( "MGS: Pong" );
    m_spriteBatch.DrawString ( m_largeFont, "MGS: Pong", new Vector2 ( 400 - measure.X / 2, 10 ), Color.White );
 
    // Draw the different options, we are going to align it to the dashed line in the background.
    m_spriteBatch.DrawString ( m_medFont, "Easy", new Vector2 ( 415, 155 ), Color.White );
    m_spriteBatch.DrawString ( m_medFont, "Medium", new Vector2 ( 415, 203 ), Color.White );
    m_spriteBatch.DrawString ( m_medFont, "Hard", new Vector2 ( 415, 255 ), Color.White );
    m_spriteBatch.DrawString ( m_medFont, "Two Player", new Vector2 ( 415, 305), Color.White );
    m_spriteBatch.DrawString ( m_medFont, "Quit", new Vector2 ( 415, 355 ), Color.White );
 
    // Draw the selector icon depending on what
    // selection the user is currently on.
    Vector2 pos = new Vector2(358, 0);
    switch ( m_curIndex )
    {
        case 0:
            pos.Y = 160;
            break;
        case 1:
            pos.Y = 208;
            break;
        case 2:
            pos.Y = 260;
            break;
        case 3:
            pos.Y = 310;
            break;
        case 4:
            pos.Y = 360;
            break;
    }
    m_spriteBatch.Draw ( m_selector, pos, Color.Yellow );
 
    // End drawing.
    m_spriteBatch.End ();
}
#endregion

Leave a Reply