Microsoft XNA 4.0 Game Development: Receiving Player Input

Exclusive offer: get 50% off this eBook here
Microsoft XNA 4.0 Game Development Cookbook

Microsoft XNA 4.0 Game Development Cookbook — Save 50%

Over 35 intermediate-advanced recipes for taking your XNA development arsenal further with this book and ebook

$29.99    $15.00
by Luke Drumm | July 2012 | Cookbooks Microsoft

One of the key aspects that separates a computer game from that of, for example, a movie, is its interactive nature and its ability to be influenced by the player to achieve a different outcome each and every time.

In this article by Luke Drumm,author of Microsoft XNA 4.0 Game Development Cookbook, we will examine some different ways of capturing the player's intent that may not be immediately obvious or trivial to implement when we first set ourselves the challenge.

(For more resources on Microsoft XNA 4.0, see here.)

Adding text fields

I'm generally a big fan of having as few text fields in an application as possible, and this holds doubly true for a game but there are some occasions when receiving some sort of textual information from the player is required so in these regrettable occasions, a textbox or field may be an appropriate choice.

Unfortunately, a premade textbox isn't always available to us on any given gaming project, so sometimes we must create our own.

Getting ready

This recipe only relies upon the presence of a single SpriteFont file referring to any font at any desired size.

How to do it...

To start adding textboxes to your own games:

  1. Add a SpriteFont to the solution named Text:

    <?xml version="1.0" encoding="utf-8"?>
    <XnaContent xmlns:Graphics=
    "Microsoft.Xna.Framework.Content.Pipeline.Graphics">
      <Asset Type="Graphics:FontDescription">
        <FontName>Segoe UI Mono</FontName>
        <Size>28</Size>
        <Spacing>0</Spacing>
        <UseKerning>true</UseKerning>
        <Style>Regular</Style>
        <CharacterRegions>
          <CharacterRegion>
            <Start>&#32;</Start>
            <End>&#126;</End>
          </CharacterRegion>
        </CharacterRegions>
      </Asset>
    </XnaContent>

  2. Begin a new class to contain the text field logic, and embed a static mapping of the keyboard keys to their respective text characters:

    class Textbox
    {
           
        private static Dictionary<Keys, char> characterByKey;

  3. Create a static constructor to load the mapping of keys to characters:

    static Textbox()
    {
        characterByKey = new Dictionary<Keys, char>()
        {
            {Keys.A, 'a'},
            {Keys.B, 'b'},
            {Keys.C, 'c'},
            {Keys.D, 'd'},
         {Keys.E, 'e'},
            {Keys.F, 'f'},
            {Keys.G, 'g'},
            {Keys.H, 'h'},
            {Keys.I, 'i'},
            {Keys.J, 'j'},
            {Keys.K, 'k'},
            {Keys.L, 'l'},
            {Keys.M, 'm'},
            {Keys.N, 'n'},
            {Keys.O, 'o'},
            {Keys.P, 'p'},
            {Keys.Q, 'q'},
            {Keys.R, 'r'},
            {Keys.S, 's'},
            {Keys.T, 't'},
            {Keys.U, 'u'},
            {Keys.V, 'v'},
            {Keys.W, 'w'},
            {Keys.X, 'x'},
            {Keys.Y, 'y'},
            {Keys.Z, 'z'},
            {Keys.D0, '0'},
            {Keys.D1, '1'},
            {Keys.D2, '2'},
            {Keys.D3, '3'},
            {Keys.D4, '4'},
            {Keys.D5, '5'},
            {Keys.D6, '6'},
            {Keys.D7, '7'},
            {Keys.D8, '8'},
            {Keys.D9, '9'},
            {Keys.NumPad0, '0'},
            {Keys.NumPad1, '1'},
            {Keys.NumPad2, '2'},
            {Keys.NumPad3, '3'},
            {Keys.NumPad4, '4'},
            {Keys.NumPad5, '5'},
            {Keys.NumPad6, '6'},
            {Keys.NumPad7, '7'},
            {Keys.NumPad8, '8'},
            {Keys.NumPad9, '9'},
            {Keys.OemPeriod, '.'},
            {Keys.OemMinus, '-'},
        {Keys.Space, ' '}
        };
    }

  4. Add the public instance fields that will determine the look and content of the display:

    public StringBuilder Text;
    public Vector2 Position;
    public Color ForegroundColor;
    public Color BackgroundColor;
    public bool HasFocus;

  5. Include instance-level references to the objects used in the rendering:

    GraphicsDevice graphicsDevice;
    SpriteFont font;
    SpriteBatch spriteBatch;        
    RenderTarget2D renderTarget;
    KeyboardState lastKeyboard;
    bool renderIsDirty = true;

  6. Begin the instance constructor by measuring the overall height of some key characters to determine the required height of the display, and create a render target to match:

    public Textbox(GraphicsDevice graphicsDevice, int width,
    SpriteFont font)
    {
        this.font = font;
        var fontMeasurements = font.MeasureString("dfgjlJL");
        var height = (int)fontMeasurements.Y;
        var pp = graphicsDevice.PresentationParameters;
        renderTarget = new RenderTarget2D(graphicsDevice,
            width,
            height,
            false, pp.BackBufferFormat, pp.DepthStencilFormat);

  7. Complete the constructor by instantiating the text container and SpriteBatch:

        Text = new StringBuilder();
        this.graphicsDevice = graphicsDevice;
        spriteBatch = new SpriteBatch(graphicsDevice);
    }

  8. Begin the Update() method by determining if we need to take any notice of the keyboard:

    public void Update(GameTime gameTime)
    {
        if (!HasFocus)
        {
            return;
        }

  9. Retrieve all of the keys that are currently being depressed by the player and iterate through them, ignoring any that have been held down since the last update:

    var keyboard = Keyboard.GetState();
    foreach (var key in keyboard.GetPressedKeys())
    {
        if (!lastKeyboard.IsKeyUp(key))
        {
            continue;
        }

  10. Add the logic to remove a character from the end of the text, if either the Backspace or Delete key has been pressed:

    if (key == Keys.Delete ||
        key == Keys.Back)
    {
        if (Text.Length == 0)
        {
            continue;
        }
        Text.Length--;
        renderIsDirty = true;
        continue;
    }

  11. Complete the loop and the method by adding the corresponding character for any keys we recognize, taking note of the case as we do so:

    char character;
            if (!characterByKey.TryGetValue(key, out character))
            {
                continue;
            }
            if (keyboard.IsKeyDown(Keys.LeftShift) ||
            keyboard.IsKeyDown(Keys.RightShift))
            {
                character = Char.ToUpper(character);
            }
            Text.Append(character);
            renderIsDirty = true;
        }
                
        lastKeyboard = keyboard;
    }

  12. Add a new method to render the contents of the text field to RenderTarget if it has changed:

    public void PreDraw()
    {
        if (!renderIsDirty)
        {
            return;
        }
        renderIsDirty = false;
        var existingRenderTargets = graphicsDevice.GetRenderTargets();
        graphicsDevice.SetRenderTarget(renderTarget);
        spriteBatch.Begin();
        graphicsDevice.Clear(BackgroundColor);
        spriteBatch.DrawString(
            font, Text,
            Vector2.Zero, ForegroundColor);
        spriteBatch.End();
        graphicsDevice.SetRenderTargets(existingRenderTargets);
    }

  13. Complete the class by adding a method to render the image of RenderTarget to the screen:

    public void Draw()
    {
        spriteBatch.Begin();
        spriteBatch.Draw(renderTarget, Position, Color.White);
        spriteBatch.End();
    }

  14. In your game's LoadContent() method, create a new instance of the text field:

    Textbox textbox;
    protected override void LoadContent()
    {
        textbox = new Textbox(
            GraphicsDevice,
            400,
            Content.Load<SpriteFont>("Text"))
        {
            ForegroundColor = Color.YellowGreen,
            BackgroundColor = Color.DarkGreen,
            Position = new Vector2(100,100),
            HasFocus = true
        };
    }

  15. Ensure that the text field is updated regularly via your game's Update() method:

    protected override void Update(GameTime gameTime)
    {
        textbox.Update(gameTime);
        base.Update(gameTime);
    }

  16. In your game's Draw() method, let the text field perform its RenderTarget updates prior to rendering the scene including the text fi eld:

    protected override void Draw(GameTime gameTime)
    {
        textbox.PreDraw();
        GraphicsDevice.Clear(Color.Black);
        textbox.Draw();
        base.Draw(gameTime);
    }

Running the code should deliver a brand new textbox just waiting for some interesting text like the following:

How it works...

In the Update() method, we retrieve a list of all of the keys that are being depressed by the player at that particular moment in time.

Comparing this list to the list we captured in the previous update cycle allows us to determine which keys have only just been depressed.

Next, via a dictionary, we translate the newly depressed keys into characters and append them onto a StringBuilder instance.

We could have just as easily used a regular string, but due to the nature of string handling in .NET, the StringBuilder class is a lot more efficient in terms of memory use and garbage creation.

We could have also rendered our text directly to the screen, but it turns out that drawing text is a mildly expensive process, with each letter being placed on the screen as an individual image. So in order to minimize the cost and give the rest of our game as much processing power as possible, we render the text to RenderTarget only when the text changes, and just keep on displaying RenderTarget on screen during all those cycles when no changes occur.

There's more...

If you're constructing a screen that has more than one text field on it, you'll find the HasFocus state of the text field implementation to be a handy addition. This will allow you to restrict the keyboard input only to one text field at a time.

In the case of multiple text fields, I'd recommend taking a leaf from the operating system UI handbooks and adding some highlighting around the edges of a text field to clearly indicate which text field has focus.

Also, the addition of a visible text cursor at the end of any text within a text field with focus may help draw the player's eyes to the correct spot.

On the phone

If you do have access to a built-in text field control such as the one provided in the "Windows Phone XNA and Silverlight" project type, but still wish to render the control yourself, I recommend experimenting with enabling the prebuilt control, making it invisible, and feeding the text from it into your own text field display.

Microsoft XNA 4.0 Game Development Cookbook Over 35 intermediate-advanced recipes for taking your XNA development arsenal further with this book and ebook
Published: June 2012
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

(For more resources on Microsoft XNA 4.0, see here.)

Creating dialog wheels

Games such as BioWare's Mass Effect series have propelled the dialog wheel style of option selection to the forefront of modern gaming UI.

While it's essentially a dropped-down list in terms of functionality, the ability for a player to easily select an option via a gamepad, and the possibility of developing a "muscle memory" for popular or regular options, makes it a compelling choice for console games developers.

Getting ready

This recipe requires a SpriteFont file along with six images, with each image containing one grayscale segment of the wheel.

Here's one example of a complete wheel:

Here's one of the segment's images:

Note how the image of the individual segment hasn't been cropped or moved from its original position. Keeping each segment in place allows us to reconstruct the entire wheel easily by just layering the images directly on top of each other.

I constructed the example segments in a 3D rendering package, but any style of interlocking 2D imagery will do, as long as it follows a recognizable circular flow.

How to do it...

To add a dialogue wheel to your own game:

  1. Add a SpriteFont file to the solution named WheelText along with an image for each segment:

    <?xml version="1.0" encoding="utf-8"?>
    <XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.
    Pipeline.Graphics">
      <Asset Type="Graphics:FontDescription">
        <FontName>Segoe UI Mono</FontName>
        <Size>14</Size>
        <Spacing>0</Spacing>
        <UseKerning>true</UseKerning>
        <Style>Bold</Style>
        <CharacterRegions>
          <CharacterRegion>
            <Start>&#32;</Start>
            <End>&#126;</End>
          </CharacterRegion>
        </CharacterRegions>
      </Asset>
    </XnaContent>

  2. Create a new class to hold the dialog wheel logic and add in some public fields to indicate its state:

    class Wheel
    {
        public bool IsDisplayed = false;
        public bool HasSelected = false;
        public int SelectedIndex = -1;
        public int FocusedSegmentIndex = 0;

  3. Include some private instance variables to hold the remaining details:

        private List<Texture2D> segments;
        private SpriteFont font;
        private List<Vector2> segmentTextOrigins;
        private Vector2 segmentOrigin;
        private List<string> segmentTexts;
        private List<Vector2> textOffsets = new List<Vector2>();
        private static Vector2 textShadowOffset = new Vector2(3, 3);
        int lastPacketNumber;
        GamePadState lastGamePadState;
        Vector2 direction;

  4. Add a constructor to take in the textures for the wheel, along with the text font and the significant positions:

    public Wheel(
        List<Texture2D> segments,
        SpriteFont font,
        List<Vector2> segmentTextOrigins)
    {
        this.segments = segments;
        this.font = font;
        this.segmentTextOrigins = segmentTextOrigins;
        segmentOrigin = new Vector2(
            segments[0].Width / 2,
            segments[0].Height / 2);
    }

  5. Create a method to indicate that the wheel should be displayed, taking in the text options, and calculating the horizontal alignment of each:

    public void Display(List<string> segmentTexts)
    {
        this.segmentTexts = segmentTexts;
        textOffsets.Clear();
        var halfTextCount = segmentTexts.Count / 2;
        for (var textIndex = 0;
            textIndex < segmentTexts.Count();
            textIndex++)
        {
            if (textIndex < halfTextCount)
            {
                textOffsets.Add(Vector2.Zero);
                continue;
            }
            var text = segmentTexts[textIndex];
            var textMeasurements = font.MeasureString(text);
            textOffsets.Add(new Vector2(-textMeasurements.X, 0));
        }
        IsDisplayed = true;
        HasSelected = false;
        SelectedIndex = -1;
    }

  6. Add a corresponding method to cancel the display of the wheel:

    public void Cancel()
    {
        IsDisplayed = false;
    }

  7. Create an Update() method to check if the player has made any changes. If they have, whether it has been to select an item, or change item focus:

    public void Update(GameTime gameTime)
    {
        if (!IsDisplayed)
        {
            return;
        }
        var gamePad = GamePad.GetState(PlayerIndex.One);
        if (gamePad.PacketNumber == lastPacketNumber)
        {
            return;
        }
        lastPacketNumber = gamePad.PacketNumber;
        if (FocusedSegmentIndex >= 0 &&
            gamePad.IsButtonDown(Buttons.A) &&
            lastGamePadState.IsButtonUp(Buttons.A))
        {
            SelectedIndex = FocusedSegmentIndex;
            HasSelected = true;
            IsDisplayed = false;
        }
        UpdateFocusedSegment(gamePad);
    }

  8. Add the logic to detect what, if any, item the player's left thumbstick is pointing toward:

    private void UpdateFocusedSegment(GamePadState gamePad)
    {
        direction.X = gamePad.ThumbSticks.Left.X;
        direction.Y = gamePad.ThumbSticks.Left.Y;
        var minimumDistance = .35;
        if (direction.Length() <= minimumDistance)
        {
            return;
        }
        var angle = (float)-Math.Atan2(direction.Y, direction.X) +
                    (float)MathHelper.PiOver2;
        if (angle < 0)
        {
            angle += MathHelper.TwoPi;
        }
        var segmentIndex = (int)(angle /
                   (MathHelper.TwoPi / segments.Count()));
        if (!string.IsNullOrWhiteSpace(segmentTexts[segmentIndex]))
        {
            FocusedSegmentIndex = segmentIndex;
        }
    }

  9. Begin the Draw() method by iterating through each of the wheel segments, determining the corresponding imagery and text:

    public void Draw(SpriteBatch spriteBatch, Vector2 origin)
    {
        if (!IsDisplayed)
        {
            return;
        }
        for (var segmentIndex = 0;
             segmentIndex < segments.Count();
             segmentIndex++)
        {
            var segment = segments[segmentIndex];
            var text = segmentTexts[segmentIndex];

  10. Render the segment's image in a different color, depending on whether it has focus or not:

            var segmentColor = Color.DarkRed;
            if (segmentIndex == FocusedSegmentIndex)
            {
                segmentColor = Color.Orange;
            }
        spriteBatch.Draw(
            segment,
            origin - segmentOrigin,
            segmentColor);

  11. Complete the loop and the method by rendering each segment's text, changing the color, and adding a drop shadow depending on if it's the focused segment:

            var textOrigin = segmentTextOrigins[segmentIndex];
            var textOffset = textOffsets[segmentIndex];
            var textColor = Color.Gray;
            if (segmentIndex == FocusedSegmentIndex)
            {
                textColor = Color.White;
                spriteBatch.DrawString(
                    font, text,
                    origin +
                        textOrigin +
                        textOffset +
                        textShadowOffset,
                    Color.Black);
            }
            spriteBatch.DrawString(
                font, text,
                origin +
                    textOrigin +
                    textOffset,
                textColor);
        }
    }

  12. In your game's LoadContent()> method, create a new instance of the wheel, loading it with the segment imagery, font, and text positions before requesting it be displayed in the middle, at the bottom of the screen:

    Wheel wheel;
    Vector2 wheelOrigin;
    protected override void LoadContent()
    {
        spriteBatch = new SpriteBatch(GraphicsDevice);
        wheel = new Wheel(
            new List<Texture2D>() {
                Content.Load<Texture2D>("Wheel0"),
                Content.Load<Texture2D>("Wheel1"),
                Content.Load<Texture2D>("Wheel2"),
                Content.Load<Texture2D>("Wheel3"),
                Content.Load<Texture2D>("Wheel4"),
                Content.Load<Texture2D>("Wheel5")},
            Content.Load<SpriteFont>("WheelText"),
            new List<Vector2>() {
                new Vector2(80,-110),
                new Vector2(140,-40),
                new Vector2(110,40),
                new Vector2(-110,40),
                new Vector2(-140,-40),
                new Vector2(-80,-110) });
        wheelOrigin = new Vector2(
                    GraphicsDevice.Viewport.Width / 2,
                    GraphicsDevice.Viewport.Height - 100);
        wheel.Display(
            new List<string>() {
                "I love you",
                "Huh?",
                "I hate you",
                "Only on Tuesdays",
                "",
                "Try the fish" });
    }

  13. Update the state of the wheel regularly:

    protected override void Update(GameTime gameTime)
    {
        wheel.Update(gameTime);
        base.Update(gameTime);
    }

  14. Render the wheel to the screen as part of your game's normal draw sequence:

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.DarkSlateGray);
        spriteBatch.Begin();
        wheel.Draw(spriteBatch, wheelOrigin);
        spriteBatch.End();
        base.Draw(gameTime);
    }

Upon executing the code, you should fi nd yourself with a brand new dialogue wheel as seen here:

How it works...

One of the key aspects of this example is in the UpdateFocusedSegment() method, where the X and Y ranges from the gamepad's left thumbstick are translated into a single number that goes from zero when the thumbstick is directed straight up, and proceeds through to two Pi as it's directed in a clockwise circle, back around the top again.

Once this single number representation of the thumbstick angle is achieved, it's just a case of dividing up the possible range into the number of segments, and calculating which segment's range the number currently resides in.

There's more...

You may have noticed that we only perform these calculations if the thumbstick's "distance" from the center is greater than a predefined threshold. This is for the player's comfort, and means that a segment won't gain focus until the player actively pushes the thumbstick in a particular direction. Without it, the wheel starts to feel very "twitchy" to the player, and it's all too easy to accidently select the wrong segment.

Microsoft XNA 4.0 Game Development Cookbook Over 35 intermediate-advanced recipes for taking your XNA development arsenal further with this book and ebook
Published: June 2012
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

Further resources on this subject:


About the Author :


Luke Drumm

Luke Drumm is an experienced software developer and consultant who wrote his first computer game at age 10 and has been enthusiastically exploring the world of game development ever since.

With the first public release of XNA in 2006, Luke quickly latched onto the technology and began creating and talking about how to create games within XNA at every possible opportunity, culminating in his regular presence at conferences, game camps and user groups and his becoming a recipient of a Microsoft MVP award for XNA and DirectX for at least four successive years.

Luke lives in Sydney, Australia, with his amazing, patient, and supportive wife Cheryl and two dogs that may or may not rule the roost.

Books From Packt


XNA 4.0 Game Development by Example: Beginner's Guide – Visual Basic Edition
XNA 4.0 Game Development by Example: Beginner's Guide – Visual Basic Edition

Windows Phone 7 XNA Cookbook
Windows Phone 7 XNA Cookbook

3D Graphics with XNA Game Studio 4.0
3D Graphics with XNA Game Studio 4.0

XNA 4.0 Game Development by Example: Beginner's Guide
XNA 4.0 Game Development by Example: Beginner's Guide

Unity 3 Game Development Hotshot
Unity 3 Game Development Hotshot

 Construct Game Development Beginners Guide
Construct Game Development Beginners Guide

Monkey Game Development: Beginner's Guide
Monkey Game Development: Beginner's Guide

Corona SDK Mobile Game Development: Beginner's Guide
Corona SDK Mobile Game Development: Beginner's Guide


No votes yet

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
1
p
H
d
b
1
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software