Rendering convincing speech bubbles for your game
Hi there. Today I will show you a simple way to achieve good looking and convincing speech bubbles in your game.
My name is Alex and I am a programmer at Handcrafted Games where I work on our debut game. Throughout the next months I will be sharing different visual techniques that we have found useful and explaining how to do them for yourself.
The problem we’ll be solving today is how to draw a speech bubble like the one from this Batman example that can fit (almost) any text perfectly while retaining the nice smooth shapes that are typical of hand-drawn comics. The code to do this turns out to be pretty straightforward and can be implemented in any engine or platform.
Personally I find immense joy in discovering these techniques myself so I will try to guide you through the creative process instead of just giving you some code to copy & paste and hopefully you can share some of that excitement with me.
When we decided that we wanted to have speech bubbles in our game the first thing that came to mind was to ask the artists to draw some. Later on I received these nice looking speech bubbles and got to work.
The task of fitting a piece of text inside a box is not unknown to me. I have used many GUI systems (and have written some myself) where this kind of thing happens all the time so at first this seemed like an exercise in basic geometry. Word wrapping can be tricky but it’s nothing too daunting. After finishing the first prototype I realized why using fixed sized images won’t cut it.
Arbitrary pieces of text were seemingly getting split up in rather a unpredictable way which results in those ugly holes in the text.
Ugh. Not good enough for our purpose. I decided to go back and look at some more comic books and hopefully get an idea on how to proceed.
At some point it dawned on me — it is the bubble that the artist draws around the text, not the other way around. Trying to fit the text inside a given region will never give us the consistent and good looking results we want. This thought, while obvious in hindsight, was the critical bit I needed to solve the rest of this puzzle.
The other thing I noticed from hand-drawn comics — that the text is usually wrapped in the shape of a diamond, this gives us the optimal text surface area to background area ratio. For us, this means that we should center our text horizontally. So far so good, we can write some code now.
The first thing we need is a way to wrap the text in a diamond shape. I haven’t yet discovered the perfect method for doing this and my implementation turned out somewhat messy and full of checks for different cases so I’ll leave this one bit out as an exercise for the reader. Assuming we did an okay job we can feed our method a piece of text and get a result that looks somewhat like this.
Well.. it is kind of the diamond shape we wanted. Next we will calculate the rectangle of each line of text and then combine all the lines to get the polygon representing the shape of our text. This is the piece of geometry that will be transformed into a speech bubble.
Somehow this polygon has to become a smooth oval and it turns out we need to do two relatively simple transformations to achieve it. First, let’s connect those vertices on the edges so we get a convex polygon.
The resulting polygon (in green) is the convex hull of the set of points making up our text shape. There are many algorithms that can do this for us and for our small set of points it doesn’t matter that much which one we’ll pick. A Google search for “Convex hull algorithm” brings up many implementations and variants, any correct one will work for this.
The computed convex hull gives us a result that isn’t yet visually pleasing, but the shape is getting closer to what we want. The next part is really simple, we’ll find the center of our polygon and displace all the vertices away from it which gives us some nice padding.
This is already much better, certainly not that far away from our desired shape, only needs to be smoothed. We will perform an iterative subdivision process where we add new vertices and displace the old ones towards the mid-points of their new neighbors (it’s much simpler than it sounds). Here is an illustration that better explains the technique:
For each existing edge (blue), we add a new vertex (red) at its midpoint. Then we displace the old vertices (green) towards the median of their respective new neighbors (red).
The image above shows us the result of applying the subdivision process three times consecutively. We start seeing diminishing returns after 3 iterations and that’s how many we decided to use for our game. The desired shape has been achieved, but we are still missing a critical piece.
There. A triangle for the direction arrow and our speech bubble is starting to look nicely. We still have to add an outline and deal with the aliasing so it’s time to turn to the GPU for help.
We’ll use a shader implementing the Sobel operator which computes the outlines of our polygon by examining its pixels and we’ll use that information to shade our outlines in black. Here is an example implementation of the Sobel operator in GLSL.
float sobel(in sampler2D texture, in vec2 step, in vec2 uv) {
float tl = texture(texture, uv + vec2(-step.x, step.y)).b;
float left = texture(texture, uv + vec2(-step.x, 0.0)).b;
float bl = texture(texture, uv + vec2(-step.x, -step.y)).b;
float top = texture(texture, uv + vec2(0.0, step.y)).b;
float bottom = texture(texture, uv + vec2(0.0, -step.y)).b;
float tr = texture(texture, uv + vec2(step.x, step.y)).b;
float right = texture(texture, uv + vec2(step.x, 0)).b;
float br = texture(texture, uv + vec2(step.x, -step.y)).b;
float x = tl + 2.0 * left + bl - tr - 2.0 * right - br;
float y = -tl - 2.0 * top - tr + bl + 2.0 * bottom + br;
return clamp(sqrt(x*x + y*y), 0.0, 1.0);
}
After running this on our mesh we get this result back.
Not bad at all. All that’s left is to do apply some anti-aliasing to smooth those rough edges. I decided to go with FXAA, but any post-processing AA technique should work. In our engine we draw the speech bubbles to an off-screen buffer so we can run FXAA on them separately from the rest of the scene. If you are making a 3D game then you most likely already have an anti-aliasing step and do not need to do this separately.
And there it is, a beautiful speech bubble that can fit pretty much any piece of text we throw at it (with minor tweaking). At this point you can think of ways to add animation or additional effects like drop shadow.
This was my first article on the visual techniques we are using for our game. I will be doing more posts like these in the coming weeks. If you found this useful and interesting then check out our website and follow us on Twitter and we will let you know when we have something new for you.