0% found this document useful (0 votes)
48 views75 pages

Car Racing Game Project in Js

The document is a mini project report for an online car racing game developed using JavaScript, submitted by J. Bhavana for a Bachelor's degree at the University of Madras. It outlines the game's structure, including modules for game logic, screen rendering, and audio, and discusses the implementation details and challenges faced during development. The project serves as a tech demo rather than a fully polished game, focusing on the mechanics of creating a pseudo-3D racing experience.

Uploaded by

aryavardhan9966
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
48 views75 pages

Car Racing Game Project in Js

The document is a mini project report for an online car racing game developed using JavaScript, submitted by J. Bhavana for a Bachelor's degree at the University of Madras. It outlines the game's structure, including modules for game logic, screen rendering, and audio, and discusses the implementation details and challenges faced during development. The project serves as a tech demo rather than a fully polished game, focusing on the mechanics of creating a pseudo-3D racing experience.

Uploaded by

aryavardhan9966
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 75

Car racing game project in js

BCA (University of Madras)

Scan to open on Studocu

Downloaded by college kosam


Studocu is not sponsored or endorsed by any college or university

Downloaded by college kosam


A MINI PROJECT REPORT

“ONLINE CAR RACING GAME USING JAVASCRIPT ”

A Project Submitted for Fulfillment of the Award of Degree

BACHELOR OF COMPUTER APPLICATION


University of Madras

Submitted by
J.BHAVANA
(Reg. No:
212000320)

Under the Guidance of

Prof. Dr.R.SELVAM, MCA, M.Phil., Ph.D,


Assistant Professor, Computer Science
Department 2021-2023

UG DEPARTMENT OF COMPUTER APPLICATION,

SRI SUBRAMANIYA SWAMY GOVT ARTS COLLEGE


TIRUTTANI – 631209.

Downloaded by college kosam


SRI SUBRAMANIYA SWAMY GOVT ARTS
COLLEGE TIRUTTANI-631 209
DEPARTMENT OF COMPUTER APPLICATION

Bonafide Certificate

This is to certify that the project entitled “ ONLINE CAR RACING


GAME USING JAVASCRIPT” being submitted to the S.S Govt Arts
College,Thiruttani , by “J.BHAVANA” (Reg.No: 212000320) for the partial
fulfillment of degree of BCA (Computer Application) during the college 2021-
2023, under my guidance and supervision.

Internal Guide, Head of the department,


Department of Computer Science Department of Computer
Science S.S.Govt. Arts. College, S.S.Govt. Arts. College,
Tiruttani. Tiruttani.

Submitted for the Viva-Voice Examination held on ……………………

Internal Examiner External Examiner

Downloaded by college kosam


DECLARATION

This is to certify that Project entitle “ ONLINE CAR RACING


GAME USING JAVASCRIPT” submitted to the Govt Arts
College,Thiruttani in partial fulfillment of the requirement for the degree of
BACHELOR OF COMPUTER APPLICATION is a record of original
project work done by me, under the guidance and supervision of Dr. R.
SELVAM, MCA, M.Phil, Ph.D, Assistant professor and Head in
Department of Computer Science, S.S. Government Arts College, Tiruttani,
and it has not from the basis for award of Degree of Similar title to any
candidate of any University.

Downloaded by college kosam


INTRODUCTION:

In 1895 the first true race was held, from Paris to Bordeaux, France, and back, a distance of
1,178 km. The winner made an average speed of 24.15 kph. Organized automobile racing
began in the United States with an 87-km race from Chicago to Evanston, Illinois, and back
on Thanksgiving Day in 1895.

This game consists of three major modules. First part is the Game Logic Generator which
calculate the logic of this game, such as to detect bumps to obstacle, speed control based
on keyboard input, opponents control and road generation, and this module is based on
software. The second part is the Screen Rendering Module, we adopt the Sprite Graphics
technique to decompose the display screen into 7 layers, which will be explained in derails
in later section. The last part is the Audio Module which generate the proper sound under
the control of game logic

Downloaded by college kosam


ABSTRACT

I wasn’t really much for going to the arcades when I was younger… I didn’t need ‘em with
awesome C64 games sitting at home… but there were 3 arcade games that could always get my
money - Donkey Kong, Dragons Lair, and Outrun…

… and I really loved Outrun, the speed, the hills, the palm trees and the music - even the lowly
c64 version.

Downloaded by college kosam


So, I wanted to try my hand at an old-school pseudo-3d racing game, a-la outrun, pitstop or
pole-position. I dont plan on building a fully fleshed out and finished game but thought it
would be fun to re-examine the mechanics of how these games pulled off their tricks. The
curves, the hills, the sprites and the feeling of speed…

So, here’s a “weekend” project that ended up taking about 5 or 6 weekends…

Downloaded by college kosam


The playable version is more of a tech demo than an actual game. In fact, if you were going to
build a real pseudo-3d racing game, this would be just the minimal point from which to start
turning it into a game.

Without the polish, it’s a little ugly, but its fully functional, and I can show you how to
implement it yourself in four easy sections…

Enjoy!

A note on performance

The performance of this game is very machine/browser dependent. It works quite well in
modern browsers, especially those with GPU canvas acceleration, but a bad graphics driver can
kill it stone dead. So your mileage may vary. There are controls provided to change the
rendering resolution and the draw distance to scale to fit your machine.

Currently supported browsers include:

Downloaded by college kosam


 Firefox (v12+) works great, 60fps at high res - Nice!
 Chrome (v19+) works great, 60fps at high res… provided you dont have a bad GPU driver
 IE9 - ok, 30fps at medium res… not great, but at least it works

The current state of mobile browser performance is pretty dismal. Dont expect this to be
playable on any mobile device.

NOTE: I havent actually spent anytime optimizing for performance yet. So it might be possible
to make it play well on older browsers, but that’s not really what this project is about.

A note on code structure

This project happens to be implemented in javascript (because its easy for prototyping) but is
not intended to demonstrate javascript techniques or best practices. In fact, in order to keep it
simple to understand it embeds the javascript for each example directly in the HTML page
(horror!) and, even worse, uses global variables and functions (OMG!).

If I was building a real game I would have much more structure and organization to the code

How to build a racing game - straight roads

Well, we’re going to need to

 revise some trigonometry


 revise basic 3d projection
 build a game loop
 load some sprite images
 build some road geometry
 render the background
 render the road
 render the car
 enable keyboard support to drive the car

Before we do any of that, lets go off and read Lou’s Pseudo 3d Page - its the main source of
information (that I could find) online about how to build a pseudo-3d racing game.

NOTE: Lou’s page doesn’t render well in google chrome - so its best viewed using Firefox or IE

Downloaded by college kosam


GAME DESIGN AND OPERATING:

Finished reading Lou’s article ? Great! We’re going to build a variation on his ‘Realistic Hills
Using 3d-Projected Segments’ approach. We will do it gradually, over the course of the next 4
articles, but we will start off here with v1, building very simple straight road geometry and
projecting it onto our HTML5 canvas element.

Some Trigonometry

Before we get down to the implementation, lets use some basic trigonometry to remind
ourselves how to project a point in a 3D world onto a 2D screen.

At its most basic, without getting into vectors and matrices, 3D projection uses a law of similar
triangles. If we were to label:

 h = camera height
 d = distance from camera to screen
 z = distance from camera to car
 y = screen y coordinate

Then we could use the law of similar triangles to calculate

y = h*d/z

as shown in the diagram below:

Downloaded by college kosam


We could have also drawn a similar diagram from a top-down view instead of a side-on view
and derived a similar equation for calculating the screen x coordinate as

x = w*d/z

Where w = half the width of the road (from camera to road edge)

You can see that for both x and y, what we are really doing is scaling by a factor of

d/z

Coordinate Systems

This sounds nice and simple in diagram form, but once you start coding its easy to get a little
confused because we have been a bit loose in naming our variables and its not clear which
represent 3d world coordinates and which represent 2d screen coordinates. We’ve also
assumed that the camera is at the origin of our world when in reality it will be following our
car.

More formally we should be:

1. translating from world coordinates to camera coordinates


2. projecting camera coordinates onto a normalized projection plane
3. scaling the projected coordinates to physical screen (in our case canvas) coordinates

Downloaded by college kosam


NOTE: in a true 3d system a rotation step would come between steps 1 and 2, but since we’re
going to be faking curves we dont need to worry about rotation

Projection

And so we can present our formal projection equations as follows:

 The translate equations calculate the point relative to the camera


 The project equations are variations of our ‘law of similar triangles’ above
 The scale equations take into account the difference between:
o math - where 0,0 is at the center and the y axis goes up and
o computers - where 0,0 is at the top-left and the y axis goes down, as shown
below:

Downloaded by college kosam


NOTE: In a full blown 3d system we would more formally define a Vector and a Matrix class to
perform more robust 3d mathematics, and if we were going to do that then we might as well
just use WebGL (or equivalent)… but thats not really the point of this project. I really wanted to
stick to old-school ‘just-enough’ pseudo-3d to build an outrun-style game.

Some More Trigonometry

Downloaded by college kosam


One last piece of the puzzle is how to calculate d - the distance from the camera to the
projection plane.

Instead of hard coding a value for d, its more useful to derive it from the desired vertical field
of view. This way we can choose to ‘zoom’ the camera if needed.

Assuming we are projecting onto a normalized projection plane, with coordinates from -1 to
+1, we can calculate d as follows:

d = 1/tan(fov/2)

Setting up fov as one (of many) variables we will be able to tweak in order to fine tune the
rendering algorithm.

Javascript Code Structure

I mentioned in the introduction that this code does not exactly follow javascript best practices
- its a quick and dirty demo with simple global variables and functions. However, since I am
going to build 4 separate versions (straights, curves, hills and sprites) I will keep some re-
usable methods inside common.js within the following modules:

 Dom - a few minor DOM helpers.


 Util - generic utilities, mostly math helpers.
 Game - generic game helpers such as an image loader and the game loop.
 Render - canvas rendering helpers.

Downloaded by college kosam


Source code:

ROAD:

<!DOCTYPE html>

<html>
<head>
<title>Javascript Racer - v1 (straight)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link href="common.css" rel="stylesheet" type="text/css" />
</head>

<body>

<table id="controls">
<tr>
<td colspan="2">
<a href='v1.straight.html'>straight</a> |
<a href='v2.curves.html'>curves</a> |
<a href='v3.hills.html'>hills</a> |
<a href='v4.final.html'>final</a>
</td>
</tr>
<tr><td id="fps" colspan="2" align="right"></td></tr>
<tr>
<th><label for="resolution">Resolution :</label></th>
<td>
<select id="resolution" style="width:100%">
<option value='fine'>Fine (1280x960)</option>
<option selected value='high'>High (1024x768)</option>
<option value='medium'>Medium (640x480)</option>
<option value='low'>Low (480x360)</option>
</select>
</td>
</tr>
<tr>
<th><label for="lanes">Lanes :</label></th>

Downloaded by college kosam


<td>
<select id="lanes">
<option>1</option>
<option>2</option>
<option selected>3</option>
<option>4</option>
</select>
</td>
</tr>
<tr>
<th><label for="roadWidth">Road Width (<span id="currentRoadWidth"></span>)
:</label></th>
<td><input id="roadWidth" type='range' min='500' max='3000' title="integer (500-
3000)"></td>
</tr>
<tr>
<th><label for="cameraHeight">CameraHeight (<span id="currentCameraHeight"></span>)
:</label></th>
<td><input id="cameraHeight" type='range' min='500' max='5000' title="integer (500-
5000)"></td>
</tr>
<tr>
<th><label for="drawDistance">Draw Distance (<span id="currentDrawDistance"></span>)
:</label></th>
<td><input id="drawDistance" type='range' min='100' max='500' title="integer (100-
500)"></td>
</tr>
<tr>
<th><label for="fieldOfView">Field of View (<span id="currentFieldOfView"></span>)
:</label></th>
<td><input id="fieldOfView" type='range' min='80' max='140' title="integer (80-140)"></td>
</tr>
<tr>
<th><label for="fogDensity">Fog Density (<span id="currentFogDensity"></span>)
:</label></th>
<td><input id="fogDensity" type='range' min='0' max='50' title="integer (0-50)"></td>
</tr>
</table>

Downloaded by college kosam


<div id='instructions'>
<p>Use the <b>arrow keys</b> to drive the car.</p>
</div>

<div id="racer">
<canvas id="canvas">
Sorry, this example cannot be run because your browser does not support the
&lt;canvas&gt; element
</canvas>
Loading...
</div>

<audio id='music'>
<source src="music/racer.ogg">
<source src="music/racer.mp3">
</audio>
<span id="mute"></span>

<script src="stats.js"></script>
<script src="common.js"></script>
<script>

var fps = 60; // how many 'update' frames per


second var step = 1/fps; // how long is each
frame (in seconds) var width = 1024; // logical
canvas width
var height = 768; // logical canvas height
var segments = []; // array of road segments
var stats = Game.stats('fps'); // mr.doobs FPS
counter var canvas = Dom.get('canvas');
// our canvas...
var ctx = canvas.getContext('2d'); // ...and its drawing context
var background = null; // our background image (loaded
below) var sprites = null; // our spritesheet (loaded below)
var resolution = null; // scaling factor to provide resolution independence
(computed)
var roadWidth = 2000; // actually half the roads width, easier math if the road
spans from -roadWidth to +roadWidth
var segmentLength = 200; // length of a single segment

Downloaded by college kosam


var rumbleLength = 3; // number of segments per red/white rumble
strip var trackLength = null; // z length of entire track (computed)
var lanes = 3; // number of lanes
var fieldOfView = 100; // angle (degrees) for field of
view var cameraHeight = 1000; // z height of camera
var cameraDepth = null; // z distance camera is from screen
(computed) var drawDistance = 300; // number of segments to draw
var playerX = 0; // player x offset from center of road (-1 to 1 to stay
independent of roadWidth)
var playerZ = null; // player relative z distance from camera
(computed) var fogDensity = 5; // exponential fog density
var position = 0; // current camera Z position (add playerZ to get player's
absolute Z position)
var speed = 0; // current speed
var maxSpeed = segmentLength/step; // top speed (ensure we can't move more than 1
segment in a single frame to make collision detection easier)
var accel = maxSpeed/5; // acceleration rate - tuned until it 'felt'
right var breaking = -maxSpeed; // deceleration
rate when braking
var decel = -maxSpeed/5; // 'natural' deceleration rate when neither
accelerating, nor braking
var offRoadDecel = -maxSpeed/2; // off road deceleration is somewhere in between
var offRoadLimit = maxSpeed/4; // limit when off road deceleration no longer
applies
(e.g. you can always go at least this speed even when off road)

var keyLeft = false;


var keyRight = false;
var keyFaster = false;
var keySlower =
false;

//=========================================================================
// UPDATE THE GAME WORLD
//=========================================================================

function update(dt) {

position = Util.increase(position, dt * speed, trackLength);

Downloaded by college kosam


var dx = dt * 2 * (speed/maxSpeed); // at top speed, should be able to cross from left to
right (-1 to 1) in 1 second

if (keyLeft)
playerX = playerX - dx;
else if (keyRight)
playerX = playerX + dx;

if (keyFaster)
speed = Util.accelerate(speed, accel, dt);
else if (keySlower)
speed = Util.accelerate(speed, breaking, dt);
else
speed = Util.accelerate(speed, decel, dt);

if (((playerX < -1) || (playerX > 1)) && (speed > offRoadLimit))
speed = Util.accelerate(speed, offRoadDecel, dt);

playerX = Util.limit(playerX, -2, 2); // dont ever let player go too far out of
bounds speed = Util.limit(speed, 0, maxSpeed); // or exceed maxSpeed

//=========================================================================
// RENDER THE GAME WORLD
//=========================================================================

function render() {

var baseSegment =
findSegment(position); var maxy = height;

ctx.clearRect(0, 0, width, height);

Render.background(ctx, background, width, height, BACKGROUND.SKY);


Render.background(ctx, background, width, height, BACKGROUND.HILLS);
Render.background(ctx, background, width, height, BACKGROUND.TREES);

Downloaded by college kosam


var n, segment;

for(n = 0 ; n < drawDistance ; n++) {

segment = segments[(baseSegment.index + n) %
segments.length]; segment.looped = segment.index <
baseSegment.index;
segment.fog = Util.exponentialFog(n/drawDistance, fogDensity);

Util.project(segment.p1, (playerX * roadWidth), cameraHeight, position - (segment.looped


? trackLength : 0), cameraDepth, width, height, roadWidth);
Util.project(segment.p2, (playerX * roadWidth), cameraHeight, position - (segment.looped
? trackLength : 0), cameraDepth, width, height, roadWidth);

if ((segment.p1.camera.z <= cameraDepth) || // behind us


(segment.p2.screen.y >= maxy)) // clip by (already rendered) segment
continue;

Render.segment(ctx, width,
lanes,
segment.p1.screen.x,
segment.p1.screen.y,
segment.p1.screen.w,
segment.p2.screen.x,
segment.p2.screen.y,
segment.p2.screen.w,
segment.fog,
segment.color);

maxy = segment.p2.screen.y;
}

Render.player(ctx, width, height, resolution, roadWidth, sprites, speed/maxSpeed,


cameraDepth/playerZ,
width/2,
height,
speed * (keyLeft ? -1 : keyRight ? 1 : 0),
0);
}

Downloaded by college kosam


//=========================================================================
// BUILD ROAD GEOMETRY
//=========================================================================

function resetRoad() {
segments = [];
for(var n = 0 ; n < 500 ; n++) {
segments.push({
index: n,
p1: { world: { z: n *segmentLength }, camera: {}, screen: {} },
p2: { world: { z: (n+1)*segmentLength }, camera: {}, screen: {} },
color: Math.floor(n/rumbleLength)%2 ? COLORS.DARK : COLORS.LIGHT
});
}

segments[findSegment(playerZ).index + 2].color =
COLORS.START; segments[findSegment(playerZ).index + 3].color
= COLORS.START; for(var n = 0 ; n < rumbleLength ; n++)
segments[segments.length-1-n].color = COLORS.FINISH;

trackLength = segments.length * segmentLength;


}

function findSegment(z) {
return segments[Math.floor(z/segmentLength) % segments.length];
}

//=========================================================================
// THE GAME LOOP
//=========================================================================

Game.run({
canvas: canvas, render: render, update: update, stats: stats, step: step,
images: ["background", "sprites"],
keys: [
{ keys: [KEY.LEFT, KEY.A], mode: 'down', action: function() { keyLeft = true; } },
{ keys: [KEY.RIGHT, KEY.D], mode: 'down', action: function() { keyRight = true; } },
{ keys: [KEY.UP, KEY.W], mode: 'down', action: function() { keyFaster = true; } },

Downloaded by college kosam


{ keys: [KEY.DOWN, KEY.S], mode: 'down', action: function() { keySlower = true; } },
{ keys: [KEY.LEFT, KEY.A], mode: 'up', action: function() { keyLeft = false; } },
{ keys: [KEY.RIGHT, KEY.D], mode: 'up', action: function() { keyRight = false; } },
{ keys: [KEY.UP, KEY.W], mode: 'up', action: function() { keyFaster = false; } },
{ keys: [KEY.DOWN, KEY.S], mode: 'up', action: function() { keySlower = false; } }
],
ready: function(images) {
background =
images[0]; sprites
= images[1];
reset();
}
});

function reset(options) {
options = options ||
{};
canvas.width = width = Util.toInt(options.width, width);
canvas.height = height = Util.toInt(options.height,
height); lanes = Util.toInt(options.lanes, lanes);
roadWidth = Util.toInt(options.roadWidth, roadWidth);
cameraHeight = Util.toInt(options.cameraHeight, cameraHeight);
drawDistance = Util.toInt(options.drawDistance, drawDistance);
fogDensity = Util.toInt(options.fogDensity, fogDensity);
fieldOfView = Util.toInt(options.fieldOfView, fieldOfView);
segmentLength = Util.toInt(options.segmentLength, segmentLength);
rumbleLength = Util.toInt(options.rumbleLength, rumbleLength);
cameraDepth = 1 / Math.tan((fieldOfView/2) * Math.PI/180);
playerZ = (cameraHeight * cameraDepth);
resolution = height/480;
refreshTweakUI();

if ((segments.length==0) || (options.segmentLength) || (options.rumbleLength))


resetRoad(); // only rebuild road when necessary
}

//=========================================================================
// TWEAK UI HANDLERS
//=========================================================================

Downloaded by college kosam


Dom.on('resolution', 'change', function(ev) {
var w, h, ratio;
switch(ev.target.options[ev.target.selectedIndex].value) {
case 'fine': w = 1280; h = 960; ratio=w/width; break;
case 'high': w = 1024; h = 768; ratio=w/width; break;
case 'medium': w = 640; h = 480; ratio=w/width;
break; case 'low':w = 480; h = 360; ratio=w/width;
break;
}
reset({ width: w, height: h })
Dom.blur(ev);
});

Dom.on('lanes', 'change', function(ev) { Dom.blur(ev); reset({


lanes: ev.target.options[ev.target.selectedIndex].value }); });
Dom.on('roadWidth', 'change', function(ev) { Dom.blur(ev); reset({
roadWidth: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')),
Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('cameraHeight', 'change', function(ev) { Dom.blur(ev); reset({
cameraHeight: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')),
Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('drawDistance', 'change', function(ev) { Dom.blur(ev); reset({
drawDistance: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')),
Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('fieldOfView', 'change', function(ev) { Dom.blur(ev); reset({
fieldOfView: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')),
Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('fogDensity', 'change', function(ev) { Dom.blur(ev); reset({
fogDensity: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')),
Util.toInt(ev.target.getAttribute('max'))) }); });

function refreshTweakUI() {
Dom.get('lanes').selectedIndex = lanes-1;
Dom.get('currentRoadWidth').innerHTML = Dom.get('roadWidth').value = roadWidth;
Dom.get('currentCameraHeight').innerHTML = Dom.get('cameraHeight').value =
cameraHeight;
Dom.get('currentDrawDistance').innerHTML = Dom.get('drawDistance').value =
drawDistance;
Dom.get('currentFieldOfView').innerHTML = Dom.get('fieldOfView').value = fieldOfView;

Downloaded by college kosam


Dom.get('currentFogDensity').innerHTML = Dom.get('fogDensity').value = fogDensity;
}

//=========================================================================

</script>

</body>

Downloaded by college kosam


Curve:
!DOCTYPE html>

<html>
<head>
<title>Javascript Racer - v2 (curves)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link href="common.css" rel="stylesheet" type="text/css" />
</head>

<body>

<table id="controls">
<tr>
<td colspan="2">
<a href='v1.straight.html'>straight</a> |
<a href='v2.curves.html'>curves</a> |
<a href='v3.hills.html'>hills</a> |
<a href='v4.final.html'>final</a>
</td>
</tr>
<tr><td id="fps" colspan="2" align="right"></td></tr>
<tr>
<th><label for="resolution">Resolution :</label></th>
<td>
<select id="resolution" style="width:100%">
<option value='fine'>Fine (1280x960)</option>
<option selected value='high'>High (1024x768)</option>
<option value='medium'>Medium (640x480)</option>
<option value='low'>Low (480x360)</option>
</select>
</td>
</tr>
<tr>
<th><label for="lanes">Lanes :</label></th>
<td>
<select id="lanes">

Downloaded by college kosam


<option>1</option>
<option>2</option>
<option selected>3</option>
<option>4</option>
</select>
</td>
</tr>
<tr>
<th><label for="roadWidth">Road Width (<span id="currentRoadWidth"></span>)
:</label></th>
<td><input id="roadWidth" type='range' min='500' max='3000' title="integer (500-
3000)"></td>
</tr>
<tr>
<th><label for="cameraHeight">CameraHeight (<span id="currentCameraHeight"></span>)
:</label></th>
<td><input id="cameraHeight" type='range' min='500' max='5000' title="integer (500-
5000)"></td>
</tr>
<tr>
<th><label for="drawDistance">Draw Distance (<span id="currentDrawDistance"></span>)
:</label></th>
<td><input id="drawDistance" type='range' min='100' max='500' title="integer (100-
500)"></td>
</tr>
<tr>
<th><label for="fieldOfView">Field of View (<span id="currentFieldOfView"></span>)
:</label></th>
<td><input id="fieldOfView" type='range' min='80' max='140' title="integer (80-140)"></td>
</tr>
<tr>
<th><label for="fogDensity">Fog Density (<span id="currentFogDensity"></span>)
:</label></th>
<td><input id="fogDensity" type='range' min='0' max='50' title="integer (0-50)"></td>
</tr>
</table>

<div id='instructions'>

Downloaded by college kosam


<p>Use the <b>arrow keys</b> to drive the car.</p>
</div>

<div id="racer">
<canvas id="canvas">
Sorry, this example cannot be run because your browser does not support the
&lt;canvas&gt; element
</canvas>
Loading...
</div>

<audio id='music'>
<source src="music/racer.ogg">
<source src="music/racer.mp3">
</audio>
<span id="mute"></span>

<script src="stats.js"></script>
<script src="common.js"></script>
<script>

var fps = 60; // how many 'update' frames per second


var step = 1/fps; // how long is each frame (in seconds)
var width = 1024; // logical canvas width
var height = 768; // logical canvas height
var centrifugal = 0.3; // centrifugal force multiplier when going around curves
var offRoadDecel = 0.99; // speed multiplier when off road (e.g. you lose 2%
speed each update frame)
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
var segments = []; // array of road segments

Downloaded by college kosam


var stats = Game.stats('fps'); // mr.doobs FPS
counter var canvas = Dom.get('canvas');
// our canvas...
var ctx = canvas.getContext('2d'); // ...and its drawing context
var background = null; // our background image (loaded
below) var sprites = null; // our spritesheet (loaded below)
var resolution = null; // scaling factor to provide resolution independence
(computed)
var roadWidth = 2000; // actually half the roads width, easier math if the road
spans from -roadWidth to +roadWidth
var segmentLength = 200; // length of a single segment
var rumbleLength = 3; // number of segments per red/white rumble
strip var trackLength = null; // z length of entire track (computed)
var lanes = 3; // number of lanes
var fieldOfView = 100; // angle (degrees) for field of
view var cameraHeight = 1000; // z height of camera
var cameraDepth = null; // z distance camera is from screen
(computed) var drawDistance = 300; // number of segments to draw
var playerX = 0; // player x offset from center of road (-1 to 1 to stay
independent of roadWidth)
var playerZ = null; // player relative z distance from camera
(computed) var fogDensity = 5; // exponential fog density
var position = 0; // current camera Z position (add playerZ to get player's
absolute Z position)
var speed = 0; // current speed
var maxSpeed = segmentLength/step; // top speed (ensure we can't move more than 1
segment in a single frame to make collision detection easier)
var accel = maxSpeed/5; // acceleration rate - tuned until it 'felt'
right var breaking = -maxSpeed; // deceleration
rate when braking
var decel = -maxSpeed/5; // 'natural' deceleration rate when neither
accelerating, nor braking
var offRoadDecel = -maxSpeed/2; // off road deceleration is somewhere in between
var offRoadLimit = maxSpeed/4; // limit when off road deceleration no longer
applies
(e.g. you can always go at least this speed even when off road)

var keyLeft = false;


var keyRight = false;
var keyFaster = false;
var keySlower =

Downloaded by college kosam


false;

Downloaded by college kosam


//=========================================================================
// UPDATE THE GAME WORLD
//=========================================================================

function update(dt) {

var playerSegment = findSegment(position+playerZ);


var speedPercent = speed/maxSpeed;
var dx = dt * 2 * speedPercent; // at top speed, should be able to cross from left to
right (-1 to +1) in 1 second

position = Util.increase(position, dt * speed, trackLength);

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);

if (keyLeft)
playerX = playerX - dx;
else if (keyRight)
playerX = playerX + dx;

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

if (keyFaster)
speed = Util.accelerate(speed, accel, dt);
else if (keySlower)
speed = Util.accelerate(speed, breaking, dt);
else
speed = Util.accelerate(speed, decel, dt);

if (((playerX < -1) || (playerX > 1)) && (speed > offRoadLimit))
speed = Util.accelerate(speed, offRoadDecel, dt);

playerX = Util.limit(playerX, -2, 2); // dont ever let player go too far out of
bounds speed = Util.limit(speed, 0, maxSpeed); // or exceed maxSpeed

Downloaded by college kosam


}

//=========================================================================
// RENDER THE GAME WORLD
//=========================================================================

function render() {

var baseSegment = findSegment(position);


var basePercent = Util.percentRemaining(position,
segmentLength); var maxy = height;

var x = 0;
var dx = - (baseSegment.curve * basePercent);

ctx.clearRect(0, 0, width, height);

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);

var n, segment;

for(n = 0 ; n < drawDistance ; n++) {

segment = segments[(baseSegment.index + n) %
segments.length]; segment.looped = segment.index <
baseSegment.index;
segment.fog = Util.exponentialFog(n/drawDistance, fogDensity);

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;

if ((segment.p1.camera.z <= cameraDepth) || // behind us

Downloaded by college kosam


(segment.p2.screen.y >= maxy)) // clip by (already rendered)
segment continue;

Render.segment(ctx, width,
lanes,
segment.p1.screen.x,
segment.p1.screen.y,
segment.p1.screen.w,
segment.p2.screen.x,
segment.p2.screen.y,
segment.p2.screen.w,
segment.fog,
segment.color);

maxy = segment.p2.screen.y;
}

Render.player(ctx, width, height, resolution, roadWidth, sprites, speed/maxSpeed,


cameraDepth/playerZ,
width/2,
height,
speed * (keyLeft ? -1 : keyRight ? 1 : 0),
0);
}

//=========================================================================
// BUILD ROAD 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
});
}

Downloaded by college kosam


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));
}

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

function addStraight(num) {
num = num || ROAD.LENGTH.MEDIUM;
addRoad(num, num, num, 0);
}

function addCurve(num, curve) {


num = num || ROAD.LENGTH.MEDIUM;
curve = curve || ROAD.CURVE.MEDIUM;
addRoad(num, num, num, curve);
}

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);

Downloaded by college kosam


}

function resetRoad() {
segments = [];

addStraight(ROAD.LENGTH.SHORT/4);
addSCurves();
addStraight(ROAD.LENGTH.LONG);
addCurve(ROAD.LENGTH.MEDIUM, ROAD.CURVE.MEDIUM);
addCurve(ROAD.LENGTH.LONG, ROAD.CURVE.MEDIUM);
addStraight();
addSCurves();
addCurve(ROAD.LENGTH.LONG, -ROAD.CURVE.MEDIUM);
addCurve(ROAD.LENGTH.LONG, ROAD.CURVE.MEDIUM);
addStraight();
addSCurves();
addCurve(ROAD.LENGTH.LONG, -ROAD.CURVE.EASY);

segments[findSegment(playerZ).index + 2].color =
COLORS.START; segments[findSegment(playerZ).index + 3].color
= COLORS.START; for(var n = 0 ; n < rumbleLength ; n++)
segments[segments.length-1-n].color = COLORS.FINISH;

trackLength = segments.length * segmentLength;


}

function findSegment(z) {
return segments[Math.floor(z/segmentLength) % segments.length];
}

//=========================================================================
// THE GAME LOOP
//=========================================================================

Game.run({
canvas: canvas, render: render, update: update, stats: stats, step: step,
images: ["background", "sprites"],
keys: [

Downloaded by college kosam


{ keys: [KEY.LEFT, KEY.A], mode: 'down', action: function() { keyLeft = true; } },
{ keys: [KEY.RIGHT, KEY.D], mode: 'down', action: function() { keyRight = true; } },
{ keys: [KEY.UP, KEY.W], mode: 'down', action: function() { keyFaster = true; } },
{ keys: [KEY.DOWN, KEY.S], mode: 'down', action: function() { keySlower = true; } },
{ keys: [KEY.LEFT, KEY.A], mode: 'up', action: function() { keyLeft = false; } },
{ keys: [KEY.RIGHT, KEY.D], mode: 'up', action: function() { keyRight = false; } },
{ keys: [KEY.UP, KEY.W], mode: 'up', action: function() { keyFaster = false; } },
{ keys: [KEY.DOWN, KEY.S], mode: 'up', action: function() { keySlower = false; } }
],
ready: function(images) {
background =
images[0]; sprites
= images[1];
reset();
}
});

function reset(options) {
options = options ||
{};
canvas.width = width = Util.toInt(options.width, width);
canvas.height = height = Util.toInt(options.height,
height); lanes = Util.toInt(options.lanes, lanes);
roadWidth = Util.toInt(options.roadWidth, roadWidth);
cameraHeight = Util.toInt(options.cameraHeight, cameraHeight);
drawDistance = Util.toInt(options.drawDistance, drawDistance);
fogDensity = Util.toInt(options.fogDensity, fogDensity);
fieldOfView = Util.toInt(options.fieldOfView, fieldOfView);
segmentLength = Util.toInt(options.segmentLength, segmentLength);
rumbleLength = Util.toInt(options.rumbleLength, rumbleLength);
cameraDepth = 1 / Math.tan((fieldOfView/2) * Math.PI/180);
playerZ = (cameraHeight * cameraDepth);
resolution = height/480;
refreshTweakUI();

if ((segments.length==0) || (options.segmentLength) || (options.rumbleLength))


resetRoad(); // only rebuild road when necessary
}

//=========================================================================

Downloaded by college kosam


// TWEAK UI HANDLERS
//=========================================================================

Dom.on('resolution', 'change', function(ev) {


var w, h, ratio;
switch(ev.target.options[ev.target.selectedIndex].value) {
case 'fine': w = 1280; h = 960; ratio=w/width; break;
case 'high': w = 1024; h = 768; ratio=w/width; break;
case 'medium': w = 640; h = 480; ratio=w/width;
break; case 'low':w = 480; h = 360; ratio=w/width;
break;
}
reset({ width: w, height: h })
Dom.blur(ev);
});

Dom.on('lanes', 'change', function(ev) { Dom.blur(ev); reset({


lanes: ev.target.options[ev.target.selectedIndex].value }); });
Dom.on('roadWidth', 'change', function(ev) { Dom.blur(ev); reset({
roadWidth: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')),
Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('cameraHeight', 'change', function(ev) { Dom.blur(ev); reset({
cameraHeight: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')),
Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('drawDistance', 'change', function(ev) { Dom.blur(ev); reset({
drawDistance: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')),
Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('fieldOfView', 'change', function(ev) { Dom.blur(ev); reset({
fieldOfView: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')),
Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('fogDensity', 'change', function(ev) { Dom.blur(ev); reset({
fogDensity: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')),
Util.toInt(ev.target.getAttribute('max'))) }); });

function refreshTweakUI() {
Dom.get('lanes').selectedIndex = lanes-1;
Dom.get('currentRoadWidth').innerHTML = Dom.get('roadWidth').value = roadWidth;
Dom.get('currentCameraHeight').innerHTML = Dom.get('cameraHeight').value =
cameraHeight;

Downloaded by college kosam


Dom.get('currentDrawDistance').innerHTML = Dom.get('drawDistance').value =
drawDistance;
Dom.get('currentFieldOfView').innerHTML = Dom.get('fieldOfView').value = fieldOfView;
Dom.get('currentFogDensity').innerHTML = Dom.get('fogDensity').value = fogDensity;
}

//=========================================================================

</script>

</body>

Downloaded by college kosam


Hills:
<!DOCTYPE html>

<html>
<head>
<title>Javascript Racer - v3 (hills)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link href="common.css" rel="stylesheet" type="text/css" />
</head>

<body>

<table id="controls">
<tr>
<td colspan="2">
<a href='v1.straight.html'>straight</a> |
<a href='v2.curves.html'>curves</a> |
<a href='v3.hills.html'>hills</a> |
<a href='v4.final.html'>final</a>
</td>
</tr>
<tr><td id="fps" colspan="2" align="right"></td></tr>
<tr>
<th><label for="resolution">Resolution :</label></th>
<td>
<select id="resolution" style="width:100%">
<option value='fine'>Fine (1280x960)</option>
<option selected value='high'>High (1024x768)</option>
<option value='medium'>Medium (640x480)</option>
<option value='low'>Low (480x360)</option>
</select>
</td>
</tr>
<tr>
<th><label for="lanes">Lanes :</label></th>
<td>
<select id="lanes">

Downloaded by college kosam


<option>1</option>
<option>2</option>
<option selected>3</option>
<option>4</option>
</select>
</td>
</tr>
<tr>
<th><label for="roadWidth">Road Width (<span id="currentRoadWidth"></span>)
:</label></th>
<td><input id="roadWidth" type='range' min='500' max='3000' title="integer (500-
3000)"></td>
</tr>
<tr>
<th><label for="cameraHeight">CameraHeight (<span id="currentCameraHeight"></span>)
:</label></th>
<td><input id="cameraHeight" type='range' min='500' max='5000' title="integer (500-
5000)"></td>
</tr>
<tr>
<th><label for="drawDistance">Draw Distance (<span id="currentDrawDistance"></span>)
:</label></th>
<td><input id="drawDistance" type='range' min='100' max='500' title="integer (100-
500)"></td>
</tr>
<tr>
<th><label for="fieldOfView">Field of View (<span id="currentFieldOfView"></span>)
:</label></th>
<td><input id="fieldOfView" type='range' min='80' max='140' title="integer (80-140)"></td>
</tr>
<tr>
<th><label for="fogDensity">Fog Density (<span id="currentFogDensity"></span>)
:</label></th>
<td><input id="fogDensity" type='range' min='0' max='50' title="integer (0-50)"></td>
</tr>
</table>

<div id='instructions'>

Downloaded by college kosam


<p>Use the <b>arrow keys</b> to drive the car.</p>
</div>

<div id="racer">
<canvas id="canvas">
Sorry, this example cannot be run because your browser does not support the
&lt;canvas&gt; element
</canvas>
Loading...
</div>

<audio id='music'>
<source src="music/racer.ogg">
<source src="music/racer.mp3">
</audio>
<span id="mute"></span>

<script src="stats.js"></script>
<script src="common.js"></script>
<script>

var fps = 60; // how many 'update' frames per second


var step = 1/fps; // how long is each frame (in seconds)
var width = 1024; // logical canvas width
var height = 768; // logical canvas height
var centrifugal = 0.3; // centrifugal force multiplier when going around curves
var offRoadDecel = 0.99; // speed multiplier when off road (e.g. you lose 2%
speed each update frame)
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
var segments = []; // array of road segments

Downloaded by college kosam


var stats = Game.stats('fps'); // mr.doobs FPS
counter var canvas = Dom.get('canvas');
// our canvas...
var ctx = canvas.getContext('2d'); // ...and its drawing context
var background = null; // our background image (loaded
below) var sprites = null; // our spritesheet (loaded below)
var resolution = null; // scaling factor to provide resolution independence
(computed)
var roadWidth = 2000; // actually half the roads width, easier math if the road
spans from -roadWidth to +roadWidth
var segmentLength = 200; // length of a single segment
var rumbleLength = 3; // number of segments per red/white rumble
strip var trackLength = null; // z length of entire track (computed)
var lanes = 3; // number of lanes
var fieldOfView = 100; // angle (degrees) for field of
view var cameraHeight = 1000; // z height of camera
var cameraDepth = null; // z distance camera is from screen
(computed) var drawDistance = 300; // number of segments to draw
var playerX = 0; // player x offset from center of road (-1 to 1 to stay
independent of roadWidth)
var playerZ = null; // player relative z distance from camera
(computed) var fogDensity = 5; // exponential fog density
var position = 0; // current camera Z position (add playerZ to get player's
absolute Z position)
var speed = 0; // current speed
var maxSpeed = segmentLength/step; // top speed (ensure we can't move more than 1
segment in a single frame to make collision detection easier)
var accel = maxSpeed/5; // acceleration rate - tuned until it 'felt'
right var breaking = -maxSpeed; // deceleration
rate when braking
var decel = -maxSpeed/5; // 'natural' deceleration rate when neither
accelerating, nor braking
var offRoadDecel = -maxSpeed/2; // off road deceleration is somewhere in between
var offRoadLimit = maxSpeed/4; // limit when off road deceleration no longer
applies
(e.g. you can always go at least this speed even when off road)

var keyLeft = false;


var keyRight = false;
var keyFaster = false;
var keySlower =

Downloaded by college kosam


false;

Downloaded by college kosam


//=========================================================================
// UPDATE THE GAME WORLD
//=========================================================================

function update(dt) {

var playerSegment = findSegment(position+playerZ);


var speedPercent = speed/maxSpeed;
var dx = dt * 2 * speedPercent; // at top speed, should be able to cross from left to
right (-1 to 1) in 1 second

position = Util.increase(position, dt * speed, trackLength);

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);

if (keyLeft)
playerX = playerX - dx;
else if (keyRight)
playerX = playerX + dx;

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

if (keyFaster)
speed = Util.accelerate(speed, accel, dt);
else if (keySlower)
speed = Util.accelerate(speed, breaking, dt);
else
speed = Util.accelerate(speed, decel, dt);

if (((playerX < -1) || (playerX > 1)) && (speed > offRoadLimit))
speed = Util.accelerate(speed, offRoadDecel, dt);

playerX = Util.limit(playerX, -2, 2); // dont ever let it go too far out of
bounds speed = Util.limit(speed, 0, maxSpeed); // or exceed maxSpeed

Downloaded by college kosam


}

//=========================================================================
// RENDER THE GAME WORLD
//=========================================================================

function render() {

var baseSegment = findSegment(position);


var basePercent = Util.percentRemaining(position, segmentLength);
var playerSegment = findSegment(position+playerZ);
var playerPercent = Util.percentRemaining(position+playerZ, segmentLength);
var playerY = Util.interpolate(playerSegment.p1.world.y, playerSegment.p2.world.y,
playerPercent);
var maxy = height;

var x = 0;
var dx = - (baseSegment.curve * basePercent);

ctx.clearRect(0, 0, width, height);

Render.background(ctx, background, width, height,


BACKGROUND.SKY, skyOffset, resolution * skySpeed * playerY);
Render.background(ctx, background, width, height, BACKGROUND.HILLS, hillOffset,
resolution * hillSpeed * playerY);
Render.background(ctx, background, width, height, BACKGROUND.TREES, treeOffset,
resolution * treeSpeed * playerY);

var n, segment;

for(n = 0 ; n < drawDistance ; n++) {

segment = segments[(baseSegment.index + n) %
segments.length]; segment.looped = segment.index <
baseSegment.index;
segment.fog = Util.exponentialFog(n/drawDistance, fogDensity);

Util.project(segment.p1, (playerX * roadWidth) - x, playerY + cameraHeight, position -


(segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);

Downloaded by college kosam


Util.project(segment.p2, (playerX * roadWidth) - x - dx, playerY + cameraHeight, position -
(segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);

x = x + dx;
dx = dx + segment.curve;

if ((segment.p1.camera.z <= cameraDepth) || // behind us


(segment.p2.screen.y >= segment.p1.screen.y) || // back face cull
(segment.p2.screen.y >= maxy)) // clip by (already rendered) segment
continue;

Render.segment(ctx, width,
lanes,
segment.p1.screen.x,
segment.p1.screen.y,
segment.p1.screen.w,
segment.p2.screen.x,
segment.p2.screen.y,
segment.p2.screen.w,
segment.fog,
segment.color);

maxy = segment.p2.screen.y;
}

Render.player(ctx, width, height, resolution, roadWidth, sprites, speed/maxSpeed,


cameraDepth/playerZ,
width/2,
(height/2) - (cameraDepth/playerZ * Util.interpolate(playerSegment.p1.camera.y,
playerSegment.p2.camera.y, playerPercent) * height/2),
speed * (keyLeft ? -1 : keyRight ? 1 : 0),
playerSegment.p2.world.y - playerSegment.p1.world.y);
}

//=========================================================================
// BUILD ROAD GEOMETRY
//=========================================================================

Downloaded by college kosam


function lastY() { return (segments.length == 0) ? 0 : segments[segments.length-
1].p2.world.y; }

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

function addRoad(enter, hold, leave, curve, y) {


var startY = lastY();
var endY = startY + (Util.toInt(y, 0) *
segmentLength); var n, total = enter + hold + leave;
for(n = 0 ; n < enter ; n++)
addSegment(Util.easeIn(0, curve, n/enter), Util.easeInOut(startY, endY, n/total));
for(n = 0 ; n < hold ; n++)
addSegment(curve, Util.easeInOut(startY, endY, (enter+n)/total));
for(n = 0 ; n < leave ; n++)
addSegment(Util.easeInOut(curve, 0, n/leave), Util.easeInOut(startY, endY,
(enter+hold+n)/total));
}

var ROAD = {
LENGTH: { NONE: 0, SHORT: 25, MEDIUM: 50, LONG: 100 },
HILL: { NONE: 0, LOW: 20, MEDIUM: 40, HIGH: 60 },
CURVE: { NONE: 0, EASY: 2, MEDIUM: 4, HARD: 6 }
};

function addStraight(num) {
num = num || ROAD.LENGTH.MEDIUM;
addRoad(num, num, num, 0, 0);
}

Downloaded by college kosam


function addHill(num, height) {
num = num || ROAD.LENGTH.MEDIUM;
height = height || ROAD.HILL.MEDIUM;
addRoad(num, num, num, 0, height);
}

function addCurve(num, curve, height) {


num = num ||
ROAD.LENGTH.MEDIUM;
curve = curve || ROAD.CURVE.MEDIUM;
height = height || ROAD.HILL.NONE;
addRoad(num, num, num, curve, height);
}

function addLowRollingHills(num, height)


{ num = num || ROAD.LENGTH.SHORT;
height = height || ROAD.HILL.LOW;
addRoad(num, num, num, 0, height/2);
addRoad(num, num, num, 0, -height);
addRoad(num, num, num, 0, height);
addRoad(num, num, num, 0, 0);
addRoad(num, num, num, 0, height/2);
addRoad(num, num, num, 0, 0);
}

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

function addDownhillToEnd(num) {

Downloaded by college kosam


num = num || 200;
addRoad(num, num, num, -ROAD.CURVE.EASY, -lastY()/segmentLength);
}

function resetRoad() {
segments = [];

addStraight(ROAD.LENGTH.SHORT/2);
addHill(ROAD.LENGTH.SHORT, ROAD.HILL.LOW);
addLowRollingHills();
addCurve(ROAD.LENGTH.MEDIUM, ROAD.CURVE.MEDIUM, ROAD.HILL.LOW);
addLowRollingHills();
addCurve(ROAD.LENGTH.LONG, ROAD.CURVE.MEDIUM, ROAD.HILL.MEDIUM);
addStraight();
addCurve(ROAD.LENGTH.LONG, -ROAD.CURVE.MEDIUM,
ROAD.HILL.MEDIUM); addHill(ROAD.LENGTH.LONG, ROAD.HILL.HIGH);
addCurve(ROAD.LENGTH.LONG, ROAD.CURVE.MEDIUM, -ROAD.HILL.LOW);
addHill(ROAD.LENGTH.LONG, -ROAD.HILL.MEDIUM);
addStraight();
addDownhillToEnd();

segments[findSegment(playerZ).index + 2].color =
COLORS.START; segments[findSegment(playerZ).index + 3].color
= COLORS.START; for(var n = 0 ; n < rumbleLength ; n++)
segments[segments.length-1-n].color = COLORS.FINISH;

trackLength = segments.length * segmentLength;


}

function findSegment(z) {
return segments[Math.floor(z/segmentLength) % segments.length];
}

//=========================================================================
// THE GAME LOOP
//=========================================================================

Game.run({

Downloaded by college kosam


canvas: canvas, render: render, update: update, stats: stats, step: step,
images: ["background", "sprites"],
keys: [
{ keys: [KEY.LEFT, KEY.A], mode: 'down', action: function() { keyLeft = true; } },
{ keys: [KEY.RIGHT, KEY.D], mode: 'down', action: function() { keyRight = true; } },
{ keys: [KEY.UP, KEY.W], mode: 'down', action: function() { keyFaster = true; } },
{ keys: [KEY.DOWN, KEY.S], mode: 'down', action: function() { keySlower = true; } },
{ keys: [KEY.LEFT, KEY.A], mode: 'up', action: function() { keyLeft = false; } },
{ keys: [KEY.RIGHT, KEY.D], mode: 'up', action: function() { keyRight = false; } },
{ keys: [KEY.UP, KEY.W], mode: 'up', action: function() { keyFaster = false; } },
{ keys: [KEY.DOWN, KEY.S], mode: 'up', action: function() { keySlower = false; } }
],
ready: function(images) {
background =
images[0]; sprites
= images[1];
reset();
}
});

function reset(options) {
options = options ||
{};
canvas.width = width = Util.toInt(options.width, width);
canvas.height = height = Util.toInt(options.height,
height); lanes = Util.toInt(options.lanes, lanes);
roadWidth = Util.toInt(options.roadWidth, roadWidth);
cameraHeight = Util.toInt(options.cameraHeight, cameraHeight);
drawDistance = Util.toInt(options.drawDistance, drawDistance);
fogDensity = Util.toInt(options.fogDensity, fogDensity);
fieldOfView = Util.toInt(options.fieldOfView, fieldOfView);
segmentLength = Util.toInt(options.segmentLength, segmentLength);
rumbleLength = Util.toInt(options.rumbleLength, rumbleLength);
cameraDepth = 1 / Math.tan((fieldOfView/2) * Math.PI/180);
playerZ = (cameraHeight * cameraDepth);
resolution = height/480;
refreshTweakUI();

if ((segments.length==0) || (options.segmentLength) || (options.rumbleLength))


resetRoad(); // only rebuild road when necessary

Downloaded by college kosam


}

//=========================================================================
// TWEAK UI HANDLERS
//=========================================================================

Dom.on('resolution', 'change', function(ev) {


var w, h, ratio;
switch(ev.target.options[ev.target.selectedIndex].value) {
case 'fine': w = 1280; h = 960; ratio=w/width; break;
case 'high': w = 1024; h = 768; ratio=w/width; break;
case 'medium': w = 640; h = 480; ratio=w/width;
break; case 'low':w = 480; h = 360; ratio=w/width;
break;
}
reset({ width: w, height: h })
Dom.blur(ev);
});

Dom.on('lanes', 'change', function(ev) { Dom.blur(ev); reset({


lanes: ev.target.options[ev.target.selectedIndex].value }); });
Dom.on('roadWidth', 'change', function(ev) { Dom.blur(ev); reset({
roadWidth: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')),
Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('cameraHeight', 'change', function(ev) { Dom.blur(ev); reset({
cameraHeight: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')),
Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('drawDistance', 'change', function(ev) { Dom.blur(ev); reset({
drawDistance: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')),
Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('fieldOfView', 'change', function(ev) { Dom.blur(ev); reset({
fieldOfView: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')),
Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('fogDensity', 'change', function(ev) { Dom.blur(ev); reset({
fogDensity: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')),
Util.toInt(ev.target.getAttribute('max'))) }); });

function refreshTweakUI() {
Dom.get('lanes').selectedIndex = lanes-1;

Downloaded by college kosam


Dom.get('currentRoadWidth').innerHTML = Dom.get('roadWidth').value = roadWidth;
Dom.get('currentCameraHeight').innerHTML = Dom.get('cameraHeight').value =
cameraHeight;
Dom.get('currentDrawDistance').innerHTML = Dom.get('drawDistance').value =
drawDistance;
Dom.get('currentFieldOfView').innerHTML = Dom.get('fieldOfView').value = fieldOfView;
Dom.get('currentFogDensity').innerHTML = Dom.get('fogDensity').value = fogDensity;
}

//=========================================================================

</script>

</body>

Downloaded by college kosam


Final:
<!DOCTYPE html>

<html>
<head>
<title>Javascript Racer - v4 (final)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link href="common.css" rel="stylesheet" type="text/css" />
</head>

<body>

<table id="controls">
<tr>
<td colspan="2">
<a href='v1.straight.html'>straight</a> |
<a href='v2.curves.html'>curves</a> |
<a href='v3.hills.html'>hills</a> |
<a href='v4.final.html'>final</a>
</td>
</tr>
<tr><td id="fps" colspan="2" align="right"></td></tr>
<tr>
<th><label for="resolution">Resolution :</label></th>
<td>
<select id="resolution" style="width:100%">
<option value='fine'>Fine (1280x960)</option>
<option selected value='high'>High (1024x768)</option>
<option value='medium'>Medium (640x480)</option>
<option value='low'>Low (480x360)</option>
</select>
</td>
</tr>
<tr>
<th><label for="lanes">Lanes :</label></th>
<td>
<select id="lanes">

Downloaded by college kosam


<option>1</option>
<option>2</option>
<option selected>3</option>
<option>4</option>
</select>
</td>
</tr>
<tr>
<th><label for="roadWidth">Road Width (<span id="currentRoadWidth"></span>)
:</label></th>
<td><input id="roadWidth" type='range' min='500' max='3000' title="integer (500-
3000)"></td>
</tr>
<tr>
<th><label for="cameraHeight">CameraHeight (<span id="currentCameraHeight"></span>)
:</label></th>
<td><input id="cameraHeight" type='range' min='500' max='5000' title="integer (500-
5000)"></td>
</tr>
<tr>
<th><label for="drawDistance">Draw Distance (<span id="currentDrawDistance"></span>)
:</label></th>
<td><input id="drawDistance" type='range' min='100' max='500' title="integer (100-
500)"></td>
</tr>
<tr>
<th><label for="fieldOfView">Field of View (<span id="currentFieldOfView"></span>)
:</label></th>
<td><input id="fieldOfView" type='range' min='80' max='140' title="integer (80-140)"></td>
</tr>
<tr>
<th><label for="fogDensity">Fog Density (<span id="currentFogDensity"></span>)
:</label></th>
<td><input id="fogDensity" type='range' min='0' max='50' title="integer (0-50)"></td>
</tr>
</table>

<div id='instructions'>

Downloaded by college kosam


<p>Use the <b>arrow keys</b> to drive the car.</p>
</div>

<div id="racer">
<div id="hud">
<span id="speed" class="hud"><span id="speed_value" class="value">0</span>
mph</span>
<span id="current_lap_time" class="hud">Time: <span id="current_lap_time_value"
class="value">0.0</span></span>
<span id="last_lap_time" class="hud">Last Lap: <span id="last_lap_time_value"
class="value">0.0</span></span>
<span id="fast_lap_time" class="hud">Fastest Lap: <span id="fast_lap_time_value"
class="value">0.0</span></span>
</div>
<canvas id="canvas">
Sorry, this example cannot be run because your browser does not support the
&lt;canvas&gt; element
</canvas>
Loading...
</div>

<audio id='music'>
<source src="music/racer.ogg">
<source src="music/racer.mp3">
</audio>
<span id="mute"></span>

<script src="stats.js"></script>
<script src="common.js"></script>
<script>

var fps = 60; // how many 'update' frames per second


var step = 1/fps; // how long is each frame (in seconds)
var width = 1024; // logical canvas width
var height = 768; // logical canvas height
var centrifugal = 0.3; // centrifugal force multiplier when going around curves
var offRoadDecel = 0.99; // speed multiplier when off road (e.g. you lose 2%
speed each update frame)

Downloaded by college kosam


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
var segments = []; // array of road
segments var cars = []; // array of cars on the
road
var stats = Game.stats('fps'); // mr.doobs FPS
counter var canvas = Dom.get('canvas');
// our canvas...
var ctx = canvas.getContext('2d'); // ...and its drawing context
var background = null; // our background image (loaded
below) var sprites = null; // our spritesheet (loaded below)
var resolution = null; // scaling factor to provide resolution independence
(computed)
var roadWidth = 2000; // actually half the roads width, easier math if the road
spans from -roadWidth to +roadWidth
var segmentLength = 200; // length of a single segment
var rumbleLength = 3; // number of segments per red/white rumble
strip var trackLength = null; // z length of entire track (computed)
var lanes = 3; // number of lanes
var fieldOfView = 100; // angle (degrees) for field of
view var cameraHeight = 1000; // z height of camera
var cameraDepth = null; // z distance camera is from screen
(computed) var drawDistance = 300; // number of segments to draw
var playerX = 0; // player x offset from center of road (-1 to 1 to stay
independent of roadWidth)
var playerZ = null; // player relative z distance from camera
(computed) var fogDensity = 5; // exponential fog density
var position = 0; // current camera Z position (add playerZ to get player's
absolute Z position)
var speed = 0; // current speed
var maxSpeed = segmentLength/step; // top speed (ensure we can't move more than
1 segment in a single frame to make collision detection easier)
var accel = maxSpeed/5; // acceleration rate - tuned until it 'felt' right

Downloaded by college kosam


var breaking = -maxSpeed; // deceleration rate when braking
var decel = -maxSpeed/5; // 'natural' deceleration rate when neither
accelerating, nor braking
var offRoadDecel = -maxSpeed/2; // off road deceleration is somewhere in between
var offRoadLimit = maxSpeed/4; // limit when off road deceleration no longer
applies
(e.g. you can always go at least this speed even when off road)
var totalCars = 200; // total number of cars on the
road var currentLapTime = 0; // current lap time
var lastLapTime = null; // last lap time

var keyLeft = false;


var keyRight = false;
var keyFaster = false;
var keySlower =
false;

var hud = {
speed: { value: null, dom: Dom.get('speed_value') },
current_lap_time: { value: null, dom: Dom.get('current_lap_time_value')
}, last_lap_time: { value: null, dom: Dom.get('last_lap_time_value') },
fast_lap_time: { value: null, dom: Dom.get('fast_lap_time_value') }
}

//=========================================================================
// UPDATE THE GAME WORLD
//=========================================================================

function update(dt) {

var n, car, carW, sprite, spriteW;


var playerSegment = findSegment(position+playerZ);
var playerW = SPRITES.PLAYER_STRAIGHT.w * SPRITES.SCALE;
var speedPercent = speed/maxSpeed;
var dx = dt * 2 * speedPercent; // at top speed, should be able to cross from left to
right (-1 to 1) in 1 second
var startPosition = position;

updateCars(dt, playerSegment,

playerW);

Downloaded by college kosam


position = Util.increase(position, dt * speed, trackLength);

if (keyLeft)
playerX = playerX - dx;
else if (keyRight)
playerX = playerX + dx;

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

if (keyFaster)
speed = Util.accelerate(speed, accel, dt);
else if (keySlower)
speed = Util.accelerate(speed, breaking, dt);
else
speed = Util.accelerate(speed, decel, dt);

if ((playerX < -1) || (playerX > 1)) {

if (speed > offRoadLimit)


speed = Util.accelerate(speed, offRoadDecel, dt);

for(n = 0 ; n < playerSegment.sprites.length ; n++) {


sprite = playerSegment.sprites[n];
spriteW = sprite.source.w * SPRITES.SCALE;
if (Util.overlap(playerX, playerW, sprite.offset + spriteW/2 * (sprite.offset > 0 ? 1 : -1),
spriteW)) {
speed = maxSpeed/5;
position = Util.increase(playerSegment.p1.world.z, -playerZ, trackLength); // stop in
front of sprite (at front of segment)
break;
}
}
}

for(n = 0 ; n < playerSegment.cars.length ; n++)


{ car = playerSegment.cars[n];
carW = car.sprite.w * SPRITES.SCALE;

Downloaded by college kosam


if (speed > car.speed) {
if (Util.overlap(playerX, playerW, car.offset, carW, 0.8))
{ speed = car.speed * (car.speed/speed);
position = Util.increase(car.z, -playerZ, trackLength);
break;
}
}
}

playerX = Util.limit(playerX, -3, 3); // dont ever let it go too far out of
bounds speed = Util.limit(speed, 0, maxSpeed); // or exceed maxSpeed

skyOffset = Util.increase(skyOffset, skySpeed * playerSegment.curve * (position-


startPosition)/segmentLength, 1);
hillOffset = Util.increase(hillOffset, hillSpeed * playerSegment.curve * (position-
startPosition)/segmentLength, 1);
treeOffset = Util.increase(treeOffset, treeSpeed * playerSegment.curve * (position-
startPosition)/segmentLength, 1);

if (position > playerZ) {


if (currentLapTime && (startPosition < playerZ))
{ lastLapTime = currentLapTime;
currentLapTime = 0;
if (lastLapTime <= Util.toFloat(Dom.storage.fast_lap_time)) {
Dom.storage.fast_lap_time = lastLapTime;
updateHud('fast_lap_time', formatTime(lastLapTime));
Dom.addClassName('fast_lap_time', 'fastest');
Dom.addClassName('last_lap_time', 'fastest');
}
else {
Dom.removeClassName('fast_lap_time', 'fastest');
Dom.removeClassName('last_lap_time', 'fastest');
}
updateHud('last_lap_time', formatTime(lastLapTime));
Dom.show('last_lap_time');
}
else {
currentLapTime += dt;

Downloaded by college kosam


}
}

updateHud('speed', 5 * Math.round(speed/500));
updateHud('current_lap_time', formatTime(currentLapTime));
}

//

function updateCars(dt, playerSegment, playerW)


{ var n, car, oldSegment, newSegment;
for(n = 0 ; n < cars.length ; n++)
{ car = cars[n];
oldSegment = findSegment(car.z);
car.offset = car.offset + updateCarOffset(car, oldSegment, playerSegment,
playerW); car.z = Util.increase(car.z, dt * car.speed, trackLength);
car.percent = Util.percentRemaining(car.z, segmentLength); // useful for interpolation
during rendering phase
newSegment = findSegment(car.z);
if (oldSegment != newSegment) {
index =
oldSegment.cars.indexOf(car);
oldSegment.cars.splice(index, 1);
newSegment.cars.push(car);
}
}
}

function updateCarOffset(car, carSegment, playerSegment, playerW) {

var i, j, dir, segment, otherCar, otherCarW, lookahead = 20, carW = car.sprite.w *


SPRITES.SCALE;

// optimization, dont bother steering around other cars when 'out of sight' of the
player if ((carSegment.index - playerSegment.index) > drawDistance)
return 0;

for(i = 1 ; i < lookahead ; i++) {


segment = segments[(carSegment.index+i)%segments.length];

Downloaded by college kosam


if ((segment === playerSegment) && (car.speed > speed) && (Util.overlap(playerX,
playerW, car.offset, carW, 1.2))) {
if (playerX > 0.5)
dir = -1;
else if (playerX < -0.5)
dir = 1;
else
dir = (car.offset > playerX) ? 1 : -1;
return dir * 1/i * (car.speed-speed)/maxSpeed; // the closer the cars (smaller i) and the
greated the speed ratio, the larger the offset
}

for(j = 0 ; j < segment.cars.length ; j++) {


otherCar = segment.cars[j];
otherCarW = otherCar.sprite.w * SPRITES.SCALE;
if ((car.speed > otherCar.speed) && Util.overlap(car.offset, carW, otherCar.offset,
otherCarW, 1.2)) {
if (otherCar.offset > 0.5)
dir = -1;
else if (otherCar.offset < -0.5)
dir = 1;
else
dir = (car.offset > otherCar.offset) ? 1 : -1;
return dir * 1/i * (car.speed-otherCar.speed)/maxSpeed;
}
}
}

// if no cars ahead, but I have somehow ended up off road, then steer back
on if (car.offset < -0.9)
return 0.1;
else if (car.offset > 0.9)
return -0.1;
else
return 0;
}

Downloaded by college kosam


//

function updateHud(key, value) { // accessing DOM can be slow, so only do it if value has
changed
if (hud[key].value !== value) {
hud[key].value = value;
Dom.set(hud[key].dom, value);
}
}

function formatTime(dt) {
var minutes = Math.floor(dt/60);
var seconds = Math.floor(dt - (minutes * 60));
var tenths = Math.floor(10 * (dt - Math.floor(dt)));
if (minutes > 0)
return minutes + "." + (seconds < 10 ? "0" : "") + seconds + "." +
tenths; else
return seconds + "." + tenths;
}

//=========================================================================
// RENDER THE GAME WORLD
//=========================================================================

function render() {

var baseSegment = findSegment(position);


var basePercent = Util.percentRemaining(position, segmentLength);
var playerSegment = findSegment(position+playerZ);
var playerPercent = Util.percentRemaining(position+playerZ, segmentLength);
var playerY = Util.interpolate(playerSegment.p1.world.y, playerSegment.p2.world.y,
playerPercent);
var maxy = height;

var x = 0;
var dx = - (baseSegment.curve * basePercent);

ctx.clearRect(0, 0, width, height);

Downloaded by college kosam


Render.background(ctx, background, width, height,
BACKGROUND.SKY, skyOffset, resolution * skySpeed * playerY);
Render.background(ctx, background, width, height, BACKGROUND.HILLS, hillOffset,
resolution * hillSpeed * playerY);
Render.background(ctx, background, width, height, BACKGROUND.TREES, treeOffset,
resolution * treeSpeed * playerY);

var n, i, segment, car, sprite, spriteScale, spriteX, spriteY;

for(n = 0 ; n < drawDistance ; n++) {

segment = segments[(baseSegment.index + n) %
segments.length]; segment.looped = segment.index <
baseSegment.index;
segment.fog = Util.exponentialFog(n/drawDistance,
fogDensity); segment.clip = maxy;

Util.project(segment.p1, (playerX * roadWidth) - x, playerY + cameraHeight, position -


(segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);
Util.project(segment.p2, (playerX * roadWidth) - x - dx, playerY + cameraHeight, position -
(segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);

x = x + dx;
dx = dx + segment.curve;

if ((segment.p1.camera.z <= cameraDepth) || // behind us


(segment.p2.screen.y >= segment.p1.screen.y) || // back face cull
(segment.p2.screen.y >= maxy)) // clip by (already rendered) hill
continue;

Render.segment(ctx, width,
lanes,
segment.p1.screen.x,
segment.p1.screen.y,
segment.p1.screen.w,
segment.p2.screen.x,
segment.p2.screen.y,
segment.p2.screen.w,
segment.fog,

Downloaded by college kosam


segment.color);

maxy = segment.p1.screen.y;
}

for(n = (drawDistance-1) ; n > 0 ; n--) {


segment = segments[(baseSegment.index + n) % segments.length];

for(i = 0 ; i < segment.cars.length ; i++)


{ car = segment.cars[i];
sprite = car.sprite;
spriteScale = Util.interpolate(segment.p1.screen.scale, segment.p2.screen.scale,
car.percent);
spriteX = Util.interpolate(segment.p1.screen.x, segment.p2.screen.x, car.percent)
+ (spriteScale * car.offset * roadWidth * width/2);
spriteY = Util.interpolate(segment.p1.screen.y, segment.p2.screen.y, car.percent);
Render.sprite(ctx, width, height, resolution, roadWidth, sprites, car.sprite, spriteScale,
spriteX, spriteY, -0.5, -1, segment.clip);
}

for(i = 0 ; i < segment.sprites.length ; i++)


{ sprite = segment.sprites[i];
spriteScale = segment.p1.screen.scale;
spriteX = segment.p1.screen.x + (spriteScale * sprite.offset * roadWidth *
width/2); spriteY = segment.p1.screen.y;
Render.sprite(ctx, width, height, resolution, roadWidth, sprites, sprite.source, spriteScale,
spriteX, spriteY, (sprite.offset < 0 ? -1 : 0), -1, segment.clip);
}

if (segment == playerSegment) {
Render.player(ctx, width, height, resolution, roadWidth, sprites, speed/maxSpeed,
cameraDepth/playerZ,
width/2,
(height/2) - (cameraDepth/playerZ * Util.interpolate(playerSegment.p1.camera.y,
playerSegment.p2.camera.y, playerPercent) * height/2),
speed * (keyLeft ? -1 : keyRight ? 1 : 0),
playerSegment.p2.world.y - playerSegment.p1.world.y);
}

Downloaded by college kosam


}
}

function findSegment(z) {
return segments[Math.floor(z/segmentLength) % segments.length];
}

//=========================================================================
// BUILD ROAD GEOMETRY
//=========================================================================

function lastY() { return (segments.length == 0) ? 0 : segments[segments.length-


1].p2.world.y; }

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

function addSprite(n, sprite, offset) {


segments[n].sprites.push({ source: sprite, offset: offset });
}

function addRoad(enter, hold, leave, curve, y) {


var startY = lastY();
var endY = startY + (Util.toInt(y, 0) *
segmentLength); var n, total = enter + hold + leave;
for(n = 0 ; n < enter ; n++)
addSegment(Util.easeIn(0, curve, n/enter), Util.easeInOut(startY, endY, n/total));
for(n = 0 ; n < hold ; n++)

Downloaded by college kosam


addSegment(curve, Util.easeInOut(startY, endY, (enter+n)/total));
for(n = 0 ; n < leave ; n++)
addSegment(Util.easeInOut(curve, 0, n/leave), Util.easeInOut(startY, endY,
(enter+hold+n)/total));
}

var ROAD = {
LENGTH: { NONE: 0, SHORT: 25, MEDIUM: 50, LONG: 100 },
HILL: { NONE: 0, LOW: 20, MEDIUM: 40, HIGH: 60 },
CURVE: { NONE: 0, EASY: 2, MEDIUM: 4, HARD: 6 }
};

function addStraight(num) {
num = num || ROAD.LENGTH.MEDIUM;
addRoad(num, num, num, 0, 0);
}

function addHill(num, height) {


num = num || ROAD.LENGTH.MEDIUM;
height = height || ROAD.HILL.MEDIUM;
addRoad(num, num, num, 0, height);
}

function addCurve(num, curve, height) {


num = num ||
ROAD.LENGTH.MEDIUM;
curve = curve || ROAD.CURVE.MEDIUM;
height = height || ROAD.HILL.NONE;
addRoad(num, num, num, curve, height);
}

function addLowRollingHills(num, height)


{ num = num || ROAD.LENGTH.SHORT;
height = height || ROAD.HILL.LOW;
addRoad(num, num, num, 0, height/2);
addRoad(num, num, num, 0, -height);
addRoad(num, num, num, ROAD.CURVE.EASY, height);
addRoad(num, num, num, 0, 0);
addRoad(num, num, num, -ROAD.CURVE.EASY, height/2);

Downloaded by college kosam


addRoad(num, num, num, 0, 0);
}

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

function addBumps() {
addRoad(10, 10, 10, 0,
5);
addRoad(10, 10, 10, 0, -2);
addRoad(10, 10, 10, 0, -5);
addRoad(10, 10, 10, 0, 8);
addRoad(10, 10, 10, 0, 5);
addRoad(10, 10, 10, 0, -7);
addRoad(10, 10, 10, 0, 5);
addRoad(10, 10, 10, 0, -2);
}

function addDownhillToEnd(num) {
num = num || 200;
addRoad(num, num, num, -ROAD.CURVE.EASY, -lastY()/segmentLength);
}

function resetRoad() {
segments = [];

addStraight(ROAD.LENGTH.SHORT);
addLowRollingHills();
addSCurves();

Downloaded by college kosam


addCurve(ROAD.LENGTH.MEDIUM, ROAD.CURVE.MEDIUM, ROAD.HILL.LOW);
addBumps();
addLowRollingHills();
addCurve(ROAD.LENGTH.LONG*2, ROAD.CURVE.MEDIUM, ROAD.HILL.MEDIUM);
addStraight();
addHill(ROAD.LENGTH.MEDIUM, ROAD.HILL.HIGH);
addSCurves();
addCurve(ROAD.LENGTH.LONG, -ROAD.CURVE.MEDIUM,
ROAD.HILL.NONE); addHill(ROAD.LENGTH.LONG, ROAD.HILL.HIGH);
addCurve(ROAD.LENGTH.LONG, ROAD.CURVE.MEDIUM, -ROAD.HILL.LOW);
addBumps();
addHill(ROAD.LENGTH.LONG, -ROAD.HILL.MEDIUM);
addStraight();
addSCurves();
addDownhillToEnd();

resetSprites();
resetCars();

segments[findSegment(playerZ).index + 2].color =
COLORS.START; segments[findSegment(playerZ).index + 3].color
= COLORS.START; for(var n = 0 ; n < rumbleLength ; n++)
segments[segments.length-1-n].color = COLORS.FINISH;

trackLength = segments.length * segmentLength;


}

function resetSprites() {
var n, i;

addSprite(20, SPRITES.BILLBOARD07, -1);


addSprite(40, SPRITES.BILLBOARD06, -1);
addSprite(60, SPRITES.BILLBOARD08, -1);
addSprite(80, SPRITES.BILLBOARD09, -1);
addSprite(100, SPRITES.BILLBOARD01, -1);
addSprite(120, SPRITES.BILLBOARD02, -1);
addSprite(140, SPRITES.BILLBOARD03, -1);
addSprite(160, SPRITES.BILLBOARD04, -1);

Downloaded by college kosam


addSprite(180, SPRITES.BILLBOARD05, -1);

addSprite(240, SPRITES.BILLBOARD07, -1.2);


addSprite(240, SPRITES.BILLBOARD06, 1.2);
addSprite(segments.length - 25, SPRITES.BILLBOARD07, -1.2);
addSprite(segments.length - 25, SPRITES.BILLBOARD06, 1.2);

for(n = 10 ; n < 200 ; n += 4 + Math.floor(n/100)) {


addSprite(n, SPRITES.PALM_TREE, 0.5 +
Math.random()*0.5); addSprite(n, SPRITES.PALM_TREE, 1 +
Math.random()*2);
}

for(n = 250 ; n < 1000 ; n += 5) {


addSprite(n, SPRITES.COLUMN, 1.1);
addSprite(n + Util.randomInt(0,5), SPRITES.TREE1, -1 - (Math.random() * 2));
addSprite(n + Util.randomInt(0,5), SPRITES.TREE2, -1 - (Math.random() * 2));
}

for(n = 200 ; n < segments.length ; n += 3) {


addSprite(n, Util.randomChoice(SPRITES.PLANTS), Util.randomChoice([1,-1]) * (2 +
Math.random() * 5));
}

var side, sprite, offset;


for(n = 1000 ; n < (segments.length-50) ; n += 100)
{ side = Util.randomChoice([1, -1]);
addSprite(n + Util.randomInt(0, 50), Util.randomChoice(SPRITES.BILLBOARDS), -side);
for(i = 0 ; i < 20 ; i++) {
sprite =
Util.randomChoice(SPRITES.PLANTS); offset
= side * (1.5 + Math.random());
addSprite(n + Util.randomInt(0, 50), sprite, offset);
}

function resetCars() {

Downloaded by college kosam


cars = [];
var n, car, segment, offset, z, sprite, speed;
for (var n = 0 ; n < totalCars ; n++) {
offset = Math.random() * Util.randomChoice([-0.8, 0.8]);
z = Math.floor(Math.random() * segments.length) *
segmentLength; sprite = Util.randomChoice(SPRITES.CARS);
speed = maxSpeed/4 + Math.random() * maxSpeed/(sprite == SPRITES.SEMI ? 4 : 2);
car = { offset: offset, z: z, sprite: sprite, speed: speed };
segment =
findSegment(car.z);
segment.cars.push(car);
cars.push(car);
}
}

//=========================================================================
// THE GAME LOOP
//=========================================================================

Game.run({
canvas: canvas, render: render, update: update, stats: stats, step: step,
images: ["background", "sprites"],
keys: [
{ keys: [KEY.LEFT, KEY.A], mode: 'down', action: function() { keyLeft = true; } },
{ keys: [KEY.RIGHT, KEY.D], mode: 'down', action: function() { keyRight = true; } },
{ keys: [KEY.UP, KEY.W], mode: 'down', action: function() { keyFaster = true; } },
{ keys: [KEY.DOWN, KEY.S], mode: 'down', action: function() { keySlower = true; } },
{ keys: [KEY.LEFT, KEY.A], mode: 'up', action: function() { keyLeft = false; } },
{ keys: [KEY.RIGHT, KEY.D], mode: 'up', action: function() { keyRight = false; } },
{ keys: [KEY.UP, KEY.W], mode: 'up', action: function() { keyFaster = false; } },
{ keys: [KEY.DOWN, KEY.S], mode: 'up', action: function() { keySlower = false; } }
],
ready: function(images) {
background =
images[0]; sprites
= images[1];
reset();
Dom.storage.fast_lap_time = Dom.storage.fast_lap_time || 180;
updateHud('fast_lap_time', formatTime(Util.toFloat(Dom.storage.fast_lap_time)));
}

Downloaded by college kosam


});

function reset(options) {
options = options ||
{};
canvas.width = width = Util.toInt(options.width, width);
canvas.height = height = Util.toInt(options.height,
height); lanes = Util.toInt(options.lanes, lanes);
roadWidth = Util.toInt(options.roadWidth, roadWidth);
cameraHeight = Util.toInt(options.cameraHeight, cameraHeight);
drawDistance = Util.toInt(options.drawDistance, drawDistance);
fogDensity = Util.toInt(options.fogDensity, fogDensity);
fieldOfView = Util.toInt(options.fieldOfView, fieldOfView);
segmentLength = Util.toInt(options.segmentLength, segmentLength);
rumbleLength = Util.toInt(options.rumbleLength, rumbleLength);
cameraDepth = 1 / Math.tan((fieldOfView/2) * Math.PI/180);
playerZ = (cameraHeight * cameraDepth);
resolution = height/480;
refreshTweakUI();

if ((segments.length==0) || (options.segmentLength) || (options.rumbleLength))


resetRoad(); // only rebuild road when necessary
}

//=========================================================================
// TWEAK UI HANDLERS
//=========================================================================

Dom.on('resolution', 'change', function(ev) {


var w, h, ratio;
switch(ev.target.options[ev.target.selectedIndex].value) {
case 'fine': w = 1280; h = 960; ratio=w/width; break;
case 'high': w = 1024; h = 768; ratio=w/width; break;
case 'medium': w = 640; h = 480; ratio=w/width;
break; case 'low':w = 480; h = 360; ratio=w/width;
break;
}
reset({ width: w, height: h })
Dom.blur(ev);
});

Downloaded by college kosam


Dom.on('lanes', 'change', function(ev) { Dom.blur(ev); reset({
lanes: ev.target.options[ev.target.selectedIndex].value }); });
Dom.on('roadWidth', 'change', function(ev) { Dom.blur(ev); reset({
roadWidth: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')),
Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('cameraHeight', 'change', function(ev) { Dom.blur(ev); reset({
cameraHeight: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')),
Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('drawDistance', 'change', function(ev) { Dom.blur(ev); reset({
drawDistance: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')),
Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('fieldOfView', 'change', function(ev) { Dom.blur(ev); reset({
fieldOfView: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')),
Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('fogDensity', 'change', function(ev) { Dom.blur(ev); reset({
fogDensity: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')),
Util.toInt(ev.target.getAttribute('max'))) }); });

function refreshTweakUI() {
Dom.get('lanes').selectedIndex = lanes-1;
Dom.get('currentRoadWidth').innerHTML = Dom.get('roadWidth').value = roadWidth;
Dom.get('currentCameraHeight').innerHTML = Dom.get('cameraHeight').value =
cameraHeight;
Dom.get('currentDrawDistance').innerHTML = Dom.get('drawDistance').value =
drawDistance;
Dom.get('currentFieldOfView').innerHTML = Dom.get('fieldOfView').value = fieldOfView;
Dom.get('currentFogDensity').innerHTML = Dom.get('fogDensity').value = fogDensity;
}

//=========================================================================

</script>

</body>
</html>

Downloaded by college kosam


Conclusion

Phew! That was a long last lap, but there you have it, our final version has
entered that stage where it can legitimately be called a game. It’s still very
far from a finished game, but it’s a game nonetheless.

It’s quite astounding what it takes to actually finish a game, even a simple
one. And this is not a project that I plan on polishing into a finished state. It
should really just be considered how to get started with a pseudo-3d racing
game.

The code is available on github for anyone who wants to try to turn this into a
more polished racing game. You might want to consider:

 car sound fx
 better synchronized music
 full screen mode
 HUD fx (flash on fastest lap, confetti, color coded speedometer, etc)
 more accurate sprite collision
 better car AI (steering, braking etc)
 an actual crash when colliding at high speed
 more bounce when car is off road
 screen shake when off-road or collision
 throw up dirt particles when off road
 more dynamic camera (lower at faster speed, swoop over hills etc)

Downloaded by college kosam


 automatic resolution & drawDistance detection
 more realistic projection based curves with x,y rotation
 fix the sub-pixel aliasing artifacts on curves
 smarter fog to cover sprites (blue against sky, cover sprites)
 multiple stages
 different maps
 a lap map, with current position indicator
 road splits and joins
 day/night cycle
 weather effects
 tunnels, bridges, clouds, walls, buildings
 city, desert, ocean
 add city of seattle and space needle to background layers
 ‘bad guys’ - add some competitor drivers to race against
 game modes - fastest lap, 1-on-1 racing, collect coins ? shoot bad guys ?
 a whole lot of gameplay tuning
 etc
 etc
 …

So there you have it. Another ‘weekend’ project that took a lot longer than
expected but, I think, turned out pretty well in the end. These written up
articles maybe had a little too much low level detail, but hopefully they still
made some sense.

Comments and feedback are always welcome

(below) Enjoy!

Downloaded by college kosam


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

Downloaded by college kosam

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy