| <!DOCTYPE html>
|
| <html lang="en">
|
|
|
| <head>
|
| <meta charset="UTF-8">
|
| <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| <title>Retro GTA 1-Style Game</title>
|
| <script src="https://cdn.tailwindcss.com"></script>
|
| <style>
|
| body {
|
| margin: 0;
|
| padding: 0;
|
| overflow: hidden;
|
| background-color: #222;
|
| font-family: 'Courier New', monospace;
|
| }
|
|
|
| #game-container {
|
| position: relative;
|
| width: 100vw;
|
| height: 100vh;
|
| display: flex;
|
| justify-content: center;
|
| align-items: center;
|
| }
|
|
|
| #game-canvas {
|
| background-color: #333;
|
| border: 4px solid #555;
|
| box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
|
| image-rendering: pixelated;
|
| }
|
|
|
| #hud {
|
| position: absolute;
|
| top: 10px;
|
| left: 10px;
|
| color: white;
|
| background-color: rgba(0, 0, 0, 0.7);
|
| padding: 10px;
|
| border: 2px solid #555;
|
| font-size: 14px;
|
| }
|
|
|
| #menu {
|
| position: absolute;
|
| top: 50%;
|
| left: 50%;
|
| transform: translate(-50%, -50%);
|
| background-color: rgba(0, 0, 0, 0.8);
|
| border: 4px solid #555;
|
| padding: 20px;
|
| color: white;
|
| text-align: center;
|
| z-index: 100;
|
| }
|
|
|
| .pixel-button {
|
| background-color: #444;
|
| color: white;
|
| border: 2px solid #666;
|
| padding: 10px 20px;
|
| margin: 5px;
|
| font-family: 'Courier New', monospace;
|
| cursor: pointer;
|
| transition: all 0.2s;
|
| }
|
|
|
| .pixel-button:hover {
|
| background-color: #666;
|
| border-color: #888;
|
| }
|
|
|
| .mission-marker {
|
| position: absolute;
|
| width: 16px;
|
| height: 16px;
|
| background-color: red;
|
| border: 2px solid white;
|
| border-radius: 50%;
|
| transform: translate(-50%, -50%);
|
| }
|
|
|
| .car {
|
| position: absolute;
|
| width: 32px;
|
| height: 32px;
|
| background-color: blue;
|
| transform-origin: center;
|
| }
|
|
|
| .pedestrian {
|
| position: absolute;
|
| width: 12px;
|
| height: 12px;
|
| background-color: green;
|
| border-radius: 50%;
|
| }
|
|
|
| .building {
|
| position: absolute;
|
| background-color: #555;
|
| border: 2px solid #333;
|
| }
|
|
|
| .road {
|
| position: absolute;
|
| background-color: #444;
|
| }
|
| </style>
|
| </head>
|
|
|
| <body>
|
| <div id="game-container">
|
| <canvas id="game-canvas"></canvas>
|
|
|
| <div id="hud">
|
| <div>Time: <span id="time">12:00</span></div>
|
| <div>Score: <span id="score">0</span></div>
|
| <div>Wanted: <span id="wanted">★</span></div>
|
| <div>Health: <span id="health">100%</span></div>
|
| <div>Money: $<span id="money">1000</span></div>
|
| </div>
|
|
|
| <div id="menu" style="display: none;">
|
| <h1 class="text-2xl mb-4">RETRO GTA</h1>
|
| <div class="mb-6">
|
| <button id="start-game" class="pixel-button">START GAME</button>
|
| <button id="controls" class="pixel-button">CONTROLS</button>
|
| </div>
|
| <div id="controls-info" style="display: none;">
|
| <p class="mb-2">WASD or Arrow Keys - Move</p>
|
| <p class="mb-2">Space - Handbrake</p>
|
| <p class="mb-2">E - Enter/Exit Vehicle</p>
|
| <p class="mb-2">M - Mission Start</p>
|
| <button id="back-to-menu" class="pixel-button mt-4">BACK</button>
|
| </div>
|
| </div>
|
| </div>
|
|
|
| <script>
|
|
|
| const gameState = {
|
| player: {
|
| x: 400,
|
| y: 300,
|
| speed: 0,
|
| angle: 0,
|
| maxSpeed: 5,
|
| acceleration: 0.1,
|
| rotationSpeed: 3,
|
| inVehicle: true,
|
| health: 100,
|
| money: 1000,
|
| score: 0,
|
| wantedLevel: 0
|
| },
|
| time: {
|
| hours: 12,
|
| minutes: 0
|
| },
|
| vehicles: [],
|
| pedestrians: [],
|
| buildings: [],
|
| roads: [],
|
| mission: {
|
| active: false,
|
| marker: { x: 600, y: 200 }
|
| },
|
| keys: {}
|
| };
|
|
|
|
|
| const canvas = document.getElementById('game-canvas');
|
| const ctx = canvas.getContext('2d');
|
| const menu = document.getElementById('menu');
|
| const startButton = document.getElementById('start-game');
|
| const controlsButton = document.getElementById('controls');
|
| const controlsInfo = document.getElementById('controls-info');
|
| const backButton = document.getElementById('back-to-menu');
|
| const timeDisplay = document.getElementById('time');
|
| const scoreDisplay = document.getElementById('score');
|
| const wantedDisplay = document.getElementById('wanted');
|
| const healthDisplay = document.getElementById('health');
|
| const moneyDisplay = document.getElementById('money');
|
|
|
|
|
| function resizeCanvas() {
|
| canvas.width = window.innerWidth * 0.9;
|
| canvas.height = window.innerHeight * 0.9;
|
| }
|
|
|
|
|
| function initGame() {
|
| resizeCanvas();
|
| showMenu();
|
| generateCity();
|
| window.addEventListener('resize', resizeCanvas);
|
| window.addEventListener('keydown', handleKeyDown);
|
| window.addEventListener('keyup', handleKeyUp);
|
|
|
| startButton.addEventListener('click', startGame);
|
| controlsButton.addEventListener('click', showControls);
|
| backButton.addEventListener('click', showMenu);
|
| }
|
|
|
|
|
| function showMenu() {
|
| menu.style.display = 'block';
|
| controlsInfo.style.display = 'none';
|
| }
|
|
|
|
|
| function showControls() {
|
| menu.style.display = 'block';
|
| controlsInfo.style.display = 'block';
|
| }
|
|
|
|
|
| function startGame() {
|
| menu.style.display = 'none';
|
| gameLoop();
|
| setInterval(updateTime, 60000);
|
| }
|
|
|
|
|
| function generateCity() {
|
|
|
| for (let i = 0; i < 10; i++) {
|
| gameState.roads.push({
|
| x: i * 100,
|
| y: 200,
|
| width: 80,
|
| height: 20
|
| });
|
|
|
| gameState.roads.push({
|
| x: 400,
|
| y: i * 80,
|
| width: 20,
|
| height: 60
|
| });
|
| }
|
|
|
|
|
| for (let i = 0; i < 15; i++) {
|
| gameState.buildings.push({
|
| x: Math.random() * canvas.width,
|
| y: Math.random() * canvas.height,
|
| width: 60 + Math.random() * 100,
|
| height: 80 + Math.random() * 120
|
| });
|
| }
|
|
|
|
|
| for (let i = 0; i < 20; i++) {
|
| gameState.pedestrians.push({
|
| x: Math.random() * canvas.width,
|
| y: Math.random() * canvas.height,
|
| speed: 0.5 + Math.random() * 1.5,
|
| direction: Math.random() * 360
|
| });
|
| }
|
|
|
|
|
| for (let i = 0; i < 5; i++) {
|
| gameState.vehicles.push({
|
| x: Math.random() * canvas.width,
|
| y: Math.random() * canvas.height,
|
| speed: 1 + Math.random() * 3,
|
| angle: Math.random() * 360,
|
| color: `hsl(${Math.random() * 360}, 70%, 50%)`
|
| });
|
| }
|
| }
|
|
|
|
|
| function handleKeyDown(e) {
|
| gameState.keys[e.key] = true;
|
|
|
|
|
| if (e.key === 'Escape') {
|
| if (menu.style.display === 'none') {
|
| menu.style.display = 'block';
|
| } else {
|
| menu.style.display = 'none';
|
| }
|
| }
|
|
|
|
|
| if (e.key === 'm' && !gameState.mission.active) {
|
| gameState.mission.active = true;
|
| gameState.mission.marker = {
|
| x: Math.random() * canvas.width,
|
| y: Math.random() * canvas.height
|
| };
|
| }
|
| }
|
|
|
|
|
| function handleKeyUp(e) {
|
| gameState.keys[e.key] = false;
|
| }
|
|
|
|
|
| function updateTime() {
|
| gameState.time.minutes += 10;
|
| if (gameState.time.minutes >= 60) {
|
| gameState.time.minutes = 0;
|
| gameState.time.hours = (gameState.time.hours + 1) % 24;
|
| }
|
|
|
| const hoursStr = gameState.time.hours.toString().padStart(2, '0');
|
| const minsStr = gameState.time.minutes.toString().padStart(2, '0');
|
| timeDisplay.textContent = `${hoursStr}:${minsStr}`;
|
| }
|
|
|
|
|
| function update() {
|
| const player = gameState.player;
|
|
|
|
|
| if (player.inVehicle) {
|
|
|
| if (gameState.keys['ArrowUp'] || gameState.keys['w']) {
|
| player.speed = Math.min(player.speed + player.acceleration, player.maxSpeed);
|
| } else if (gameState.keys['ArrowDown'] || gameState.keys['s']) {
|
| player.speed = Math.max(player.speed - player.acceleration, -player.maxSpeed / 2);
|
| } else {
|
|
|
| player.speed *= 0.95;
|
| if (Math.abs(player.speed) < 0.1) player.speed = 0;
|
| }
|
|
|
|
|
| if (player.speed !== 0) {
|
| if (gameState.keys['ArrowLeft'] || gameState.keys['a']) {
|
| player.angle -= player.rotationSpeed * (player.speed / player.maxSpeed);
|
| }
|
| if (gameState.keys['ArrowRight'] || gameState.keys['d']) {
|
| player.angle += player.rotationSpeed * (player.speed / player.maxSpeed);
|
| }
|
| }
|
|
|
|
|
| if (gameState.keys[' ']) {
|
| player.speed *= 0.8;
|
| }
|
|
|
|
|
| const rad = player.angle * Math.PI / 180;
|
| player.x += Math.sin(rad) * player.speed;
|
| player.y -= Math.cos(rad) * player.speed;
|
|
|
|
|
| for (const building of gameState.buildings) {
|
| if (checkCollision(
|
| player.x, player.y, 32, 32,
|
| building.x, building.y, building.width, building.height
|
| )) {
|
|
|
| player.speed = -player.speed * 0.5;
|
| player.health -= 5;
|
| healthDisplay.textContent = `${player.health}%`;
|
|
|
|
|
| if (gameState.player.wantedLevel < 5) {
|
| gameState.player.wantedLevel++;
|
| wantedDisplay.textContent = '★'.repeat(gameState.player.wantedLevel);
|
| }
|
| }
|
| }
|
| }
|
|
|
|
|
| for (const vehicle of gameState.vehicles) {
|
| const rad = vehicle.angle * Math.PI / 180;
|
| vehicle.x += Math.sin(rad) * vehicle.speed;
|
| vehicle.y -= Math.cos(rad) * vehicle.speed;
|
|
|
|
|
| if (Math.random() < 0.01) {
|
| vehicle.angle += (Math.random() - 0.5) * 30;
|
| }
|
|
|
|
|
| if (vehicle.x < 0) vehicle.x = canvas.width;
|
| if (vehicle.x > canvas.width) vehicle.x = 0;
|
| if (vehicle.y < 0) vehicle.y = canvas.height;
|
| if (vehicle.y > canvas.height) vehicle.y = 0;
|
| }
|
|
|
|
|
| for (const ped of gameState.pedestrians) {
|
| const rad = ped.direction * Math.PI / 180;
|
| ped.x += Math.sin(rad) * ped.speed;
|
| ped.y -= Math.cos(rad) * ped.speed;
|
|
|
|
|
| if (Math.random() < 0.02) {
|
| ped.direction += (Math.random() - 0.5) * 90;
|
| }
|
|
|
|
|
| if (ped.x < 0 || ped.x > canvas.width || ped.y < 0 || ped.y > canvas.height) {
|
| ped.direction = Math.atan2(canvas.width / 2 - ped.x, canvas.height / 2 - ped.y) * 180 / Math.PI;
|
| }
|
| }
|
|
|
|
|
| if (gameState.mission.active) {
|
| const marker = gameState.mission.marker;
|
| if (Math.sqrt((player.x - marker.x) ** 2 + (player.y - marker.y) ** 2) < 30) {
|
| gameState.mission.active = false;
|
| player.money += 500;
|
| player.score += 1000;
|
| moneyDisplay.textContent = player.money;
|
| scoreDisplay.textContent = player.score;
|
| }
|
| }
|
| }
|
|
|
|
|
| function checkCollision(x1, y1, w1, h1, x2, y2, w2, h2) {
|
| return x1 < x2 + w2 &&
|
| x1 + w1 > x2 &&
|
| y1 < y2 + h2 &&
|
| y1 + h1 > y2;
|
| }
|
|
|
|
|
| function render() {
|
|
|
| ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
|
| ctx.fillStyle = '#444';
|
| for (const road of gameState.roads) {
|
| ctx.fillRect(road.x, road.y, road.width, road.height);
|
| }
|
|
|
|
|
| ctx.fillStyle = '#555';
|
| ctx.strokeStyle = '#333';
|
| ctx.lineWidth = 2;
|
| for (const building of gameState.buildings) {
|
| ctx.fillRect(building.x, building.y, building.width, building.height);
|
| ctx.strokeRect(building.x, building.y, building.width, building.height);
|
|
|
|
|
| ctx.fillStyle = '#777';
|
| for (let i = 0; i < 3; i++) {
|
| for (let j = 0; j < 3; j++) {
|
| ctx.fillRect(
|
| building.x + 10 + i * 15,
|
| building.y + 10 + j * 15,
|
| 8, 8
|
| );
|
| }
|
| }
|
| ctx.fillStyle = '#555';
|
| }
|
|
|
|
|
| ctx.fillStyle = 'green';
|
| for (const ped of gameState.pedestrians) {
|
| ctx.beginPath();
|
| ctx.arc(ped.x, ped.y, 6, 0, Math.PI * 2);
|
| ctx.fill();
|
| }
|
|
|
|
|
| for (const vehicle of gameState.vehicles) {
|
| ctx.save();
|
| ctx.translate(vehicle.x, vehicle.y);
|
| ctx.rotate(vehicle.angle * Math.PI / 180);
|
| ctx.fillStyle = vehicle.color;
|
| ctx.fillRect(-16, -16, 32, 32);
|
|
|
|
|
| ctx.fillStyle = '#333';
|
| ctx.fillRect(-12, -12, 24, 8);
|
| ctx.fillRect(-12, 4, 24, 8);
|
| ctx.restore();
|
| }
|
|
|
|
|
| const player = gameState.player;
|
| ctx.save();
|
| ctx.translate(player.x, player.y);
|
| ctx.rotate(player.angle * Math.PI / 180);
|
| ctx.fillStyle = 'blue';
|
| ctx.fillRect(-16, -16, 32, 32);
|
|
|
|
|
| ctx.fillStyle = '#333';
|
| ctx.fillRect(-12, -12, 24, 8);
|
| ctx.fillRect(-12, 4, 24, 8);
|
| ctx.fillStyle = 'red';
|
| ctx.fillRect(12, -8, 4, 4);
|
| ctx.fillRect(12, 4, 4, 4);
|
| ctx.fillStyle = 'yellow';
|
| ctx.fillRect(-16, -8, 4, 4);
|
| ctx.fillRect(-16, 4, 4, 4);
|
| ctx.restore();
|
|
|
|
|
| if (gameState.mission.active) {
|
| ctx.fillStyle = 'red';
|
| ctx.beginPath();
|
| ctx.arc(
|
| gameState.mission.marker.x,
|
| gameState.mission.marker.y,
|
| 10, 0, Math.PI * 2
|
| );
|
| ctx.fill();
|
| ctx.strokeStyle = 'white';
|
| ctx.lineWidth = 2;
|
| ctx.stroke();
|
|
|
|
|
| ctx.beginPath();
|
| ctx.moveTo(player.x, player.y);
|
| ctx.lineTo(gameState.mission.marker.x, gameState.mission.marker.y);
|
| ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
|
| ctx.lineWidth = 1;
|
| ctx.stroke();
|
| }
|
|
|
|
|
| const minimapSize = 150;
|
| const minimapX = canvas.width - minimapSize - 20;
|
| const minimapY = 20;
|
|
|
| ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
|
| ctx.fillRect(minimapX, minimapY, minimapSize, minimapSize);
|
| ctx.strokeStyle = '#555';
|
| ctx.lineWidth = 2;
|
| ctx.strokeRect(minimapX, minimapY, minimapSize, minimapSize);
|
|
|
|
|
| ctx.fillStyle = '#666';
|
| for (const road of gameState.roads) {
|
| const mx = minimapX + (road.x / canvas.width) * minimapSize;
|
| const my = minimapY + (road.y / canvas.height) * minimapSize;
|
| const mw = (road.width / canvas.width) * minimapSize;
|
| const mh = (road.height / canvas.height) * minimapSize;
|
| ctx.fillRect(mx, my, mw, mh);
|
| }
|
|
|
|
|
| ctx.fillStyle = 'blue';
|
| const px = minimapX + (player.x / canvas.width) * minimapSize;
|
| const py = minimapY + (player.y / canvas.height) * minimapSize;
|
| ctx.beginPath();
|
| ctx.arc(px, py, 3, 0, Math.PI * 2);
|
| ctx.fill();
|
|
|
|
|
| if (gameState.mission.active) {
|
| ctx.fillStyle = 'red';
|
| const mx = minimapX + (gameState.mission.marker.x / canvas.width) * minimapSize;
|
| const my = minimapY + (gameState.mission.marker.y / canvas.height) * minimapSize;
|
| ctx.beginPath();
|
| ctx.arc(mx, my, 3, 0, Math.PI * 2);
|
| ctx.fill();
|
| }
|
| }
|
|
|
|
|
| function gameLoop() {
|
| update();
|
| render();
|
| requestAnimationFrame(gameLoop);
|
| }
|
|
|
|
|
| window.onload = initGame;
|
| </script>
|
| </body>
|
|
|
| </html> |