• entries
    707
  • comments
    1173
  • views
    434118

GameFontLib is finished...for now.

Sign in to follow this  

155 views

Huzzah! 'tis done!

Well, for now. Obviously I'll find bugs along the way and I might have to optimize something or other, but it works as it is and my todo list is empty, so I'm moving on to my first prototype.

I benchmarked it against ID3DXFont (using ID3DXSprite) and have to say I was surprised by the results. I figured my engine would do better (mainly because ID3DXFont sucks), but in debug release mine was reporting more than double ID3DXFont's speeds and in release mode it was over three and a half times faster. It really isn't that big of a thing, seeing as how it was with the v-sync off and both would render perfectly fine, I was just surprised to see that big of a difference (especially with some of my formatting code and word-breaking code.)

The only thing I think I'll need to add is the ability to offset the text that you're rendering based on the position that it's told (for like a text-box with scrolling or something.) However, I'll save that for a later date.

Here's the interface in it's entirety:

class GameFont
{
IDirect3DDevice9 *GfxDevice;
CharacterSet CharacterSet;
IDirect3DTexture9 **Pages;

IDirect3DStateBlock9 *StateBlock, *SavedStateBlock;
IDirect3DVertexBuffer9 *VertexBuffer;
IDirect3DIndexBuffer9 *IndexBuffer;
IDirect3DTexture9 *CurrentTexture;
void *Vertices;
unsigned int LetterCount;
short Size;
float ScaleFactor;
std::map<unsigned long, unsigned long> ColorTable;

void FlushBatch(bool Finishing);
public:
GameFont();
~GameFont();

bool CreateFromFile(const std::string &PackFilePath, IDirect3DDevice9 *GraphicsDevice);
void Unload();

void OnDeviceLost();
bool OnDeviceReset();

long MeasureStringWidth(const std::string &String, unsigned long FormatFlags) const;
long MeasureStringHeight(const std::string &String, unsigned long FormatFlags) const;
void MeasureString(const std::string &String, unsigned long FormatFlags, long &Width, long &Height) const;
long GetKerning(char First, char Second) const;

std::string BreakWords(const std::string &String, long BoundsWidth) const;

void BeginBatch();
void DrawLetter(char Character, float X, float Y, unsigned long Color, long BoundsX, long BoundsY, long BoundsWidth, long BoundsHeight);
void DrawStringEx(const std::string &String, int Count, unsigned long DefaultColor, long X, long Y, long BoundsWidth, long BoundsHeight, unsigned long FormatFlags);
inline void DrawString(const std::string &String, int Count, unsigned long DefaultColor, long X, long Y, unsigned long FormatFlags) { DrawStringEx(String, Count, DefaultColor, X, Y, 0, 0, FormatFlags); }
void EndBatch();

void GetDevice(IDirect3DDevice9 **Device);

void SetSize(short Size);
short GetSize();
inline void SetScaleFactor(float Factor) { ScaleFactor = Factor; }
inline float GetScaleFactor() const { return ScaleFactor; }

inline const struct CharacterSet &GetCharacterSet() const { return CharacterSet; }
inline const CharacterInformation &GetCharacterInformation(unsigned char Character) const { return CharacterSet[Character]; }

inline short GetLineHeight() const { return CharacterSet.LineHeight; }
inline short GetBase() const { return CharacterSet.Base; }
inline short GetScaleWidth() const { return CharacterSet.ScaleWidth; }
inline short GetScaleHeight() const { return CharacterSet.ScaleHeight; }
inline short GetPageCount() const { return CharacterSet.PageCount; }

inline unsigned int GetKerningCount() const { return CharacterSet.KerningCount; }
inline const KerningPair *GetKerningPair(unsigned int Index) const { if(Index >= CharacterSet.KerningCount) return 0; return &CharacterSet.Kernings[Index]; }

inline short GetCharacterX(unsigned char Character) const { return CharacterSet[Character].X; }
inline short GetCharacterY(unsigned char Character) const { return CharacterSet[Character].Y; }
inline short GetCharacterWidth(unsigned char Character) const { return CharacterSet[Character].Width; }
inline short GetCharacterHeight(unsigned char Character) const { return CharacterSet[Character].Height; }
inline short GetCharacterXOffset(unsigned char Character) const { return CharacterSet[Character].XOffset; }
inline short GetCharacterYOffset(unsigned char Character) const { return CharacterSet[Character].YOffset; }
inline short GetCharacterXAdvance(unsigned char Character) const { return CharacterSet[Character].XAdvance; }
inline short GetCharacterPage(unsigned char Character) const { return CharacterSet[Character].Page; }

void GetPage(short Index, IDirect3DTexture9 **Texture) const;

void SetColor(const std::string &Name, unsigned long Color);
unsigned long GetColor(const std::string &Name) const;
};



And the code I have running in the test bed:

#include
#include
#include
#include

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
HWND WindowHandle = 0;
IDirect3D9 *Direct3D = 0;
IDirect3DDevice9 *GfxDevice = 0;
dbeals::GameFontLib::GameFont Font;
const unsigned long CLR_WHITE = 0xffffffff;
const unsigned long CLR_BLACK = 0xff000000;

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bool InitializeWindow();
bool InitializeGraphics();
bool LoadContent();
void OnFrame();
void CleanUp();

LRESULT CALLBACK WindowProcedure(HWND Handle, UINT Message, WPARAM Param0, LPARAM Param1);

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
if(!InitializeWindow())
return 0;

if(InitializeGraphics())
{
if(LoadContent())
{
MSG Msg = { 0 };
while(true)
{
if(PeekMessage(&Msg, 0, 0, 0, PM_REMOVE))
{
if(Msg.message == WM_QUIT)
break;
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
else
{
GfxDevice->Clear(0, 0, D3DCLEAR_TARGET, CLR_BLACK, 1.0f, 0);
GfxDevice->BeginScene();

OnFrame();

GfxDevice->EndScene();
GfxDevice->Present(0, 0, 0, 0);
}
}
}
}
CleanUp();
return 0;
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
LRESULT CALLBACK WindowProcedure(HWND Handle, UINT Message, WPARAM Param0, LPARAM Param1)
{
if(Message == WM_CLOSE)
PostQuitMessage(0);
return DefWindowProc(Handle, Message, Param0, Param1);
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bool InitializeWindow()
{
WNDCLASS WndClass = { 0 };
WndClass.hInstance = GetModuleHandle(0);
WndClass.hCursor = LoadCursor(0, IDC_ARROW);
WndClass.hIcon = 0;
WndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
WndClass.lpszClassName = "GameFont TestBed";
WndClass.lpfnWndProc = WindowProcedure;
if(!RegisterClass(&WndClass))
return false;

unsigned long Style = WS_OVERLAPPEDWINDOW | WS_VISIBLE;
RECT Rect = { 0, 0, 1024, 768 };
AdjustWindowRect(&Rect, Style, FALSE);
long Width = Rect.right - Rect.left;
long Height = Rect.bottom - Rect.top;

GetWindowRect(GetDesktopWindow(), &Rect);
long X = ((Rect.right - Rect.left) / 2) - (Width / 2);
long Y = ((Rect.bottom - Rect.top) / 2) - (Height / 2);

return (WindowHandle = CreateWindowExA(0, WndClass.lpszClassName, "GameFont Test Bed", Style, X, Y, Width, Height, 0, 0, WndClass.hInstance, 0)) != 0;
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bool InitializeGraphics()
{
if(!(Direct3D = Direct3DCreate9(D3D_SDK_VERSION)))
return false;

D3DPRESENT_PARAMETERS PresentParams;
ZeroMemory(&PresentParams, sizeof(PresentParams));
PresentParams.BackBufferWidth = 1024;
PresentParams.BackBufferHeight = 768;
PresentParams.BackBufferFormat = D3DFMT_A8R8G8B8;
PresentParams.hDeviceWindow = WindowHandle;
PresentParams.Windowed = true;
PresentParams.SwapEffect = D3DSWAPEFFECT_DISCARD;
if(FAILED(Direct3D->CreateDevice(0, D3DDEVTYPE_HAL, WindowHandle, D3DCREATE_HARDWARE_VERTEXPROCESSING, &PresentParams, &GfxDevice)))
return false;
return true;
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bool LoadContent()
{
if(!Font.CreateFromFile("Consolas.pfnt", GfxDevice))
return false;
Font.SetColor("CharName", 0xffff00ff);
return true;
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void OnFrame()
{
Font.BeginBatch();
// Draw the whole string (-1 count) using the default color of white, starting at (462,334) with the bounds (100, 100). Align it in the center of the bounds and break the words to fit based on the width.
Font.DrawStringEx("This is a test string using $CharName.", -1, CLR_WHITE, 462, 334, 100, 100, dbeals::GameFontLib::FormatFlags::AlignCenter | dbeals::GameFontLib::FormatFlags::BreakWords);
Font.EndBatch();
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void CleanUp()
{
Font.Unload();

if(GfxDevice)
{
GfxDevice->Release();
GfxDevice = 0;
}

if(Direct3D)
{
Direct3D->Release();
Direct3D = 0;
}
}



Not bad in my opinion; a total of 7 lines to get a font loaded and rendering.

There's also the ability to do your own formatting. All you have to do is write a function, iterate the string (testing for your own formatting), and then call GameFont::DrawLetter(). Obviously it's not THAT easy (you're pretty much replacing DrawStringEx(), so you have to handle all of the position, aligning, etc), but the ability is there. At some point I could add some sort of base class (i.e. BaseStringFormatter) and supply a default one; then you'd just have to implement your own.

I was thinking about adding some sort of "effect" system, but it increases the complexity of the library when it can be added by itself. For example, I wrote two test classes named 'DelayedString' and 'ShimmeringString'. Using the former, I can set a delay and it slowly 'types' the string to the screen. Using the latter, I set a delay and insert color-tags to brighten the current string; this way it kind of looks like a light is flying in front of the string, one character at a time.

It's not a bad little lib in my opinion and I'm honestly quite happy with it. Even more though that I'm done with it; I will curse script-style fonts until the day I die lol.

As a side note, I will be uploading it at some point, but I want to actually get into something and really TEST it before I upload it and have hundreds of comments on how it doesn't work and I suck XD

Now, onto my first prototype!
Sign in to follow this  


0 Comments


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now