I’ve been playing around with OpenGL for the first time in a while for the last couple of days and I’ve now written up a group of classes that make my life a lot easier so that I don’t have to worry as much about displaying graphics in 2D games. Although the classes aren’t exactly Cocos2D standard they do show how to set up OpenGL with GLKit and produce complex 2D games without too much overhead.
My current challenge has been displaying text in OpenGL. Displaying text in a game is incredibly useful; you can feedback to the user on performance, score, achievements and simple notifications. I’ve been working on a simple game that needs to display the score at the top of the screen. Working on this I found that there were at least three good approaches.
The first is to use bitmap fonts and create vertices for each character and display the appropriate texture in each position. An advantage of this method is that it is very quick if you have relatively simple text to display. There are three main problems with this:
- It wastes time and space: You have to create new font assets from a system font or build a custom font. This is going to take a lot of time without some automation and it will add to the size of your end executable.
- You will likely limit yourself to regular English text: Unless you create fonts for a huge variety of character sets you are limited to ASCII characters which makes English the only viable language, which is a problem if you are displaying foreign names from Game Center for example.
- Bad scaling: You have to include your bitmap font at the highest resolution it will need to be and if you are using iPhone, iPad, retina and non-retina graphics in your game this is going to be pretty large.
The second method is to use UIKit elements such as UILabel to present text. This works alright in a 2D scenario where you don’t need to regularly update text (if you do, enjoy seeing your frame rates drop to about 10fps) but Quartz transformations aren’t ever going to allow you to perfectly position your text where you need in a 3D environment. I don’t particularly like this method, but it will work for some people. It is worth noting that this method doesn’t fall down on any of the problems that the first method does, however.
Finally, the most effective method in my opinion is to use GLKit to create textures from CGImages which can be drawn to with Quartz 2D. This is incredibly advantageous because it means that you can use any of the system fonts with a huge variety of character sets. You can also effectively scale for different screen resolutions, which you simply wouldn’t be able to do with bitmapped fonts. Here is roughly how I’m doing it at the moment:
//You may wish to use the extras to set an appropriate scale factor
CGSize size = CGSizeMake(100,100);
float scale = [[UIScreen mainScreen] scale];
UIGraphicsBeginImageContextWithOptions(size, NO, scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGImageRef image = CGBitmapCreateFromContext(context);
texture = [GLKTextureLoader textureWithCGImage:image options:nil error:nil];
self.scoreTexture = texture;
Initially I found this code to work very well, however as soon as I needed to update the image regularly I found that my app would go from using 10MB of memory total (including other sprites) to around 1GB of virtual + real memory before it crashed out. This seemed very odd as I was replacing scoreTexture each time. I was releasing unneeded Quartz resources. It turns out the real reason is that GL takes over the texture memory, so you actually have to use OpenGL functions to release the texture first:
GLuint name = self.scoreTexture.name;
//Replace GLTextureInfo in memory
Thanks to alokoko on Stack Overflow for point this out.
Once you’ve done this you can easily update text regularly and draw on screen however there are disadvantages. I firstly tried doing the new texture generation in a separate thread using NSOperationQueue however I found it a little unreliable because there was no guarantee of the length of time it takes to generate the new texture, so the queue rapidly filled up which produced further memory errors. Ultimately, it seemed that the safest way of doing this was creating a new texture up to 10 times a second in a separate thread and then queuing an operation to update the main texture in memory on the main thread.
Of course, there are other approaches and I would be interested to hear if you have any better ones.