I've been working on a bitmap character renderer for my game engine in C#, however, I've been running into some spacing-related problems.
This script parses the font and gets all the relevant data from the bitmap file. The bitmap file uses the Angel Code format, with characters defined like this:
char id=35 x=357 y=327 width=51 height=57 xoffset=-7 yoffset=15 xadvance=53
Where id
is the Unicode value, x
, y
, width
, and height
are texture coordinates, and xoffset
, yoffset
, and xadvance
describe where to place each character and how much to move the cursor after placing. Full documentation here.
It works almost perfectly, however, the character spacing is too close or too far, resulting in overlapping or generally wrong placement, like this:
As per the code:
private string resourceDir = "../../../Pastel/Resources/";
public Texture fontTexture;
public Vector2 fontTexScale;
public float fontLineHeight;
public Dictionary<int, Character> charactersDictionary;
public struct Character
{
public int charId;
public float x;
public float y;
public float width;
public float height;
public float xOffset;
public float yOffset;
public float xAdvance;
public Vector3[] vertexPositions;
public Vector2[] vertexUvs;
}
public GuiFontManager(string filePath)
{
ParseFont(filePath);
}
public void ParseFont(string filePath)
{
charactersDictionary = new();
if (File.Exists(resourceDir + "Textures/Text/" + filePath))
{
// Read a text file line by line.
string[] rawData = File.ReadAllLines(resourceDir + "Textures/Text/" + filePath);
string[] firstLineSplit = rawData[1].Split(" ", StringSplitOptions.RemoveEmptyEntries);
// get line height
fontLineHeight = float.Parse(firstLineSplit[1].Split("=")[1]);
// get texture scale
fontTexScale.X = float.Parse(firstLineSplit[3].Split("=")[1]);
fontTexScale.Y = float.Parse(firstLineSplit[4].Split("=")[1]);
// get character variables
for (int i = 5; i < rawData.Length - 1; i++)
{
Character character = new Character();
string[] lineSplit = rawData[i].Split(" ", StringSplitOptions.RemoveEmptyEntries);
for (int j = 1; j < 9; j++)
{
string[] lineVariables = lineSplit[j].Split("=");
switch (lineVariables[0])
{
case "id":
character.charId = int.Parse(lineVariables[1]);
break;
case "x":
character.x = float.Parse(lineVariables[1]) / fontTexScale.X;
break;
case "y":
character.y = -float.Parse(lineVariables[1]) / fontTexScale.Y;
break;
case "width":
character.width = float.Parse(lineVariables[1]) / fontTexScale.X;
break;
case "height":
character.height = float.Parse(lineVariables[1]) / fontTexScale.Y;
break;
case "xoffset":
character.xOffset = float.Parse(lineVariables[1]) / fontTexScale.X;
break;
case "yoffset":
character.yOffset = -float.Parse(lineVariables[1]) / fontTexScale.Y;
break;
case "xadvance":
character.xAdvance = float.Parse(lineVariables[1]) / fontTexScale.X;
break;
default:
break;
}
}
float width = character.width;
float height = character.height;
character.vertexPositions = new Vector3[]
{
new Vector3(-width, height, 0f),
new Vector3(width, height, 0f),
new Vector3(width, -height, 0f),
new Vector3(-width, -height, 0f)
};
float u = character.x;
float v = character.y;
character.vertexUvs = new Vector2[]
{
new Vector2(u, v),
new Vector2(u + width, v),
new Vector2(u + width, v - height),
new Vector2(u, v - height),
};
charactersDictionary.Add(character.charId, character);
}
}
}
Then, when adding to the screen, I use this:
public override void Text(string text, GuiFontManager guiFont, float scale = 5f, bool centred = false, float maxLineLength = 1000f)
{
char[] characters = text.ToCharArray();
List<Vector3> vertexPositions = new();
List<Vector2> vertexUvs = new();
// Get character positions in line
float xDelta = 0;
foreach (char character in characters)
{
GuiFontManager.Character textCharacter = guiFont.charactersDictionary[character];
for (int i = 0; i < textCharacter.vertexPositions.Length; i++)
{
vertexPositions.Add((textCharacter.vertexPositions[i] * scale) + (xDelta + (textCharacter.xOffset * scale), (textCharacter.yOffset * scale), 0f));
}
vertexUvs.AddRange(textCharacter.vertexUvs);
xDelta += textCharacter.xAdvance * scale;
}
this.vertexPositions = vertexPositions.ToArray();
this.vertexUvs = vertexUvs.ToArray();
}
I've spent a while trying to find the solution but can't figure it out. Can anyone see the problem?