<!
DOCTYPE html>
<html>
<head>
<title>3D Car Racing Game</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #000;
}
canvas {
display: block;
}
#gameUI {
position: absolute;
top: 10px;
left: 10px;
color: white;
font-family: Arial, sans-serif;
font-size: 16px;
}
#startScreen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: white;
font-family: Arial, sans-serif;
}
#startScreen h1 {
font-size: 48px;
margin-bottom: 30px;
}
#startButton {
padding: 15px 30px;
font-size: 24px;
background-color: #e74c3c;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
#startButton:hover {
background-color: #c0392b;
}
</style>
</head>
<body>
<canvas id="gameCanvas"></canvas>
<div id="gameUI">
Speed: <span id="speedDisplay">0</span> km/h |
Score: <span id="scoreDisplay">0</span> |
Time: <span id="timeDisplay">0</span>s
</div>
<div id="startScreen">
<h1>3D Car Racer</h1>
<button id="startButton">Start Game</button>
<div style="margin-top: 30px;">
<p>Controls:</p>
<p>Arrow Keys to drive</p>
<p>Space to brake</p>
</div>
</div>
<script>
// Game canvas setup
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Game state
let gameRunning = false;
let score = 0;
let gameTime = 0;
let playerSpeed = 0;
let maxSpeed = 300;
let acceleration = 0.5;
let deceleration = 0.3;
let handling = 2;
let playerX = 0;
let roadWidth = 2000;
let segmentLength = 200;
let drawDistance = 300;
let cameraHeight = 1000;
let curve = 0;
let hill = 0;
let trackLength = 500;
let segments = [];
// Other cars
let cars = [];
let obstacles = [];
let lastCarSpawn = 0;
let lastObstacleSpawn = 0;
// Colors
const colors = {
sky: '#70c5ce',
road: '#555555',
grass: '#2d862d',
rumble: '#ffffff',
lane: '#cccccc',
playerCar: '#e74c3c',
enemyCars: ['#3498db', '#f1c40f', '#9b59b6', '#1abc9c'],
obstacles: '#e67e22'
};
// Input
const keys = {
ArrowUp: false,
ArrowDown: false,
ArrowLeft: false,
ArrowRight: false,
' ': false
};
// Initialize track segments
function initSegments() {
segments = [];
for (let i = 0; i < trackLength; i++) {
// Add some curves and hills
let curveVal = 0;
let hillVal = 0;
// Add some random curves
if (i > 50 && i % 100 === 0) {
curveVal = Math.random() * 4 - 2;
}
// Add some random hills
if (i > 100 && i % 150 === 0) {
hillVal = Math.random() * 100 - 50;
}
segments.push({
z: i * segmentLength,
curve: curveVal,
hill: hillVal,
worldY: 0,
worldX: 0
});
}
// Calculate world coordinates
updateWorldCoordinates();
}
// Update world coordinates for curves and hills
function updateWorldCoordinates() {
let x = 0;
let y = 0;
for (let i = 0; i < segments.length; i++) {
segments[i].worldX = x;
segments[i].worldY = y;
x += segments[i].curve * segmentLength;
y += segments[i].hill;
}
}
// Project 3D coordinates to 2D screen
function project(x, y, z, cameraZ) {
const scale = cameraHeight / (z - cameraZ);
const projectedX = canvas.width / 2 + (x * scale);
const projectedY = canvas.height / 2 - (y * scale);
const projectedW = scale;
return { x: projectedX, y: projectedY, w: projectedW };
}
// Draw the scene
function render() {
// Clear screen
ctx.fillStyle = colors.sky;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Find player segment
let playerSegment = findSegment(playerSpeed);
let baseSegment = Math.floor(playerSpeed / segmentLength) % segments.length;
let cameraZ = playerSpeed;
// Draw from back to front
for (let n = 0; n < drawDistance; n++) {
const segment = segments[(baseSegment + n) % segments.length];
const p = project(0, segment.worldY, segment.z - cameraZ, 0);
// Draw grass
ctx.fillStyle = colors.grass;
ctx.fillRect(0, p.y, canvas.width, canvas.height - p.y);
// Draw road
const roadW = roadWidth * p.w;
const roadX = canvas.width / 2 + (segment.worldX - playerX) * p.w;
ctx.fillStyle = colors.road;
ctx.fillRect(roadX - roadW / 2, p.y, roadW, canvas.height - p.y);
// Draw rumble strips
const rumbleW = roadW * 0.1;
ctx.fillStyle = colors.rumble;
ctx.fillRect(roadX - roadW / 2 - rumbleW, p.y, rumbleW, canvas.height - p.y);
ctx.fillRect(roadX + roadW / 2, p.y, rumbleW, canvas.height - p.y);
// Draw lane markings
if (n % 10 < 5) {
const laneW = roadW * 0.02;
ctx.fillStyle = colors.lane;
ctx.fillRect(roadX - laneW / 2, p.y, laneW, canvas.height - p.y);
}
}
// Draw other cars
for (const car of cars) {
const carSegment = segments[Math.floor(car.z / segmentLength) % segments.length];
const dz = car.z - cameraZ;
if (dz > -100 && dz < drawDistance * segmentLength) {
const p = project(car.x - playerX, carSegment.worldY, dz, 0);
const carW = 50 * p.w;
const carH = 30 * p.w;
ctx.fillStyle = car.color;
ctx.fillRect(p.x - carW / 2, p.y - carH, carW, carH);
// Add some details
ctx.fillStyle = '#ffffff';
ctx.fillRect(p.x - carW * 0.4, p.y - carH * 0.8, carW * 0.3, carH * 0.3);
ctx.fillRect(p.x + carW * 0.1, p.y - carH * 0.8, carW * 0.3, carH * 0.3);
}
}
// Draw obstacles
for (const obstacle of obstacles) {
const obstacleSegment = segments[Math.floor(obstacle.z / segmentLength) %
segments.length];
const dz = obstacle.z - cameraZ;
if (dz > -100 && dz < drawDistance * segmentLength) {
const p = project(obstacle.x - playerX, obstacleSegment.worldY, dz, 0);
const obstacleW = 60 * p.w;
const obstacleH = 60 * p.w;
ctx.fillStyle = colors.obstacles;
ctx.fillRect(p.x - obstacleW / 2, p.y - obstacleH, obstacleW, obstacleH);
}
}
// Draw player car
const playerW = 60;
const playerH = 30;
ctx.fillStyle = colors.playerCar;
ctx.fillRect(canvas.width / 2 - playerW / 2, canvas.height - playerH - 20, playerW,
playerH);
// Add details to player car
ctx.fillStyle = '#ffffff';
ctx.fillRect(canvas.width / 2 - playerW * 0.4, canvas.height - playerH - 10,
playerW * 0.3, playerH * 0.3);
ctx.fillRect(canvas.width / 2 + playerW * 0.1, canvas.height - playerH - 10,
playerW * 0.3, playerH * 0.3);
}
// Find current segment
function findSegment(z) {
return segments[Math.floor(z / segmentLength) % segments.length];
}
// Spawn other cars
function spawnCars() {
const now = Date.now();
if (now - lastCarSpawn > 3000 && Math.random() < 0.1) {
const z = playerSpeed + 2000 + Math.random() * 3000;
const x = (Math.random() * 2 - 1) * roadWidth * 0.4;
const color = colors.enemyCars[Math.floor(Math.random() *
colors.enemyCars.length)];
cars.push({ x, z, color, speed: 100 + Math.random() * 50 });
lastCarSpawn = now;
}
}
// Spawn obstacles
function spawnObstacles() {
const now = Date.now();
if (now - lastObstacleSpawn > 2000 && Math.random() < 0.1) {
const z = playerSpeed + 1500 + Math.random() * 2000;
const x = (Math.random() * 2 - 1) * roadWidth * 0.4;
obstacles.push({ x, z });
lastObstacleSpawn = now;
}
}
// Update game state
function update(deltaTime) {
if (!gameRunning) return;
// Update game time
gameTime += deltaTime / 1000;
document.getElementById('timeDisplay').textContent = Math.floor(gameTime);
// Handle acceleration
if (keys.ArrowUp) {
playerSpeed = Math.min(playerSpeed + acceleration * deltaTime, maxSpeed);
} else if (keys.ArrowDown) {
playerSpeed = Math.max(playerSpeed - deceleration * deltaTime, 0);
} else {
// Natural deceleration
playerSpeed = Math.max(playerSpeed - deceleration * 0.5 * deltaTime, 0);
}
// Handle braking
if (keys[' ']) {
playerSpeed = Math.max(playerSpeed - deceleration * 2 * deltaTime, 0);
}
// Handle steering
if (keys.ArrowLeft) {
playerX -= handling * (playerSpeed / maxSpeed) * deltaTime;
}
if (keys.ArrowRight) {
playerX += handling * (playerSpeed / maxSpeed) * deltaTime;
}
// Keep player on road
const halfRoad = roadWidth / 2;
playerX = Math.max(-halfRoad, Math.min(playerX, halfRoad));
// Update score
score += playerSpeed * deltaTime / 100;
document.getElementById('scoreDisplay').textContent = Math.floor(score);
document.getElementById('speedDisplay').textContent = Math.floor(playerSpeed / 5);
// Spawn other cars and obstacles
spawnCars();
spawnObstacles();
// Update other cars
for (let i = cars.length - 1; i >= 0; i--) {
cars[i].z -= cars[i].speed * deltaTime / 100;
// Remove cars that are behind
if (cars[i].z < playerSpeed - 500) {
cars.splice(i, 1);
}
}
// Update obstacles
for (let i = obstacles.length - 1; i >= 0; i--) {
obstacles[i].z -= playerSpeed * deltaTime / 100;
// Remove obstacles that are behind
if (obstacles[i].z < playerSpeed - 500) {
obstacles.splice(i, 1);
}
// Check collision with player
const dz = obstacles[i].z - playerSpeed;
if (dz > -50 && dz < 50) {
const dx = Math.abs(obstacles[i].x - playerX);
if (dx < 50) {
// Collision detected
playerSpeed *= 0.5;
obstacles.splice(i, 1);
score = Math.max(0, score - 100);
}
}
}
// Check collision with other cars
for (let i = cars.length - 1; i >= 0; i--) {
const dz = cars[i].z - playerSpeed;
if (dz > -50 && dz < 50) {
const dx = Math.abs(cars[i].x - playerX);
if (dx < 50) {
// Collision detected
playerSpeed *= 0.7;
cars[i].speed *= 0.5;
score = Math.max(0, score - 50);
}
}
}
}
// Game loop
let lastTime = 0;
function gameLoop(timestamp) {
const deltaTime = timestamp - lastTime;
lastTime = timestamp;
update(deltaTime);
render();
requestAnimationFrame(gameLoop);
}
// Event listeners
window.addEventListener('keydown', (e) => {
if (keys.hasOwnProperty(e.key)) {
keys[e.key] = true;
e.preventDefault();
}
});
window.addEventListener('keyup', (e) => {
if (keys.hasOwnProperty(e.key)) {
keys[e.key] = false;
e.preventDefault();
}
});
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
document.getElementById('startButton').addEventListener('click', () => {
document.getElementById('startScreen').style.display = 'none';
gameRunning = true;
initSegment();
requestAnimationFrame(gameLoop);
});
// Initialize game
function initGame() {
score = 0;
gameTime = 0;
playerSpeed = 0;
playerX = 0;
cars = [];
obstacles = [];
initSegments();
}
// Start the game when the start button is clicked
document.getElementById('startButton').addEventListener('click', () => {
document.getElementById('startScreen').style.display = 'none';
initGame();
gameRunning = true;
lastTime = performance.now();
requestAnimationFrame(gameLoop);
});
// Initialize the game on load
initGame();
</script>
</body>
</html>