Showing posts with label UI. Show all posts
Showing posts with label UI. Show all posts

Jun 1, 2011

Bitmap Fonts

While I haven't written anything about Bulldog in the last 6 weeks. I definitely haven't been idle in the coding/design department.

Following on from my last post about the icon graphics, I've drawn about 15 additional icons for in-game objects. I've also started to put together the user interface, with displays for the player's Health, Energy, Water and Morale stats. I've written alot of additional behind the scenes systems that help hold everything together, but aren't really that interesting in terms of function, so haven't bothered to talk about them in the blog.

However, this past weekend I tackled a feature I've had on the TODO list for quite some time : creating a better font rendering engine. The standard font routines available in SlimDX (or DirectX for that matter) are terrible in both performance and functionality.

The usual way of overcoming this is to produce a bitmap representation of a font at a specific size and then create a system that generates quads for every letter in the text you want to render and apply the individual letter bitmaps to the quads for rendering. I've seen this approach go wrong in some commercial AAA games and produce horrible artifacts at the edge of letters where they've partially overlapped without proper blending, so I decided to try a slightly different approach.

Rather than have a quad for every letter, I create a single big quad for the entire text, and then generate a bitmap of the text to be displayed in memory by placing the letter bitmaps in the appropriate positions, then slap that bitmap onto the quad for rendering. There's some overhead to constructing the text bitmap, but it only needs to be generated once (unless the text changes), and from then on its a single large quad with a texture, which should be much faster than 100's of small quads.

To test the font engine, I created a bitmap font with a hand-drawn appearance, so that it has a comic-like quality to it, but still is quite readable and the letter forms are consistent. The font itself was drawn in Illustrator, then output as a high resolution bitmap, which I then downsampled in Photoshop.

A screenshot from Adobe Illustrator showing the curves used to create the letter shapes.

Creating my own font means it should match in better with the overall graphic style of the game, than if I used a very clean font like Arial. The image below shows the completed font at 22 pixel resolution, which I've called "Olivia22".

The finished "Olivia22" font.

Throwing together the bitmap font engine and drawing the font didn't actually take as long as I thought. The font took about 8 hours to do all 95 glyphs, and the font engine was about the same. The part I was surprised about was how long it took to setup the kerning information for the font.


Kerning

Kerning is the process of adjusting the space between letters in a word to make the characters sit together in an aesthetically pleasing manner. Unless you're involved in graphic design it may not be apparent that a process like kerning is even required. Kerning is typically done with pairs of letters. A "kerning pair" defines the gap between two particular letters, and at render time each letter pair is checked against the list of kerning pairs and the second letter is offset left or right by the appropriate amount.

For overhanging letters such as "W" or "V", kerning is particularly important, especially when paired with letters like "A", or any of the curvy lower case letters, such as "o", "c" or "a". So for my font to "read well" I had to create kerning pairs for problem combinations of letters. The process took many hours of adjusting values, running the program against a reference text and inspecting each word individually. Over the course of 6 hours I put together close to 500 kerning pairs and just about had my eyeballs fall out of their sockets.

The image below is part of the rendered reference text, showing the kerned version of the font. (This is actually from a work in progress, so I've since tweaked some of the pairs to be better).

A crop of part of the rendered reference text


Even after so much work the result is far from perfect. The biggest limitation with the way I've implemented the font renderer is that the smallest distance a letter can be moved left or right is an entire pixel. In many situations, the "correct kerning" is a movement of less than a pixel, so the space can never be correct for that particularly instance. This is less of a problem with larger font sizes, but with the 22 pixel high font I've created, the kerning values are within the range of +3 to -3, which is pretty small, and leads to some compromise.

In an ideal world the font renderer would use the original vector graphics as the source for the letters, and create its own anti-aliased versions at whatever size is required, however I can't be bothered creating such a thing at this stage, especially when I'm trying to hit an end of year deadline. Perhaps in the future?

Another thing which has become apparent is that my process of kerning is time consuming and fiddly. My plan is to create another 3 or so variations on the current font, (ie different sizes and normal/bold versions). If its going to be 6 hours of kerning for every one of those I'll go insane. So I've come up with an improved workflow for kerning that should shave hours off the time.

The current workflow involves setting the kerning values in the font's data file, running the test code that takes the reference text and produces an output png of the rendered text, loading the png in photoshop and looking over the entire text to spot problems and then repeating the entire process.

The improved workflow relies on me getting the TextBox control working, and adding the ability to tweak the kerning per character pair directly in the text using the cursor keys. This should greatly speed up the process, as the kerning changes will be visible immediately, and without having to load the output in an external application.

Mar 28, 2011

Bugs

As part of this blog I wanted to talk about my failures as well as my successes, so in the spirit of that I'll talk about things which have tripped me up recently and what the solution was, in the hope that someone else with the same problem might be able to learn something.

Full Screen Mode
One of the early things I implemented in the main engine was the ability to go to full screen mode. It's not a big thing, its pretty much in every game. Though you need to properly reset the Direct3D device and free up all your resources and load them in again in order for it to work. Which can be tricky for those unfamiliar with Direct3D to get their head around. I consulted many tutorials and got it working and then ignored it for several months.

I switched the game to full screen mode on the weekend and the screen resized to fit the entire screen but the rendering stopped, and the entire app hung. Obviously I'd broken something in the last few months. To cut a long story short, I'd implemented a new way of handling the Resize events and that was firing when it shouldn't and was stopping the rendering. After I fixed that, it worked again... mostly.

My UI elements, (which admittedly didn't exist when I coded the full screen mode) were not receiving mouse clicks correctly, as though the coordinate systems were offset somehow. I discovered that the actual dimensions of the full screen window weren't correct. Though I was setting it to be 1680x1050 (the max resolution of the laptop I do development on), the window was coming out as 1664x1015 or something. Stretched across the entire screen this would cause misalignment of the mouse cursor vs the controls and exhibit the behaviour I noted.

After much googling, I finally discovered that you need to set the borderstyle of the form to none when going to fullscreen and then restore it when switching back. When I originally implemented full screen mode that little quirk wasn't mentioned in the tutorials I looked at, and because it looked correct, I assumed it was correct, due to the perception that full screen mode should have been a cinch.

I guess the lesson here is never assume that you're done correctly just because it's a simple task, and the output seems correct.

Mar 7, 2011

Progress Report: Windowing System part 2

Since my last post I've been working on adding more controls to the UI, and in so doing managed to break the entire UI system for about 4 days. Argh. I didn't really have a lot of spare time to spend on fixing it, so the game engine was left in a non-functioning state for the entire time, which I hate doing. I could have rolled back the changes since I use source control quite religiously, but the change was necessary and I powered through and got it back up and running.

My initial implementation of the skinning system as detailed in my previous post made a few assumptions that came back to bite me when I tried to implement the checkbox control. I'd decided that controls will only need 4 different states, which I reasoned at the time was:
  • Normal
  • Highlighted (mouse over the control)
  • Disabled (that greyed out non functional look)
  • MouseDown (for buttons and the like).

When it came to the checkbox control I realised there are really 6 states involved:
  • Normal Unticked
  • Highlighted Unticked
  • Disabled Unticked
  • Normal Ticked
  • Highlighted Ticked
  • Disabled Ticked

The Button Control skinning was implemented naively, as it too should really have 6 states. Furthermore, some controls may want more than 6 states, and some may only require 3, so hardcoding the system to have 4 skinning states wasn't going to work. The coding change to allow for a flexible number of states was doing my head in so I ended up with a bastard child of different approaches all working against each other which broke everything.

The good news is that its working now, and I've pretty much implemented every UI control I'll need in the near future. I may need to tweak a few things in the existing controls to add functionality over time, but I shouldn't need to make big changes like the ones that spawned the unholy abomination this week.

The "game" parts of the game.

An important gameplay element that relies heavily on the UI is inventory management, which is pretty much ubiquitous in video games. Now that I actually have a rudimentary UI happening, I've managed to get a basic test inventory working, where you can drag and drop items from slot to slot and you have a tooltip window appear when you mouse over items.

It doesn't sound like it should be much work to implement, and truth be told it isn't. However, it relies on a lot of other elements (some small, some huge) being in place.  Working on the UI for several weeks is fairly boring as you dont make any tangible progress on the "game" part of the game, as being able to click buttons and checkboxes without any actually context isn't exactly awesome fun.

Prototype game mode for Bulldog :p

In reality, there is a HUGE framework of non-game parts that have to be built before you can get anywhere, so in the beginning, identifiable progress can be slow, but once you reach a certain threshold you're mostly doing "game" parts, and it all starts to come together. Kind of like paying a mortgage - in the early years its all interest payments and very little principal, and later its mostly principal. Well, thats the theory anyway. :)

So, even though its a small feature in the grand scheme of things, the inventory screen is very important, and getting a working prototype up and running is a good feeling.

Feb 22, 2011

Progress Report: Windowing System

At the moment I'm working on the windowing system for the UI. Simple things like Buttons and Checkboxes and all those standard UI controls we take for granted in Windows or other platforms need to be developed from scratch when you're working outside of their standard UI framework, which is exactly what happens when you use a graphics library like DirectX or OpenGL.

So far I've implemented the Window control and the Button control, which is a surprising amount of work once you start having to develop a control hierarchy, and have a way for controls to receive mouse up/down events. It's not overly complex work, but it's one of those situations where you need to put in a whole lot of work before you can get anything remotely functional.

Hand in hand with the windowing system is the skinning system. Since we're dealing with a 3D world, at its simplest level the visual aspect of the control is a mesh that you then slap a texture on. The skinning system manages that process in such a way that you can resize the control without having to redesign the texture in a paint program and it still have it look correct.

The textures I'm currently using for the windowing system are 64x64 pixels, and an example of one I'm using for window controls is below:

Source texture for window control

If you take this window texture and naively stretch it to the size of a 300x200 pixel window this is the result:

EWWWW... UGLY!

Simply stretching the image doesn't look very good at all. We could solve the problem by redrawing the window texture at 300x200 in Photoshop, and the result will look nice, but then we'd need to draw a window texture for every window that might pop up in the application, and we lose the ability to dynamically size a window based on content without encountering the same issue above.

A solution to this problem is to break the original image down into a 3x3 grid and then tile each component (corner, edge, centre) using different methods. Stretching still becomes an issue with the edge and centre parts if the texture is not homogenous in the direction of stretching, so instead of stretching the image, I tile it. The layout/tiling scheme works as follows:

  • Corners: no tiling.
  • Edges: tiling in 1 axis only.
  • Centre: tiling in both axes.


Tiling Scheme (image scaled to 4x normal size)

In my windowing system I use control points located at (15,15) and (47,47), meaning the edges are all 16 pixels and the centre is a 32x32 pixel block.

Internally the skinning system actually breaks the source texture into 4 sub-textures to get it to be able to tile correctly, as the tiling would be across the entire texture not just the parts we want. In addition, as a consequence of the 3x3 layout of the texturing, the mesh for the controls is more complex, consisting of 18 triangles instead of just two.

Below is an example of the finished result as it would appear in the app:

The tiled window.

The beauty of having the look of the control be derived from a simple 64x64 texture is that it is relatively simple to alter the look of the entire UI of the application, which is a great benefit when you're in the early stages of development and haven't settled on a final look and feel.

As I mentioned at the start of the post, I've gotten the Button and Window controls working, and will probably spend a bit more time tidying them up and then move onto drag and drop functionality, which is necessary for the inventory screen.