In the previous chapters, you have learned the basic tenants of programming and applied them to the JavaScript language. As your programs have become more powerful, you have spent more effort making your programs function across browsers. It would be nice to have some kind of trick that would eliminate the browser dependency issues and let you concentrate on writing functional programs. Such a technology exists, and you will learn about it in this chapter. Specifically, you will learn:
***PD: begin unnumbered list
What an API library is
How to import external JavaScript Libraries
What a sprite is and how to make one
The main properties and methods of sprite objects
Moving sprites
Dealing with frame animation in sprites
Reacting to collisions between sprites
Determining elapsed time for scorekeeping
***PD: end unnumbered list
To illustrate all these points, you will build a program that simulates a top-down view of a racing car. The user will be able to control the car through on-screen controls, and will have to drive the car as quickly as possible through a series of tracks.
***PD: Please insert Figure jg09Fig01.pcx CarStart.html
Figure 01
The instruction form contains a button to launch the game
***PD: Please insert Figure jg09Fig02.pcx Car Oval Track
Figure 02
You control the car with the little dashboard and the mouse
***PD: Please insert Figure jg09Fig03.pcx Car Curve Track
Crash
Figure 03
Avoid the black obstacles. You stop if you hit them.
***PD: Please insert Figure jg09Fig04.pcx Car Eight Track
Figure 04
The tracks get more difficult as you advance in the game.
The game looks interesting enough, but there are some other features that make it very nice from a technical point of view. The game is designed to be very easy to customize. It is reasonably simple to build new tracks. As long as you understand the principles, you can easily customize the tracks however you wish. In fact, you could even add a track editor and let the user design new tracks. (That will
be one of the exercises at the end of the chapter!) The racing game is reasonably browser independent, and it does not appear to have a single line of browser-checking code, yet it uses positionable elements and dynamic images.
Game developers have been using graphic objects for a long time. They invented a term for a special kind of graphic used in game programming, called the sprite. In theoretical terms, a sprite is an object that can be used in a game environment. It can be moved to a specific position. It should be able to change its image on demand. It should be able to recognize collisions with other sprites, and it would be very nice if it had some other attributes like speed and direction. JavaScript does not directly provide the sprite element, but all these characteristics of the sprite object could theoretically done through the techniques you learned in the last chapter. It would be great if there was some kind of object you could just magically invoke that would act like a sprite. It would be even better if you could just tell the sprite object to move or change its graphic and you wouldn't have to worry about what kind of CSSP or layer manipulation was happening behind the scene.
Fortunately, a number of such elements have been invented.
Several authors have been working on special libraries that simplify the tedium of programming with positionable elements in JavaScript. Using these libraries can spare you the challenges of writing cross platform dynamic HTML and
let you focus on the interesting things, such as writing your games and other programs.
***PD: begin note
A code library such as this one is sometimes called an API, which stands for Application
Programming Interface.
***PD: end note
In the rest of this book, I will demonstrate one such program which is optimized for game programming. The library is called gameLib and it is written by Scott
Porter. This excellent set of programs make JavaScript game development much easier than it was just a couple of years ago.
***PD: begin In Real World
The ability to use APIs is one of the most important skills in modern programming. It is extremely common to have problems that extend beyond the original scope of a language. It's very common for programmers working on all kinds of projects. to find or build APIs to fulfill those needs. There are APIs for graphics, adding special input/output devices, working with databases, and many other kinds of applications.
***PD: end In Real World
***PD: begin On CD
The latest version of the gamelib library and its accompanying documentation are provided on the CD-ROM that accompanies this book. Please see that documentation for instructions on installing the library.
***PD: begin On CD
You can investigate the gamelib API by examining a very simple program that uses the library to draw a sphere on the screen. At first the API will not seem to provide much benefit, but you will see how powerful sprite objects can be.
***PD: Please insert Figure jg09Fig05.pcx sprite
Figure 05
The page looks unremarkable from the user's point of view
While this page could be produced using standard HTML, it was not. There are a number of interesting things happening in this program.
The programs in this chapter will all use modules from the
GameLib library. Rather than copying in all the code of the library, you can use a variation of the script tag to export text files with JavaScript functions. Here is the code that performs that function in the sprite program:
***PD: Begin Code
<html>
<head>
<title>Sprite</title>
<!-- import the basic libraries -->
<script language="Javascript" src="gamelib/gamelib_core.js">
</script>
<script language="Javascript" src="gamelib/gamelib_sprites.js">
</script>
***PD: End Code
The script tag has a src parameter which can be assigned a
URL. In this case, I am loading up two modules. The first one is called gamelib_core.js This module contains a bunch of utility functions and methods that are used by the other modules. As a programmer, you will not need all these features directly, but you may want to look at the documentation to see which ones might be useful. Every program that uses the gamelib library must import the gamelib_core module.
I also imported the gamelib_sprites module. This is also a text file that contains JavaScript code for manipulating sprite objects. The definition of the sprite object is contained in this code, as well as all the code that will enable the sprite objects to operate in a platformappropriate manner. Importing the library in this way has the same net effect as copying the entire JavaScript file
and pasting it into your program, but you don't have to look at all the details of the code.
The sprite object defined by the gamelib API is a very powerful object. It uses JavaScript's variation of object oriented programming, which means that the sprite object has properties and methods. Properties are characteristics which describe the object, such as x, y, height, and width.
In the gamelib API, most properties are read-only, which means you use them to get information about the sprite object. To change the sprite's behavior or appearance, you will use methods instead. A method is a special function that is attached to a particular type of object. Here is a list of the sprite object's primary methods and properties.
***PD: begin note
Don't worry if you don't understand these tables on your first glance. You'll get to see most of these properties and methods in action as the chapter progresses. I just wanted you to get a bird's eye view of the sprite object's properties and methods before you delve into using them.
***PD: end note
Should we include a more complete list as an appendix or tear-out or something? It's on the documentation that comes with the API but it's in a very basic format. I don't
want to put everything here, because it will be overwhelming. -Andy H.
Key Properties of the sprite object in gamelib
Property on (read only)
Description Example
Describes whether sprite is visible if (mySprite.on){
//do something
} x, y, height, width (read only)
Describe the current size and position of the sprite if (mySprite.x <
0){ mySprite.x = 0;
} bounces (read write) xdir, ydir (read only)
Determines behavior when sprite reaches limits if true, sprite will bounce off border mySprite.bounces = true; speed of sprite in x and y directions if (mySprite.xdir
== 0){
alert ("not going sideways!"); xydegs (read only) Shows direction sprite is moving
} if
(mySprite.xydegs
in degrees == 0){
alert ("going north!");
} // end if speed (read only) Shows speed of sprite collides (read write)
Determines if the sprite registers collisions alert
(mySprite.speed); mySprite.collides
= true;
Principle Methods of the Sprite object in gamelib
Method hasHit(object)
Description Example
Determines if one sprite has collided with another if
(car.hasHit(barrie r)){
alert("crash!");
} moveTo(x,y) setDir(x,y) moves the sprite directly to the given x,y coordinates
Determines how many pixels in x and y dimensions mySprite.moveTo(10
0, 50); mySprite.setDir(1,
3);
setXYdegs(direc) setSpeed(speed) setImage(image, width, height, frames, anims) setFrame(number) switchOn(), switchOff() setXlimits(min, max), setYlimits(min, max) sprite will move on each interval
//moves sprite 1 to left, 3 down
Sets direction of sprite in degrees mySprite.setXYdegs
(45);
//moves sprite northeast
Determines how many pixels sprite will move on each interval mySprite.setSpeed(
3);
Determines the image of a sprite.
See below for details mySprite.setImage(
"car.gif", 20, 20,
1, 1);
Sets a particular frame of the image mySprite.setFrame(
2); turns the sprite on or off, controlling its visibility mySprite.switchOn(
); determines where the sprite can be placed or moved on the screen. mySprite.setXlimit
s(0,300);
//sprite's x will stay between zero and 300
From a quick look at these tables, you can see that the sprite object is a very powerful thing. In general, you can use the sprite's methods to control the sprite, and its properties to inquire about the sprite. It looks a little bit intimidating, but managing sprites is not very difficult at all.
Here is the code fragment that starts up the sprite for the ball program:
***PD: Begin Code
<script> var ball; function init(){
ball = new Sp_Sprite();
ball.setImage("redball.gif", 20, 20, 1, 1);
ball.moveTo(100,100);
ball.setXlimits(0, 500);
ball.setYlimits(0, 300);
ball.setFrame(0);
ball.switchOn();
} // end init
</script>
***PD: End Code
The code starts by creating a variable (ball) which will contain the sprite. The variable is declared outside any functions, so it can be used anywhere. I have also defined an init() function, which will be called when the body loads. This is normal for any type of initialization, and you will usually need something like this when you work with sprites.
Inside init(), I created a new sprite with this line: ball = new Sp_Sprite();
Notice the new keyword. You have used it before when creating instances of objects. This line generates a new
Sp_Sprite object called ball.
***PD: begin note
It's technically an Sp_Sprite because that's the name of the class that Porter built. He decided to begin the name of everything defined in his sprite module with Sp_ so it would be easier to sort out when debugging the API package. You'll really just use the instances anyway (like ball in this example) so it doesn't really matter.
***PD: end note
A sprite is not very interesting unless it is displaying some sort of image, so you'll almost always want to attach an image to a sprite. When you set up a sprite's image, you
will need to specify a number of parameters about the object. For the ball example, here was the setImage line: ball.setImage("redball.gif", 20, 20, 1, 1);
Since ball is an instance of the sprite class, you can invoke all of its methods. I set its image to
"redball.gif". You can assign any image to a sprite, with an absolute or local URL. The next two numbers (20, 20) indicate the size of the sprite. This does not have to be the same as the size of the starting image. The next two parameters (1, 1) refer to the frame and animation levels of the image. I'll discuss frame animation later in this chapter and in the next chapter, but for now, I'll leave these values at one to make a simple image with one frame and one animation.
This line: ball.moveTo(100,100); moves the ball to the indicated position. When the sprite is displayed, this is the place it will appear on the screen. (remember that (0, 0) is in the upper left hand corner). The coordinates indicate the location of the top left corner of the sprite.
The next two lines determine boundaries on the screen.
Whenever you create a sprite, you should set its limits using these two methods.
ball.setXlimits(0, 500);
ball.setYlimits(0, 300);
I decided that my playing field would go from (0, 0) to
(500, 300). This is a reasonable size for a game, as it will fit inside most commonly used screen resolutions. Of course the ball isn't moving yet, so boundaries would seem to be a silly thing, but I will be modifying this program soon to move the ball.
***PD: begin caution
Even if you have sprites that will not move at all, be sure to set limits for them. The default values for the x and y limits seem to be very small, and your sprite might not appear in the right place (or at all) on the screen.
Be sure that you have limits large enough to show the sprite. Also be careful that the sprite is placed far enough from these limits so that it is still visible. If you have created sprites that are not appearing in the correct place, check to see that you have set the limits correctly.
***PD: end caution
The last two lines of the init function do some more housekeeping. ball.setFrame(0); is used to specify the original frame be used (more on frames in the next section. For now, just accept that it is necessary to set the frame to zero.)
ball.switchOn(); turns the ball on and makes it visible to the user.
All of the sprite stuff seems like a lot of work, but it really pays off when you start to have sprites do interesting things, like move around on the screen and crash into things.
By making a few modifications to the sprite program, you can make a ball that bounces around on the screen.
***PD: Please insert Figure jg09Fig06.pcx MoveSprite
Figure 06
When the user clicks on a button, the ball moves in that direction.
Although you can't see it from the screen shot, the ball automatically bounces off any of the limits, and reverses direction appropriately. The most exciting thing about this is that most of the new code is HTML for the buttons. The code to handle the motion of the ball is largely taken care of in the sprite object itself.
I added two modifications to the init method. Here they are:
ball.bounces = true;
Gl_start();
The ball.bounces line tells the ball sprite that it should use 'bouncy' behavior. This means that when it hits a wall, its direction should be reversed. Note that this is one of the unusual cases where you control a sprite by a property.
The Gl_start() line is a subtle but powerful command. It starts up the actual gamelib engine. This in turn starts up a special timer that repeats 20 times per second. This timer will activate all the motion in your program.
***PD: begin tip
If you want to have this type of behavior in your code without using the gamelib library, you should investigate the setTimeOut() function supplied by both browsers. It is the underlying technology being used by the gamelib
API to manage timed behavior.
***PD: end tip
The HTML is reasonably straightforward. It is a form with a set of command buttons on it. I used a table for neatness, and placed that table in a span aligned to the right so it would stay out of the way. Here's the HTML for one row of the table.
***PD: Begin Code
<tr>
<td><input type = "button"
value = "W "
onClick = "moveBall(270)">
</td>
<td><input type = "button"
value = " "
onClick = "moveBall(999)">
</td>
<td><input type = "button"
value = "E "
onClick = "moveBall(90)">
</td>
</tr>
***PD: End Code
All the buttons call the moveBall function, but they pass a different parameter. You might recognize that West is 270 degrees on a standard compass, and East is 90 degrees. The center button will cause the ball to stop, so I encoded the special value 999, which usually would not be considered a valid direction.
The moveBall function manipulates the ball sprite to make the animation occur. Here is the code for that function:
***PD: Begin Code function moveBall(direc){
if (direc == 999){
ball.setSpeed(0);
} else {
ball.setSpeed(10);
ball.setXYdegs(direc);
} // end if
} // end moveBall
***PD: End Code
The direction value is stored in the parameter direc. The first thing this function does is check to see if the center button was pressed. If so, the ball's speed is set to zero, effectively stopping the ball. If the direc value is anything but 999, the ball's Xydegs value is set to that that direction, and the ball's speed is set to 10. The next time the timer loop occurs (remember, this happens 20 times per second) the ball will be moved approximately 10 pixels in the indicated direction.
***PD: begin note
The ability to work in a direction and speed is one of the nicest features of the sprite object. In game development, the direction and speed of a sprite are probably what you are most concerned about, but all the document object model gives you is the ability to set an element to a specified X,Y coordinate. Without the sprite object, you would need to do some trigonometric conversions. The sprite object handles all the math necessary to convert a speed and direction into a pair of coordinates.
This is pretty handy if your trig skills are a bit out of date.
***PD: end note
Once the sprite is set up and the gamelib is started, movement is automatic. The ball pretty much moves on its own, bounces when it hits a boundary, and responds to button presses from the user.
***PD: begin In Real World
Sprites are clearly useful in game programming, but they can also be handy in any application where you need some kind of flashy movement or image-swapping. If, for example, you're building a page for a courier company, you can incorporate vans and aircraft that move across the screen as part of the page's navigation scheme.
***PD: end In Real World
If you wish to animate the movement of a ball, you can get away with only one image, but if the image you are working with is a car or other vehicle, you have to change the graphic when the direction changes, or the car will look silly. A vehicle usually goes in the direction of its nose, so it would be great to have some way to point the car in the current direction
Here is a program that faces exactly the dilemma described above. The sprite is a car. The car's nose is always pointing in the direction the car is travelling.
***PD: Please insert Figure jg09Fig07.pcx MoveCar SE
Figure 07
The car is moving to the southeast and pointed in that direction
Since the car is a sprite object, it has special methods that make it easy to choose one of a set of images.
The gamelib library uses a special technique called graphic clipping. The best way to describe how this works is to look at the actual image file that is applied to the car sprite.
***PD: Please insert Figure jg09Fig08.pcx car.gif in Gimp
Figure 08
The car image is actually a set of eight images in one file!
The image file was specially prepared for use as a sprite image. I designed the car facing north as a 30 by 30 pixel image. I then made a copy, which I rotated by 45 degrees. I made a larger image that was 40 pixels tall by 320 pixels tall. I carefully placed the first (north) car image so it was centered at (20, 20) in the long skinny image. I then took the next (northeast) car image, and centered it at
(40, 20) of the longer image. I repeated the process until
I had all eight cardinal directions represented in the long image. I saved this image as car.gif. It is important that the smaller images are all the same distance apart, but the exact number of images does not matter.
For this program, I decided to start with a set of variables to make things a little easier.
***PD: Begin Code var car; var NORTH = 0; var NORTHEAST = 1; var EAST = 2; var SOUTHEAST = 3; var SOUTH = 4; var SOUTHWEST = 5; var WEST = 6; var NORTHWEST = 7; var direction = EAST;
***PD: End Code
The car variable will hold the sprite. The capitalized variables will be used for convenience, to hold the various directions. The directions correlate to the order of the car images in the graphic. The variable called direction will be used to hold the current direction the car sprite is facing.
You only need to change one line of code in the initialization to deal with a sprite that uses frame animation.
***PD: Begin Code function init(){
car = new Sp_Sprite();
car.setImage("car.gif", 30, 30, 8, 1);
car.moveTo(100,100);
car.setXlimits(0, 500);
car.setYlimits(0, 300);
car.setFrame(0);
car.switchOn();
car.setSpeed(0);
Gl_start();
} // end init
***PD: End Code
The only line that changes is the setImage() method of the sprite. In this case, I specified that the sprite's image should be set to "sprite.gif". Recall that this image actually is 40 by 320 pixels, and it actually contains eight different car images. The next two parameters (30,
30) indicate the size at which the sprite should be drawn on the screen. The sprite will be drawn as a 30 by 30 square. The next parameter determines how many frames are in the image. This number is set to 8, which indicates that the original image should be split into eight even pieces.
In effect, the sprite will only show one eighth of the original image at any one time, and it will show that piece
of the image as a 30 by 30 square. The last parameter (1) will be used in the next chapter to add another element of animation. For now, leave it to one.
***PD: begin note
I also turned off the bouncing feature, because
I didn't think it made sense for the car to bounce off the wall like a ball would.
***PD: end note
***PD: begin sidebar
Why not simply have eight different images
It may seem crazy to force all the small images into one long image, but it actually makes sense. First of all, since the images are all related, it's much easier to keep track of one image that eight. The most important reason is efficiency. Every time the browser has to load up an image, there is a lot of overhead. The browser contacts the server, sends the image over the Internet, and closes the connection.
Eight small images take much longer to download than one larger one. Also, all the image clipping will happen on an image that exists in the computer's memory. This is much faster than swapping between multiple image files. Most browsers have a limited image cache, and can keep very few images in memory. This means that if you have a lot of image swapping going on, you could have even more network accesses
slowing down the system. With the single image technique, the image is entirely in memory.
***PD: end sidebar
The rest of the moveCar program is also similar to the ball sprite, with a few significant differences. Here's the new version of the button code. (Again, I'm only showing you the middle row, because you can easily see what's going on from this HTML)
***PD: Begin Code
<tr>
<td><input type = "button"
value = "W "
onClick = "moveCar(WEST)">
</td>
<td><input type = "button"
value = " "
onClick = "moveCar(999)">
</td>
<td><input type = "button"
value = "E "
onClick = "moveCar(EAST)">
</td>
</tr>
***PD: End Code
This time, rather than specifying the new angle in degrees,
I decided to specify the direction through the special constants I had created. The center button will still have the 999 value, because this button will still be used to stop the car.
The moveCar function manages the car's direction and changes the frame so the car image is pointing in the right direction.
***PD: Begin Code function moveCar(direc){
if (direc == 999){
car.setSpeed(0);
} else {
car.setSpeed(3);
car.setXYdegs(direc * 45);
car.setFrame(direc);
} // end if
} // end moveCar
***PD: End Code
The setXYdegs () method changes the sprite's direction based on the value of direc. I noticed that all the directions were multiples of 45 degrees, so I could use some simple math to correlate the 0-7 direction values to compass points. I also used the setFrame() method of the car sprite to set the frame to the number associated with the car's direction. So, if the User hit the west button, the value of direc would be WEST, which evaluates to 6. The car's
direction would be set to 6 * 45, or 270 degrees. The sprite would load with the 6 th frame, which shows the car pointing to the west.
If you have sprites moving around on the screen, you will eventually have collisions between them. Collision detection is an important element of game programming, because many of the events in a game are triggered by sprites bumping into each other. The sprite class provided by the gamelib API can detect collisions in a number of ways.
To practice collision detection, your car sprite will need something to crash into. Here is a variation of the moveCar program, called collision.
***PD: Please insert Figure jg09Fig09.pcx collision, car moving
Figure 09
The car sprite works just like it did in the last program.
***PD: Please insert Figure jg09Fig10.pcx collision, car hit barrier
Figure 10
When the car hits the black box, it stops.
I introduced another sprite, which I called barrier. This sprite is simply a big black box.
This program has two sprites. One is the car (almost identical to the car in the previous program) and the other one is the barrier. Here is the code that sets up the sprites:
***PD: Begin Code function init(){
car = new Sp_Sprite();
car.setImage("car.gif", 30, 30, 8, 1);
car.moveTo(100,100);
car.setXlimits(0, 500);
car.setYlimits(0, 300);
car.setFrame(0);
car.switchOn();
car.setSpeed(0);
car.collides = true;
barrier = new Sp_Sprite();
barrier.setImage("black.gif", 100, 100, 1, 1);
barrier.setXlimits(0, 500);
barrier.setYlimits(0, 300);
barrier.moveTo(200, 200);
barrier.setFrame(0);
barrier.switchOn();
barrier.collides = true;
Gl_hook("checkCollision()");
Gl_start();
} // end init
***PD: End Code
Notice the use of the collides property in both sprites. I set this property to true in both the sprites, so they would register collisions.
***PD: begin tip
The image for the barrier sprite is simply a 10 by 10 square of black. I made a whole series of images of solid colors that are perfect for situations like this. Since the image is a solid color, it will size up very gracefully, even though it has a very small file size and will transfer quickly across the net. These little solid color images are great for normal
HTML and game programming purposes, because they can be used to make a rectangle of any color very easily. I also have an image that is simply a small transparent gif image. This is also useful, when you want a spacer, or if you want a sprite that responds to collisions, but is not visible (such as a ghost, or an invisible wall.)
***PD: end tip
If you look closely you'll notice that there is one more new line in the init() method. That line looks like this:
Gl_hook("checkCollision()");
Remember that when you call Gl_start(), you start a timer that occurs 20 times per second. This timer is used to manage sprite movement. The hook method allows you to specify a function to run every time the timer loop occurs.
In this specific case, I am telling the library to run the checkCollision() function (which you will see shortly) every time the timer occurs, which will be 20 times / second. Hooking functions to the timer in this way is the main way to add logic to your program in gamelib.
***PD: begin note
If you have looked carefully at the gamelib documentation, you may have noticed that there is a way to do collision detection without hooking a function to the timer. The approach I am showing you here is not always the best way to do it, but the ability to hook functions up is so important I wanted you to see it right away. Of course you can always decide for yourself the best approach to solving your programs.
***PD: end note
Of course, once you go through the trouble of hooking up a function, you have an obligation to write the function.
Fortunately, the checkCollision function is pretty simple:
***PD: Begin Code
function checkCollision(){
if (car.hasHit(barrier)){
car.setSpeed(0);
Gl_unhook("checkCollision()");
} // end if
} // end checkCollision
***PD: End Code
The hasHit() method of a sprite determines if a car has hit whatever sprite is specified. This method always returns back a boolean value. If the car has hit the barrier in this iteration of the function, the value is returned as true. Otherwise, it returns false.
When the car hits the barrier, we'll want to stop it. This is done by setting its speed to zero with the setSpeed method.
Remember that this function is called very often. With the speed set to zero, the car will continue to be hitting the barrier until it has changed direction and the speed has been changed to some other value. I found it best to temporarily unhook the checkCollision function so that the car sprite could be moved. The Gl_unhook function is used to stop calling the checkCollision function each time the timer ticks.
The moveCar function in this program has been modified slightly, to re-hook the checkCollision function whenever the car is pointed a new direction.
***PD: Begin Code
function moveCar(direc){
if (direc == 999){
car.setSpeed(0);
} else {
Gl_hook("checkCollision()");
car.setSpeed(3);
car.setXYdegs(direc * 45);
car.setFrame(direc);
} // end if
} // end moveCar
***PD: End Code
Of course, this is just a starting point. You could generate multiple barriers and do more complex collision detection.
Sprites and collision detection techniques provide almost all of the functionality needed for the racing program except scorekeeping. It would be nice to have some way to keep track of how long the race lasts. JavaScript provides a date object which helps in this regard.
Web browsers have ways of querying the system clock of the client machine to figure out the time. JavaScript supplies this information to the programmer via a special object called the Date object. Here is a program that demonstrates the date object in action.
***PD: Please insert Figure jg09Fig11.pcx Timer after a few button clicks
Figure 11
Each time the button is pressed, the elapsed time since the page was loaded is added to the text area.
The program evaluates the current date, and it then evaluates the precise time each time the button is pressed.
It does some mathematical manipulations of these values to determine the elapsed time.
The key to this program is the timer object built into
JavaScript.
Here's the code for the program:
***PD: Begin Code var startTime = new Date(); var startInt = startTime.getTime(); var elapsedTime = new Date(); var currentTime = new Date(); function getTime(){
var output = "";
currentTime = new Date();
var currentInt = currentTime.getTime();
var elapsedInt = currentInt - startInt;
elapsedTime = new Date(elapsedInt);
output += elapsedTime.getMinutes() + ":";
output += elapsedTime.getSeconds() + ".";
output += elapsedTime.getMilliseconds() + "\n";
document.myForm.txtOutput.value += output;
} // end getTime
***PD: End Code
You invoke the date object with new Date() This creates a new Date object based on the current time. The date object has a number of interesting methods and properties.
Key properties and methods of the Date object member new Date() example var myDate = new
Date(); new Date(integer) Creates a new date object based on the integer passed var myDate = new
Date(intValue); getTime() Returns back the number of milliseconds since midnight Jan 01
1970 as an integer var myInt = myDate.getTime; getDate() getMonth() description
Creates a date object based on the current date and time
Returns the day of the month alert(myDate.getDa
te());
Returns the month alert ("today is the " + myDate.getDate() +
getYear() getHours() getMinutes()
Returns the year that corresponds with this Date object
Returns the hour of the day
Returns the minutes
" of " + myDate.getMonth())
; alert(myDate.getYe
ar()); alert(myDate.getHo
urs()); alert(myDate.getMi
nutes()); getSeconds() Returns the seconds alert(myDate.getMi
nutes()); toString() Returns the entire date as a string value alert(myDate.toStr
ing());
The date object is usually used simply to get the current date from the system. Here's a simple script embedded in the HTML that uses the date object in a more typical way:
<script> document.write(new Date());
</script>
This code is embedded directly into the HTML, and it generates a date object immediately. The value of that date object is translated to the web page as a long string.
***PD: begin In Real World
The ability to deal with dates and times is very important in many aspects of programming. You might use similar techniques to determine how long it has been since the user last visited your site, or you might want to be able to calculate a person's age in years, months, and days. Of course, it's nice to just be able to display the current date and time in whatever format you wish.
***PD: end In Real World
For the racing game, you will not need the date or time of day, but the elapsed time between the start and end of a race. This is not difficult to do, but it involves understanding how the Date object really works. The date is actually stored as a very long integer that counts how many milliseconds (1000 th of a second) have occurred since midnight Dec 31 st , 1970 GMT. The date object is basically just a series of methods that extract more useful information (like what month, day, or hour it is) from that big integer. The getTime() method returns back the actual number that relates to the time. This wouldn't seem very useful, except you can take the integer value of two times, and subtract them. The result will give you an integer that can be translated back to a date value. You can count the
minutes, seconds, and milliseconds of this new date object to get an elapsed time.
Here's how I really did it.
Outside the method, I created startTime and elapsedTime as new Date objects.
The getTime() function is called each time the user wants to find a new elapsed time. The first thing the function does is re-initialize elapsedTime as a new Date object, so it will have a different value than startTime (which correlates to when the page was loaded).
These two lines
var startInt = startTime.getTime();
var currentInt = currentTime.getTime(); get integer values related to the two date objects. This line: var elapsedInt = currentInt - startInt; creates a new integer by subtracting the millisecond value of startInt from that of currentInt.
The next line elapsedTime = new Date(elapsedInt); creates a new date based on the difference.
Finally, I use a set of date methods to get other useful values from the date object:
output += elapsedTime.getMinutes() + ":";
output += elapsedTime.getSeconds() + ".";
output += elapsedTime.getMilliseconds() + "\n";
document.myForm.txtOutput.value += output;
The racer program is primarily a combination of the techniques described throughout this chapter. It is heavily reliant on sprite technology. The program actually features
11 different sprites, and you can easily customize it to use more, if you want more complex tracks. The basic organization of the program relies on a sprite for the car, an array of sprites for barriers, and another array of sprites for time sensors. The barriers are placed on the track to make the different track designs, and the sensors are placed around the track. A lap is considered complete only when all five timeSensors have been triggered by the car.
The initial page for the racing program uses a now familiar strategy. This page contains the help screen and a button that calls up the actual page.
The HTML for the page is very simple, because most of the work happens with sprites. The body calls the init function, and all the user interaction is done with an image map:
***PD: Begin Code
<body bgColor = "gray"
onload = "init()">
<IMG SRC="track.gif" WIDTH=600 HEIGHT=600 BORDER=0 USEMAP="#map">
<MAP NAME="map">
<!-- #$-:Image Map file created by GIMP Imagemap Plugin -->
<!-- #$-:GIMP Imagemap Plugin by Maurits Rijk -->
<!-- #$-:Please do not edit lines starting with "#$" -->
<!-- #$VERSION:1.3 -->
<!-- #$AUTHOR:Unknown User -->
<AREA SHAPE="RECT" COORDS="351,454,371,489" HREF="javascript:accell()">
<AREA SHAPE="RECT" COORDS="227,455,247,489" HREF="javascript:decell()">
<AREA SHAPE="POLY"
COORDS="303,395,325,403,342,424,344,448,335,468,317,482,307,485,301,485"
HREF="javaScript:turnRight()">
<AREA SHAPE="POLY"
COORDS="299,396,280,400,261,418,255,440,259,461,270,475,288,485,299,486"
HREF="javascript:turnLeft()">
</MAP>
</body>
***PD: End Code
An image map is one way to get input from the user without having to use form controls. I wanted the user to interact with a steering wheel and control pedals, so I drew them in an image, and created an HTML image map. If you are working with simple regions (like rectangles), you can build image
maps by hand but for more complex images you may want to use an editor.
***PD: begin On CD
The Gimp, a powerful free image editor, is included on the CD-ROM that accompanies this book. I used the image map tool that comes with the Gimp to generate the image map automatically for this program. The image map tool is available by right-clicking on an image, then choosing filters-web-image map.
***PD: end On CD
***PD: Please insert Figure jg09Fig12.pcx track.gif in Gimp
Image Map Editor
Figure 12
Defining an image map in the Gimp
Note that instead of specifying a normal URL, I called the turnLeft function by setting JavaScript:turnLeft() as the
URL for this region.
The image map editor creates a text file you can copy and paste into your page.
The car program is reasonably complex, and it has a number of variables. It is also not surprising that it uses the gamelib core and sprite libraries. Here is the code that sets all this up:
***PD: Begin Code
<!-- import the basic libraries -->
<script language="Javascript" src="gamelib/gamelib_core.js">
</script>
<script language="Javascript" src="gamelib/gamelib_sprites.js">
</script>
<script> var car; var barrier = new Array(); var numSensors = 5; var timeSensor = new Array(numSensors); var lapStatus = new Array(numSensors); var NORTH = 0; var NORTHEAST = 1; var EAST = 2; var SOUTHEAST = 3; var SOUTH = 4; var SOUTHWEST = 5; var WEST = 6; var NORTHWEST = 7; var direction = EAST; var speed = 3;
var startTime = new Date(); var trackNum = 1; var lap = 0;
***PD: End Code
The car is a sprite, just like the one used earlier in this chapter. Additionally, the program sets up an array of barriers (which will be sprites) and an array of timesensors (which will also be sprites). Another array is used to keep track of the car's position on the screen.
These arrays make the game flexible, because the barriers and timesensors can be placed anywhere on the screen and the program will be able to tell if the computer has completed a lap. A set of variables is used to manage the directions, just like the earlier program in the chapter.
The direction is initialized to EAST, starting speed is set to 3, and a new startTime is established (useful for determining elapsed time later). Finally the track number is set to 1 (start with the easiest track) and the lap is set to zero.
This function is called when the page's body loads up. It is chiefly concerned with initializing sprites.
***PD: Begin Code function init(){
car = new Sp_Sprite();
car.setImage("car.gif", 40, 40, 8, 1);
car.moveTo(30,40);
car.setXlimits(20, 600);
car.setYlimits(30, 400);
car.setFrame(EAST);
car.setZ(99);
car.switchOn();
car.setSpeed(3);
car.setXYdegs(EAST * 45);
car.collides = true;
//set up barriers
var barCounter = 0;
for(barCounter = 0; barCounter < 5; barCounter++){
barrier[barCounter] = new Sp_Sprite();
barrier[barCounter].setXlimits(20, 600);
barrier[barCounter].setYlimits(30, 400);
barrier[barCounter].setImage("black.gif", 20,20,1,1);
barrier[barCounter].moveTo(10,10);
barrier[barCounter].setFrame(0);
barrier[barCounter].switchOn();
barrier[barCounter].collides = true;
} // end for loop
//set up time sensors
for (tsCount = 0; tsCount < numSensors; tsCount++){
timeSensor[tsCount] = new Sp_Sprite();
timeSensor[tsCount].setImage("white.gif", 10, 10, 1, 1)
timeSensor[tsCount].setXlimits(20, 600);
timeSensor[tsCount].setYlimits(30, 400);
timeSensor[tsCount].moveTo(10,10);
timeSensor[tsCount].setFrame(0);
timeSensor[tsCount].switchOn();
timeSensor[tsCount].collides = true;
} // end for loop
Gl_hook("checkCollision()");
Gl_start();
setUpTrack();
} // end init
***PD: End Code
The program sets up the car sprite. This setup should be very familiar now. The only new technique is the setZ method. Notice that I set the z value of the car to 99.
This tells the system that the car should be displayed as if it were on top of everything else. All sprites with lower Z values will appear to be underneath the car if they happen to be drawn in the same place.
There are five barriers in an array. Each one is initially set up to be exactly the same, although they will be moved later. For convenience, all the barrier creation was done in a for loop.
I also have an array of five timesensors. These are simply sprites that I intend the player to drive over. I'll place them all over the track later so that the driver can't cheat and drive over the start line, turn around and drive
over the start line again to finish. The time sensors are also initialized in a for loop for convenience.
I hooked the checkCollision() function to the timer loop so it will be called frequently, and started up the gamelib engine with Gl_start()
The last line of init() is a call to the setupTrack() function, which is described next.
This function examines the value of the trackNum variable and sets up the track in one of three configurations. To add or modify tracks, you can simply change this function.
***PD: Begin Code function setUpTrack(){
//preset for a specific track
switch(trackNum){
case 1:
// oval
barrier[0].resize(400, 200);
barrier[0].moveTo(100, 110);
barrier[1].resize(400, 200);
barrier[1].moveTo(100, 110);
barrier[2].resize(400, 200);
barrier[2].moveTo(100, 110);
barrier[3].resize(400, 200);
barrier[3].moveTo(100, 110);
barrier[4].resize(400, 200);
barrier[4].moveTo(100, 110);
timeSensor[0].resize(10, 80);
timeSensor[0].moveTo(100, 30);
timeSensor[1].resize(10, 80);
timeSensor[1].moveTo(300, 30);
timeSensor[2].resize(90, 10);
timeSensor[2].moveTo(500, 200);
timeSensor[3].resize(10, 80);
timeSensor[3].moveTo(300,310);
timeSensor[4].resize(70, 10);
timeSensor[4].moveTo(30, 200);
break;
. . . //other track definitions
} // end switch
resetStatus();
alert("ready?");
lap = 0;
startTime = new Date();
} // end setUpTrack
***PD: End Code
***PD: begin On CD
To save space, I only showed the portion of this code that creates the first track. See the code on the CD-ROM for the other track descriptions
***PD: end On CD
New tracks are created by changing the size and location of the barrier and timeSensor sprites. The first track is very simple, so I put all the barriers in the same place. Notice how the track sensors are placed around the track to ensure that the player has to complete the entire circuit to be credited for a lap. After the track is set up, the resetStatus() function cleans up the lap counter (see the description of that function for details). An alert statement gives the player an opportunity to start the program when he or she is ready. Finally, the startTime is reset to the current time.
This function checks for collisions with the barriers and the timesensors. If a barrier collision occurs, the car is stopped. The timesensor code checks to see that all sensors have been hit by a call to the lapFinished() function. If the lap is finished and the sensor that was hit is sensor zero (the start-finish line), the function calls the showTime() and resetStatus() functions to display the time and reset the sensor counter.
***PD: Begin Code
function checkCollision(){
var newDir;
//check for crashes
for (barCounter = 0; barCounter < 5; barCounter++){
if (car.hasHit(barrier[barCounter])){
Gl_unhook("checkCollision()");
newDir = Math.floor(Math.random() * 7);
speed = 0;
//direction = newDir;
car.setSpeed(speed);
//car.setXYdegs(direction);
//car.setFrame(direction);
} // end if
} // end for loop
//check for sensors
for (tsCount = 0; tsCount < 5; tsCount++){
if (car.hasHit(timeSensor[tsCount])){
if(lapFinished()){
if (car.hasHit(timeSensor[0])){
showTime();
resetStatus();
} // end 'start gate' if
} //end 'finished' if
lapStatus[tsCount] = 1;
} // end ' hit sensor' if
} // end for loop
} // end checkCollision
***PD: End Code
This function checks the lapStatus array to see if all the elements have been turned on, meaning the car has hit every sensor at least once. If so, the function returns the value true, otherwise, it returns the value false.
***PD: Begin Code function lapFinished(){
var finished = true;
for (counter=0; counter < numSensors; counter++){
if (lapStatus[counter] == 0){
finished = false;
} // end if
} // end loop
return finished;
} // end lapFinished
***PD: End Code
This function resets the value of the lapStatus array to all zeros. It is called at the beginning of each lap.
***PD: Begin Code function resetStatus(){
for (tsCount = 0; tsCount < numSensors; tsCount++){
lapStatus[tsCount] = 0;
} // end for loop
} // end resetStatus
***PD: End Code
This function calculates the elapsed time for the current race and displays it on the screen. It also checks for the lap number, and if it is the last lap, displays the race time. The program then moves on to the next track.
***PD: Begin Code function showTime(){
//returns back the time from this lap.
var currentTime = new Date();
var currentInt = currentTime.getTime();
var startInt = startTime.getTime();
var elapsedInt = currentInt - startInt;
var elapsedTime = new Date(elapsedInt);
lap++;
var output = "";
output += elapsedTime.getMinutes() + ":";
output += elapsedTime.getSeconds() + ".";
output += elapsedTime.getMilliseconds();
window.status = output;
if (lap==5){
alert(output);
trackNum++;
if (trackNum > 3){
trackNum = 1;
} // end if
setUpTrack();
} // end if
} // end showTime
***PD: End Code
These functions manipulate the direction variable in response to the user's mouse clicks on the steering wheel.
The car's direction and image are set based on the new value of direction.
***PD: Begin Code function turnLeft(){
direction--;
if (direction < NORTH){
direction = NORTHWEST;
} // end if
car.setXYdegs(direction * 45);
car.setFrame(direction);
} // end turnLeft function turnRight(){
direction++;
if (direction > NORTHWEST){
direction = NORTH;
} // end if
car.setXYdegs(direction * 45);
car.setFrame(direction);
} // end turnRight
***PD: End Code
This function decrements the speed of the car.
***PD: Begin Code function decell(){
speed--;
if (speed < 0){
speed = 0;
} // end if
car.setSpeed(speed);
} // end decell
***PD: End Code
This function re-hooks the checkCollision function (in case the car had crashed into a barrier) and increases the car's speed.
***PD: Begin Code function accell(){
Gl_hook("checkCollision()");
car.collides = true;
speed++;
if (speed > 10){
speed = 10;
} // end if
car.setSpeed(speed);
} // end accell
***PD: End Code
In this chapter you learned how an API can be used to greatly simplify the process of writing cross-browser
dynamic HTML. Specifically, you learned how to set up the gamelib core library and the sprite module. You learned how to create and use sprites, including adding motion and collision detection. You also learned how to check elapsed time using the built-in Date object. You put all these things together in a program that can be easily modified and improved.
The Syntax summary has been integrated into the chapter.
Should I leave it there and refer back to it from here, move it all here, or repeat it? (or do something else) -
Andy
***PD: begin unnumbered list
Add another track to the racer program. Modify the setupTrack function so it has a fourth track, and set the values of the barriers and timesensors to build a totally new track configuration.
Improve the sprite images to make your own car, better looking barriers and sensors, or an entirely different theme.
Make a simple version of pac-man. Define a path with barriers, and build a sprite to travel on the path.
Add ghosts to the pac-man game.
***PD: end unnumbered list