Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8" /> | |
<title>Highway Dodge</title> | |
<style> | |
body { | |
margin: 0; | |
overflow: hidden; | |
font-family: "Arial", sans-serif; | |
background-color: #87ceeb; | |
} | |
#startScreen, | |
#gameOverScreen { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
display: flex; | |
flex-direction: column; | |
justify-content: center; | |
align-items: center; | |
background-color: rgba(0, 0, 0, 0.7); | |
z-index: 1000; | |
} | |
#gameContainer { | |
position: relative; | |
width: 100%; | |
height: 100vh; | |
} | |
.game-button { | |
padding: 15px 30px; | |
font-size: 24px; | |
background-color: #4caf50; | |
color: white; | |
border: none; | |
border-radius: 10px; | |
cursor: pointer; | |
transition: background-color 0.3s ease; | |
margin: 10px; | |
} | |
.game-button:hover { | |
background-color: #45a049; | |
} | |
#hud { | |
position: absolute; | |
top: 10px; | |
left: 10px; | |
color: white; | |
font-family: Arial, sans-serif; | |
font-size: 20px; | |
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); | |
z-index: 100; | |
} | |
#score, | |
#timer { | |
margin-bottom: 10px; | |
} | |
#gameTitle { | |
font-size: 48px; | |
color: white; | |
margin-bottom: 30px; | |
text-shadow: 3px 3px 6px rgba(0, 0, 0, 0.5); | |
} | |
#finalScore { | |
font-size: 36px; | |
color: white; | |
margin-bottom: 20px; | |
text-align: center; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="gameContainer"> | |
<div id="startScreen"> | |
<h1 id="gameTitle">Highway Dodge</h1> | |
<button id="startButton" class="game-button">Start Game</button> | |
</div> | |
<div id="gameOverScreen" style="display: none"> | |
<div id="finalScore">Score: 0</div> | |
<button id="restartButton" class="game-button">Try Again</button> | |
</div> | |
<div id="hud" style="display: none"> | |
<div id="score">Score: 0</div> | |
<div id="timer">Time: 00:00</div> | |
</div> | |
</div> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
<script> | |
// Game configuration | |
const config = { | |
roadLength: 10000, | |
roadWidth: 15, // Wider road for 3 lanes | |
playerSpeed: 0.3, // Decreased player speed | |
trafficSpeed: 0.5, // Increased base traffic speed | |
colors: [ | |
{ color: 0xff0000, name: "Red" }, | |
{ color: 0x0000ff, name: "Blue" }, | |
{ color: 0x00ff00, name: "Green" }, | |
{ color: 0x000000, name: "Black" }, | |
{ color: 0x800080, name: "Purple" }, | |
{ color: 0xa52a2a, name: "Brown" }, | |
{ color: 0xffa500, name: "Orange" }, | |
{ color: 0xffff00, name: "Yellow" }, | |
], | |
maxTrafficVehicles: 170, // Increased for more traffic | |
clusterChance: 0.9, // Chance of creating a vehicle cluster | |
clusterSize: { min: 2, max: 5 }, // Range of vehicles in a cluster | |
clusterSpacing: { min: 3, max: 5 }, // Spacing between vehicles in a cluster | |
lanePositions: [-5, 0, 5], // Three lanes | |
vehicleTypes: ["car", "bus", "truck"], | |
vehicleProperties: { | |
car: { length: 2, width: 1, height: 0.8, speed: 1 }, // Increased speed | |
bus: { length: 3.5, width: 1.2, height: 1.2, speed: 0.8 }, // Increased speed | |
truck: { length: 4, width: 1.3, height: 1.5, speed: 0.6 }, // Increased speed | |
}, | |
}; | |
// Game state | |
const state = { | |
score: 0, | |
gameOver: false, | |
difficulty: 1, | |
startTime: 0, | |
elapsedTime: 0, | |
difficultyMultiplier: 1, | |
}; | |
// Scene, camera, and renderer setup | |
const scene = new THREE.Scene(); | |
scene.fog = new THREE.Fog(0x87ceeb, 10, 70); | |
const camera = new THREE.PerspectiveCamera( | |
75, | |
window.innerWidth / window.innerHeight, | |
0.1, | |
1000 | |
); | |
const renderer = new THREE.WebGLRenderer(); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
document.getElementById("gameContainer").appendChild(renderer.domElement); | |
// Lighting | |
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); | |
scene.add(ambientLight); | |
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); | |
directionalLight.position.set(0, 10, 0); | |
scene.add(directionalLight); | |
// Create vehicle models | |
function createVehicleModel(type, color, isPlayer = false) { | |
const props = config.vehicleProperties[type]; | |
const group = new THREE.Group(); | |
const baseHeight = 0.6; | |
// Base of the vehicle | |
const baseGeometry = new THREE.BoxGeometry( | |
props.width, | |
props.height * 0.5, | |
props.length | |
); | |
const baseMaterial = new THREE.MeshPhongMaterial({ color: color }); | |
const base = new THREE.Mesh(baseGeometry, baseMaterial); | |
base.position.y = baseHeight; | |
group.add(base); | |
// Roof - different for each type | |
if (type === "car") { | |
const roofGeometry = new THREE.BoxGeometry( | |
props.width * 0.8, | |
props.height * 0.5, | |
props.length * 0.75 | |
); | |
const roofMaterial = new THREE.MeshPhongMaterial({ | |
color: isPlayer ? 0xff4500 : color, | |
opacity: 1, | |
transparent: false, | |
}); | |
const roof = new THREE.Mesh(roofGeometry, roofMaterial); | |
roof.position.y = baseHeight + props.height * 0.5; | |
group.add(roof); | |
} else if (type === "bus") { | |
const roofGeometry = new THREE.BoxGeometry( | |
props.width, | |
props.height * 0.5, | |
props.length * 0.95 | |
); | |
const roofMaterial = new THREE.MeshPhongMaterial({ | |
color: isPlayer ? 0xff4500 : color, | |
}); | |
const roof = new THREE.Mesh(roofGeometry, roofMaterial); | |
roof.position.y = baseHeight + props.height * 0.5; | |
group.add(roof); | |
} else if (type === "truck") { | |
// Cab for truck | |
const cabGeometry = new THREE.BoxGeometry( | |
props.width, | |
props.height * 0.7, | |
props.length * 0.3 | |
); | |
const cabMaterial = new THREE.MeshPhongMaterial({ | |
color: isPlayer ? 0xff4500 : color, | |
}); | |
const cab = new THREE.Mesh(cabGeometry, cabMaterial); | |
cab.position.y = baseHeight + props.height * 0.35; | |
cab.position.z = props.length * 0.35; | |
group.add(cab); | |
// Cargo container | |
const cargoGeometry = new THREE.BoxGeometry( | |
props.width * 1.1, | |
props.height * 1, | |
props.length * 0.65 | |
); | |
const cargoMaterial = new THREE.MeshPhongMaterial({ | |
color: 0x888888, | |
}); | |
const cargo = new THREE.Mesh(cargoGeometry, cargoMaterial); | |
cargo.position.y = baseHeight + props.height * 0.5; | |
cargo.position.z = -props.length * 0.17; | |
group.add(cargo); | |
} | |
// Windows (for cars and buses) | |
if (type !== "truck") { | |
const windowMaterial = new THREE.MeshPhongMaterial({ | |
color: 0x87ceeb, | |
opacity: 0.7, | |
transparent: true, | |
}); | |
// Side windows | |
const windowSize = type === "car" ? 0.4 : 0.6; | |
const windowFront = new THREE.Mesh( | |
new THREE.PlaneGeometry(props.width * 0.9, windowSize), | |
windowMaterial | |
); | |
windowFront.rotation.y = Math.PI / 2; | |
windowFront.position.set( | |
props.width / 2 + 0.01, | |
baseHeight + props.height * 0.5, | |
0 | |
); | |
group.add(windowFront); | |
const windowBack = windowFront.clone(); | |
windowBack.position.set( | |
-props.width / 2 - 0.01, | |
baseHeight + props.height * 0.5, | |
0 | |
); | |
windowBack.rotation.y = -Math.PI / 2; | |
group.add(windowBack); | |
} | |
// Wheels | |
const wheelRadius = type === "car" ? 0.2 : 0.3; | |
const wheelGeometry = new THREE.CylinderGeometry( | |
wheelRadius, | |
wheelRadius, | |
0.2, | |
16 | |
); | |
const wheelMaterial = new THREE.MeshPhongMaterial({ color: 0x000000 }); | |
// Position wheels based on vehicle type | |
const wheelOffsetX = (props.width / 2) * 0.8; | |
const wheelOffsetY = baseHeight - props.height * 0.3; | |
let wheelZPositions; | |
if (type === "car") { | |
wheelZPositions = [props.length / 2 - 0.3, -props.length / 2 + 0.3]; | |
} else if (type === "bus") { | |
wheelZPositions = [ | |
props.length / 2 - 0.5, | |
0, | |
-props.length / 2 + 0.5, | |
]; | |
} else { | |
// truck | |
wheelZPositions = [ | |
props.length / 2 - 0.5, | |
0, | |
-props.length / 2 + 0.5, | |
]; | |
} | |
// Add wheels | |
wheelZPositions.forEach((zPos) => { | |
const wheelLeft = new THREE.Mesh(wheelGeometry, wheelMaterial); | |
wheelLeft.rotation.z = Math.PI / 2; | |
wheelLeft.position.set(-wheelOffsetX, wheelOffsetY, zPos); | |
group.add(wheelLeft); | |
const wheelRight = wheelLeft.clone(); | |
wheelRight.position.set(wheelOffsetX, wheelOffsetY, zPos); | |
group.add(wheelRight); | |
}); | |
// Add collision box | |
group.userData = { | |
type: type, | |
width: props.width, | |
length: props.length, | |
speed: props.speed, | |
boundingBox: new THREE.Box3().setFromObject(group), | |
}; | |
return group; | |
} | |
// Background | |
const backgroundGeometry = new THREE.SphereGeometry(500, 60, 40); | |
const backgroundMaterial = new THREE.MeshBasicMaterial({ | |
color: 0x87ceeb, | |
side: THREE.BackSide, | |
}); | |
const background = new THREE.Mesh(backgroundGeometry, backgroundMaterial); | |
scene.add(background); | |
// Road | |
const roadGeometry = new THREE.PlaneGeometry( | |
config.roadWidth, | |
config.roadLength | |
); | |
const roadMaterial = new THREE.MeshPhongMaterial({ color: 0x505050 }); | |
const road = new THREE.Mesh(roadGeometry, roadMaterial); | |
road.rotation.x = -Math.PI / 2; | |
scene.add(road); | |
// Lane markings | |
function createLaneMarkings() { | |
// Lane dividers | |
for (let i = -1; i <= 1; i += 2) { | |
const lineGeometry = new THREE.PlaneGeometry(0.15, config.roadLength); | |
const lineMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff }); | |
const line = new THREE.Mesh(lineGeometry, lineMaterial); | |
line.rotation.x = -Math.PI / 2; | |
line.position.x = i * 2.5; // Center line between lanes | |
line.position.y = 0.01; // Slightly above road | |
scene.add(line); | |
} | |
// Dashed center lines | |
for ( | |
let z = -config.roadLength / 2; | |
z < config.roadLength / 2; | |
z += 5 | |
) { | |
const dashGeometry = new THREE.PlaneGeometry(0.15, 3); | |
const dashMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff }); | |
// Left dashed line | |
const leftDash = new THREE.Mesh(dashGeometry, dashMaterial); | |
leftDash.rotation.x = -Math.PI / 2; | |
leftDash.position.set(-2.5, 0.01, z); | |
scene.add(leftDash); | |
// Right dashed line | |
const rightDash = new THREE.Mesh(dashGeometry, dashMaterial); | |
rightDash.rotation.x = -Math.PI / 2; | |
rightDash.position.set(2.5, 0.01, z); | |
scene.add(rightDash); | |
} | |
} | |
// Player Car | |
const playerColor = 0xff0000; | |
const playerCar = createVehicleModel("car", playerColor, true); | |
playerCar.position.y = 0; | |
scene.add(playerCar); | |
// Traffic Vehicles | |
const trafficVehicles = []; | |
function createTrafficVehicle() { | |
// Randomly select vehicle type | |
const vehicleType = | |
config.vehicleTypes[ | |
Math.floor(Math.random() * config.vehicleTypes.length) | |
]; | |
const colorObj = | |
config.colors[Math.floor(Math.random() * config.colors.length)]; | |
const vehicle = createVehicleModel(vehicleType, colorObj.color); | |
// Place in a random lane | |
const laneIndex = Math.floor( | |
Math.random() * config.lanePositions.length | |
); | |
vehicle.position.x = config.lanePositions[laneIndex]; | |
// Stagger vehicle positions - longer range for traffic jam effect | |
vehicle.position.z = -Math.random() * config.roadLength * 0.75; | |
// Add to scene and array | |
scene.add(vehicle); | |
trafficVehicles.push(vehicle); | |
// Update the bounding box after positioning | |
vehicle.userData.boundingBox.setFromObject(vehicle); | |
} | |
// Spawn initial traffic | |
function initTraffic() { | |
createLaneMarkings(); | |
for (let i = 0; i < config.maxTrafficVehicles; i++) { | |
createTrafficVehicle(); | |
} | |
} | |
// Camera positioning | |
camera.position.y = 3; | |
camera.position.z = 7; | |
camera.lookAt(playerCar.position); | |
// Improved collision detection using bounding boxes | |
function checkCollisions() { | |
if (state.gameOver) return; | |
// Update player bounding box | |
playerCar.userData.boundingBox.setFromObject(playerCar); | |
for (let i = 0; i < trafficVehicles.length; i++) { | |
// Update vehicle bounding box | |
trafficVehicles[i].userData.boundingBox.setFromObject( | |
trafficVehicles[i] | |
); | |
// Check for intersection | |
if ( | |
playerCar.userData.boundingBox.intersectsBox( | |
trafficVehicles[i].userData.boundingBox | |
) | |
) { | |
endGame(); | |
break; | |
} | |
} | |
} | |
// Format time to MM:SS | |
function formatTime(milliseconds) { | |
const totalSeconds = Math.floor(milliseconds / 1000); | |
const minutes = Math.floor(totalSeconds / 60); | |
const seconds = totalSeconds % 60; | |
return `${minutes.toString().padStart(2, "0")}:${seconds | |
.toString() | |
.padStart(2, "0")}`; | |
} | |
// Movement | |
const keys = {}; | |
window.addEventListener("keydown", (e) => { | |
keys[e.key] = true; | |
}); | |
window.addEventListener("keyup", (e) => { | |
keys[e.key] = false; | |
}); | |
function updatePlayerMovement() { | |
if (state.gameOver) return; | |
if ( | |
keys["ArrowLeft"] && | |
playerCar.position.x > -(config.roadWidth / 2 - 1) | |
) { | |
playerCar.position.x -= config.playerSpeed; | |
} | |
if ( | |
keys["ArrowRight"] && | |
playerCar.position.x < config.roadWidth / 2 - 1 | |
) { | |
playerCar.position.x += config.playerSpeed; | |
} | |
if (keys["ArrowUp"]) { | |
playerCar.position.z -= config.playerSpeed * 2; | |
state.score++; | |
document.getElementById( | |
"score" | |
).textContent = `Score: ${state.score}`; | |
} | |
if (keys["ArrowDown"]) { | |
playerCar.position.z += config.playerSpeed; | |
} | |
} | |
// Update traffic vehicles | |
function updateTrafficVehicles() { | |
// Increase difficulty based on score and time | |
state.difficultyMultiplier = | |
1 + state.score / 2000 + state.elapsedTime / 80000; | |
trafficVehicles.forEach((vehicle, index) => { | |
// Move vehicles with their specific speeds | |
const vehicleSpeed = | |
config.trafficSpeed * | |
vehicle.userData.speed * | |
state.difficultyMultiplier; | |
vehicle.position.z += vehicleSpeed; | |
// Update bounding box with new position | |
vehicle.userData.boundingBox.setFromObject(vehicle); | |
// Award points when successfully dodging a vehicle | |
if ( | |
!vehicle.userData.passed && | |
vehicle.position.z > playerCar.position.z | |
) { | |
vehicle.userData.passed = true; | |
state.score++; | |
document.getElementById( | |
"score" | |
).textContent = `Score: ${state.score}`; | |
} | |
// Remove vehicles that are out of view and create new ones | |
if (vehicle.position.z > 15) { | |
scene.remove(vehicle); | |
trafficVehicles.splice(index, 1); | |
createTrafficVehicle(); | |
} | |
}); | |
} | |
// Update timer | |
function updateTimer() { | |
state.elapsedTime = Date.now() - state.startTime; | |
document.getElementById("timer").textContent = `Time: ${formatTime( | |
state.elapsedTime | |
)}`; | |
} | |
// Animation loop | |
function animate() { | |
requestAnimationFrame(animate); | |
updatePlayerMovement(); | |
updateTrafficVehicles(); | |
checkCollisions(); | |
updateTimer(); | |
// Update camera to follow player | |
camera.position.x = playerCar.position.x; | |
camera.position.z = playerCar.position.z + 7; // Positioned further back | |
camera.lookAt( | |
new THREE.Vector3( | |
playerCar.position.x, | |
playerCar.position.y, | |
playerCar.position.z - 3 | |
) | |
); | |
renderer.render(scene, camera); | |
} | |
// Start Game | |
function startGame() { | |
// Reset game state | |
state.score = 0; | |
state.gameOver = false; | |
state.difficulty = 1; | |
state.difficultyMultiplier = 1; | |
// Hide start screen | |
document.getElementById("startScreen").style.display = "none"; | |
document.getElementById("gameOverScreen").style.display = "none"; | |
document.getElementById("hud").style.display = "block"; | |
// Reset player position | |
playerCar.position.x = 0; | |
playerCar.position.z = 0; | |
// Clear existing traffic | |
trafficVehicles.forEach((vehicle) => scene.remove(vehicle)); | |
trafficVehicles.length = 0; | |
// Start timer and animation but delay traffic spawn | |
document.getElementById("score").textContent = `Score: ${state.score}`; | |
document.getElementById("timer").textContent = `Time: 00:00`; | |
// Show countdown or ready message | |
const readyMessage = document.createElement("div"); | |
readyMessage.style.position = "absolute"; | |
readyMessage.style.top = "50%"; | |
readyMessage.style.left = "50%"; | |
readyMessage.style.transform = "translate(-50%, -50%)"; | |
readyMessage.style.fontSize = "48px"; | |
readyMessage.style.color = "white"; | |
readyMessage.style.textShadow = "2px 2px 4px black"; | |
readyMessage.style.zIndex = "1000"; | |
readyMessage.textContent = "Ready..."; | |
document.getElementById("gameContainer").appendChild(readyMessage); | |
// Start animation immediately | |
animate(); | |
// Wait 3 seconds before spawning traffic | |
setTimeout(() => { | |
// Remove ready message | |
document.getElementById("gameContainer").removeChild(readyMessage); | |
// Start the timer now | |
state.startTime = Date.now(); | |
// Spawn traffic | |
initTraffic(); | |
}, 3000); | |
} | |
// End Game | |
function endGame() { | |
state.gameOver = true; | |
// Show game over screen | |
document.getElementById("hud").style.display = "none"; | |
document.getElementById("gameOverScreen").style.display = "flex"; | |
document.getElementById("finalScore").textContent = `Score: ${ | |
state.score | |
} | |
Time: ${formatTime(state.elapsedTime)}`; | |
} | |
// Event Listeners | |
document | |
.getElementById("startButton") | |
.addEventListener("click", startGame); | |
document | |
.getElementById("restartButton") | |
.addEventListener("click", startGame); | |
// Handle window resizing | |
window.addEventListener("resize", () => { | |
camera.aspect = window.innerWidth / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
}); | |
</script> | |
</body> | |
</html> | |