Space Racers Breakdown Part 1

EDIT: Download it from here!

Happy new year and, indeed, happy Chinese new year! It's been a little quiet on the blog front, so I'm going to start off the blogging new year with a couple of articles that are, hopefully, a bit more interesting than some of my recent content. To kick it all off check out this trailer of a game I made, written in C and C++ using mainly SFML: Space Racers!


Space Racers has been my pet project since around June last year. From the video it should be pretty obvious that it's based on the classic 90s era Micro Machines games by Codemasters, particularly the Sega Genesis / Megadrive port. It has been a fun and definitely educational project to work on, and has helped me further develop some of my side projects such as the Tiled map loader. It also features some real time 3D graphics which are overlaid to create the details and buildings which appear in the video, and has been a great exercise in using OpenGL. Unfortunately I've lost interest a little bit in the project itself (I haven't actually worked on it since last November) so to try and maximise its usefulness I intend to document some of the features, such as the vehicle physics, spread over a couple of articles as a kind of post mortem. When researching the project certain topics are apparently not that well documented, so hopefully sharing my experiences may help other people who wish to work on something similar. One day I may even release the source code - although I fear that although there may be techniques worth learning from the Space Racers project my coding practices probably don't count, and I'd not like to be blamed for passing on any of  my bad habits...

In this post I'll start with a break down of the tile maps used in the game, and how the Tiled map loader is used to parse the stored data into a navigable track.

The visual elements of the tracks themselves are fairly basic. The base layer for each map / track (be forewarned - I'm likely to use these terms interchangeably) is created using a simple tile set which I drew in Photoshop using the grid tool. Indeed the hard edges are not very inspired and, if I'm honest, I could have put a bit more effort into it.

It serves its purpose though and, when combined with a tile set consisting of various decals, gives a reasonable impression of a race track. The decals are placed on two separate layers, mainly to make sure that the decals are drawn on top, but also because the decals themselves are separated into 'standard' decals and those used as neon lights. Because the neon graphics are stored on their own layer which, via the map loader class, can be drawn on its own means that the layer can be preprocessed via a RenderTexture and a set of tuned GLSL shaders based on a bloom effect. This is what gives the layer its neon appearance, before it is drawn over the track layer.
    The power of Tiled comes in to play, however, in its ability to store non-visual data not just as a set of objects, but also by being able to isolate tile set layers. Looking closely at the video it's noticeable that the track appears to bend and distort the moon behind it as if it were made of glass or some other non-flat, semi-transparent material. This is done with a second tile set which mimics the first (the set used to draw the underlying track) but is created, again in Photoshop, as a set of black and white tiles representing height data, which is then converted to a normal map using SSBump.

This means that any track I create with the standard tile set can be recreated on a new layer as a normal map image. By being able to isolate this (or any other layer) when drawing with the Tiled map loader I can use the normal data to pre-render the background on another RenderTexture via a fragment shader which uses the normal coordinates as a DuDv map (the blue channel data is ignored) to distort the image. The standard tile set used to create the visible track layer is semi-transparent (an option which can be set via a slider in Tiled) so when it is drawn over the distorted background, it appears as though the track itself is causing the distortion.
    Collision data is also stored in the Tiled map. In this instance I decided to roll with my own physics - mainly as exercise - although collisions may have been better handled by Box2D. I'll go into more detail about handling the collisions in an article specifically about car physics in the future because it's fairly complex. Here, however, I'll point out the different object layers used to detect collisions. The collision types are separated into four groups:

 Solids
Nodes
Space
Kill

Each of these performs a particular task, and only the solid objects involve any real complexity in handling their collisions. Nodes are placed around the track and numbered sequentially. These are used for the navigation of AI controlled vehicles, and determining the order of players around the track. They are also used to make sure no short cuts are taken by players, by checking that the player's current node is sequentially numbered compared to the previous node. Again AI and navigation deserve their own post, so I'll go into more detail at a later point. Space and Kill objects are very similar, in that their job is to detect when a vehicle leaves a track, and trigger a reset sequence. The Space type detects when a vehicle has left the track and is effectively 'in space' and therefore should fall off (I know, I'm kind of playing fast and loose with the laws of gravity considering the whole game is set in space). The vehicles all perform slightly differently, however, and of the three the space ship has the ability to leave the track without falling off. Preventing the abuse of this necessitates the Kill object type, which is used to outright kill any ships which fly too far out. I've also used Kill objects to detect when players hit an electric barrier, and then destroy their vehicle. If you've read my previous post on collision detection you'll know that map objects parsed by the map loader have some basic detection functions, which include MapObject::Contains(point). This is all that is needed for the basic detection used in all but the solid types. Each vehicle has a list of four points surrounding it, so by checking how many points an object contains it is fairly trivial to work out whereabouts the vehicle is in relation to the map, and whether or not it needs to be updated in some way (usually by resetting it). The detection is, of course, optimised via the map loader's QuadTree class.

That about sums up the 2D map data, which is created in Tiled. There are 3D meshes used to embellish the map details, which I'll post about in the future, hopefully not too far away.

Part 2: Node Navigation

Comments

Popular Posts