Search code examples
vb.netxnamonogame

Center text when using BmFont (MonoGame)


Is there a way to center text when drawing using BmFont? I'm using the code from here: Using a BMP image as font in Monogame

The only code change I did was that I added a _TextWidth int to DrawText which uses DX - X to get the text length, then a function GetTextLength to return it to the button class, which then takes the ((button width - text width) / 2) to get the starting point of the text, centering it. However, there seems to be some problems and the text doesn't center using its own text width, but using the next button's text width instead. Returning the text width is fine, but for some reason when it draws on-screen it's always the wrong values.

Added/changed code:

Public Sub DrawText(ByVal spritebatch As SpriteBatch, ByVal X As Integer, ByVal Y As Integer, ByVal Text As String)
    Dim DX As Integer = X
    Dim DY As Integer = Y
    For Each C As Char In Text
        Dim FC As New FontChar
        If _CharacterMap.TryGetValue(C, FC) Then
            Dim sourceRectangle = New Rectangle(FC.X, FC.Y, FC.Width, FC.Height)
            Dim position = New Vector2(DX + FC.XOffset, DY + FC.YOffset)
            spritebatch.Draw(_Texture, position, sourceRectangle, Color.White)
            DX += FC.XAdvance
        End If
    Next
    _TextWidth = DX - X
End Sub

Public Function GetTextLength() As Integer
    Return _TextWidth
End Function

Screenshot:

Uncentered text, which could be centered if the right values were used for each button


Solution

  • MonoGame.Extended has an implementation of the BMFont renderer with some improvements from that old tutorial, including a SpriteBatch.DrawString extension.

    I've also got a tutorial on my blog about how to set it up. However, it doesn't deal with centering the text so I'll go over that now.

    If you choose to use MonoGame.Extended, you should be able to get the centering effect as follows.

    First, load your bitmap font as you normally would a SpriteFont. Note, that for this to work you'll need to setup MonoGame.Extended with the MonoGame Pipeline tool.

    _bitmapFont = Content.Load(Of BitmapFont)("my-font")
    

    Next, in your Draw method you can use GetStringRectangle to measure the size of the text, then calculate the the correct position. Something like this (sorry if my VB.net is a little rusty):

    Dim text = "Start game"
    Dim textRectangle = _bitmapFont.GetStringRectangle(text, Vector2.Zero)
    Dim textOffset = buttonWidth / 2F - textRectangle.Width / 2F
    
    _spriteBatch.DrawString(_bitmapFont, text, buttonPosition + textOffset, Color.White)
    

    Of course, you don't have to use MonoGame.Extended to do this if you don't want too. I think your code just has a bug, because it looks like it's intended to do what MonoGame.Extended does anyway.

    As far as I can tell, your code is calculating the width of the text in the DrawText method and returning it in the GetTextLength method. The problem with this approach is that if you call GetTextLength before DrawText you're going to get the wrong value.

    I assume you're doing something like this:

    Dim y = buttonPos.Y
    Dim x = buttonPos.X - buttonWidth / 2F - GetTextLength() / 2F
    
    DrawText(_spriteBatch, x, y, text)
    

    Unfortunately, this will not work because GetTextLength won't return the correct value until after DrawText runs.

    The fix of course, is to actually calculate the text length properly rather than storing it in a member variable during the draw operation.

    Public Function GetTextLength(ByVal Text As String) As String
        Dim TextWidth As Integer
        For Each C As Char In Text
        Dim FC As New FontChar
        If _CharacterMap.TryGetValue(C, FC) Then
            TextWidth += FC.XAdvance
        End If
        Next
        GetTextLength = TextWidth
    End Sub