XNA Creators Club Online
Page 1 of 1 (3 items)
Sort Posts: Previous Next

Fonts, I18N, and Controller Buttons

Last post 1/17/2009 7:42 PM by Thunderfist. 2 replies.
  • 1/14/2009 11:29 PM

    Fonts, I18N, and Controller Buttons

    Our game used to use a custom BitmapFont I found on the net and extensively modified.

    We just switched to doing fonts with the LocalizationPipeline and its LocalizedFontProcessor that scans all resources and generates the SpriteFont ranges etc.
    It works great.  We get English, Japanese, and Korean just fine.

    Our old custom font code let us used various characters for the controller buttons.  For example @ was the A button.

    How does one add custom glyphs to a BitmapFont?

    I can think of two ways.
    - Chop the strings at the special characters, render the first half, then the special glyph, then the second half.  Kind of a hack.
    - Some supported way of mapping an arbitrary Unicode character to a Texture2D, maybe defining a subrectangle in the Texture2D

    Any ideas?

    P.S. For any out there that do not know this, I18N stands for Internationalization since there are 18 characters between the I and the n.
           I18N is the process of writing an application so it can be adapted to most foreign languages.
    P.P.S. L10N is Localization, the process of taking an I18N'd application and tooling for a given location.


    Inside every complex program is a simple program trying to get out.

    Richard Keene - CTO - Sandswept Studios
    http://www.sandsweptstudios.net
  • 1/15/2009 12:54 PM In reply to

    Re: Fonts, I18N, and Controller Buttons

    I've always seen it done the first way you suggest: scan the string for special characters, split either side of it, and render each part separately. (I use something like {A} for the A button - if @, what if you wanted to show an email address? But that's by the by). It can complicate your text code, but it's the most flexible way, and your text code will probably already be getting complicated if you want to do automatic word wrapping, add characters one by one in a "typing" effect, etc.
    Pandemonium, an occasionally updated blog about my game, XNA, games development, and the games industry; XapParse, a parser for XAP (XACT) files
  • 1/17/2009 7:42 PM In reply to

    Re: Fonts, I18N, and Controller Buttons

    Our solution ended up being to write a class called StringDraw that handles meta character tags...

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;

    namespace Detour
    {
        /// <summary>
        /// The meta-string drawing container.  Never call this directly.  Use Detour.DrawString methods.
        /// </summary>
        public class StringDraw : DetourBase
        {
            Texture2D ControllerButtons;
            public SpriteFont TheFont;
            public Vector2 TheScreenLocation;
            public Vector2 TheOriginalScreenLocation;
            public int ClipLength;
            public Color TheColor;
            public String TheString;
            public object[] TheArgs;

            int Idx;
            StringBuilder Sb = new StringBuilder();

            /// <summary>
            /// Where the images are in the xboxControllerSpriteFont.png Texture2D called ControllerButtons below.
            /// </summary>
            Rectangle ARect = new Rectangle(520, 32, 47, 47);
            Vector2 AOrigin = new Vector2(0, 5);
            Rectangle BRect = new Rectangle(615, 32, 47, 47);
            Vector2 BOrigin = new Vector2(0, 5);
            Rectangle XRect = new Rectangle(472, 32, 47, 47);
            Vector2 XOrigin = new Vector2(0, 5);
            Rectangle YRect = new Rectangle(567, 32, 47, 47);
            Vector2 YOrigin = new Vector2(0, 5);

            Rectangle StartRect = new Rectangle(420, 26, 52, 52);
            Vector2 StartOrigin = new Vector2(0, 8);
            Rectangle BackRect = new Rectangle(290, 26, 49, 52);
            Vector2 BackOrigin = new Vector2(0, 8);

            Rectangle DPadRect = new Rectangle(90, 0, 111, 110);
            Vector2 DPadOrigin = new Vector2(0, 30);
            Rectangle LStickRect = new Rectangle(0, 13, 91, 92);
            Vector2 LStickOrigin = new Vector2(0, 25);
            Rectangle RStickRect = new Rectangle(201, 13, 91, 92);
            Vector2 RStickOrigin = new Vector2(0, 25);

            Rectangle GuideButtonRect = new Rectangle(338, 12, 83, 85);
            Vector2 GuideButtonOrigin = new Vector2(0, 20);

            Rectangle LeftBumperRect = new Rectangle(662, 28, 129, 54);
            Vector2 LeftBumperOrigin = new Vector2(0, 7);
            Rectangle RightBumperRect = new Rectangle(895, 28, 129, 54);
            Vector2 RightBumperOrigin = new Vector2(0, 7);

            Rectangle LeftTriggerRect = new Rectangle(843, 6, 52, 99);
            Vector2 LeftTriggerOrigin = new Vector2(0, 30);
            Rectangle RightTriggerRect = new Rectangle(790, 6, 52, 99);
            Vector2 RightTriggerOrigin = new Vector2(0, 30);

            List<CompiledNode> CompileBuffer;

            public void Draw(Vector2 screenLocation, int clipLength, List<CompiledNode> nodes, params object[] args)
            {
                if (ControllerButtons == null)
                {
                    ControllerButtons = Gm.Content.Load<Texture2D>("xboxControllerSpriteFont");
                }

                TheArgs = args;
                TheScreenLocation = screenLocation;
                TheOriginalScreenLocation = TheScreenLocation;

                foreach (CompiledNode c in nodes)
                {
                    c.Draw();
                }
            }

            public void Draw(SpriteFont font, Vector2 screenLocation, Color c, int clipLength, String s, params object[] args)
            {
                List<CompiledNode> nodes = Compile(font, screenLocation, c, clipLength, s);
                Draw(screenLocation, clipLength, nodes, args);
            }

            /// <summary>
            /// Draw the given string on the screen with meta-codes.
            /// <pre>
            /// Codes are
            /// A A button
            /// B B button
            /// X X button
            /// Y Y button
            /// S Start Button
            /// BK Back Button
            /// LT Left Trigger
            /// RT Right Trigger
            /// LB Left Bumper
            /// RB Right Bumper
            /// O Open square bracket
            /// C Close square bracket
            /// LS Left Stick
            /// RS Right stick
            /// LD Left DPad stick.
            /// FNN Change to Font of NN point size. NN Must be one of the supported fonts we generate.
            /// #RRGGBBAA Change color or #RRGGBB or #RGBA or #RGB or # (default to white)
            /// </pre>
            ///
            /// </summary>
            /// <param name="f"></param>
            /// <param name="screenLocation"></param>
            /// <param name="c"></param>
            /// <param name="clipLength">Clip character ouput to this length.  Lets us do a typewriter effect.</param>
            /// <param name="s"></param>
            /// <param name="args">Any square brackets get escaped so are OK in args that are Strings.</param>
            public List<CompiledNode> Compile(SpriteFont f, Vector2 screenLocation, Color c, int clipLength, String s)
            {
                // We get all params into instance vars so we can have methods that do things
                // like FlushBuffer etc.
                TheFont = f;
                TheColor = c;
                TheScreenLocation = screenLocation;
                ClipLength = clipLength;
                TheOriginalScreenLocation = screenLocation;
                Idx = 0;
                Sb.Length = 0;
                CompileBuffer = new List<CompiledNode>();
                TheString = s;

                CompileCore();

                return CompileBuffer;
            }

            private void CompileCore()
            {
                CompileBuffer.Add(new ColorChange(this, TheColor));
                CompileBuffer.Add(new FontChange(this, TheFont));

                while (Idx < TheString.Length)
                {
                    if (TheString[Idx] == '{')
                    {
                        FlushBufferCompile();

                        int closeBracket = TheString.IndexOf("}", Idx);
                        if (closeBracket < 0)
                        {
                            // Off end, missing close curly bracket. Abort silently.
                            return;
                        }
                        String argsIdxStr = TheString.Substring(Idx + 1, closeBracket - Idx - 1).ToUpper();
                        Idx = closeBracket + 1;

                        int argIdx = Int32.Parse(argsIdxStr);
                        CompileBuffer.Add(new ParamOutput(this, argIdx));
                    }
                    else if (TheString[Idx] == '[')
                    {
                        int closeBracket = TheString.IndexOf("]", Idx);
                        if (closeBracket < 0)
                        {
                            // Off end, missing close bracket. Abort silently.
                            return;
                        }
                        String token = TheString.Substring(Idx + 1, closeBracket - Idx - 1).ToUpper();
                        Idx = closeBracket + 1;

                        if (token == "O")
                        {
                            Sb.Append("[");
                        }
                        else if (token == "C")
                        {
                            Sb.Append("[");
                        }
                        else
                        {
                            FlushBufferCompile();
                            if (token == "A")
                            {
                                CompileBuffer.Add(new GlyphOutput(this, ARect, AOrigin));
                            }
                            else if (token == "B")
                            {
                                CompileBuffer.Add(new GlyphOutput(this, BRect, BOrigin));
                            }
                            else if (token == "X")
                            {
                                CompileBuffer.Add(new GlyphOutput(this, XRect, XOrigin));
                            }
                            else if (token == "Y")
                            {
                                CompileBuffer.Add(new GlyphOutput(this, YRect, YOrigin));
                            }
                            else if (token == "S")
                            {
                                CompileBuffer.Add(new GlyphOutput(this, StartRect, StartOrigin));
                            }
                            else if (token == "BK")
                            {
                                CompileBuffer.Add(new GlyphOutput(this, BackRect, BackOrigin));
                            }
                            else if (token == "GB")
                            {
                                CompileBuffer.Add(new GlyphOutput(this, GuideButtonRect, GuideButtonOrigin));
                            }
                            else if (token == "LT")
                            {
                                CompileBuffer.Add(new GlyphOutput(this, LeftTriggerRect, LeftTriggerOrigin));
                            }
                            else if (token == "RT")
                            {
                                CompileBuffer.Add(new GlyphOutput(this, RightTriggerRect, RightTriggerOrigin));
                            }
                            else if (token == "LB")
                            {
                                CompileBuffer.Add(new GlyphOutput(this, LeftBumperRect, LeftBumperOrigin));
                            }
                            else if (token == "RB")
                            {
                                CompileBuffer.Add(new GlyphOutput(this, RightBumperRect, RightBumperOrigin));
                            }
                            else if (token == "LS")
                            {
                                CompileBuffer.Add(new GlyphOutput(this, LStickRect, LStickOrigin));
                            }
                            else if (token == "RS")
                            {
                                CompileBuffer.Add(new GlyphOutput(this, RStickRect, RStickOrigin));
                            }
                            else if (token == "DP")
                            {
                                CompileBuffer.Add(new GlyphOutput(this, DPadRect, DPadOrigin));
                            }
                            else if (token.StartsWith("#"))
                            {
                                String hex = token.Substring(1);
                                TheColor = ColorUtil.FromHexString(hex);
                                CompileBuffer.Add(new ColorChange(this, TheColor));
                            }
                            else if (token.StartsWith("F"))
                            {
                                String szs = token.Substring(1);
                                int sz = Int32.Parse(szs);
                                if (sz == 12)
                                {
                                    TheFont = Gm.Font12;
                                }
                                else if (sz == 18)
                                {
                                    TheFont = Gm.Font18;
                                }
                                else if (sz == 22)
                                {
                                    TheFont = Gm.Font22;
                                }
                                else if (sz == 24)
                                {
                                    TheFont = Gm.Font24;
                                }
                                CompileBuffer.Add(new FontChange(this, TheFont));
                            }
                        }
                    }
                    else if (TheString[Idx] == '\n')
                    {
                        FlushAndNewlineCompile();
                        Idx++;
                        // We handle CR, LF, CRLF, and LFCR.
                        if (Idx < TheString.Length && TheString[Idx] == '\r')
                        {
                            Idx++;
                        }
                    }
                    else if (TheString[Idx] == '\r')
                    {
                        FlushAndNewlineCompile();
                        Idx++;
                        if (Idx < TheString.Length && TheString[Idx] == '\n')
                        {
                            Idx++;
                        }
                    }
                    else
                    {
                        Sb.Append(TheString[Idx]);
                        Idx++;
                    }
                }
                FlushBufferCompile();
            }

            public void DrawGlyph(Rectangle ARect, Vector2 origin)
            {
                float scale = 1.0f;

                if (TheFont == Gm.Font12)
                {
                    // Make it 10% taller than the font size.
                    scale = (12.0f / ARect.Height) * 1.1f;
                }
                Gm.TheSpriteBatch.Draw(ControllerButtons, TheScreenLocation, ARect, TheColor, 0, origin, scale, SpriteEffects.None, 0);
                TheScreenLocation.X += ARect.Width;
            }

            private void FlushAndNewlineCompile()
            {
                FlushBufferCompile();
                CompileBuffer.Add(new NewlineOutput(this));
            }

            public void NewLine()
            {
                TheScreenLocation.X = TheOriginalScreenLocation.X;
                TheScreenLocation.Y = TheScreenLocation.Y + TheFont.LineSpacing;
            }

            private void FlushBufferCompile()
            {
                if (Sb.Length > 0)
                {
                    CompileBuffer.Add(new StringOutput(this, Sb.ToString(), TheFont));
                    Sb.Length = 0;
                }
            }

            public void DrawSimpleString()
            {
                Gm.TheSpriteBatch.DrawString(TheFont, Sb.ToString(), TheScreenLocation, TheColor);
            }

            /// <summary>
            /// Converts any [ or ] to [O] or [C] to escape square brackets.  They might be in someone's gamer tag
            /// or in some other user input string.
            /// </summary>
            /// <param name="s"></param>
            /// <returns></returns>
            public static String SanitizeArgString(String s)
            {
                if (s == null)
                {
                    return null;
                }
                StringBuilder sb = new StringBuilder(s.Length);
                foreach (Char c in s)
                {
                    if (c == '[')
                    {
                        sb.Append("[O]");
                    }
                    else if (c == ']')
                    {
                        sb.Append("[C]");
                    }
                    else
                    {
                        sb.Append(c);
                    }
                }
                return sb.ToString();
            }
        }

        public abstract class CompiledNode : DetourBase
        {
            public StringDraw Owner;

            public CompiledNode(StringDraw owner)
            {
                Owner = owner;
            }

            public abstract void Draw();
        }

        class ColorChange : CompiledNode
        {
            Color TheColor;

            public ColorChange(StringDraw owner, Color c)
                : base(owner)
            {
                TheColor = c;
            }

            public override void Draw()
            {
                Owner.TheColor = TheColor;
            }
        }

        class FontChange : CompiledNode
        {
            SpriteFont TheFont;

            public FontChange(StringDraw owner, SpriteFont f)
                : base(owner)
            {
                TheFont = f;
            }

            public override void Draw()
            {
                Owner.TheFont = TheFont;
            }
        }

        class StringOutput : CompiledNode
        {
            String TheString;
            float TheWidth;

            public StringOutput(StringDraw owner, String s, SpriteFont f)
                : base(owner)
            {
                TheString = s;
                TheWidth = f.MeasureString(s).X;
            }

            public override void Draw()
            {
                Gm.TheSpriteBatch.DrawString(Owner.TheFont, TheString, Owner.TheScreenLocation, Owner.TheColor);
                Owner.TheScreenLocation.X = Owner.TheScreenLocation.X + TheWidth;
            }
        }

        class ParamOutput : CompiledNode
        {
            int paramIdx;

            public ParamOutput(StringDraw owner, int paramidx)
                : base(owner)
            {
                paramIdx = paramidx;
            }

            public override void Draw()
            {
                String s = StringDraw.SanitizeArgString(Owner.TheArgs[paramIdx].ToString());
                Gm.TheSpriteBatch.DrawString(Owner.TheFont, s, Owner.TheScreenLocation, Owner.TheColor);
                Owner.TheScreenLocation.X = Owner.TheScreenLocation.X + Owner.TheFont.MeasureString(s).X;
            }
        }

        class NewlineOutput : CompiledNode
        {

            public NewlineOutput(StringDraw owner)
                : base(owner)
            {
            }

            public override void Draw()
            {
                Owner.NewLine();
            }
        }

        class GlyphOutput : CompiledNode
        {
            Rectangle TheRect;
            Vector2 TheOrigin;

            public GlyphOutput(StringDraw owner, Rectangle rect, Vector2 origin)
                : base(owner)
            {
                TheRect = rect;
                TheOrigin = origin;
            }

            public override void Draw()
            {
                Owner.DrawGlyph(TheRect, TheOrigin); ;
            }
        }
    }
    Inside every complex program is a simple program trying to get out.

    Richard Keene - CTO - Sandswept Studios
    http://www.sandsweptstudios.net
Page 1 of 1 (3 items) Previous Next