Geeks With Blogs

News Bob Taco Industries is an ISV focused on game and app development for Microsoft platforms headed up by Michael B. McLaughlin. Mike is a Microsoft Visual C++ MVP (previously an XNA/DirectX MVP from 2011-2013), a developer, a writer, a consultant, and a retired lawyer. If you're a developer who is just getting started, consider checking out the BTI website's section for developers for links to code samples and other helpful sites.
Bob Taco Industries Blogging Division

In this post, I’m working with the GameStateManagement Sample for Windows Phone 7. All source code not a part of that sample is Copyright 2010 Michael B. McLaughlin. If you wish to use it, you may do so provided that you agree to the terms of the Microsoft Permissive License, available here. If you do not agree to those terms, you may not use this source code (and should probably stop reading now). Since this code is built upon the GameStateManagement Sample, you will also need that and thus will also need to agree to the license for it (which, not surprisingly, is also the Microsoft Permissive License, a copy of which can be downloaded at the link to the GameStateManagement Sample above).

My original purpose in writing this code was to display as a texture a solid colored background with an optional border. This is a sample image I am using:

border

To make use of the code, you will need to know the width in pixels of the border. If there is no border or if the image itself is smaller than the border, it will simply scale accordingly (for example, this lets you use 1x1 textures – I use a 1x1 black texture with the alpha set to 80% as a “drop shadow” style texture – more on this later). Anyway, first we need to add a new class to the project. Here it is:

    /// <summary>
    /// A class for drawing a solid colored texture with a border where you do not wish the width and height (in pixels) of the border to change.
    /// </summary>
    public class BorderedTextureBox
    {
        /// <summary>
        /// The texture to be drawn.
        /// </summary>
        Texture2D texture;
        
        /// <summary>
        /// The width in pixels of the border along the width (i.e. horizontal sides) of the texture.
        /// </summary>
        int borderWidth;
        
        /// <summary>
        /// The width in pixels of the border along the height (i.e. vertical sides) of the texture.
        /// </summary>
        int borderHeight;
        
        /// <summary>
        /// The Rectangles used to construct the properly scaled texture. These deal with unscaled corners, scaled verticals along the left and right
        /// borders (sampled from the middle of each, respectively), scaled horizontals along the top and bottom (same sampling method), and a
        /// scaled middle, sampled from the middle of the image and scaled to fill in all areas not covered by the border.
        /// </summary>
        Rectangle upperLeftSource, upperRightSource, upperStretchSource, lowerLeftSource, lowerRightSource, lowerStretchSource, leftStretchSource, rightStretchSource, middleSource;
 
        /// <summary>
        /// Used to avoid recalculating the other Rectangles every frame if the destination passed is the same as it was last frame.
        /// </summary>
        Rectangle previousDestination;
        
        /// <summary>
        /// The span, in pixels, between the end of the border on the left and the beginning of the border on the right. Used as a scaling factor.
        /// </summary>
        float horizontalGapWidth;
 
        /// <summary>
        /// The span, in pixels, between the end of the border on the top and the beginning of the border on the bottom. Used as a scaling factor.
        /// </summary>
        float verticalGapWidth;
 
        /// <summary>
        /// Create a new BorderedTextureBox using the specified parameters.
        /// </summary>
        /// <param name="texture">The texture to use.</param>
        /// <param name="borderSize">The width in pixels of the border around the texture.</param>
        public BorderedTextureBox(Texture2D texture, int borderSize)
            : this(texture, borderSize, borderSize)
        {
        }
 
        /// <summary>
        /// Create a new BorderedTextureBox using the specified parameters.
        /// </summary>
        /// <param name="texture">The texture to use.</param>
        /// <param name="borderWidth">The width in pixels of the border along the width (i.e. horizontal sides) of the texture.</param>
        /// <param name="borderHeight">The width in pixels of the border along the height (i.e. vertical sides) of the texture.</param>
        public BorderedTextureBox(Texture2D texture, int borderWidth, int borderHeight)
        {
            this.texture = texture;
            this.borderWidth = borderWidth;
            this.borderHeight = borderHeight;
        }
 
        /// <summary>
        /// Draws the texture.
        /// </summary>
        /// <param name="spriteBatch">The SpriteBatch with which to draw the texture. The calling method is responsible for calling Begin and End for the SpriteBatch.</param>
        /// <param name="destination">A rectangle specifying the destination. This will be used to appropriately scale the image.</param>
        /// <param name="color">The color to tint the texture. Use Color.White for no tinting. (Same as SpriteBatch.Draw).</param>
        /// <param name="layerDepth">The layerDepth to be passed to SpriteBatch.Draw. Must be between 0.0f and 1.0f, inclusive.</param>
        public void Draw(SpriteBatch spriteBatch, Rectangle destination, Color color, float layerDepth)
        {
            if (texture == null)
            {
#if DEBUG
                // In debug configuration, we want to know about the problem, so throw an exception.
                throw new InvalidOperationException("Texture is null.");
#else
                // In release configuration, we don't want the game to crash over a missing texture, so return silently.
                return;
#endif
            }
            // If both borderWidth and borderHeight are 0, or if the either of the destination dimensions are less than the value of the combined corners,
            // or if the provided texture's dimensions are less than the borderWidth or borderHeight, 
            // then just draw it auto-scaled to the size requested
            if ((borderWidth == 0 && borderHeight == 0) || destination.Width < borderWidth * 2 || destination.Height < borderHeight * 2 || texture.Width <= borderWidth * 2 || texture.Height <= borderHeight * 2)
            {
                spriteBatch.Draw(texture, destination, color);
                return;
            }
 
            if (destination.X != previousDestination.X || destination.Y != previousDestination.Y || destination.Width != previousDestination.Width || destination.Height != previousDestination.Height)
            {
                int halfWidth = texture.Width / 2;
                int halfHeight = texture.Height / 2;
 
                // Upper corners
                upperLeftSource = new Rectangle(0, 0, borderWidth, borderHeight);
                upperRightSource = new Rectangle(texture.Width - borderWidth, 0, borderWidth, borderHeight);
 
                // Lower corners
                lowerLeftSource = new Rectangle(0, texture.Height - borderWidth, borderWidth, borderHeight);
                lowerRightSource = new Rectangle(texture.Width - borderWidth, texture.Height - borderHeight, borderWidth, borderHeight);
 
                // Horizontal borders
                upperStretchSource = new Rectangle(halfWidth, 0, 1, borderHeight);
                lowerStretchSource = new Rectangle(halfWidth, texture.Height - borderHeight, 1, borderHeight);
 
                // Vertical borders
                leftStretchSource = new Rectangle(0, halfHeight, borderWidth, 1);
                rightStretchSource = new Rectangle(texture.Width - borderWidth, halfHeight, borderWidth, 1);
 
                // Middle (grab from the very middle to avoid sampling issues)
                middleSource = new Rectangle(halfWidth, halfHeight, 1, 1);
 
                // Determine the span between corners
                horizontalGapWidth = destination.Width - (borderWidth * 2);
                verticalGapWidth = destination.Height - (borderHeight * 2);
 
                // Assign previousDestination to avoid having to do these calculations whenever this doesn't change.
                previousDestination = destination;
            }
 
            // First draw the corners since they require no scaling
            // Draw upperLeft
            spriteBatch.Draw(texture,
                new Vector2(destination.X, destination.Y),
                upperLeftSource,
                color,
                0.0f,
                Vector2.Zero,
                Vector2.One,
                SpriteEffects.None,
                layerDepth);
 
            // Draw upperRight
            spriteBatch.Draw(texture,
                new Vector2(destination.X + destination.Width - borderWidth, destination.Y),
                upperRightSource,
                color,
                0.0f,
                Vector2.Zero,
                Vector2.One,
                SpriteEffects.None,
                layerDepth);
 
            // Draw lowerLeft
            spriteBatch.Draw(texture,
                new Vector2(destination.X, destination.Y + destination.Height - borderHeight),
                lowerLeftSource,
                color,
                0.0f,
                Vector2.Zero,
                Vector2.One,
                SpriteEffects.None,
                layerDepth);
 
            // Draw lowerRight
            spriteBatch.Draw(texture,
                new Vector2(destination.X + destination.Width - borderWidth, destination.Y + destination.Height - borderHeight),
                lowerRightSource,
                color,
                0.0f,
                Vector2.Zero,
                Vector2.One,
                SpriteEffects.None,
                layerDepth);
 
            // Draw the horizontals
            // Draw upperStretch
            spriteBatch.Draw(texture,
                new Vector2(destination.X + borderWidth, destination.Y),
                upperStretchSource,
                color,
                0.0f,
                Vector2.Zero,
                new Vector2(horizontalGapWidth, 1.0f),
                SpriteEffects.None,
                layerDepth);
 
            // Draw lowerStretch
            spriteBatch.Draw(texture,
                new Vector2(destination.X + borderWidth, destination.Y + destination.Height - borderHeight),
                lowerStretchSource,
                color,
                0.0f,
                Vector2.Zero,
                new Vector2(horizontalGapWidth, 1.0f),
                SpriteEffects.None,
                layerDepth);
 
            // Draw the verticals
            // Draw leftStretch
            spriteBatch.Draw(texture,
                new Vector2(destination.X, destination.Y + borderHeight),
                leftStretchSource,
                color,
                0.0f,
                Vector2.Zero,
                new Vector2(1.0f, verticalGapWidth),
                SpriteEffects.None,
                layerDepth);
 
            // Draw rightStretch
            spriteBatch.Draw(texture,
                new Vector2(destination.X + destination.Width - borderWidth, destination.Y + borderHeight),
                rightStretchSource,
                color,
                0.0f,
                Vector2.Zero,
                new Vector2(1.0f, verticalGapWidth),
                SpriteEffects.None,
                layerDepth);
 
            // Draw middle
            spriteBatch.Draw(texture,
                new Vector2(destination.X + borderWidth, destination.Y + borderHeight),
                middleSource,
                color,
                0.0f,
                Vector2.Zero,
                new Vector2(horizontalGapWidth, verticalGapWidth),
                SpriteEffects.None,
                layerDepth);
        }
    }

Once we’ve got that class added, we can then proceed to our modifications. MenuEntry is primarily focused with drawing a string. We need to modify it so that it will draw a texture underneath the text. But sometimes you want a fancier look – perhaps a drop shadow effect like I was mentioning up above. You could do such a thing with a custom shader, only Windows Phone 7 currently doesn’t support custom shaders. I’d much rather do it with a 1x1 semi-transparent texture. Here’s the image I use (scaled up so you can see it):

shadow

So like I was saying, we need to modify MenuEntry.cs – so open that file up. The file is divided up very nicely using the #region - #endregion “tags”. First, to the “Fields” region, we must add a few class-level fields:

        /// <summary>
        /// The text color for a selected menu item (unused on WP7).
        /// </summary>
        private Color textSelectedColor;
 
        /// <summary>
        /// The text color for an unselected menu item (used for all menu items on WP7).
        /// </summary>
        private Color textColor;
 
        /// <summary>
        /// The texture, if any, located underneath the menuEntryBackground.
        /// </summary>
        private BorderedTextureBox menuEntrySubBackground;
 
        /// <summary>
        /// The texture, if any, located underneath the menu entry text.
        /// </summary>
        private BorderedTextureBox menuEntryBackground;
 
        /// <summary>
        /// The amount of "padding" in pixels to add between the left and right of the string and the edge of the menuEntryBackground texture.
        /// </summary>
        private int menuEntryBackgroundXPadding;
 
        /// <summary>
        /// The amount of "padding" in pixels to add between the top and bottom of the string and the edge of the menuEntryBackground texture.
        /// </summary>
        private int menuEntryBackgroundYPadding;
 
        /// <summary>
        /// The amount of "padding" in pixels to add between the left and right of the string and the edge of the menuEntrySubBackground texture.
        /// </summary>
        private int menuEntrySubBackgroundXPadding;
 
        /// <summary>
        /// The amount of "padding" in pixels to add between the top and bottom of the string and the edge of the menuEntrySubBackground texture.
        /// </summary>
        private int menuEntrySubBackgroundYPadding;
 
        /// <summary>
        /// The distance in pixels which the menuEntrySubBackground image will be offset along the X axis from menuEntryBackground.
        /// Positive for right, negative for left.
        /// </summary>
        private int menuEntrySubBackgroundXOffset;
 
        /// <summary>
        /// The distance in pixels which the menuEntrySubBackground image will be offset along the X axis from menuEntryBackground.
        /// Positive for down, negative for up.
        /// </summary>
        private int menuEntrySubBackgroundYOffset;
 

In the original MenuEntry.cs, the MenuEntry colors are hard coded. I wanted to be able to specify what color I wanted my menu entries to be, so I made those parameters class level fields and added them to the constructor. Speaking of which, here’s the new constructors (which should completely replace the old constructors:

        /// <summary>
        /// Constructs a new menu entry with the specified text and default colors of yellow (selected) and white (unselected).
        /// </summary>
        /// <param name="text">The text for the MenuEntry item.</param>
        public MenuEntry(string text)
            : this(text, Color.Yellow, Color.White, null, 0, 0, 0, 0, null, 0, 0, 0, 0, 0, 0)
        {
        }
 
        /// <summary>
        /// Constructs a new menu entry with the specified text.
        /// </summary>
        /// <param name="text">The text for the MenuEntry item.</param>
        /// <param name="textSelectedColor">The color when the menu entry is the selected menu item (not used on WP7).</param>
        /// <param name="textColor">The text color when the menu entry is not the selected item (used for all items on WP7).</param>
        /// <param name="menuEntryBackground">The image, if any, to be drawn behind the MenuEntry text. Use null if not desired.</param>
        /// <param name="menuEntrySubBackground">The image, if any, to be drawn behind the menuEntryBackground image. Use null if not desired.</param>
        /// <param name="menuEntryBackgroundBorderWidth">The width in pixels of the border along the width (i.e. horizontal sides) of the menuEntryBackground image. Use 0 for no border.</param>
        /// <param name="menuEntryBackgroundBorderHeight">The width in pixels of the border along the height (i.e. vertical sides) of the menuEntryBackground image. Use 0 for no border.</param>
        /// <param name="menuEntryBackgroundXPadding">The amount of "padding" in pixels to add between the left and right of the string and the edge of the menuEntryBackground texture.</param>
        /// <param name="menuEntryBackgroundYPadding">The amount of "padding" in pixels to add between the top and bottom of the string and the edge of the menuEntryBackground texture.</param>
        /// <param name="menuEntrySubBackgroundBorderWidth">The width in pixels of the border along the width (i.e. horizontal sides) of the menuEntrySubBackground image. Use 0 for no border.</param>
        /// <param name="menuEntrySubBackgroundBorderHeight">The width in pixels of the border along the height (i.e. vertical sides) of the menuEntrySubBackground image. Use 0 for no border.</param>
        /// <param name="menuEntrySubBackgroundXPadding">The amount of "padding" in pixels to add between the left and right of the string and the edge of the menuEntrySubBackground texture.</param>
        /// <param name="menuEntrySubBackgroundYPadding">The amount of "padding" in pixels to add between the top and bottom of the string and the edge of the menuEntrySubBackground texture.</param>
        /// <param name="menuEntrySubBackgroundXOffset">The distance in pixels which the menuEntrySubBackground image will be offset along the X axis from menuEntryBackground. Positive for right, negative for left.</param>
        /// <param name="menuEntrySubBackgroundYOffset">The distance in pixels which the menuEntrySubBackground image will be offset along the X axis from menuEntryBackground. Positive for down, negative for up.</param>
        public MenuEntry(string text, Color textSelectedColor, Color textColor, Texture2D menuEntryBackground, int menuEntryBackgroundBorderWidth, int menuEntryBackgroundBorderHeight, int menuEntryBackgroundXPadding, int menuEntryBackgroundYPadding, Texture2D menuEntrySubBackground, int menuEntrySubBackgroundBorderWidth, int menuEntrySubBackgroundBorderHeight, int menuEntrySubBackgroundXPadding, int menuEntrySubBackgroundYPadding, int menuEntrySubBackgroundXOffset, int menuEntrySubBackgroundYOffset)
        {
            this.text = text;
 
            this.textSelectedColor = textSelectedColor;
            this.textColor = textColor;
 
            this.menuEntryBackgroundXPadding = menuEntryBackgroundXPadding;
            this.menuEntryBackgroundYPadding = menuEntryBackgroundYPadding;
 
            this.menuEntrySubBackgroundXPadding = menuEntrySubBackgroundXPadding;
            this.menuEntrySubBackgroundYPadding = menuEntrySubBackgroundYPadding;
 
            this.menuEntrySubBackgroundXOffset = menuEntrySubBackgroundXOffset;
            this.menuEntrySubBackgroundYOffset = menuEntrySubBackgroundYOffset;
 
            if (menuEntryBackground != null)
            {
                this.menuEntryBackground = new BorderedTextureBox(menuEntryBackground, menuEntryBackgroundBorderWidth, menuEntryBackgroundBorderHeight);
            }
            else
            {
                this.menuEntryBackground = null;
            }
            if (menuEntrySubBackground != null)
            {
                this.menuEntrySubBackground = new BorderedTextureBox(menuEntrySubBackground, menuEntrySubBackgroundBorderWidth, menuEntrySubBackgroundBorderHeight);
            }
            else
            {
                this.menuEntrySubBackground = null;
            }
        }
 

Finally, we need to modify our Draw method. First up in draw, we must replace the line that creates and sets the “Color color” variable with this:

            Color color = isSelected ? textSelectedColor : textColor;

 

As you can see, we’re no longer using the hard-coded yellow and white but instead are using our custom values. Then, we need to add some drawing code. At the bottom of the Draw method is a line that draws the string itself. We modify this line ever so slightly as well so we’ll just overwrite it. As such, the following code should be inserted after the line that creates and sets “Vector2 origin”:

            // Measure the string for computational purposes.
            Vector2 measurement = font.MeasureString(text);
 
            // Determine the proper "upper left corner" of the menuEntryBackground image.
            Vector2 menuEntryBackgroundPosition = new Vector2(position.X - menuEntryBackgroundXPadding, position.Y - origin.Y - menuEntryBackgroundYPadding);
 
            // Determine the proper width and height of the menuEntryBackground image, assigning the width to the X field and the height to the Y field.
            Vector2 menuEntryBackgroundDimensions = new Vector2(measurement.X + (menuEntryBackgroundXPadding * 2), measurement.Y + (menuEntryBackgroundYPadding * 2));
 
            // Determine the proper "upper left corner" of the menuEntrySubBackground image.
            Vector2 menuEntrySubBackgroundPosition = new Vector2(position.X - menuEntrySubBackgroundXPadding + menuEntrySubBackgroundXOffset, position.Y - origin.Y - menuEntrySubBackgroundYPadding + menuEntrySubBackgroundYOffset);
 
            // Determine the proper width and height of the menuEntrySubBackground image, assigning the width to the X field and the height to the Y field.
            Vector2 menuEntrySubBackgroundDimensions = new Vector2(measurement.X + (menuEntrySubBackgroundXPadding * 2), measurement.Y + (menuEntrySubBackgroundYPadding * 2));
 
            // If menuEntrySubBackground exists, draw it first. Notice how we create a Rectangle and use the offsets here.
            if (menuEntrySubBackground != null)
            {
                menuEntrySubBackground.Draw(spriteBatch,
                    new Rectangle((int)menuEntrySubBackgroundPosition.X, (int)menuEntrySubBackgroundPosition.Y, (int)menuEntrySubBackgroundDimensions.X, (int)menuEntrySubBackgroundDimensions.Y),
                    new Color(255, 255,255) * screen.TransitionAlpha,
                    0.0f);
            }
 
            // If menuEntryBackground exists, draw it second.
            if (menuEntryBackground != null)
            {
                menuEntryBackground.Draw(spriteBatch,
                    new Rectangle((int)menuEntryBackgroundPosition.X, (int)menuEntryBackgroundPosition.Y, (int)menuEntryBackgroundDimensions.X, (int)menuEntryBackgroundDimensions.Y),
                    new Color(255, 255, 255) * screen.TransitionAlpha,
                    0.0f);
            }
 
            // Lastly, draw the menu entry text. Note we change the original 0 of the layerDepth to 0.0f. This is just for consistency and shouldn't change anything.
            spriteBatch.DrawString(font, text, position, color, 0, origin, scale, SpriteEffects.None, 0.0f);

After the above code insertion should be the } brace that closes the Draw method. And voila. We now have a menu entry that can display a properly scaled background image with an optional drop shadow, and that image can have a border of a constant size (though with that, we lose the ability to have the anything other than a solid color taken from the middle pixel of the image as the background for our “button” – the primary reason for this has to do with texture sampling when scaling) or can have a borderless image (which will use the entire image, not just a single pixel, scaled up to size).

So how do we implement this? We aren’t quite ready yet. You see, while this will allow us to draw an image behind a menu entry, it still leaves our menu screen’s title both imageless and with the default color. And so we must modify further. So, open up MenuScreen.cs. This is the class that all menu screens inherit from. Once again we need to first add some fields to the “Fields” region. So go ahead and add these lines:

        /// <summary>
        /// The color of the menuTitle text.
        /// </summary>
        Color menuTitleColor;
 
        /// <summary>
        /// The texture, if any, located underneath the menuTitleSubBackground.
        /// </summary>
        private BorderedTextureBox menuTitleSubBackground;
 
        /// <summary>
        /// The texture, if any, located underneath the menu title text.
        /// </summary>
        private BorderedTextureBox menuTitleBackground;
 
        /// <summary>
        /// The amount of "padding" in pixels to add between the left and right of the string and the edge of the menuTitleBackground texture.
        /// </summary>
        private int menuTitleBackgroundXPadding;
 
        /// <summary>
        /// The amount of "padding" in pixels to add between the top and bottom of the string and the edge of the menuTitleBackground texture.
        /// </summary>
        private int menuTitleBackgroundYPadding;
 
        /// <summary>
        /// The amount of "padding" in pixels to add between the left and right of the string and the edge of the menuTitleSubBackground texture.
        /// </summary>
        private int menuTitleSubBackgroundXPadding;
 
        /// <summary>
        /// The amount of "padding" in pixels to add between the top and bottom of the string and the edge of the menuTitleSubBackground texture.
        /// </summary>
        private int menuTitleSubBackgroundYPadding;
 
        /// <summary>
        /// The distance in pixels which the menuTitleSubBackground image will be offset along the X axis from menuTitleBackground.
        /// Positive for right, negative for left.
        /// </summary>
        private int menuTitleSubBackgroundXOffset;
 
        /// <summary>
        /// The distance in pixels which the menuTitleSubBackground image will be offset along the X axis from menuTitleBackground.
        /// Positive for down, negative for up.
        /// </summary>
        private int menuTitleSubBackgroundYOffset;
 

 

Next up we change the existing constructor, add a new overload, and add two parameter setting methods. So delete the existing constructor and in its place, add this:

        /// <summary>
        /// Constructor for the base class that menus inherit from. Uses a default light grey color from the sample.
        /// </summary>
        /// <param name="menuTitle">The text to display as a title.</param>
        public MenuScreen(string menuTitle)
            : this(menuTitle, new Color(192, 192, 192))
        {
        }
 
 
        /// <summary>
        /// Constructor for the base class that menus inherit from.
        /// </summary>
        /// <param name="menuTitle">The text to display as a title.</param>
        /// <param name="menuTitleColor">The color to display the text in.</param>
        public MenuScreen(string menuTitle, Color menuTitleColor)
        {
            // menus generally only need Tap for menu selection
            EnabledGestures = GestureType.Tap;
 
            this.menuTitle = menuTitle;
            this.menuTitleColor = menuTitleColor;
 
            menuTitleBackground = null;
            menuTitleSubBackground = null;
 
            TransitionOnTime = TimeSpan.FromSeconds(0.5);
            TransitionOffTime = TimeSpan.FromSeconds(0.5);
        }
 
        /// <summary>
        /// Sets a background image that will be displayed beneath the menu title text.
        /// </summary>
        /// <param name="menuTitleBackgroundImage">The image.</param>
        /// <param name="menuTitleBackgroundBorderWidth">The width in pixels of the border along the width (i.e. horizontal sides) of the menuTitleBackground image. Use 0 for no border.</param>
        /// <param name="menuTitleBackgroundBorderHeight">The width in pixels of the border along the height (i.e. vertical sides) of the menuTitleBackground image. Use 0 for no border.</param>
        /// <param name="menuTitleBackgroundXPadding">The amount of "padding" in pixels to add between the left and right of the string and the edge of the menuTitleBackground texture.</param>
        /// <param name="menuTitleBackgroundYPadding">The amount of "padding" in pixels to add between the top and bottom of the string and the edge of the menuTitleBackground texture.</param>
        public void SetMenuTitleBackground(Texture2D menuTitleBackgroundImage, int menuTitleBackgroundBorderWidth, int menuTitleBackgroundBorderHeight, int menuTitleBackgroundXPadding, int menuTitleBackgroundYPadding)
        {
            this.menuTitleBackground = new BorderedTextureBox(menuTitleBackgroundImage, menuTitleBackgroundBorderWidth, menuTitleBackgroundBorderHeight);
            this.menuTitleBackgroundXPadding = menuTitleBackgroundXPadding;
            this.menuTitleBackgroundYPadding = menuTitleBackgroundYPadding;
        }
 
        /// <summary>
        /// Sets a sub-background image that will be displayed beneath the background image (if any) that appears behind the menu title text.
        /// </summary>
        /// <param name="menuTitleSubBackgroundImage">The image.</param>
        /// <param name="menuTitleSubBackGroundBorderWidth">The width in pixels of the border along the width (i.e. horizontal sides) of the menuTitleSubBackground image. Use 0 for no border.</param>
        /// <param name="menuTitleSubBackgroundBorderHeight">The width in pixels of the border along the height (i.e. vertical sides) of the menuTitleSubBackground image. Use 0 for no border.</param>
        /// <param name="menuTitleSubBackgroundXPadding">The amount of "padding" in pixels to add between the left and right of the string and the edge of the menuTitleSubBackground texture.</param>
        /// <param name="menuTitleSubBackgroundYPadding">The amount of "padding" in pixels to add between the top and bottom of the string and the edge of the menuTitleSubBackground texture.</param>
        /// <param name="menuTitleSubBackgroundXOffset">The distance in pixels which the menuTitleSubBackground image will be offset along the X axis from menuTitleBackground. Positive for right, negative for left.</param>
        /// <param name="menuTitleSubBackgroundYOffset">The distance in pixels which the menuTitleSubBackground image will be offset along the X axis from menuTitleBackground. Positive for down, negative for up.</param>
        public void SetMenuTitleSubBackground(Texture2D menuTitleSubBackgroundImage, int menuTitleSubBackGroundBorderWidth, int menuTitleSubBackgroundBorderHeight, int menuTitleSubBackgroundXPadding, int menuTitleSubBackgroundYPadding, int menuTitleSubBackgroundXOffset, int menuTitleSubBackgroundYOffset)
        {
            this.menuTitleSubBackground = new BorderedTextureBox(menuTitleSubBackgroundImage, menuTitleSubBackGroundBorderWidth, menuTitleSubBackgroundBorderHeight);
            this.menuTitleSubBackgroundXPadding = menuTitleSubBackgroundXPadding;
            this.menuTitleSubBackgroundYPadding = menuTitleSubBackgroundYPadding;
            this.menuTitleSubBackgroundXOffset = menuTitleSubBackgroundXOffset;
            this.menuTitleSubBackgroundYOffset = menuTitleSubBackgroundYOffset;
        }

 

Lastly, we change the Draw method. Starting at the line that sets the titleColor (including that line), we’re going to replace everything in the method from there down to the end. So highlight that line and everything that follows it inside the Draw method, delete it, and replace it with this:

            // Use the menuTitleColor multiplied by the TransitionAlpha for transition effects to look good and proper.
            Color titleColor = menuTitleColor * TransitionAlpha;
 
            // Unchanged from the original sample.
            float titleScale = 1.25f;
 
            // Unchanged from the original sample.
            titlePosition.Y -= transitionOffset * 100;
 
            // Measure the string for computational purposes.
            Vector2 measurement = font.MeasureString(menuTitle);
 
            // Determine the proper "upper left corner" of the menuTitleBackground image. Notice how we need to take titleScale into account.
            Vector2 menuTitleBackgroundPosition = new Vector2(titlePosition.X - (titleOrigin.X * titleScale) - menuTitleBackgroundXPadding, titlePosition.Y - (titleOrigin.Y * titleScale) - menuTitleBackgroundYPadding);
 
            // Determine the proper width and height of the menuEntryBackground image, assigning the width to the X field and the height to the Y field.
            Vector2 menuTitleBackgroundDimensions = new Vector2((measurement.X * titleScale) + (menuTitleBackgroundXPadding * 2), (measurement.Y * titleScale) + (menuTitleBackgroundYPadding * 2));
 
            // Determine the proper "upper left corner" of the menuTitleBackground image. Notice how we need to take titleScale into account.
            Vector2 menuTitleSubBackgroundPosition = new Vector2(titlePosition.X - (titleOrigin.X * titleScale) - menuTitleSubBackgroundXPadding + menuTitleSubBackgroundXOffset, titlePosition.Y - (titleOrigin.Y * titleScale) - menuTitleSubBackgroundYPadding + menuTitleSubBackgroundYOffset);
 
            // Determine the proper width and height of the menuEntryBackground image, assigning the width to the X field and the height to the Y field.
            Vector2 menuTitleSubBackgroundDimensions = new Vector2((measurement.X * titleScale) + (menuTitleSubBackgroundXPadding * 2), (measurement.Y * titleScale) + (menuTitleSubBackgroundYPadding * 2));
 
            // If menuTitleSubBackground exists, draw it first. Notice how we create a Rectangle and use the offsets here.
            if (menuTitleSubBackground != null)
            {
                menuTitleSubBackground.Draw(spriteBatch,
                    new Rectangle((int)menuTitleSubBackgroundPosition.X, (int)menuTitleSubBackgroundPosition.Y, (int)menuTitleSubBackgroundDimensions.X, (int)menuTitleSubBackgroundDimensions.Y),
                    new Color(255, 255, 255) * TransitionAlpha,
                    0.0f);
            }
 
            // If menuEntryBackground exists, draw it second.
            if (menuTitleBackground != null)
            {
                menuTitleBackground.Draw(spriteBatch,
                    new Rectangle((int)menuTitleBackgroundPosition.X, (int)menuTitleBackgroundPosition.Y, (int)menuTitleBackgroundDimensions.X, (int)menuTitleBackgroundDimensions.Y),
                    new Color(255, 255, 255) * TransitionAlpha,
                    0.0f);
            }
 
            // Lastly, draw the menu entry text. Note we change the original 0 of the layerDepth to 0.0f. This is just for consistency and shouldn't change anything.
            spriteBatch.DrawString(font, menuTitle, titlePosition, titleColor, 0,
                                   titleOrigin, titleScale, SpriteEffects.None, 0.0f);
 
            spriteBatch.End();

 

As you can see, much the same as the MenuEntry Draw method changes. With those changes complete, we can now have custom colors for both menu titles and menu entries, and we can also have background textures (with optional borders) and textures underneath those for producing drop shadow effects (or whatever else you might want, but drop shadows seem like the most obvious use).

So how do we use all this new found power? Open up MainMenuScreen.cs and we’ll get right to it. These changes may seem a little more drastic, but they aren’t all that bad. First up, we’re adding a field to this class. Since it doesn’t have any fields, we’re also going to add the proper region tag for consistency. So add these lines at the top of the class:

        #region Fields
        
        /// <summary>
        /// Our ContentManager.
        /// </summary>
        ContentManager content;
 
        #endregion
 

 

Once again we’re replacing the existing constructor. This time we’re also adding two methods immediately after it. Here’s the new constructor with new methods:

        /// <summary>
        /// Constructor sets the menu title and menu title color.
        /// </summary>
        public MainMenuScreen()
            : base("My Magic Game", Color.DarkRed)
        {
        }
 
        /// <summary>
        /// Loads the assets used for this menu screen.
        /// </summary>
        public override void LoadContent()
        {
            if (content == null)
                content = new ContentManager(ScreenManager.Game.Services, "Content");
 
            SetMenuTitleSubBackground(content.Load<Texture2D>("shadow"), 0, 0, 30, 4, 4, 4);
            SetMenuTitleBackground(content.Load<Texture2D>("border"), 5, 5, 30, 4);
 
            // Create our menu entries.
            MenuEntry playGameMenuEntry = new MenuEntry("Play Game", Color.RoyalBlue, Color.RoyalBlue, content.Load<Texture2D>("blank"), 0, 0, 18, 0, content.Load<Texture2D>("shadow"), 0, 0, 18, 0, 4, 4);
            MenuEntry optionsMenuEntry = new MenuEntry("Options", Color.RoyalBlue, Color.RoyalBlue, content.Load<Texture2D>("blank"), 0, 0, 18, 0, content.Load<Texture2D>("shadow"), 0, 0, 18, 0, 4, 4);
 
            // Hook up menu event handlers.
            playGameMenuEntry.Selected += PlayGameMenuEntrySelected;
            optionsMenuEntry.Selected += OptionsMenuEntrySelected;
 
            // Add entries to the menu.
            MenuEntries.Add(playGameMenuEntry);
            MenuEntries.Add(optionsMenuEntry);
        }
 
        /// <summary>
        /// Unloads the assets for this menu screen.
        /// </summary>
        public override void UnloadContent()
        {
            if (content != null)
            {
                content.Unload();
            }
        }
 

 

And that’s all there is to it. As you can see, what we did was move the creation of the MenuEntry items out of the constructor and into the LoadContent method. This allows us to initialize them with textures. We’re also setting the textures for the menuTitle which were inherited from the MenuScreen base class. Now this code will only compile and work if you have textures named shadow, border, and blank in the main folder of your Content project. If not it won’t. The texture named blank should already be in there, and I’ve supplied you above with the two other textures. If you save border.png, you’ll see that the border is 5 pixels wide on all sides. You can, of course, use your own textures with your own names and your own border widths (which need neither be 5px nor even be the same width horizontally and vertically).

If you’re curious, here’s an example of the result:

borderedmenustest

Good luck!

Posted on Monday, August 2, 2010 9:27 AM xna , wp7 , tutorial | Back to top


Comments on this post: Making a MenuEntry in XNA 4.0’s GSM Sample Look Like a Button

Comments are closed.
Comments have been closed on this topic.
Copyright © Michael B. McLaughlin | Powered by: GeeksWithBlogs.net