Making a racing game was always a dream for me. And definitely one of the most challenging tasks I've found so far. But fortunately I have WPF by my side, and by now the task was not only accomplished, but in a easy way. Well, maybe not that easy, but the fact is that Windows Presentation Foundation provided all the tools. All the tools. So, I dare say that once you start using WPF, it's hard to give it up.

This article tells the story about the concepts behind the application and the techniques involved. The goal is to make our readers learn something about WPF, or at least, to enjoy the reading and the application.

To use WPF GrandPrix provided with this article, if you already have Visual Studio 2010, that's enough to run the application. If you don't, you can download the following 100% free development tool directly from Microsoft:

For the game, it was used only one racing circuit, "The José Carlos Pace", also known as Interlagos Circuit, in São Paulo, Brazil. I chose this particular circuit not only because I'm Brazilian, but also because the circuit is a good test for driving abilities - it has many curves, where the speed must be very slow, and long, straight parts where you can easily develop the car's maximum speed. So it serves well the purpose of stressing our application to the limit.

Figure 1. The Interlagos Circuit in real life.

Figure 2. The Interlagos Circuit in the game.

Although there's only one circuit available for the game, you can replace it by another circuit if you want - all you need is to copy the file Interlagos.xaml and replace the points that make up the circuit figure by the points needed to create the circuit of your preference. This may not appear so friendly at first glance, but the fact is that the application is ready to work with whatever circuit you draw - all you need is to redefine those points for the new circuit.

The following code shows the XAML code containing the points used to generate the track:

Figure 3. Straight lines are replaced by Bézier Curves in the corners of the track.

The application allows us to create a circuit made of straight lines, which in turn are made of the points we saw before. Later on, while rendering the circuit, the application will create Bezier curves at each corner of the circuit. This allows us to have more realistic and smooth tracks.

How Do We Do It?

First, we must keep in mind that to draw quadratic Bezier Curves, we need 3 control points. So, we have to determine 3 Control Points for each corner of the circuit. The middle control point is the corner point itself, while the other 2 control points are located at the neighboring segments, at some distance from the central control point.

Figure 4. Each Bezier curve segment requires 3 control points.

Figure 5. Animation of a Bezier curve (Source: Wikipedia).

Next, we have to create the entire path, using all those control points. We do this by alternating between straight lines (which defines most of the circuit) and the bezier curves that connects those straight lines:

Once rendered, the circuit's background image becomes quite large. So large that it became a bottleneck in the application's performance. The solution I found was to break that large image into smaller controls containing smaller portions of that large image, so that it would be possible to make visible only the squares shown on the screen at each given moment. That is, since the application's "camera" can show only a portion of the circuit at a time, all the rest of the circuit can be made invisible. Surely, there can be better and more elegant ways to handle this, but this technique in particular definitely solved the performance problem, so I'm happy with it.

Figure 7. We can gain performance by making most of the track invisible.

The code below shows that only a portion of 5 x 5 cells of the circuit is made visible in the screen - all the other cells are hidden:

After some time thinking of how to draw the circuit track, I ended up with a simple solution: I just used the original track points to redraw a large path using those same track points. But that's not just a path. It's a series of layered paths: the broader one is used to draw the side red/white tracks. Another path is narrower and represents the asphalt. The central path is thinner and splits the track in two bands:

The front wheels can turn to left or right, depending on the user's action. Each wheel can turn to a maximum of 30 degrees to each side. When the user releases his/her steering wheel (oops, the left/right arrow keys on the keyboard), the wheels will automatically and slowly become aligned with the car direction.

Each car holds a specific position at the beginning of the race. In our application, the red one is the last car in the row, so the user always start in the last position. So, he or she must gain positions to win the race.

Figure 10. The starting grid at the beginning of the first track segment.

Below, we have the snippet that shows how to define the initial car positions:

For us as humans, it may appear so easy to accelerate, keep the car on track, slow down and turn left and right when needed. But for our poor virtual pilots, those tasks are everything but trivial.

The thing is, we must make our virtual drivers appear as real ones. We must give them some intelligence so they don't look like a bunch of idiots crashing onto each other and running off the track, driving aimlessly. Instead, we should provide them with some real driving "feeling" and assure that they "know" what is the race objective.

First of all, the drivers should know which direction is correct. As explained at the beginning of the article, the entire track is made of straight line segments, connected by round corners. By default, the game application assumes that the cars should depart from the starting grid (segment 0) and run towards the next segment (segment 1, 2, 3 and so on), ending in the point where the last segment reaches the first segment.

Once the virtual racers know the right direction, we should give them a goal. The main goal of the race, of course, is to cross the chequered line. But if you look closely, the race goal can be broken down into smaller goals, which are to complete each segment as fast as possible.

By "completing each segment", we must understand as running towards the end of the current segment. Now we have the segment direction and the car's direction. Having this information at hand, it's possible to adjust the car's direction; then the car can be aligned so as to run along a straight line beginning at the car's current position and ending at the end of the next segment.

The problem of always pursuing the end of the current is that, when you reach that point, it's already too late to adjust your direction for the next segment. In the real world, when you get near the end of the segment, you must already go for the next segment.

In the real world, you must not perform curves in high speed, and in the game, this is no different. If you don't slow down in time, you will certainly end up getting off the track. So, it's advisable to reach the maximum speed in the straight tracks and slow down while getting closer to the curves.

Figure 13. Curves require lowering speed. Otherwise the cars will get off the track, stuck in the grass.

Most of the racing games provide an "on screen" display where you can see circuit map with points corresponding to the relative positions of the race competitors. This application is no exception to that. For this feature, we just display the original circuit user control (that same user control described in Interlagos.xaml file) on the top of the screen. Along with it, we create some small circles, each of which with distinct colors, representing the competitors. As a result, we have a cool and useful way of race navigation!

The race is won when some racer finally completes all the 5 laps. When this happens, his name is displayed on the screen in a big, bold message, and in addition all cars are slowed down. This gives the realistic effect of racers naturally slowing down their cars that happens at the end of real races.

Figure 16. The first racer to cross the line after the last lap will be acclaimed as winner.

The application knows that a car has won the race when the car has just left the last track segment and entered the first track segment, and finally completed all the 5 laps:

Share

About the Author

Marcelo Ricardo de Oliveira is a senior freelance software developer who lives with his lovely wife Luciana and his little buddy and stepson Kauê in Guarulhos, Brazil, is co-founder of the Brazilian TV Guide TV Map and currently works for Alura Cursos Online.