My UI designer has made a lovely photoshop PSD of the UI and everything pretty. The biggest problem I'm having is converting some of the more elegant fonts used into something renderable in-game. Is there a way to convert these font styles in Photoshop to a bitmap font of some sort?
I need to be able to render text like this inside my code:
Answer
Okay, you're going to have to forgive me for not giving you specific XNA code, because I'm not knowledgeable in that platform, but what I'm going to tell you should work on any game engine that lets you draw sprites.
Fonts is not your only problem, so I'm going to give you a piece of advice, and then I'm going to answer your question. With these two things, you should be able to make a lovey-dovey relationship with your GUI designer, and you both will be able to very happily make games.
The first thing is that you're going to sit down with your designer, and you're going to ask her to give you two sets of files. The first is a set of transparent files that make up your GUI (optimally in PSD or DXT format). For every button, fixed label, background, border and textbox, you will get one file (you can also do texture atlasing, but I'd recommend you do that after you assemble your GUI, and then adjust your source coordinates when blitting). Non-static text should be left out at this point (I'll revisit this later).
The second thing you will get is the actual GUI design, this time in Photoshop format. For this file, you're going to ask your designer to make the entire GUI design, using only the files she previously gave you.
She's then going to put each GUI element into a separate layer, using no effects whatsoever. You're going to tell her to do this pixel perfect, because the locations where she's going to put everything, is where everything will actually be in the finalized game.
Once you get that, for each layer, you're going to press Ctrl-T, and on the Info pane (F8), you will take note of the X and Y coordinates for each element. Make sure your units are set to pixels (Preferences->Units & Rulers->Units). These are the positions you're going to use when drawing your sprites.
Now, for fonts, as you may clearly know now, you won't be able to get your fonts to look exactly the same way as you see them in Photoshop using text rendering APIs. You're going to have to pre-render your glyphs, and then programatically assemble your texts. There are many ways to do this, and I will mention the one I use.
First thing is to render all your glyphs into one or more files. If you only care about English, one texture for all the glyphs will suffice, but if you want to have a more extended character set, you can use several files. Just make sure all the glyphs you want are available on the font your designer chose.
So, to render the glyphs, you can use the facilities of System.Drawing
to get the font metrics and draw your glyphs:
Color clearColor = Color.Transparent;
Color drawColor = Color.White;
Brush brush = new SolidBrush(drawColor);
TextRenderingHint renderingType = TextRenderingHint.AntiAliasGridFit; // Antialias is fine, but be careful with ClearType, which can blergh your renders when you apply effects
StringFormat stringFormat = StringFormat.GenericTypographic;
string fileNameFormat = "helvetica14_{0}.png";
string mapFileFormat = "helvetica14.txt";
string fontName = "Helvetica";
string fontPath = @"c:\windows\fonts\helvetica.ttf";
float fontSize = 14.3f;
int spacing = 2;
Font font = new Font(fontName, fontSize);
int x = 0;
int y = 0;
int width = 1024; // Force a maximum texture size
int height = 1024;
StringBuilder data = new StringBuilder();
int lineHeight = 0;
int currentPage = 1;
var families = Fonts.GetFontFamilies(fontPath);
List codepoints = new List();
HashSet usedCodepoints = new HashSet();
foreach (FontFamily family in families)
{
var typefaces = family.GetTypefaces();
foreach (Typeface typeface in typefaces)
{
GlyphTypeface glyph;
typeface.TryGetGlyphTypeface(out glyph);
foreach (KeyValuePair kvp in glyph.CharacterToGlyphMap) // Render all available glyps
{
char c = (char)kvp.Key;
if (!usedCodepoints.Contains(c))
{
codepoints.Add(c);
usedCodepoints.Add(c);
}
}
}
}
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
Graphics g = Graphics.FromImage(bitmap);
g.Clear(clearColor);
g.TextRenderingHint = renderingType;
foreach (char c in codepoints)
{
string thisChar = c.ToString();
Size s = g.MeasureString(thisChar, font); // Use this instead of MeasureText()
if (s.Width > 0)
{
s.Width += (spacing * 2);
s.Height += (spacing * 2);
if (s.Height > lineHeight)
lineHeight = s.Height;
if (x + s.Width >= width)
{
x = 0;
y += lineHeight;
lineHeight = 0;
if (y + s.Height >= height)
{
y = 0;
g.Dispose();
bitmap.Save(string.Format(fileNameFormat, currentPage));
bitmap.Dispose();
bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
g = Graphics.FromImage(bitmap);
g.Clear(clearColor);
g.TextRenderingHint = renderingType;
currentPage++;
}
}
g.DrawString(thisChar, font, brush, new PointF((float)x + spacing, (float)y + spacing), stringFormat);
data.AppendFormat("{0} {1} {2} {3} {4} {5}\n", (int)c, currentPage, x, y, s.Width, s.Height);
x += s.Width;
}
}
g.Dispose();
bitmap.Save(string.Format(fileNameFormat, currentPage));
bitmap.Dispose();
File.WriteAllText(mapFileFormat, data.ToString());
With this, you've drawn white glyphs over a transparent background on a bunch of PNG files, and made an index file which tells you for each codepoint, in which file the glyph is located, its location and dimensions. Notice that I also put two additional pixels to separate each glyph (to accomodate for further effects)
Now, for each of those files, you put it in photoshop, and do all the filters you want. You can set colors, borders, shadows, outlines, and anything else you want. Just make sure that the effects don't make the glyphs overlap. If so, adjust the spacing, re-render, rinse and repeat. Save as PNG or DXT, and along with the index file, put everything in your project.
Drawing text should be very simple. For each char you want to print, find its location using the index, draw it, advance the position and repeat. You can also adjust for spacing, kerning (tricky), vertical spacing, and even coloring. In lua:
function load_font(name)
local font = {}
font.name = name
font.height = 0
font.max_page = 0
font.glyphs = {}
font.pages = {}
font_definition = read_all_text("font/" .. name .. ".txt")
for codepoint, page, x, y, width, height in string.gmatch(font_definition, "(%d+) (%d+) (%d+) (%d+) (%d+) (%d+)") do
local page = tonumber(page)
local height_num = tonumber(height)
if height_num > font.height then
font.height = height_num
end
font.glyphs[tonumber(codepoint)] = { page=tonumber(page), x=tonumber(x), y=tonumber(y), width=tonumber(width), height=height_num }
if font.max_page < page then
font.max_page = page
end
end
for page = 1, font.max_page do
font.pages[page] = load_image("font/" .. name .. "_" .. page .. ".png")
end
return font
end
function draw_text(font, chars, range, initial_x, initial_y, width, color, spacing)
local x = initial_x - spacing
local y = initial_y - spacing
if range == nil then
range = { from=1, to=#chars }
end
for i = 1, range.to do
local char = chars[i]
local glyph = font.glyphs[char]
if char == 10 then -- line break
x = initial_x - spacing
y = y + ((font.height - (spacing * 2)) * 1.4)
elseif glyph == nil then
if unavailable_glyphs[char] == nil then
unavailable_glyphs[char] = true
end
else
if x + glyph.width - spacing > initial_x + width then
x = initial_x - spacing
y = y + ((font.height - (spacing * 2)) * 1.4)
end
if i >= range.from then
draw_sprite(font.pages[glyph.page], x, y, glyph.x, glyph.y, glyph.width, glyph.height, color)
end
x = x + glyph.width - (spacing * 2)
end
end
end
And there you go. Repeat for every other font (and optimally size as well)
Edit: I changed the code to use Graphics.MeasureString
instead of TextRenderer.MeasureText()
because they both use different measurement systems, and may lead to inconsistencies between the measured glyph and the drawn one, especially with overhanging glyphs found in some fonts. More information here.
No comments:
Post a Comment