# How to build a racing game - curves

Sun, Jun 24, 2012Earlier I published a simple outrun-style pseudo-3d racing game and followed up with an article showing how to get started with straight roads.

Today I’m going to go into more detail on how the curves work.

If you followed along with the previous article you will know that we built up our road geometry as an array of segments, each of which has world coordinates which get translated relative to the camera and then projected into the screen.

We only needed a **z** world coordinate for each point because, for straight roads, both
**x** and **y** were zero.

If we were building a full-blown 3d system we might implement curves by
calculating **x** and **z** coordinates in a kind of fan-strip as shown on
the left. However that kind of geometry can be a little complex to calculate
and would require us to add a 3d rotation step to our projection equations…

… if we wanted to go down that path we would be better off using WebGL or its equivalent, but that’s not really what this project is about. We just want to use some old-school ‘good enough’ pseudo 3d tricks to fake our curves.

So you might be surprised to learn that we wont be calculating **x** coordinates
for our road segments at all…

Instead we’ll follow Lou’s advice:

“To curve a road, you just need to change the position of the center-line in a curve shape… starting at the bottom of the screen, the amount that the center of the road shifts left or right steadily increases”

In our case, the center-line is the `cameraX`

value we pass to the projection
calculations. This means that as we `render()`

each segment of the road, we can
fake curves by offsetting the `cameraX`

value by a steadily increasing amount.

In order to know how much to offset we need to store a `curve`

value in each
segment. This value represents how much the segment should be offset from the
camera’s center line, and will be:

- negative for left hand curves
- positive for right hand curves
- smaller for easy curves
- larger for harder curves

The actual values are somewhat arbitrary, and through trial and error we can find good values to make our curves ‘feel’ right:

```
var ROAD = {
LENGTH: { NONE: 0, SHORT: 25, MEDIUM: 50, LONG: 100 }, // num segments
CURVE: { NONE: 0, EASY: 2, MEDIUM: 4, HARD: 6 }
};
```

In addition to defining good curve values. We want to avoid any jarring
transitions when a straight turns into a curve (or vice versa) by easing
into and out of the curves. We do this by slowly incrementing (or decrementing) the
`curve`

value for each segment until it reaches our desired value using traditional
easing functions such as:

```
easeIn: function(a,b,percent) { return a + (b-a)*Math.pow(percent,2); },
easeOut: function(a,b,percent) { return a + (b-a)*(1-Math.pow(1-percent,2)); },
easeInOut: function(a,b,percent) { return a + (b-a)*((-Math.cos(percent*Math.PI)/2) + 0.5); },
```

So now, given a function to add a single segment to our geometry…

```
function addSegment(curve) {
var n = segments.length;
segments.push({
index: n,
p1: { world: { z: n *segmentLength }, camera: {}, screen: {} },
p2: { world: { z: (n+1)*segmentLength }, camera: {}, screen: {} },
curve: curve,
color: Math.floor(n/rumbleLength)%2 ? COLORS.DARK : COLORS.LIGHT
});
}
```

… we can create a method to ease in, hold, and then ease out of a curved road:

```
function addRoad(enter, hold, leave, curve) {
var n;
for(n = 0 ; n < enter ; n++)
addSegment(Util.easeIn(0, curve, n/enter));
for(n = 0 ; n < hold ; n++)
addSegment(curve);
for(n = 0 ; n < leave ; n++)
addSegment(Util.easeInOut(curve, 0, n/leave));
}
```

… and we can layer additional geometry on top, such as S-Curves:

```
function addSCurves() {
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY);
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.MEDIUM);
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.EASY);
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY);
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.MEDIUM);
}
```

## Changes to the update() method

The only changes we need to make to our existing `update()`

method is to apply some
kind of centrifugal force to the car when its going around the curve.

We define an arbitrary multiplier that can be tuned until it ‘feels good’

```
var centrifugal = 0.3; // centrifugal force multiplier when going around curves
```

And we simply update the `playerX`

position based on their current speed, the
current curve amount and the centrifugal force multiplier:

```
playerX = playerX - (dx * speedPercent * playerSegment.curve * centrifugal);
```

## Rendering curves

Earlier we said we could render fake curves by offsetting the `cameraX`

value
used in the projection calculations as we `render()`

each segment of the road.

To do that we maintain an accumulating **dx** variable that increases by the
amount of `curve`

for each segment, along with an **x** variable that will be
used as the offset to the `cameraX`

value used in the projection calculations.

To implement curves we need to:

- offset each segments
**p1**projection by**x** - offset each segments
**p2**projection by**x**+**dx** - increase
**x**by**dx**for the next segment

Finally, in order to avoid jarring transitions when crossing segment boundaries
we must ensure **dx** is initialized with an interpolated value for the current
base segments curve.

Modifying our `render()`

method like so:

```
var baseSegment = findSegment(position);
var basePercent = Util.percentRemaining(position, segmentLength);
var dx = - (baseSegment.curve * basePercent);
var x = 0;
for(n = 0 ; n < drawDistance ; n++) {
...
Util.project(segment.p1, (playerX * roadWidth) - x, cameraHeight, position - (segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);
Util.project(segment.p2, (playerX * roadWidth) - x - dx, cameraHeight, position - (segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);
x = x + dx;
dx = dx + segment.curve;
...
}
```

Hmmm. If I was brutally honest, I’d have to admit that this made a lot more sense when I was writing the code than it does now trying to explain it for others. Looking back now it looks suspiciously like I have a double accumulation going on and I can’t really justify the need for bothxanddx? That’s a terrible admission as a programmer!!… You know what, forget I said anything, there’s nothing to see here, pretend you didn’t read this note and lets move on…

UPDATE. Thanks toPeteBin comments below for reminding me that a curve is a 2nd order equation, and that I do need to maintain a separatedxas the rate of change ofx. I started second guessing myself when writing this article, and I was also in a dazed and confused state of mind due to England getting knocked out of Euro2012 - on penalties - AGAIN! So its ok, there was nothing to worry about, this code is correct (at least as correct as a ‘fake’ curve can be!)

## Parallax scrolling background

Finally, we need to scroll the parallax background layers by maintaining an offset for each layer…

```
var skySpeed = 0.001; // background sky layer scroll speed when going around curve (or up hill)
var hillSpeed = 0.002; // background hill layer scroll speed when going around curve (or up hill)
var treeSpeed = 0.003; // background tree layer scroll speed when going around curve (or up hill)
var skyOffset = 0; // current sky scroll offset
var hillOffset = 0; // current hill scroll offset
var treeOffset = 0; // current tree scroll offset
```

… and increasing it during `update()`

based on the curve of the players current segment and their speed…

```
skyOffset = Util.increase(skyOffset, skySpeed * playerSegment.curve * speedPercent, 1);
hillOffset = Util.increase(hillOffset, hillSpeed * playerSegment.curve * speedPercent, 1);
treeOffset = Util.increase(treeOffset, treeSpeed * playerSegment.curve * speedPercent, 1);
```

… and then use that offset when we `render()`

the background layers

```
Render.background(ctx, background, width, height, BACKGROUND.SKY, skyOffset);
Render.background(ctx, background, width, height, BACKGROUND.HILLS, hillOffset);
Render.background(ctx, background, width, height, BACKGROUND.TREES, treeOffset);
```

## Conclusion

So there you have it, fake psuedo-3d curves:

Most of the code we added for curves revolves around constructing the road geometry with the
appropriate `curve`

value. Once we have that, providing a centrifugal force during `update()`

is easy.

Rendering the curves is only a few lines of code, but they can be conceptually hard to understand (and describe) exactly what’s happening. There are many approaches to faking curves and its easy to go down some dead ends, and even easier to get side tracked trying to do the ‘correct’ thing and before you know it you are implementing a full blown 3d system with matrices, rotation and true 3-d geometry… which I’ve already said is not the point here.

In writing this article I’m pretty sure that I actually have some problems with my curve
implementation. In trying to visualize the algorithm for this article I can’t help but wonder
why I need 2 accumulating values **dx** and **x** instead of just one… and if I’m not able
to fully explain it then something, somewhere is wrong…

… but my time on this *‘weekend’* (ha!) project is pretty much up and, to be honest, the
curves look pretty good to me - and really that’s what matters at the end of the day.

Next up, I’ll detail how the hills work. I’m pretty sure I remember how those work because they are actually true 3d height projections, no magic fakery going on there.

Until next time…

## Related Links

- read more about v1 - straight roads
- read more about v2 - curves
- read more about v3 - hills
- read more about v4 - final
- read Lou’s Pseudo 3d Page
- view the source code

or you can play…

- the straight road demo
- the curves demo
- the hills demo
- the final version