GTA 5: 2D Ultimate Edition
by CrystalPilot13505 lines20.6 KB
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
<title>GTA 5: 2D Ultimate</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Courier New', monospace; user-select: none; -webkit-user-select: none; }
body { background: #000; overflow: hidden; touch-action: none; }
canvas { display: block; width: 100vw; height: 100vh; }
#ui { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; }
/* Джойстик */
.joystick-base { position: absolute; bottom: 20px; left: 20px; width: 130px; height: 130px; border-radius: 50%; background: rgba(255,255,255,0.1); border: 2px solid rgba(255,255,255,0.4); pointer-events: auto; }
.joystick-stick { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 60px; height: 60px; border-radius: 50%; background: rgba(255,255,255,0.7); }
/* Кнопки */
.btn { position: absolute; border-radius: 50%; color: #fff; font-weight: bold; text-align: center; border: 2px solid #fff; pointer-events: auto; font-size: 14px; box-shadow: 0 0 15px rgba(0,0,0,0.7); transition: 0.1s; }
.btn:active { transform: scale(0.9); }
#btn-enter { bottom: 30px; right: 30px; width: 80px; height: 80px; line-height: 80px; background: rgba(39, 174, 96, 0.7); }
#btn-shoot { bottom: 130px; right: 30px; width: 80px; height: 80px; line-height: 80px; background: rgba(231, 76, 60, 0.7); display: none; }
#btn-switch { bottom: 30px; left: 50%; transform: translateX(-50%); width: 120px; height: 40px; line-height: 40px; background: rgba(52, 152, 219, 0.7); border-radius: 10px; font-size: 12px; }
#btn-shop { bottom: 80px; left: 50%; transform: translateX(-50%); width: 120px; height: 40px; line-height: 40px; background: rgba(241, 196, 15, 0.7); border-radius: 10px; font-size: 12px; }
/* HUD */
#hud { position: absolute; top: 10px; left: 10px; color: #fff; text-shadow: 2px 2px 0 #000; font-size: 16px; font-weight: bold; }
#wanted { position: absolute; top: 10px; right: 10px; color: #f00; font-size: 24px; text-shadow: 0 0 10px #f00; display: none; }
#mission-text { position: absolute; bottom: 150px; left: 50%; transform: translateX(-50%); color: #ffd700; font-size: 16px; font-weight: bold; text-shadow: 2px 2px 0 #000; text-align: center; max-width: 80%; display: none; background: rgba(0,0,0,0.5); padding: 5px 15px; border-radius: 10px;}
/* Мини-карта */
#minimap { position: absolute; top: 10px; right: 10px; width: 120px; height: 120px; border: 2px solid rgba(255,255,255,0.5); border-radius: 50%; overflow: hidden; display: none; background: rgba(0,0,0,0.5); }
/* Магазин */
#shop-modal { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #1a1a2e; border: 2px solid #f1c40f; border-radius: 15px; padding: 20px; display: none; flex-direction: column; gap: 10px; width: 90%; max-width: 400px; pointer-events: auto; z-index: 100; color: #fff; }
#shop-modal h2 { text-align: center; color: #f1c40f; margin-bottom: 10px; }
.shop-item { display: flex; justify-content: space-between; align-items: center; background: #2c3e50; padding: 10px; border-radius: 8px; }
.shop-item button { background: #27ae60; color: #fff; border: none; padding: 5px 15px; border-radius: 5px; cursor: pointer; font-weight: bold; }
.shop-item button:disabled { background: #555; color: #888; }
#close-shop { background: #e74c3c; padding: 10px; border: none; border-radius: 5px; color: #fff; font-weight: bold; margin-top: 10px; cursor: pointer; }
</style>
</head>
<body>
<canvas id="game"></canvas>
<div id="ui">
<div class="joystick-base" id="joy-base"><div class="joystick-stick" id="joy-stick"></div></div>
<div class="btn" id="btn-enter">ENTER</div>
<div class="btn" id="btn-shoot">FIRE</div>
<div class="btn" id="btn-switch">FRANKLIN</div>
<div class="btn" id="btn-shop">МАГАЗИН</div>
<div id="hud">
<div>💰 $<span id="money">5000</span></div>
<div>❤️ <span id="health">100</span></div>
<div>🛡️ <span id="armor">0</span></div>
</div>
<div id="wanted">★</div>
<div id="mission-text"></div>
<div id="minimap"><canvas id="minimapCanvas" width="120" height="120"></canvas></div>
<div id="shop-modal">
<h2>🛒 Магазин (24/7)</h2>
<div class="shop-item"><div>🍔 Еда (+50 HP)</div><button onclick="buyItem('food', 50)">$50</button></div>
<div class="shop-item"><div>🛡️ Броня (+50 Armor)</div><button onclick="buyItem('armor', 100)">$100</button></div>
<div class="shop-item"><div>🔫 Пистолет (Урон 50)</div><button onclick="buyItem('gun', 200)">$200</button></div>
<button id="close-shop" onclick="toggleShop()">Закрыть</button>
</div>
</div>
<script>
// === ИНИЦИАЛИЗАЦИЯ ===
const canvas = document.getElementById('game'), ctx = canvas.getContext('2d');
const miniCanvas = document.getElementById('minimapCanvas'), miniCtx = miniCanvas.getContext('2d');
let W, H;
function resize() { W = canvas.width = window.innerWidth; H = canvas.height = window.innerHeight; }
window.addEventListener('resize', resize); resize();
const WORLD = 5000;
let camera = { x: 0, y: 0 };
// === ПЕРСОНАЖИ ===
const characters = [
{ name: 'Franklin', color: '#2ecc71', speed: 280 },
{ name: 'Michael', color: '#3498db', speed: 250 },
{ name: 'Trevor', color: '#e67e22', speed: 300 }
];
let currentChar = 0;
// === ИГРОК ===
let player = {
x: WORLD/2, y: WORLD/2, r: 12, angle: 0,
hp: 100, maxHp: 100, armor: 0, maxArmor: 100,
money: 5000, weaponDmg: 20, inVehicle: null
};
// === МИР ===
let buildings = [];
let roads = [];
let parks = [];
let vehicles = [];
let peds = [];
let bullets = [];
let police = [];
let particles = [];
let missionMarkers = [];
let wantedLevel = 0;
let wantedTimer = 0;
let isNight = false;
let weather = 'clear'; // clear, rain
// Генерация карты
for (let i = 0; i < 15; i++) {
let x = Math.random() * WORLD, y = Math.random() * WORLD;
let w = 100 + Math.random() * 150, h = 100 + Math.random() * 150;
buildings.push({ x, y, w, h, color: '#2c3e50' });
}
for (let i = 0; i < 10; i++) {
roads.push({ x: i * 500, y: 0, w: 80, h: WORLD, v: true });
roads.push({ x: 0, y: i * 500, w: WORLD, h: 80, v: false });
}
for (let i = 0; i < 5; i++) {
parks.push({ x: Math.random() * WORLD, y: Math.random() * WORLD, w: 300, h: 300 });
}
// === МАШИНЫ И ПЕШЕХОДЫ ===
function spawnVehicle() {
let x = Math.random() * WORLD, y = Math.random() * WORLD;
vehicles.push({
x, y, angle: Math.random() * Math.PI * 2, speed: 0, maxSpeed: 400,
color: `hsl(${Math.random() * 360}, 80%, 50%)`, driver: null,
w: 40, h: 20, type: 'npc'
});
}
for (let i = 0; i < 30; i++) spawnVehicle();
for (let i = 0; i < 40; i++) {
peds.push({
x: Math.random() * WORLD, y: Math.random() * WORLD,
angle: Math.random() * Math.PI * 2, speed: 50,
color: `hsl(${Math.random() * 360}, 60%, 60%)`
});
}
// === МИССИИ ===
let currentMission = 0;
const missions = [
{ x: WORLD/2 + 400, y: WORLD/2 - 300, type: 'steal', text: 'Угони любую машину и доедь до точки Б.' },
{ x: WORLD/2 - 500, y: WORLD/2 + 200, type: 'kill', text: 'Убей 3 бандитов (красные пешеходы).' },
{ x: WORLD/2 + 300, y: WORLD/2 + 500, type: 'deliver', text: 'Доедь до порта, чтобы завершить сделку.' }
];
missionMarkers.push({ x: missions[0].x, y: missions[0].y, type: missions[0].type });
// === УПРАВЛЕНИЕ ===
let keys = {};
window.addEventListener('keydown', e => keys[e.code] = true);
window.addEventListener('keyup', e => keys[e.code] = false);
let joy = { active: false, dx: 0, dy: 0, maxR: 50 };
const joyBase = document.getElementById('joy-base'), joyStick = document.getElementById('joy-stick');
function handleJoy(e, start = false) {
if (start) { joy.active = true; return; }
if (!joy.active) return;
let t = e.touches ? e.touches[0] : e, r = joyBase.getBoundingClientRect();
let dx = t.clientX - (r.left + r.width / 2), dy = t.clientY - (r.top + r.height / 2), d = Math.hypot(dx, dy);
if (d > joy.maxR) { dx = dx / d * joy.maxR; dy = dy / d * joy.maxR; }
joyStick.style.transform = `translate(calc(-50% + ${dx}px), calc(-50% + ${dy}px))`;
joy.dx = dx / joy.maxR; joy.dy = dy / joy.maxR;
}
joyBase.addEventListener('touchstart', e => { e.preventDefault(); handleJoy(e, true); });
joyBase.addEventListener('touchmove', e => { e.preventDefault(); handleJoy(e); });
joyBase.addEventListener('touchend', () => { joy.active = false; joy.dx = 0; joy.dy = 0; joyStick.style.transform = 'translate(-50%,-50%)'; });
joyBase.addEventListener('mousedown', e => handleJoy(e, true));
window.addEventListener('mousemove', e => handleJoy(e));
window.addEventListener('mouseup', () => { joy.active = false; joy.dx = 0; joy.dy = 0; joyStick.style.transform = 'translate(-50%,-50%)'; });
let isShooting = false;
document.getElementById('btn-shoot').addEventListener('touchstart', e => { e.preventDefault(); isShooting = true; });
document.getElementById('btn-shoot').addEventListener('touchend', () => isShooting = false);
document.getElementById('btn-shoot').addEventListener('mousedown', () => isShooting = true);
document.getElementById('btn-shoot').addEventListener('mouseup', () => isShooting = false);
let pendingAction = null;
document.getElementById('btn-enter').addEventListener('touchstart', e => { e.preventDefault(); pendingAction = 'enter'; });
document.getElementById('btn-enter').addEventListener('mousedown', () => pendingAction = 'enter');
document.getElementById('btn-switch').addEventListener('click', () => {
if (!player.inVehicle) {
currentChar = (currentChar + 1) % 3;
document.getElementById('btn-switch').innerText = characters[currentChar].name;
document.getElementById('btn-switch').style.background = characters[currentChar].color;
}
});
document.getElementById('btn-shop').addEventListener('click', () => toggleShop());
// === МАГАЗИН ===
function toggleShop() {
let modal = document.getElementById('shop-modal');
modal.style.display = modal.style.display === 'flex' ? 'none' : 'flex';
}
function buyItem(type, price) {
if (player.money >= price) {
player.money -= price;
if (type === 'food') player.hp = Math.min(player.maxHp, player.hp + 50);
if (type === 'armor') player.armor = Math.min(player.maxArmor, player.armor + 50);
if (type === 'gun') player.weaponDmg = 50;
updateHUD();
}
}
// === HUD ===
function updateHUD() {
document.getElementById('money').innerText = player.money;
document.getElementById('health').innerText = Math.floor(player.hp);
document.getElementById('armor').innerText = Math.floor(player.armor);
}
// === ЛОГИКА ===
function update(dt) {
let kx = (keys['KeyD'] ? 1 : 0) - (keys['KeyA'] ? 1 : 0);
let ky = (keys['KeyS'] ? 1 : 0) - (keys['KeyW'] ? 1 : 0);
let mx = joy.dx + kx, my = joy.dy + ky;
if (player.inVehicle) {
let v = player.inVehicle;
let throttle = -my, turn = mx;
v.speed += throttle * v.maxSpeed * 2 * dt;
v.speed *= 0.98;
v.speed = Math.max(-v.maxSpeed * 0.5, Math.min(v.maxSpeed, v.speed));
v.angle += turn * 2.5 * dt * (v.speed > 0 ? 1 : -1);
// Коллизии с домами
let newX = v.x + Math.cos(v.angle) * v.speed * dt;
let newY = v.y + Math.sin(v.angle) * v.speed * dt;
let collide = false;
for (let b of buildings) {
if (newX > b.x && newX < b.x + b.w && newY > b.y && newY < b.y + b.h) { collide = true; break; }
}
if (!collide) { v.x = newX; v.y = newY; } else { v.speed *= -0.5; }
player.x = v.x; player.y = v.y;
} else {
if (Math.abs(mx) > 0.1 || Math.abs(my) > 0.1) {
let len = Math.hypot(mx, my);
let newX = player.x + (mx / len) * characters[currentChar].speed * dt;
let newY = player.y + (my / len) * characters[currentChar].speed * dt;
let collide = false;
for (let b of buildings) {
if (newX > b.x && newX < b.x + b.w && newY > b.y && newY < b.y + b.h) { collide = true; break; }
}
if (!collide) { player.x = newX; player.y = newY; }
player.angle = Math.atan2(my, mx);
}
player.x = Math.max(0, Math.min(WORLD, player.x));
player.y = Math.max(0, Math.min(WORLD, player.y));
}
// Стрельба
if (isShooting && !player.inVehicle) {
if (Math.random() < 0.15) {
bullets.push({
x: player.x, y: player.y,
vx: Math.cos(player.angle) * 1000, vy: Math.sin(player.angle) * 1000,
life: 1, dmg: player.weaponDmg
});
wantedLevel = Math.min(5, wantedLevel + 1);
wantedTimer = 10;
}
}
// Пули
for (let i = bullets.length - 1; i >= 0; i--) {
let b = bullets[i];
b.x += b.vx * dt; b.y += b.vy * dt; b.life -= dt * 2;
if (b.life <= 0) { bullets.splice(i, 1); continue; }
for (let j = peds.length - 1; j >= 0; j--) {
let p = peds[j];
if (Math.hypot(b.x - p.x, b.y - p.y) < 15) {
peds.splice(j, 1);
bullets.splice(i, 1);
player.money += 10;
updateHUD();
break;
}
}
}
// Полиция
if (wantedLevel > 0) {
wantedTimer -= dt;
if (wantedTimer <= 0) { wantedLevel--; wantedTimer = 10; }
if (Math.random() < 0.005 && police.length < wantedLevel * 2) {
police.push({ x: player.x + 600, y: player.y + 600, angle: 0, speed: 250 });
}
}
police.forEach(p => {
let dx = player.x - p.x, dy = player.y - p.y, d = Math.hypot(dx, dy);
if (d > 50) {
p.angle = Math.atan2(dy, dx);
p.x += Math.cos(p.angle) * p.speed * dt;
p.y += Math.sin(p.angle) * p.speed * dt;
}
if (Math.hypot(p.x - player.x, p.y - player.y) < 30) {
player.hp -= 20 * dt;
}
});
// Миссии
if (missionMarkers.length > 0) {
let m = missionMarkers[0];
if (Math.hypot(player.x - m.x, player.y - m.y) < 50) {
missionMarkers.shift();
document.getElementById('mission-text').innerText = missions[currentMission].text;
document.getElementById('mission-text').style.display = 'block';
currentMission++;
if (currentMission < missions.length) {
missionMarkers.push({ x: missions[currentMission].x, y: missions[currentMission].y });
}
}
}
// Камера
camera.x = player.x - W / 2;
camera.y = player.y - H / 2;
// Погода и время
if (Math.random() < 0.001) isNight = !isNight;
if (Math.random() < 0.0005) weather = weather === 'clear' ? 'rain' : 'clear';
// Обновление HUD
updateHUD();
document.getElementById('wanted').style.display = wantedLevel > 0 ? 'block' : 'none';
document.getElementById('wanted').innerText = '★'.repeat(wantedLevel);
}
// === РИСОВАНИЕ ===
function draw() {
// Фон (день/ночь)
ctx.fillStyle = isNight ? '#0a0a1a' : '#2c3e50';
ctx.fillRect(0, 0, W, H);
// Дороги
ctx.fillStyle = isNight ? '#1a1a2e' : '#555';
roads.forEach(r => {
if (r.v) ctx.fillRect(r.x - camera.x, 0, r.w, H);
else ctx.fillRect(0, r.y - camera.y, W, r.h);
});
// Парки (зеленые зоны)
ctx.fillStyle = isNight ? '#1a3a1a' : '#27ae60';
parks.forEach(p => {
ctx.fillRect(p.x - camera.x, p.y - camera.y, p.w, p.h);
});
// Дома
buildings.forEach(b => {
ctx.fillStyle = isNight ? '#111122' : b.color;
ctx.fillRect(b.x - camera.x, b.y - camera.y, b.w, b.h);
ctx.strokeStyle = isNight ? '#222233' : '#000';
ctx.lineWidth = 3;
ctx.strokeRect(b.x - camera.x, b.y - camera.y, b.w, b.h);
});
// Маркеры миссий
missionMarkers.forEach(m => {
ctx.fillStyle = '#ffd700';
ctx.beginPath();
ctx.arc(m.x - camera.x, m.y - camera.y, 20, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#000';
ctx.stroke();
});
// Пешеходы
peds.forEach(p => {
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x - camera.x, p.y - camera.y, 6, 0, Math.PI * 2);
ctx.fill();
});
// Машины
vehicles.forEach(v => {
ctx.save();
ctx.translate(v.x - camera.x, v.y - camera.y);
ctx.rotate(v.angle);
ctx.fillStyle = v.color;
ctx.fillRect(-v.w / 2, -v.h / 2, v.w, v.h);
ctx.restore();
});
// Полиция
police.forEach(p => {
ctx.save();
ctx.translate(p.x - camera.x, p.y - camera.y);
ctx.rotate(p.angle);
ctx.fillStyle = '#111';
ctx.fillRect(-20, -10, 40, 20);
let time = Date.now() / 200;
ctx.fillStyle = Math.floor(time) % 2 === 0 ? '#f00' : '#00f';
ctx.fillRect(-5, -12, 10, 4);
ctx.restore();
});
// Игрок
if (!player.inVehicle) {
ctx.fillStyle = characters[currentChar].color;
ctx.beginPath();
ctx.arc(player.x - camera.x, player.y - camera.y, player.r, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#000';
ctx.stroke();
// Ствол
ctx.strokeStyle = '#fff';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(player.x - camera.x, player.y - camera.y);
ctx.lineTo(player.x - camera.x + Math.cos(player.angle) * 20, player.y - camera.y + Math.sin(player.angle) * 20);
ctx.stroke();
} else {
ctx.save();
ctx.translate(player.x - camera.x, player.y - camera.y);
ctx.rotate(player.inVehicle.angle);
ctx.fillStyle = characters[currentChar].color;
ctx.fillRect(-20, -10, 40, 20);
ctx.restore();
}
// Пули
bullets.forEach(b => {
ctx.fillStyle = '#ff0';
ctx.beginPath();
ctx.arc(b.x - camera.x, b.y - camera.y, 3, 0, Math.PI * 2);
ctx.fill();
});
// Дождь
if (weather === 'rain') {
ctx.strokeStyle = 'rgba(255,255,255,0.3)';
ctx.lineWidth = 1;
for (let i = 0; i < 50; i++) {
let x = Math.random() * W, y = Math.random() * H;
ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x - 5, y + 15); ctx.stroke();
}
}
// Мини-карта
document.getElementById('minimap').style.display = 'block';
miniCtx.fillStyle = 'rgba(0,0,0,0.8)';
miniCtx.fillRect(0, 0, 120, 120);
// Игрок на мини-карте
miniCtx.fillStyle = '#fff';
miniCtx.beginPath();
miniCtx.arc(60, 60, 4, 0, Math.PI * 2);
miniCtx.fill();
// Дома на мини-карте
miniCtx.fillStyle = 'rgba(255,255,255,0.3)';
buildings.forEach(b => {
miniCtx.fillRect(b.x / WORLD * 120, b.y / WORLD * 120, b.w / WORLD * 120, b.h / WORLD * 120);
});
// Маркеры миссий
missionMarkers.forEach(m => {
miniCtx.fillStyle = '#ffd700';
miniCtx.beginPath();
miniCtx.arc(m.x / WORLD * 120, m.y / WORLD * 120, 3, 0, Math.PI * 2);
miniCtx.fill();
});
}
// === ГЛАВНЫЙ ЦИКЛ ===
let lastTime = performance.now();
function loop() {
let now = performance.now(), dt = Math.min((now - lastTime) / 1000, 0.1);
lastTime = now;
if (pendingAction === 'enter') {
if (player.inVehicle) {
player.inVehicle.driver = null;
player.x = player.inVehicle.x + 40;
player.y = player.inVehicle.y;
player.inVehicle = null;
document.getElementById('btn-shoot').style.display = 'block';
} else {
let closest = null, best = 100;
vehicles.forEach(v => {
let d = Math.hypot(v.x - player.x, v.y - player.y);
if (d < best && !v.driver) { best = d; closest = v; }
});
if (closest) {
closest.driver = player;
closest.maxSpeed = 400;
player.inVehicle = closest;
document.getElementById('btn-shoot').style.display = 'none';
}
}
pendingAction = null;
}
update(dt);
draw();
requestAnimationFrame(loop);
}
loop();
</script>
</body>
</html>Game Source: GTA 5: 2D Ultimate Edition
Creator: CrystalPilot13
Libraries: none
Complexity: complex (505 lines, 20.6 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: gta-5-2d-ultimate-edition-crystalpilot13" to link back to the original. Then publish at arcadelab.ai/publish.