|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>GTA 5 Online Clone</title> |
|
<style> |
|
body { |
|
margin: 0; |
|
padding: 0; |
|
overflow: hidden; |
|
background-color: #222; |
|
font-family: Arial, sans-serif; |
|
} |
|
|
|
#gameContainer { |
|
position: relative; |
|
width: 100vw; |
|
height: 100vh; |
|
} |
|
|
|
#gameCanvas { |
|
display: block; |
|
background-color: #87CEEB; |
|
} |
|
|
|
#uiContainer { |
|
position: absolute; |
|
top: 10px; |
|
left: 10px; |
|
color: white; |
|
font-size: 16px; |
|
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7); |
|
pointer-events: none; |
|
} |
|
|
|
#startScreen { |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
background-color: rgba(0, 0, 0, 0.8); |
|
display: flex; |
|
flex-direction: column; |
|
justify-content: center; |
|
align-items: center; |
|
color: white; |
|
z-index: 100; |
|
} |
|
|
|
#startScreen h1 { |
|
font-size: 48px; |
|
color: #FF4500; |
|
margin-bottom: 30px; |
|
text-shadow: 0 0 10px rgba(255, 69, 0, 0.7); |
|
} |
|
|
|
#startButton { |
|
padding: 15px 30px; |
|
font-size: 24px; |
|
background-color: #FF4500; |
|
color: white; |
|
border: none; |
|
border-radius: 5px; |
|
cursor: pointer; |
|
transition: all 0.3s; |
|
} |
|
|
|
#startButton:hover { |
|
background-color: #FF6347; |
|
transform: scale(1.05); |
|
} |
|
|
|
#wantedLevel { |
|
position: absolute; |
|
top: 10px; |
|
right: 10px; |
|
color: white; |
|
font-size: 24px; |
|
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7); |
|
} |
|
|
|
.wanted-star { |
|
color: yellow; |
|
margin-left: 5px; |
|
} |
|
|
|
#missionText { |
|
position: absolute; |
|
bottom: 20px; |
|
left: 0; |
|
width: 100%; |
|
text-align: center; |
|
color: white; |
|
font-size: 24px; |
|
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7); |
|
opacity: 0; |
|
transition: opacity 0.5s; |
|
} |
|
|
|
#radio { |
|
position: absolute; |
|
bottom: 60px; |
|
left: 10px; |
|
color: white; |
|
font-size: 14px; |
|
font-style: italic; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div id="gameContainer"> |
|
<canvas id="gameCanvas"></canvas> |
|
|
|
<div id="uiContainer"> |
|
<div>Money: $<span id="money">0</span></div> |
|
<div>Score: <span id="score">0</span></div> |
|
<div>Health: <span id="health">100</span>%</div> |
|
<div>Speed: <span id="speed">0</span> mph</div> |
|
</div> |
|
|
|
<div id="wantedLevel"> |
|
Wanted: <span id="wantedStars"></span> |
|
</div> |
|
|
|
<div id="missionText"></div> |
|
|
|
<div id="radio"> |
|
Radio: <span id="radioStation">Off</span> |
|
</div> |
|
|
|
<div id="startScreen"> |
|
<h1>GTA 5 Online Clone</h1> |
|
<p>Drive around the city, complete missions, and avoid the police!</p> |
|
<p>Controls: Arrow keys to drive, Space to handbrake, H to honk</p> |
|
<button id="startButton">START GAME</button> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
|
|
const canvas = document.getElementById('gameCanvas'); |
|
const ctx = canvas.getContext('2d'); |
|
const startScreen = document.getElementById('startScreen'); |
|
const startButton = document.getElementById('startButton'); |
|
const moneyDisplay = document.getElementById('money'); |
|
const scoreDisplay = document.getElementById('score'); |
|
const healthDisplay = document.getElementById('health'); |
|
const speedDisplay = document.getElementById('speed'); |
|
const wantedStarsDisplay = document.getElementById('wantedStars'); |
|
const missionText = document.getElementById('missionText'); |
|
const radioStation = document.getElementById('radioStation'); |
|
|
|
|
|
canvas.width = window.innerWidth; |
|
canvas.height = window.innerHeight; |
|
|
|
|
|
let gameRunning = false; |
|
let lastTime = 0; |
|
let score = 0; |
|
let money = 0; |
|
let playerHealth = 100; |
|
let wantedLevel = 0; |
|
let currentMission = null; |
|
let missionCompleteTimeout = null; |
|
|
|
|
|
const radioStations = [ |
|
"Off", |
|
"Los Santos Rock Radio", |
|
"Space 103.2", |
|
"West Coast Classics", |
|
"FlyLo FM", |
|
"The Lowdown 91.1", |
|
"Radio Mirror Park" |
|
]; |
|
let currentRadioIndex = 0; |
|
|
|
|
|
const player = { |
|
x: canvas.width / 2, |
|
y: canvas.height / 2, |
|
width: 40, |
|
height: 70, |
|
color: '#FF4500', |
|
speed: 0, |
|
maxSpeed: 10, |
|
acceleration: 0.1, |
|
deceleration: 0.05, |
|
rotation: 0, |
|
rotationSpeed: 0.05, |
|
health: 100, |
|
lastCollisionTime: 0 |
|
}; |
|
|
|
|
|
const roads = []; |
|
const buildings = []; |
|
const trees = []; |
|
const npcCars = []; |
|
const policeCars = []; |
|
const collectibles = []; |
|
const explosions = []; |
|
|
|
|
|
function generateCity() { |
|
|
|
roads.length = 0; |
|
buildings.length = 0; |
|
trees.length = 0; |
|
|
|
|
|
const roadWidth = 200; |
|
const horizontalRoadCount = Math.ceil(canvas.height / 300); |
|
const verticalRoadCount = Math.ceil(canvas.width / 300); |
|
|
|
for (let i = 0; i < horizontalRoadCount; i++) { |
|
roads.push({ |
|
x: 0, |
|
y: i * 300, |
|
width: canvas.width, |
|
height: roadWidth, |
|
horizontal: true |
|
}); |
|
} |
|
|
|
for (let i = 0; i < verticalRoadCount; i++) { |
|
roads.push({ |
|
x: i * 300, |
|
y: 0, |
|
width: roadWidth, |
|
height: canvas.height, |
|
horizontal: false |
|
}); |
|
} |
|
|
|
|
|
for (let i = 0; i < 50; i++) { |
|
const size = 50 + Math.random() * 100; |
|
buildings.push({ |
|
x: Math.random() * canvas.width, |
|
y: Math.random() * canvas.height, |
|
width: size, |
|
height: size, |
|
color: `rgb(${100 + Math.random() * 155}, ${100 + Math.random() * 155}, ${100 + Math.random() * 155})` |
|
}); |
|
} |
|
|
|
|
|
for (let i = 0; i < 100; i++) { |
|
trees.push({ |
|
x: Math.random() * canvas.width, |
|
y: Math.random() * canvas.height, |
|
size: 20 + Math.random() * 30, |
|
color: '#228B22' |
|
}); |
|
} |
|
|
|
|
|
const road = roads[0]; |
|
player.x = road.x + road.width / 2; |
|
player.y = road.y + road.height / 2; |
|
} |
|
|
|
|
|
function generateNPC() { |
|
npcCars.length = 0; |
|
|
|
for (let i = 0; i < 10; i++) { |
|
const road = roads[Math.floor(Math.random() * roads.length)]; |
|
|
|
npcCars.push({ |
|
x: road.horizontal ? |
|
Math.random() * canvas.width : |
|
road.x + road.width / 2, |
|
y: road.horizontal ? |
|
road.y + road.height / 2 : |
|
Math.random() * canvas.height, |
|
width: 30, |
|
height: 50, |
|
color: `hsl(${Math.random() * 360}, 70%, 50%)`, |
|
speed: 2 + Math.random() * 3, |
|
direction: road.horizontal ? (Math.random() > 0.5 ? 0 : Math.PI) : (Math.random() > 0.5 ? Math.PI/2 : 3*Math.PI/2), |
|
road: road |
|
}); |
|
} |
|
} |
|
|
|
|
|
function generateCollectibles() { |
|
collectibles.length = 0; |
|
|
|
for (let i = 0; i < 15; i++) { |
|
collectibles.push({ |
|
x: Math.random() * canvas.width, |
|
y: Math.random() * canvas.height, |
|
radius: 15, |
|
type: Math.random() > 0.5 ? 'money' : 'health', |
|
value: Math.random() > 0.7 ? 100 : 50, |
|
collected: false |
|
}); |
|
} |
|
} |
|
|
|
|
|
function drawRotatedRect(x, y, width, height, rotation, color) { |
|
ctx.save(); |
|
ctx.translate(x, y); |
|
ctx.rotate(rotation); |
|
ctx.fillStyle = color; |
|
ctx.fillRect(-width / 2, -height / 2, width, height); |
|
|
|
|
|
ctx.fillStyle = '#222'; |
|
ctx.fillRect(-width / 2 + 5, -height / 2 + 5, width - 10, height / 3); |
|
ctx.restore(); |
|
} |
|
|
|
|
|
function checkCollision(rect1, rect2) { |
|
return rect1.x < rect2.x + rect2.width && |
|
rect1.x + rect1.width > rect2.x && |
|
rect1.y < rect2.y + rect2.height && |
|
rect1.y + rect1.height > rect2.y; |
|
} |
|
|
|
|
|
function pointInRect(x, y, rect) { |
|
return x > rect.x && x < rect.x + rect.width && |
|
y > rect.y && y < rect.y + rect.height; |
|
} |
|
|
|
|
|
function getDistance(x1, y1, x2, y2) { |
|
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); |
|
} |
|
|
|
|
|
function createExplosion(x, y) { |
|
explosions.push({ |
|
x: x, |
|
y: y, |
|
radius: 5, |
|
maxRadius: 50, |
|
alpha: 1, |
|
color: `hsl(${Math.random() * 60}, 100%, 50%)` |
|
}); |
|
} |
|
|
|
|
|
function spawnPolice() { |
|
const count = wantedLevel; |
|
|
|
for (let i = 0; i < count; i++) { |
|
|
|
if (policeCars.length >= 5) break; |
|
|
|
|
|
let x, y; |
|
const side = Math.floor(Math.random() * 4); |
|
|
|
switch (side) { |
|
case 0: |
|
x = Math.random() * canvas.width; |
|
y = -50; |
|
break; |
|
case 1: |
|
x = canvas.width + 50; |
|
y = Math.random() * canvas.height; |
|
break; |
|
case 2: |
|
x = Math.random() * canvas.width; |
|
y = canvas.height + 50; |
|
break; |
|
case 3: |
|
x = -50; |
|
y = Math.random() * canvas.height; |
|
break; |
|
} |
|
|
|
policeCars.push({ |
|
x: x, |
|
y: y, |
|
width: 35, |
|
height: 60, |
|
color: '#4169E1', |
|
speed: 4 + wantedLevel, |
|
rotation: Math.atan2(player.y - y, player.x - x), |
|
health: 100, |
|
lastShotTime: 0 |
|
}); |
|
} |
|
} |
|
|
|
|
|
function updateWantedLevel(level) { |
|
wantedLevel = Math.min(Math.max(level, 0), 5); |
|
|
|
|
|
wantedStarsDisplay.innerHTML = ''; |
|
for (let i = 0; i < wantedLevel; i++) { |
|
wantedStarsDisplay.innerHTML += '<span class="wanted-star">★</span>'; |
|
} |
|
|
|
|
|
if (wantedLevel > 0 && policeCars.length < wantedLevel) { |
|
spawnPolice(); |
|
} |
|
} |
|
|
|
|
|
function showMission(text) { |
|
missionText.textContent = text; |
|
missionText.style.opacity = 1; |
|
|
|
if (missionCompleteTimeout) { |
|
clearTimeout(missionCompleteTimeout); |
|
} |
|
|
|
missionCompleteTimeout = setTimeout(() => { |
|
missionText.style.opacity = 0; |
|
}, 3000); |
|
} |
|
|
|
|
|
function startRandomMission() { |
|
const missions = [ |
|
{ |
|
name: "Collect Cash", |
|
description: "Collect $500 in cash", |
|
target: 500, |
|
current: 0, |
|
type: "money" |
|
}, |
|
{ |
|
name: "Avoid Police", |
|
description: "Avoid police for 30 seconds", |
|
target: 30, |
|
current: 0, |
|
type: "avoid" |
|
}, |
|
{ |
|
name: "Destroy Police", |
|
description: "Destroy 3 police cars", |
|
target: 3, |
|
current: 0, |
|
type: "destroy" |
|
}, |
|
{ |
|
name: "Speed Demon", |
|
description: "Drive over 100 mph for 5 seconds", |
|
target: 5, |
|
current: 0, |
|
type: "speed" |
|
} |
|
]; |
|
|
|
currentMission = missions[Math.floor(Math.random() * missions.length)]; |
|
showMission(`MISSION: ${currentMission.name} - ${currentMission.description}`); |
|
} |
|
|
|
|
|
function checkMissionProgress() { |
|
if (!currentMission) return; |
|
|
|
switch (currentMission.type) { |
|
case "money": |
|
currentMission.current = money; |
|
break; |
|
case "avoid": |
|
if (wantedLevel === 0) { |
|
currentMission.current += 1/60; |
|
} |
|
break; |
|
case "destroy": |
|
|
|
break; |
|
case "speed": |
|
const speedMph = Math.abs(player.speed) * 10; |
|
if (speedMph > 100) { |
|
currentMission.current += 1/60; |
|
} |
|
break; |
|
} |
|
|
|
|
|
if (currentMission.current >= currentMission.target) { |
|
const reward = currentMission.target * 10; |
|
money += reward; |
|
moneyDisplay.textContent = money; |
|
|
|
showMission(`MISSION COMPLETE! +$${reward}`); |
|
currentMission = null; |
|
|
|
|
|
setTimeout(startRandomMission, 5000); |
|
} |
|
} |
|
|
|
|
|
function gameLoop(timestamp) { |
|
if (!gameRunning) return; |
|
|
|
|
|
const deltaTime = timestamp - lastTime; |
|
lastTime = timestamp; |
|
|
|
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height); |
|
|
|
|
|
ctx.fillStyle = '#333'; |
|
roads.forEach(road => { |
|
ctx.fillRect(road.x, road.y, road.width, road.height); |
|
|
|
|
|
ctx.fillStyle = 'white'; |
|
if (road.horizontal) { |
|
const dashLength = 30; |
|
const gapLength = 20; |
|
const centerY = road.y + road.height / 2 - 2; |
|
|
|
for (let x = 0; x < road.width; x += dashLength + gapLength) { |
|
ctx.fillRect(road.x + x, centerY, dashLength, 4); |
|
} |
|
} else { |
|
const dashLength = 30; |
|
const gapLength = 20; |
|
const centerX = road.x + road.width / 2 - 2; |
|
|
|
for (let y = 0; y < road.height; y += dashLength + gapLength) { |
|
ctx.fillRect(centerX, road.y + y, 4, dashLength); |
|
} |
|
} |
|
ctx.fillStyle = '#333'; |
|
}); |
|
|
|
|
|
buildings.forEach(building => { |
|
ctx.fillStyle = building.color; |
|
ctx.fillRect(building.x, building.y, building.width, building.height); |
|
|
|
|
|
const windowSize = 8; |
|
const windowMargin = 10; |
|
ctx.fillStyle = '#ADD8E6'; |
|
|
|
for (let y = building.y + windowMargin; y < building.y + building.height - windowMargin; y += windowSize + windowMargin) { |
|
for (let x = building.x + windowMargin; x < building.x + building.width - windowMargin; x += windowSize + windowMargin) { |
|
|
|
if (Math.random() > 0.3) { |
|
ctx.fillRect(x, y, windowSize, windowSize); |
|
} |
|
} |
|
} |
|
}); |
|
|
|
|
|
trees.forEach(tree => { |
|
|
|
ctx.fillStyle = '#8B4513'; |
|
ctx.fillRect(tree.x - 3, tree.y - tree.size/2, 6, tree.size/2); |
|
|
|
|
|
ctx.fillStyle = tree.color; |
|
ctx.beginPath(); |
|
ctx.arc(tree.x, tree.y - tree.size/2, tree.size/2, 0, Math.PI * 2); |
|
ctx.fill(); |
|
}); |
|
|
|
|
|
npcCars.forEach((car, index) => { |
|
|
|
if (car.road.horizontal) { |
|
car.x += Math.cos(car.direction) * car.speed; |
|
|
|
|
|
if (car.direction === 0 && car.x > canvas.width + car.width/2) { |
|
car.x = -car.width/2; |
|
} else if (car.direction === Math.PI && car.x < -car.width/2) { |
|
car.x = canvas.width + car.width/2; |
|
} |
|
} else { |
|
car.y += Math.sin(car.direction) * car.speed; |
|
|
|
|
|
if (car.direction === Math.PI/2 && car.y > canvas.height + car.height/2) { |
|
car.y = -car.height/2; |
|
} else if (car.direction === 3*Math.PI/2 && car.y < -car.height/2) { |
|
car.y = canvas.height + car.height/2; |
|
} |
|
} |
|
|
|
|
|
drawRotatedRect(car.x, car.y, car.width, car.height, car.direction, car.color); |
|
|
|
|
|
const now = Date.now(); |
|
if (checkCollision( |
|
{x: player.x - player.width/2, y: player.y - player.height/2, width: player.width, height: player.height}, |
|
{x: car.x - car.width/2, y: car.y - car.height/2, width: car.width, height: car.height} |
|
) && now - player.lastCollisionTime > 1000) { |
|
player.speed *= -0.5; |
|
player.health -= 10; |
|
healthDisplay.textContent = player.health; |
|
player.lastCollisionTime = now; |
|
|
|
|
|
updateWantedLevel(wantedLevel + 1); |
|
|
|
|
|
createExplosion((player.x + car.x) / 2, (player.y + car.y) / 2); |
|
|
|
|
|
if (currentMission && currentMission.type === "destroy") { |
|
currentMission.current++; |
|
showMission(`MISSION: ${currentMission.name} - ${currentMission.current}/${currentMission.target}`); |
|
} |
|
} |
|
}); |
|
|
|
|
|
policeCars.forEach((police, index) => { |
|
|
|
const angle = Math.atan2(player.y - police.y, player.x - police.x); |
|
police.rotation = angle; |
|
|
|
police.x += Math.cos(angle) * police.speed; |
|
police.y += Math.sin(angle) * police.speed; |
|
|
|
|
|
drawRotatedRect(police.x, police.y, police.width, police.height, police.rotation, police.color); |
|
|
|
|
|
const now = Date.now(); |
|
if (now % 500 < 250) { |
|
|
|
ctx.fillStyle = 'red'; |
|
ctx.beginPath(); |
|
ctx.arc( |
|
police.x + Math.cos(police.rotation - Math.PI/2) * 20, |
|
police.y + Math.sin(police.rotation - Math.PI/2) * 20, |
|
5, 0, Math.PI * 2 |
|
); |
|
ctx.fill(); |
|
|
|
|
|
ctx.fillStyle = 'blue'; |
|
ctx.beginPath(); |
|
ctx.arc( |
|
police.x + Math.cos(police.rotation + Math.PI/2) * 20, |
|
police.y + Math.sin(police.rotation + Math.PI/2) * 20, |
|
5, 0, Math.PI * 2 |
|
); |
|
ctx.fill(); |
|
} |
|
|
|
|
|
const now = Date.now(); |
|
if (checkCollision( |
|
{x: player.x - player.width/2, y: player.y - player.height/2, width: player.width, height: player.height}, |
|
{x: police.x - police.width/2, y: police.y - police.height/2, width: police.width, height: police.height} |
|
) && now - player.lastCollisionTime > 1000) { |
|
player.speed *= -0.5; |
|
player.health -= 20; |
|
healthDisplay.textContent = player.health; |
|
player.lastCollisionTime = now; |
|
|
|
|
|
createExplosion((player.x + police.x) / 2, (player.y + police.y) / 2); |
|
|
|
|
|
if (currentMission && currentMission.type === "destroy") { |
|
currentMission.current++; |
|
showMission(`MISSION: ${currentMission.name} - ${currentMission.current}/${currentMission.target}`); |
|
} |
|
} |
|
|
|
|
|
if (wantedLevel === 0 && getDistance(police.x, police.y, player.x, player.y) > 1000) { |
|
policeCars.splice(index, 1); |
|
} |
|
}); |
|
|
|
|
|
collectibles.forEach((item, index) => { |
|
if (item.collected) return; |
|
|
|
ctx.beginPath(); |
|
ctx.arc(item.x, item.y, item.radius, 0, Math.PI * 2); |
|
|
|
if (item.type === 'money') { |
|
ctx.fillStyle = 'gold'; |
|
ctx.fill(); |
|
|
|
|
|
ctx.fillStyle = '#333'; |
|
ctx.font = `${item.radius}px Arial`; |
|
ctx.textAlign = 'center'; |
|
ctx.textBaseline = 'middle'; |
|
ctx.fillText('$', item.x, item.y); |
|
} else { |
|
ctx.fillStyle = 'red'; |
|
ctx.fill(); |
|
|
|
|
|
ctx.fillStyle = 'white'; |
|
ctx.font = `${item.radius}px Arial`; |
|
ctx.textAlign = 'center'; |
|
ctx.textBaseline = 'middle'; |
|
ctx.fillText('+', item.x, item.y); |
|
} |
|
|
|
|
|
if (getDistance(item.x, item.y, player.x, player.y) < item.radius + player.width/2) { |
|
item.collected = true; |
|
|
|
if (item.type === 'money') { |
|
money += item.value; |
|
moneyDisplay.textContent = money; |
|
showMission(`+$${item.value}`); |
|
} else { |
|
player.health = Math.min(player.health + item.value, 100); |
|
healthDisplay.textContent = player.health; |
|
showMission(`+${item.value}% Health`); |
|
} |
|
|
|
|
|
checkMissionProgress(); |
|
} |
|
}); |
|
|
|
|
|
explosions.forEach((explosion, index) => { |
|
explosion.radius += 1; |
|
explosion.alpha -= 0.02; |
|
|
|
ctx.globalAlpha = explosion.alpha; |
|
ctx.fillStyle = explosion.color; |
|
ctx.beginPath(); |
|
ctx.arc(explosion.x, explosion.y, explosion.radius, 0, Math.PI * 2); |
|
ctx.fill(); |
|
|
|
ctx.globalAlpha = 1; |
|
|
|
if (explosion.alpha <= 0) { |
|
explosions.splice(index, 1); |
|
} |
|
}); |
|
|
|
|
|
drawRotatedRect(player.x, player.y, player.width, player.height, player.rotation, player.color); |
|
|
|
|
|
ctx.fillStyle = 'red'; |
|
ctx.fillRect(10, canvas.height - 30, 200 * (player.health / 100), 20); |
|
ctx.strokeStyle = 'white'; |
|
ctx.strokeRect(10, canvas.height - 30, 200, 20); |
|
|
|
|
|
speedDisplay.textContent = Math.floor(Math.abs(player.speed) * 10); |
|
|
|
|
|
if (player.health <= 0) { |
|
showMission("WASTED"); |
|
setTimeout(resetGame, 3000); |
|
gameRunning = false; |
|
return; |
|
} |
|
|
|
|
|
checkMissionProgress(); |
|
|
|
|
|
if (wantedLevel > 0 && Math.random() < 0.005) { |
|
updateWantedLevel(wantedLevel - 1); |
|
} |
|
|
|
|
|
requestAnimationFrame(gameLoop); |
|
} |
|
|
|
|
|
const keys = { |
|
ArrowUp: false, |
|
ArrowDown: false, |
|
ArrowLeft: false, |
|
ArrowRight: false, |
|
' ': false, |
|
h: false |
|
}; |
|
|
|
let lastHonkTime = 0; |
|
|
|
document.addEventListener('keydown', (e) => { |
|
if (e.key in keys) { |
|
keys[e.key] = true; |
|
|
|
|
|
if (e.key === 'h' && Date.now() - lastHonkTime > 1000) { |
|
lastHonkTime = Date.now(); |
|
showMission("HONK!"); |
|
|
|
|
|
if (wantedLevel < 1) { |
|
updateWantedLevel(1); |
|
} |
|
} |
|
|
|
|
|
if (e.key === 'r') { |
|
currentRadioIndex = (currentRadioIndex + 1) % radioStations.length; |
|
radioStation.textContent = radioStations[currentRadioIndex]; |
|
} |
|
} |
|
}); |
|
|
|
document.addEventListener('keyup', (e) => { |
|
if (e.key in keys) { |
|
keys[e.key] = false; |
|
} |
|
}); |
|
|
|
|
|
function updatePlayer() { |
|
if (!gameRunning) return; |
|
|
|
|
|
if (keys.ArrowUp) { |
|
player.speed += player.acceleration; |
|
} else if (keys.ArrowDown) { |
|
player.speed -= player.acceleration; |
|
} else { |
|
|
|
if (player.speed > 0) { |
|
player.speed = Math.max(player.speed - player.deceleration, 0); |
|
} else if (player.speed < 0) { |
|
player.speed = Math.min(player.speed + player.deceleration, 0); |
|
} |
|
} |
|
|
|
|
|
if (keys[' ']) { |
|
player.speed *= 0.95; |
|
} |
|
|
|
|
|
if (keys.ArrowLeft) { |
|
player.rotation -= player.rotationSpeed * Math.abs(player.speed) / player.maxSpeed; |
|
} |
|
if (keys.ArrowRight) { |
|
player.rotation += player.rotationSpeed * Math.abs(player.speed) / player.maxSpeed; |
|
} |
|
|
|
|
|
if (player.speed > player.maxSpeed) { |
|
player.speed = player.maxSpeed; |
|
} else if (player.speed < -player.maxSpeed / 2) { |
|
player.speed = -player.maxSpeed / 2; |
|
} |
|
|
|
|
|
player.x += Math.cos(player.rotation) * player.speed; |
|
player.y += Math.sin(player.rotation) * player.speed; |
|
|
|
|
|
if (player.x < -player.width) player.x = canvas.width + player.width; |
|
if (player.x > canvas.width + player.width) player.x = -player.width; |
|
if (player.y < -player.height) player.y = canvas.height + player.height; |
|
if (player.y > canvas.height + player.height) player.y = -player.height; |
|
|
|
|
|
let onRoad = false; |
|
for (const road of roads) { |
|
if (pointInRect(player.x, player.y, road)) { |
|
onRoad = true; |
|
break; |
|
} |
|
} |
|
|
|
if (!onRoad && player.speed > 3 && Math.random() < 0.01) { |
|
updateWantedLevel(wantedLevel + 1); |
|
} |
|
} |
|
|
|
|
|
function startGame() { |
|
startScreen.style.display = 'none'; |
|
gameRunning = true; |
|
lastTime = performance.now(); |
|
|
|
|
|
score = 0; |
|
money = 0; |
|
playerHealth = 100; |
|
wantedLevel = 0; |
|
|
|
|
|
generateCity(); |
|
generateNPC(); |
|
generateCollectibles(); |
|
|
|
|
|
setTimeout(startRandomMission, 2000); |
|
|
|
|
|
requestAnimationFrame(gameLoop); |
|
|
|
|
|
setInterval(updatePlayer, 1000/60); |
|
} |
|
|
|
|
|
function resetGame() { |
|
startScreen.style.display = 'flex'; |
|
gameRunning = false; |
|
|
|
|
|
policeCars.length = 0; |
|
explosions.length = 0; |
|
} |
|
|
|
|
|
startButton.addEventListener('click', startGame); |
|
|
|
|
|
window.addEventListener('resize', () => { |
|
canvas.width = window.innerWidth; |
|
canvas.height = window.innerHeight; |
|
|
|
if (gameRunning) { |
|
|
|
generateCity(); |
|
generateNPC(); |
|
generateCollectibles(); |
|
} |
|
}); |
|
</script> |
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Greats/clone" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
</html> |