In this HTML lesson, we demonstrate how to use HTML, CSS, and JavaScript together to program a space game. The CSS and JavaScript portions are embedded in the head at the top of the code file, while the HTML is in the body at the bottom. We make use of CSS for all of the game animations, and the events and interactivity are programmed in JavaScript. HTML is used to code the static elements of the game. This code ties together the material from previous lessons.
The CSS section is the first code section, which is contained in the style element. It has two class selectors for the asteroid and explosion elements. These selectors are used to apply the keyframe animations to these elements to create appearence of asteroid movements and explosions. This sections handles all of the animations for the game.
The second section is the JavaScript section, which is contained in the script element. This code handles all of the game state and interactivity: The scores, damage, and asteroids are maintained in JavaScript, and the mouse events, corresponding to shots, are handled by the fnOnClick() function. JavaScript is also used to initiate the sound effects for shots, exploding asteroids, and the asteroid collisions with the ship.
The final section is the HTML code, which is in the body element. The onkeydown attribute of the body element is used to initiate the execution of the JavaScript when a key is pressed by calling the fnStart() function. A div that covers the windshield area is used to set the crosshairs cursor and process mouse clicks for shots by calling the fnOnClick() function. Invisible audio elements are included to play the game's sound effects via JavaScript controls, while the visible game elements use the inline CSS property z-index to layer themselves appropriately.
<!DOCTYPE html> <html> <head> <title>XoaX.net HTML</title> <style> .cAsteroid { position:absolute; animation: kfExpandWidth linear 2.5s 1, kfMoveDown linear 2.5s 1, kfExpandHeight linear 2.5s 1, kfMoveRight linear 2.5s 1; } @keyframes kfExpandWidth { from {width: 10%;} to {width: 100%;} } @keyframes kfMoveDown { from {top: 45%;} to {top: 0%;} } @keyframes kfExpandHeight { from {height: 10%;} to {height: 100%;} } @keyframes kfMoveRight { from {left: 45%;} to {left: 0%;} } .cExplosion { position:absolute; animation: kfDisappear linear .5s 1; } @keyframes kfDisappear { from {opacity: 100%;} to {opacity: 0%;} } </style> <script> var gqOutside = null; var giScore = 0; var giDamage = 0; var gqaAsteroids = null; var gqaExplosions = null; var giCollisionFrame = 20; var gbGameOver = true; function fnStart() { // Prevent multiple starts from key presses if (gbGameOver) { gbGameOver = false; giScore = 0; giDamage = 0; gqOutside = document.getElementById("idOutside"); // Allocate the asteroid and explosion arrays gqaAsteroids = new Array(); gqaExplosions = new Array(); // Remove the start message var qMessageElement = document.getElementById("idStartMessage"); qMessageElement.style.display = "none"; // Start the timed game loop fnGameLoop(); } } function fnGameLoop() { if (!gbGameOver) { setTimeout(fnGameLoop, 50); // Increment the asteroid frame counts for(var i = 0; i < gqaAsteroids.length; ++i) { gqaAsteroids[i].miFrame += 1; } // Place an asteroid if (Math.floor(Math.random() * 100) < (3 + giScore/10)) { fnPlaceAsteroid(); } // Move the screen during a collision if (giCollisionFrame < 20) { gqOutside.style.left = Math.floor((Math.random() * 6) - 3) + "px"; gqOutside.style.top = Math.floor((Math.random() * 6) - 3) + "px"; ++giCollisionFrame; } else { gqOutside.style.left = 0 + "px"; gqOutside.style.top = 0 + "px"; giCollisionFrame = 20; } } else { // Remove the remaining asteroids and stop their collisions for(var i = 0; i < gqaAsteroids.length; ++i) { gqOutside.removeChild(gqaAsteroids[i].mqDiv); clearTimeout(gqaAsteroids[i].mqTimeOut); } // Reset the collision shake giCollisionFrame = 20; var qMessageElement = document.getElementById("idStartMessage"); qMessageElement.style.display = "block"; } // Update the score and damage var qScore = document.getElementById("idScore"); qScore.innerHTML = giScore; var qDamage = document.getElementById("idDamage"); qDamage.innerHTML = giDamage; } function CAsteroid() { // The frame counter is used to determine how big the asteroid is this.miFrame = 0; // x between 30 and 640 - 30 - 28 = 582 this.miCenterX = Math.floor((Math.random() * 552) + 30); // y between 30 and 388 - 30 - 28 = 330 this.miCenterY = Math.floor((Math.random() * 300) + 30); // Create the container div for the image this.mqDiv = document.createElement("div"); this.mqDiv.style.position = "absolute"; this.mqDiv.style.left = this.miCenterX - 28 + "px"; this.mqDiv.style.top = this.miCenterY - 28 + "px"; this.mqDiv.style.width = "56px"; this.mqDiv.style.height = "56px"; this.mqDiv.style.zIndex = "1"; this.mqDiv.innerHTML = "<img class='cAsteroid' src='Rock.png' />"; gqOutside.appendChild(this.mqDiv); // Create the collision event function. Asteroids collide after 2.5 seconds this.mqTimeOut = setTimeout(fnAsteroidCollision, 2500); } function fnPlaceAsteroid() { var qNewAsteroid = new CAsteroid(); // Add the asteroid at the end of the array gqaAsteroids.push(qNewAsteroid); fnAsteroidResetZ(); } function fnAsteroidCollision() { // Make the collision sound and shake the screen var qCollisionAudio = document.getElementById("idCollision"); qCollisionAudio.currentTime = 0; qCollisionAudio.play(); // Initialize the screen shake giCollisionFrame = 0; // Increase the damage ++giDamage; // Remove the first asteroid in the list. This one hit the ship. var qRemoved = gqaAsteroids.shift(); // Remove the asteroid image from the HTML gqOutside.removeChild(qRemoved.mqDiv); fnAsteroidResetZ(); if (giDamage >= 3) { gbGameOver = true; } } function fnAsteroidResetZ() { for(var i = 0; i < gqaAsteroids.length; ++i) { gqaAsteroids[i].mqDiv.style.zIndex = gqaAsteroids.length - i; } } function fnAsteroidShot(iIndex, dPercent) { // Create the explosion image fnCreateExplosion(dPercent, gqaAsteroids[iIndex]); // Stop the collision event clearTimeout(gqaAsteroids[iIndex].mqTimeOut); // Remove element first because it is accessed via the array gqOutside.removeChild(gqaAsteroids[iIndex].mqDiv); // Remove the asteroid from the array gqaAsteroids.splice(iIndex, 1); // Reset the z values fnAsteroidResetZ(); ++giScore; } function fnCreateExplosion(dPercent, qAsteroid) { // Create the explosion image. The image is 56x56 var iImageSize = Math.round(dPercent*56); var qImageElement = document.createElement("img"); qImageElement.className = "cExplosion"; qImageElement.src = "ExplodingRock.png"; qImageElement.style.left = qAsteroid.miCenterX - (iImageSize/2) + "px"; qImageElement.style.top = qAsteroid.miCenterY - (iImageSize/2) + "px"; qImageElement.style.width = iImageSize + "px"; qImageElement.style.height = iImageSize + "px"; qImageElement.style.zIndex = qAsteroid.mqDiv.style.zIndex; gqOutside.appendChild(qImageElement); gqaExplosions.push(qImageElement); // Play the explosion shot sound var qAsteroidShotAudio = document.getElementById("idAsteroidShot"); qAsteroidShotAudio.currentTime = 0; qAsteroidShotAudio.play(); setTimeout(fnRemoveExplosion, 500); } function fnRemoveExplosion() { // Get the first explosion and remove it from the outside and array var qExplosionImage = gqaExplosions.shift(); gqOutside.removeChild(qExplosionImage); } function fnOnClick(e, qElement) { // Play the shot sound var qShotAudio = document.getElementById("idShot"); qShotAudio.currentTime = 0; qShotAudio.play(); // This is all to calculate the click coordinates relative to the div var dElementOffsetX = 0; var dElementOffsetY = 0; do{ dElementOffsetX += qElement.offsetLeft - qElement.scrollLeft; dElementOffsetY += qElement.offsetTop - qElement.scrollTop; qElement = qElement.offsetParent } while(qElement != document.body); var dRelX = e.pageX - dElementOffsetX; var dRelY = e.pageY - dElementOffsetY; // Check each asteroid for a hit for (var i = gqaAsteroids.length - 1; i >= 0; --i) { // Percentage complete - there are 2500/50 = 50 frames per asteroid var dPC = (gqaAsteroids[i].miFrame/50); // The max radius is 50 pixels. The min radius is 10% or 5 pixels var dHitRadius = ((50*dPC) + 5*(1 - dPC)); var dDeltaX = dRelX - gqaAsteroids[i].miCenterX; var dDeltaY = dRelY - gqaAsteroids[i].miCenterY; var dClickDistance = Math.sqrt(dDeltaX*dDeltaX + dDeltaY*dDeltaY); if (dHitRadius > dClickDistance) { fnAsteroidShot(i, dPC); return; } } } </script> </head> <body onkeydown="fnStart()"> <h1 id="idStartMessage" style="position:absolute;left:155px;top:130px;z-index:5001;color:white;text-align: center;"> GAME OVER<br />Press any key to begin!</h1> <audio id="idCollision" src="Collision.mp3"></audio> <audio id="idAsteroidShot" src="AsteroidShot.mp3"></audio> <audio id="idShot" src="Shot.mp3"></audio> <!-- The background stars and asteroids --> <div id="idOutside" style="position:absolute;left:0px;top:0px;"> <img style="position:absolute;left:0px;top:0px;z-index:-1;" src="Space.png" /> </div> <!-- The window click region --> <div style="position:absolute;left:0px;top:0px;z-index:5000;width:640px;height:388px;cursor:crosshair;" onclick="fnOnClick(event, this)"></div> <!-- The ship's window, console, and scores --> <img style="position:absolute;left:0px;top:0px;z-index:1000;" src="Windshield.png" /> <h1 style="position:absolute;left:20px;top:395px;z-index:5000;">Score: <span id="idScore">0</span></h1> <h1 style="position:absolute;left:340px;top:395px;z-index:5000;">Damage: <span id="idDamage">0</span></h1> </body> </html>
© 20072025 XoaX.net LLC. All rights reserved.