We have been quite busy working on Roche Fusion for the last few months, and would like to think that we have improved and refined the game's style a lot since its early times. I am sure, if you compare some of our older and some of our latest screen shots, you will agree.
Among many other things, the major changes we made were increasing the contrast of all our art and adding highlights and glow effects to almost everything in the game. And not just to show off, but because this gives the game a much more lively, energetic, and in the end epic feel.We play a lot with bright colours on dark backgrounds to create an engaging and hopefully memorable and fitting style for our game.
Obviously, one of the most important aspects of a shoot em up game, and even more so Roche Fusion, since we plan to go completely overboard here, are the weapons, for which our artist made some amazing concept art. Two of them I want to highlight here, namely the shock waves and black hole. The former probably dealing only little damage, but applying some negative effect to enemies hit, and the latter in contrast forming one of the ultimate weapons of the game, completely clearing the screen by sucking in enemies, projectiles, you name it within a few split seconds (and destroying them at the Roche Limit, if you feel like looking up where our game got its name from ;)).
As a long-time graphical programmer, I often think about how to implement all kinds of visual effects I come across - in code, often without realising it. So when I saw that concept of the black hole, my mind started racing. Our artist had intended for us to draw some rotating spirally sprites to give the effect of a spinning black hole, but I knew I could do better.I was convinced I could actually warp and distort the actual rendered game, instead of just 'faking' it. So I got out my text editor and started typing away.
The idea behind the effect is actually rather simple: Draw a circle where we want the black hole to be, and fill it in with whatever is actually on the screen in the same circle, while also twisting the underlying image more and more towards the center of the black hole. Add some blurry blackness and maybe a slightly inward pulling distortion and we are done!
In fact, in the end I only needed a few dozen lines of code, most of them in the custom shaders I wrote; but since it had to be just right for the effect to look good especially with changing perspective (Roche Fusion is what they call '2.5D', with a lot of parallax and other 3D effects), it took me the better part of a day, and a lot of fighting with game-space, camera-space and screen-space coordinate systems, and homogeneous transformations (read: mathy stuff).
But the results were and are definitely worth it.
But, 500 odd words into this post, what I really wanted to talk about is what I did just yesterday: Shock waves.Of course, having such an awesome - if I do say so myself - effect for the black hole, we needed something similar for the shock waves. Just drawing some circular sprites may work, but in the end that would looks somewhat flat and in the worst case just bad. So we figured, if we already have the ability to distort the image on the screen, why not use a rippling effect, like water ripples, a physical effect we are all familiar with.
In theory the implementation of this effect is very similar to the one of the black hole, but I want to go into a bit more technical detail here, in the hopes that some of you reading this might get something out of it, be it knowledge, inspiration or just a feeling of profound amazement. The latter was certainly the emotion I experienced when I saw the effect working for the first time, but I am getting ahead of myself.
Warning! Technical Mumbo Jumbo ahead!
What we first of all need is to get some basic drawing of the shock waves. What I did was simply draw a quad using the usual techniques, in the pixel shader discarding all the pixels not actually belonging to the ring we want to apply the ripple effect to, using a simple distance-to-center check (for this, offset from the center point has to be passed from vertex to pixel shader).
(This can be optimised by drawing an approximation of the circle, using more vertices, to waste less pixel shader executions.)
Since we have to later sample (take pixels from) the previously rendered image of the game, we also need to know the exact position of each pixel in the shock wave ring on the screen. What one usually does in that case, specifically for 'regular' full-screen post processing effects, is also passing down those values from vertex to pixel shader. For reasons that I will go into in a bit, I went a different route, and actually passed down the world-position of each pixel, instead of the screen-position, together with the transformation matrix, to then compute the screen-position in the pixel shader.
The following image shows the result, colouring pixels using the screen-space coordinates as red and green, normalised to [0, 1]x[0, 1].
Now, those familiar with shaders and post processing effects may think that passing down a full matrix to the pixel shader and doing transformations there is a bit of a waste; and normally I would agree completely.However, remember that Roche Fusion is 2.5D. While I have seen that term used in a number of different ways, what I mean with it here is that while the game-play itself is only 2D, many of the graphical effects are 3D, in fact to the degree of tilting the 2D game-plane depending on the movement of the player.
I made the - admittedly somewhat arbitrary - choice, based on my intuition, which I have come to trust when it comes to graphical effects, that the shock waves would not propagate in camera-space, parallel to the screen, but should actually cause ripples in the game-plane. This means, that the rings shown above are actually not perfect circles, but projectively distorted when drawn on the screen. And since we will be distorting on a per-pixel basis in just a paragraph, we thus need to be able to do projective calculations in the pixel shader. So in this case, we can actually save ourselves a lot of troubles by doing all of our calculations in world space, and then only transforming the pixels to the projective view-space once we know the final distorted position.
And that is precisely what we do. We now have each pixels position in world-space, as well as the center of the shock waves, and with that information we can easily calculate how far along the shock wave we are, or more accurately, how far away each pixel is from the front of the shock wave. We then parametrise our wavy function, made by multiplying a sine wave with a falloff function to get the 'height' of each pixel. We could add this height to the pixels world-coordinates, lifting it out of or pushing it bellow the game-plane, so to speak, however, since the game-plane is viewed almost, if not entirely from straight above, that results in a very mild effect in the center of the screen, and very amplified effect towards the edges. That is not what I wanted to achieve here, so instead, I moved the pixel's world-coordinates in the game-plane along the directions away from/towards the center of the shock wave. That makes the effect look decent at any angle.
(In this way, the wave is really more of a compression wave, like sound waves, instead of causing an oscillation of the surface. But we only take actual physics as inspiration anyway. The main goal is to make it look cool!)
We then transform those new world-space coordinates into screen-space using the transformation matrix we kept around from the vertex shader, and sample from the new distorted screen-space position resulting in the image below.
I was quite amazed when I first saw this effect. In a way it was more or less exactly what I was trying to do, but I did not anticipate it to create such an illusion of depth, actually giving the impression of a disturbed refracting surface.After this there was only some minor tweaking to be done, resulting in the final effect appearing slightly more subtle as shown bellow.
Note how the waves distort the text and background. Again the effect looks much better in motion, with the waves propagating away from a central point, pushing enemies and generally being awesome. If you would like to see that, keep an eye out for our next public build in just a few days, including this and many more awesome new things! Or maybe ask me to show it to you on out development live stream tomorrow (follow me on twitter @amulware for a notification).
Wrapping up, I hope this post was of interest to you!There are still many more things I could say about this and related topics. For example, I did not explain how we can easily render any number of overlapping distortion effects at a time with good performance and without getting ugly edges and other artefacts; but I leave that for a possible future post.Please do comment and let me know if you are interested in posts of this kind, and also if you have any questions, or suggestions for what you would like us to write about!
Thank you so much to everyone who stuck with me through this mighty long post!
Enjoy the pixels!