←
→
Brake
Accel
Settings
Quality:
Game Over
Shop
Coins: 0
Back
→
←
Acceleration: —|———
Top Speed: 450 km/h
Purchase for 30 Coins
Equip
Speed: 0 km/h
let scene, camera, renderer;
let player, opponents = [];
let coins = [];
let moveLeft = false, moveRight = false;
let brake = false, accelerate = false;
let currentSpeed = 0;
const minSpeed = 0;
const maxSpeed = 450 / 36; // Max speed converted to Three.js units
let accelerationRate = 0.5;
const brakeRate = 0.5;
let opponentSpawnInterval = 60;
let coinSpawnInterval = 180; // Add coins every 3 seconds
let frameCount = 0;
let gameOver = false;
const roadLines = [];
let clock;
let lastTime = performance.now();
let quality = 'high';
const roadWidth = 20;
let coinsCollected = 0;
let carColor = 'red'; // Default car color
let carCapabilities = {
acceleration: 0.5,
topSpeed: 450
};
let gameState = {
coins: 0,
purchasedCars: [],
equippedCar: 'red'
};
init();
animate();
function init() {
loadGameState();
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 5, 12);
renderer = new THREE.WebGLRenderer({ antialias: true });
updateQuality();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
clock = new THREE.Clock();
createPlayer(carColor);
const roadGeometry = new THREE.PlaneGeometry(roadWidth, 200);
const roadMaterial = new THREE.MeshStandardMaterial({ color: 0x808080 });
const road = new THREE.Mesh(roadGeometry, roadMaterial);
road.rotation.x = -Math.PI / 2;
road.position.y = -0.5;
scene.add(road);
const grassGeometry = new THREE.PlaneGeometry(60, 200);
const grassMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const grassLeft = new THREE.Mesh(grassGeometry, grassMaterial);
grassLeft.rotation.x = -Math.PI / 2;
grassLeft.position.set(-40, -0.51, 0);
scene.add(grassLeft);
const grassRight = new THREE.Mesh(grassGeometry, grassMaterial);
grassRight.rotation.x = -Math.PI / 2;
grassRight.position.set(40, -0.51, 0);
scene.add(grassRight);
const treeGeometry = new THREE.CylinderGeometry(0.5, 0.5, 3, 8);
const treeMaterial = new THREE.MeshStandardMaterial({ color: 0x8b4513 });
const foliageGeometry = new THREE.SphereGeometry(2, 8, 8);
const foliageMaterial = new THREE.MeshStandardMaterial({ color: 0x228b22 });
for (let i = -50; i < 50; i += 20) {
const tree = new THREE.Group();
const trunk = new THREE.Mesh(treeGeometry, treeMaterial);
const foliage = new THREE.Mesh(foliageGeometry, foliageMaterial);
foliage.position.y = 2.5;
tree.add(trunk);
tree.add(foliage);
tree.position.set(i, 1.5, -20);
scene.add(tree);
}
const wallGeometry = new THREE.PlaneGeometry(200, 10);
const wallMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000, side: THREE.DoubleSide, visible: false });
const wallLeft = new THREE.Mesh(wallGeometry, wallMaterial);
wallLeft.position.set(-roadWidth / 2, 5, 0); // Positioned right at the edge of the road
wallLeft.rotation.y = Math.PI / 2;
scene.add(wallLeft);
const wallRight = new THREE.Mesh(wallGeometry, wallMaterial);
wallRight.position.set(roadWidth / 2, 5, 0); // Positioned right at the edge of the road
wallRight.rotation.y = Math.PI / 2;
scene.add(wallRight);
const sunGeometry = new THREE.SphereGeometry(5, 32, 32);
const sunMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
const sun = new THREE.Mesh(sunGeometry, sunMaterial);
sun.position.set(0, 50, -100);
scene.add(sun);
const skyGeometry = new THREE.SphereGeometry(500, 32, 32);
const skyMaterial = new THREE.MeshBasicMaterial({ color: 0x87ceeb, side: THREE.BackSide });
const sky = new THREE.Mesh(skyGeometry, skyMaterial);
scene.add(sky);
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(0, 10, 10);
scene.add(directionalLight);
document.getElementById('leftArrow').addEventListener('touchstart', () => moveLeft = true);
document.getElementById('leftArrow').addEventListener('touchend', () => moveLeft = false);
document.getElementById('rightArrow').addEventListener('touchstart', () => moveRight = true);
document.getElementById('rightArrow').addEventListener('touchend', () => moveRight = false);
document.getElementById('brakeBtn').addEventListener('touchstart', () => brake = true);
document.getElementById('brakeBtn').addEventListener('touchend', () => brake = false);
document.getElementById('accelerateBtn').addEventListener('touchstart', () => accelerate = true);
document.getElementById('accelerateBtn').addEventListener('touchend', () => accelerate = false);
document.getElementById('settingsBtn').addEventListener('click', () => {
document.getElementById('qualitySlider').style.display =
document.getElementById('qualitySlider').style.display === 'none' ? 'flex' : 'none';
});
document.getElementById('shopButton').addEventListener('click', showShopScreen);
document.getElementById('backButton').addEventListener('click', hideShopScreen);
document.getElementById('rightArrowShop').addEventListener('click', () => changeCarColor('blue'));
document.getElementById('leftArrowShop').addEventListener('click', () => changeCarColor('red'));
document.getElementById('purchaseButton').addEventListener('click', purchaseCar);
document.getElementById('equipButton').addEventListener('click', equipCar);
document.addEventListener('dblclick', (e) => e.preventDefault());
}
function createPlayer(color) {
if (player) scene.remove(player);
player = createCarMesh(color);
player.position.y = 0.5;
scene.add(player);
}
function createShopCar(color) {
const carModelContainer = document.getElementById('carModelContainer');
carModelContainer.innerHTML = '';
const shopScene = new THREE.Scene();
const shopCamera = new THREE.PerspectiveCamera(75, carModelContainer.clientWidth / carModelContainer.clientHeight, 0.1, 1000);
shopCamera.position.set(0, 5, 10);
const shopRenderer = new THREE.WebGLRenderer({ antialias: true });
shopRenderer.setSize(carModelContainer.clientWidth, carModelContainer.clientHeight);
carModelContainer.appendChild(shopRenderer.domElement);
const shopCar = createCarMesh(color);
shopCar.position.set(0, 0.5, 0);
shopScene.add(shopCar);
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
shopScene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(0, 10, 10);
shopScene.add(directionalLight);
function animateShop() {
requestAnimationFrame(animateShop);
shopCar.rotation.y += 0.01;
shopRenderer.render(shopScene, shopCamera);
}
animateShop();
}
function createCarMesh(color) {
const playerMaterial = new THREE.MeshStandardMaterial({ color });
const carBody = new THREE.BoxGeometry(1.2, 0.6, 3);
const frontWing = new THREE.BoxGeometry(1.8, 0.12, 0.6);
const rearWing = new THREE.BoxGeometry(1.8, 0.12, 0.6);
const rearSpoiler = new THREE.BoxGeometry(0.12, 0.6, 0.6);
const frontWheel = new THREE.CylinderGeometry(0.36, 0.36, 0.24, 32);
const rearWheel = new THREE.CylinderGeometry(0.36, 0.36, 0.24, 32);
const bodyMesh = new THREE.Mesh(carBody, playerMaterial);
const frontWingMesh = new THREE.Mesh(frontWing, playerMaterial);
const rearWingMesh = new THREE.Mesh(rearWing, playerMaterial);
const rearSpoilerMesh = new THREE.Mesh(rearSpoiler, playerMaterial);
const frontWheelMeshL = new THREE.Mesh(frontWheel, playerMaterial);
const frontWheelMeshR = new THREE.Mesh(frontWheel, playerMaterial);
const rearWheelMeshL = new THREE.Mesh(rearWheel, playerMaterial);
const rearWheelMeshR = new THREE.Mesh(rearWheel, playerMaterial);
frontWingMesh.position.set(0, -0.3, 1.2);
rearWingMesh.position.set(0, -0.3, -1.2);
rearSpoilerMesh.position.set(0, 0.6, -1.2);
frontWheelMeshL.position.set(-0.84, -0.3, 1.44);
frontWheelMeshR.position.set(0.84, -0.3, 1.44);
rearWheelMeshL.position.set(-0.84, -0.3, -1.44);
rearWheelMeshR.position.set(0.84, -0.3, -1.44);
frontWheelMeshL.rotation.z = Math.PI / 2;
frontWheelMeshR.rotation.z = Math.PI / 2;
rearWheelMeshL.rotation.z = Math.PI / 2;
rearWheelMeshR.rotation.z = Math.PI / 2;
const car = new THREE.Group();
car.add(bodyMesh);
car.add(frontWingMesh);
car.add(rearWingMesh);
car.add(rearSpoilerMesh);
car.add(frontWheelMeshL);
car.add(frontWheelMeshR);
car.add(rearWheelMeshL);
car.add(rearWheelMeshR);
return car;
}
function showShopScreen() {
document.getElementById('shopScreen').style.display = 'flex';
updateShopUI();
createShopCar(carColor); // Ensure the correct car is displayed
}
function hideShopScreen() {
document.getElementById('shopScreen').style.display = 'none';
}
function changeCarColor(color) {
carColor = color;
createShopCar(color);
if (color === 'blue') {
document.getElementById('carCapabilities').style.display = 'block';
document.getElementById('acceleration').innerText = 'Acceleration: ——|———';
document.getElementById('topSpeed').innerText = 'Top Speed: 470 km/h';
if (gameState.purchasedCars.includes('blue')) {
document.getElementById('purchaseButton').style.display = 'none';
document.getElementById('equipButton').style.display = 'block';
} else {
document.getElementById('purchaseButton').style.display = 'block';
document.getElementById('equipButton').style.display = 'none';
}
} else {
document.getElementById('carCapabilities').style.display = 'none';
document.getElementById('purchaseButton').style.display = 'none';
document.getElementById('equipButton').style.display = 'block';
}
updateShopUI();
}
function purchaseCar() {
if (coinsCollected < 30) return;
if (!gameState.purchasedCars.includes('blue')) {
gameState.purchasedCars.push('blue');
coinsCollected -= 30;
document.getElementById('coinCounter').innerText = `Coins: ${coinsCollected}`;
document.getElementById('purchaseButton').innerText = 'Purchased successfully';
setTimeout(() => {
document.getElementById('purchaseButton').style.display = 'none';
document.getElementById('equipButton').style.display = 'block';
document.getElementById('purchaseButton').innerText = 'Purchase for 30 Coins';
}, 3000);
saveGameState();
}
}
function equipCar() {
gameState.equippedCar = carColor;
createPlayer(carColor);
if (carColor === 'blue') {
accelerationRate = 0.7;
maxSpeed = 470 / 36;
} else {
accelerationRate = 0.5;
maxSpeed = 450 / 36;
}
saveGameState();
updateShopUI();
}
function updateShopUI() {
if (carColor === 'red') {
document.getElementById('purchaseButton').style.display = 'none';
document.getElementById('equipButton').style.display = 'block';
document.getElementById('equipButton').innerText = gameState.equippedCar === 'red' ? 'Equipped' : 'Equip';
} else if (carColor === 'blue') {
document.getElementById('equipButton').innerText = gameState.equippedCar === 'blue' ? 'Equipped' : 'Equip';
}
}
function spawnOpponent() {
if (opponents.length > 5) return;
const opponentMaterial = new THREE.MeshStandardMaterial({ color: Math.random() * 0xffffff });
const carBody = new THREE.BoxGeometry(1.2, 0.6, 3);
const frontWing = new THREE.BoxGeometry(1.8, 0.12, 0.6);
const rearWing = new THREE.BoxGeometry(1.8, 0.12, 0.6);
const rearSpoiler = new THREE.BoxGeometry(0.12, 0.6, 0.6);
const frontWheel = new THREE.CylinderGeometry(0.36, 0.36, 0.24, 32);
const rearWheel = new THREE.CylinderGeometry(0.36, 0.36, 0.24, 32);
const bodyMesh = new THREE.Mesh(carBody, opponentMaterial);
const frontWingMesh = new THREE.Mesh(frontWing, opponentMaterial);
const rearWingMesh = new THREE.Mesh(rearWing, opponentMaterial);
const rearSpoilerMesh = new THREE.Mesh(rearSpoiler, opponentMaterial);
const frontWheelMeshL = new THREE.Mesh(frontWheel, opponentMaterial);
const frontWheelMeshR = new THREE.Mesh(frontWheel, opponentMaterial);
const rearWheelMeshL = new THREE.Mesh(rearWheel, opponentMaterial);
const rearWheelMeshR = new THREE.Mesh(rearWheel, opponentMaterial);
frontWingMesh.position.set(0, -0.3, 1.2);
rearWingMesh.position.set(0, -0.3, -1.2);
rearSpoilerMesh.position.set(0, 0.6, -1.2);
frontWheelMeshL.position.set(-0.84, -0.3, 1.44);
frontWheelMeshR.position.set(0.84, -0.3, 1.44);
rearWheelMeshL.position.set(-0.84, -0.3, -1.44);
rearWheelMeshR.position.set(0.84, -0.3, -1.44);
frontWheelMeshL.rotation.z = Math.PI / 2;
frontWheelMeshR.rotation.z = Math.PI / 2;
rearWheelMeshL.rotation.z = Math.PI / 2;
rearWheelMeshR.rotation.z = Math.PI / 2;
const opponent = new THREE.Group();
opponent.add(bodyMesh);
opponent.add(frontWingMesh);
opponent.add(rearWingMesh);
opponent.add(rearSpoilerMesh);
opponent.add(frontWheelMeshL);
opponent.add(frontWheelMeshR);
opponent.add(rearWheelMeshL);
opponent.add(rearWheelMeshR);
const spawnX = Math.floor(Math.random() * 10) * 2 - 10;
opponent.position.set(spawnX, 0.5, -50);
opponents.push(opponent);
scene.add(opponent);
}
function spawnCoin() {
if (coins.length > 5) return;
const coinGeometry = new THREE.CylinderGeometry(0.2, 0.2, 0.05, 32);
const coinMaterial = new THREE.MeshStandardMaterial({ color: 0xffff00 });
const coin = new THREE.Mesh(coinGeometry, coinMaterial);
coin.rotation.x = Math.PI / 2;
const spawnX = Math.floor(Math.random() * roadWidth) - roadWidth / 2;
coin.position.set(spawnX, 0.25, -50);
coins.push(coin);
scene.add(coin);
}
function updateQuality() {
const qualityLevel = document.getElementById('quality').value;
if (qualityLevel === 'low') {
renderer.setPixelRatio(0.5);
renderer.setSize(window.innerWidth / 2, window.innerHeight / 2);
} else if (qualityLevel === 'medium') {
renderer.setPixelRatio(1);
renderer.setSize(window.innerWidth, window.innerHeight);
} else if (qualityLevel === 'high') {
renderer.setPixelRatio(1);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setQuality('high');
}
}
function animate() {
if (gameOver) return;
requestAnimationFrame(animate);
const now = performance.now();
const delta = (now - lastTime) / 1000;
lastTime = now;
if (moveLeft && player.position.x > -roadWidth / 2 + 0.5) player.position.x -= currentSpeed * delta;
if (moveRight && player.position.x < roadWidth / 2 - 0.5) player.position.x += currentSpeed * delta;
if (accelerate) {
currentSpeed += accelerationRate * delta;
currentSpeed = Math.min(currentSpeed, maxSpeed);
} else if (brake) {
currentSpeed -= brakeRate * delta;
currentSpeed = Math.max(currentSpeed, minSpeed);
}
document.getElementById('speedometer').textContent = `Speed: ${Math.round(currentSpeed * 36)} km/h`;
frameCount++;
if (frameCount % opponentSpawnInterval === 0) {
spawnOpponent();
}
if (frameCount % coinSpawnInterval === 0) {
spawnCoin();
}
for (let i = 0; i < opponents.length; i++) {
opponents[i].position.z += currentSpeed * delta;
if (opponents[i].position.z > 10) {
scene.remove(opponents[i]);
opponents.splice(i, 1);
i--;
}
if (player.position.distanceTo(opponents[i].position) < 1.5) {
document.getElementById('gameOver').style.display = 'block';
document.getElementById('shopButton').style.display = 'block';
gameOver = true;
return;
}
}
for (let i = 0; i < coins.length; i++) {
coins[i].position.z += currentSpeed * delta;
if (coins[i].position.z > 10) {
scene.remove(coins[i]);
coins.splice(i, 1);
i--;
}
if (player.position.distanceTo(coins[i].position) < 1) {
coinsCollected++;
document.getElementById('coinCounter').innerText = `Coins: ${coinsCollected}`;
scene.remove(coins[i]);
coins.splice(i, 1);
i--;
saveGameState();
}
}
roadLines.forEach(line => {
line.position.z += currentSpeed * delta * 2;
if (line.position.z > 10) {
line.position.z = -90;
}
});
scene.children.forEach(child => {
if (child.type === 'Group' && child.children[0].geometry.type === 'CylinderGeometry') {
child.position.z += currentSpeed * delta;
if (child.position.z > 10) {
child.position.z = -50;
}
}
});
camera.position.x = player.position.x;
camera.position.z = player.position.z + 10;
camera.lookAt(player.position);
renderer.render(scene, camera);
}
function loadGameState() {
const savedState = localStorage.getItem('gameState');
if (savedState) {
gameState = JSON.parse(savedState);
coinsCollected = gameState.coins;
document.getElementById('coinCounter').innerText = `Coins: ${coinsCollected}`;
carColor = gameState.equippedCar;
createPlayer(carColor);
if (carColor === 'blue') {
accelerationRate = 0.7;
maxSpeed = 470 / 36;
} else {
accelerationRate = 0.5;
maxSpeed = 450 / 36;
}
}
}
function saveGameState() {
gameState.coins = coinsCollected;
localStorage.setItem('gameState', JSON.stringify(gameState));
}
</script>