Пост #30
Изображение
<!DOCTYPE html>
<html>
<head>
<title>2D Tank Battle - Enhanced Controls</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<style>
body { margin: 0; overflow: hidden; background: #222; font-family: Arial, sans-serif; }
canvas { display: block; }

/* UI элементы */
#ui {
position: absolute;
top: 15px;
left: 15px;
color: white;
font-size: 18px;
text-shadow: 1px 1px 2px black;
background: rgba(0,0,0,0.5);
padding: 8px 12px;
border-radius: 10px;
}

#leaderboard {
position: absolute;
top: 15px;
right: 15px;
background: rgba(0,0,0,0.7);
color: white;
padding: 10px 15px;
border-radius: 10px;
max-width: 200px;
}

/* Мобильное управление */
#mobile-controls {
display: none;
position: fixed;
bottom: 30px;
width: 100%;
height: 150px;
}

#joystick-area {
position: absolute;
left: 30px;
bottom: 0;
width: 150px;
height: 150px;
}

#joystick {
width: 80px;
height: 80px;
background: rgba(255,255,255,0.3);
border-radius: 50%;
position: absolute;
left: 35px;
top: 35px;
touch-action: none;
}

#shoot-btn {
width: 120px;
height: 120px;
background: rgba(255,50,50,0.6);
border-radius: 50%;
position: absolute;
right: 40px;
bottom: 15px;
color: white;
border: none;
font-size: 24px;
font-weight: bold;
box-shadow: 0 0 15px rgba(255,0,0,0.5);
}

/* Модальное окно */
#nickname-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.9);
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
}

#nickname-form {
background: #333;
padding: 30px;
border-radius: 15px;
text-align: center;
color: white;
max-width: 300px;
width: 80%;
}

#nickname-input {
padding: 12px;
font-size: 18px;
margin: 15px 0;
width: 100%;
box-sizing: border-box;
border-radius: 8px;
border: none;
}

/* Имена игроков */
.player-name {
position: absolute;
color: white;
font-size: 14px;
text-align: center;
transform: translateY(-30px);
text-shadow: 1px 1px 2px black;
font-weight: bold;
}

@media (max-width: 768px) {
#mobile-controls { display: block; }
#ui { font-size: 16px; }
#leaderboard { font-size: 14px; }
}
</style>
</head>
<body>
<div id="ui">Убито врагов: <span id="score">0</span></div>
<div id="leaderboard">
<h3 style="margin-top: 0;">Топ игроков</h3>
<ol id="scores-list" style="padding-left: 20px;"></ol>
</div>

<div id="mobile-controls">
<div id="joystick-area">
<div id="joystick"></div>
</div>
<button id="shoot-btn">FIRE</button>
</div>

<div id="nickname-modal">
<div id="nickname-form">
<h2 style="margin-top: 0;">Введите ваш ник</h2>
<input type="text" id="nickname-input" maxlength="12" placeholder="Танкист">
<button onclick="startGame()" style="padding: 12px 25px; font-size: 18px; background: #3498db; color: white; border: none; border-radius: 8px; cursor: pointer;">Играть</button>
</div>
</div>

<script>
// === ИНИЦИАЛИЗАЦИЯ ===
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.insertBefore(canvas, document.body.firstChild);

// Игровые объекты
const players = [];
const enemies = [];
const bullets = [];
const walls = [];
let score = 0;
let playerNickname = "Игрок";
let gameRunning = false;

// === ТАБЛИЦА РЕКОРДОВ ===
function loadLeaderboard() {
const saved = localStorage.getItem('tankLeaderboard');
return saved ? JSON.parse(saved) : [];
}

function saveLeaderboard() {
const leaderboard = loadLeaderboard();
leaderboard.push({ name: playerNickname, score: score });
leaderboard.sort((a, b) => b.score - a.score);
localStorage.setItem('tankLeaderboard', JSON.stringify(leaderboard.slice(0, 5)));
updateLeaderboard();
}

function updateLeaderboard() {
const leaderboard = loadLeaderboard();
const list = document.getElementById('scores-list');
list.innerHTML = '';
leaderboard.slice(0, 5).forEach((item, index) => {
const li = document.createElement('li');
li.textContent = `${index + 1}. ${item.name}: ${item.score}`;
list.appendChild(li);
});
}

// === НАЧАЛО ИГРЫ ===
function startGame() {
playerNickname = document.getElementById('nickname-input').value.trim() || "Танкист";
document.getElementById('nickname-modal').style.display = 'none';

// Создаем игрока
players.push({
id: Date.now(),
x: canvas.width/2,
y: canvas.height/2,
angle: 0,
speed: 3,
color: '#3498db',
turretColor: '#2980b9',
name: playerNickname,
health: 100,
controls: { up: false, left: false, right: false }
});

generateMap();
spawnEnemies(3);
gameRunning = true;
updateLeaderboard();
gameLoop();
}

// === ГЕНЕРАЦИЯ КАРТЫ ===
function generateMap() {
walls.length = 0;
// Границы карты
walls.push({x: 0, y: 0, width: canvas.width, height: 20});
walls.push({x: 0, y: 0, width: 20, height: canvas.height});
walls.push({x: canvas.width-20, y: 0, width: 20, height: canvas.height});
walls.push({x: 0, y: canvas.height-20, width: canvas.width, height: 20});

// Случайные стены
for (let i = 0; i < 15; i++) {
walls.push({
x: Math.random() * (canvas.width - 100) + 50,
y: Math.random() * (canvas.height - 100) + 50,
width: 40 + Math.random() * 60,
height: 40 + Math.random() * 60
});
}
}

// === СОЗДАНИЕ ВРАГОВ ===
function spawnEnemies(count) {
for (let i = 0; i < count; i++) {
enemies.push({
x: Math.random() * (canvas.width - 100) + 50,
y: Math.random() * (canvas.height - 100) + 50,
angle: Math.random() * Math.PI * 2,
speed: 1.5,
color: '#e74c3c',
turretColor: '#c0392b',
health: 100,
aiTimer: 0
});
}
}

// === ОТРИСОВКА ТАНКА ===
function drawTank(x, y, angle, color, turretColor, name) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(angle);

// Корпус
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(0, -20);
ctx.lineTo(25, 0);
ctx.lineTo(0, 20);
ctx.lineTo(-25, 0);
ctx.closePath();
ctx.fill();

// Башня
ctx.fillStyle = turretColor;
ctx.beginPath();
ctx.arc(0, 0, 12, 0, Math.PI * 2);
ctx.fill();

ctx.fillStyle = '#333';
ctx.fillRect(12, -3, 25, 6);

ctx.restore();

// Ник
if (name) {
ctx.fillStyle = 'white';
ctx.font = 'bold 14px Arial';
ctx.textAlign = 'center';
ctx.fillText(name, x, y - 30);
}
}

// === ИИ ВРАГОВ ===
function updateEnemies() {
enemies.forEach(enemy => {
if (players.length === 0) return;

// Находим ближайшего игрока
let closestPlayer = players[0];
let minDist = Infinity;
players.forEach(player => {
const dist = Math.hypot(player.x - enemy.x, player.y - enemy.y);
if (dist < minDist) {
minDist = dist;
closestPlayer = player;
}
});

// Движемся к игроку
const targetAngle = Math.atan2(closestPlayer.y - enemy.y, closestPlayer.x - enemy.x);
const angleDiff = ((targetAngle - enemy.angle + Math.PI * 3) % (Math.PI * 2)) - Math.PI;
enemy.angle += Math.sign(angleDiff) * 0.05;

// Движение вперед
enemy.x += Math.cos(enemy.angle) * enemy.speed;
enemy.y += Math.sin(enemy.angle) * enemy.speed;

// Стрельба
enemy.aiTimer++;
if (enemy.aiTimer > 90 && minDist < 400) {
bullets.push({
x: enemy.x,
y: enemy.y,
angle: enemy.angle,
speed: 5,
isEnemy: true
});
enemy.aiTimer = 0;
}
});
}

// === ОСНОВНОЙ ЦИКЛ ИГРЫ ===
function gameLoop() {
if (!gameRunning) return;

update();
render();
requestAnimationFrame(gameLoop);
}

function update() {
// Движение игроков
players.forEach(player => {
if (player.controls) {
if (player.controls.up) {
player.x += Math.cos(player.angle) * player.speed;
player.y += Math.sin(player.angle) * player.speed;
}
if (player.controls.left) player.angle -= 0.05;
if (player.controls.right) player.angle += 0.05;
}

// Проверка столкновений со стенами
walls.forEach(wall => {
if (player.x - 20 < wall.x + wall.width &&
player.x + 20 > wall.x &&
player.y - 20 < wall.y + wall.height &&
player.y + 20 > wall.y) {
// Отталкиваем игрока от стены
player.x -= Math.cos(player.angle) * player.speed;
player.y -= Math.sin(player.angle) * player.speed;
}
});
});

updateEnemies();
updateBullets();
checkCollisions();
}

function render() {
// Очистка
ctx.fillStyle = '#222';
ctx.fillRect(0, 0, canvas.width, canvas.height);

// Стены
ctx.fillStyle = '#555';
walls.forEach(wall => {
ctx.fillRect(wall.x, wall.y, wall.width, wall.height);
});

// Игроки
players.forEach(player => {
drawTank(player.x, player.y, player.angle, player.color, player.turretColor, player.name);

// Полоска здоровья
ctx.fillStyle = 'red';
ctx.fillRect(player.x - 20, player.y - 40, 40, 5);
ctx.fillStyle = 'lime';
ctx.fillRect(player.x - 20, player.y - 40, 40 * (player.health / 100), 5);
});

// Враги
enemies.forEach(enemy => {
drawTank(enemy.x, enemy.y, enemy.angle, enemy.color, enemy.turretColor, "Враг");
});

// Пули
ctx.fillStyle = '#f1c40f';
bullets.forEach(bullet => {
ctx.beginPath();
ctx.arc(bullet.x, bullet.y, 4, 0, Math.PI * 2);
ctx.fill();
});
}

// === УПРАВЛЕНИЕ ===
document.addEventListener('keydown', (e) => {
if (players[0]) {
if (!players[0].controls) players[0].controls = { up: false, left: false, right: false };
if (e.key.toLowerCase() === 'w' || e.key === 'ArrowUp') players[0].controls.up = true;
if (e.key.toLowerCase() === 'a' || e.key === 'ArrowLeft') players[0].controls.left = true;
if (e.key.toLowerCase() === 'd' || e.key === 'ArrowRight') players[0].controls.right = true;
if (e.key === ' ' || e.key === 'Enter') shoot(players[0]);
}
});

document.addEventListener('keyup', (e) => {
if (players[0] && players[0].controls) {
if (e.key.toLowerCase() === 'w' || e.key === 'ArrowUp') players[0].controls.up = false;
if (e.key.toLowerCase() === 'a' || e.key === 'ArrowLeft') players[0].controls.left = false;
if (e.key.toLowerCase() === 'd' || e.key === 'ArrowRight') players[0].controls.right = false;
}
});

function shoot(player) {
bullets.push({
x: player.x,
y: player.y,
angle: player.angle,
speed: 7,
isEnemy: false,
owner: player.id
});
}

// === МОБИЛЬНОЕ УПРАВЛЕНИЕ ===
const joystick = { x: 0, y: 0, active: false };
const joystickElem = document.getElementById('joystick');
const joystickArea = document.getElementById('joystick-area');
const joystickCenter = { x: 75, y: 75 };

joystickElem.addEventListener('touchstart', (e) => {
joystick.active = true;
e.preventDefault();
});

document.addEventListener('touchmove', (e) => {
if (joystick.active && players[0]) {
const rect = joystickArea.getBoundingClientRect();
const touchX = e.touches[0].clientX - rect.left;
const touchY = e.touches[0].clientY - rect.top;

// Ограничиваем движение джойстика
const dist = Math.hypot(touchX - joystickCenter.x, touchY - joystickCenter.y);
const maxDist = 40;

if (dist > maxDist) {
const angle = Math.atan2(touchY - joystickCenter.y, touchX - joystickCenter.x);
joystick.x = Math.cos(angle) * maxDist;
joystick.y = Math.sin(angle) * maxDist;
} else {
joystick.x = touchX - joystickCenter.x;
joystick.y = touchY - joystickCenter.y;
}

// Позиция джойстика
joystickElem.style.transform = `translate(${joystick.x}px, ${joystick.y}px)`;

// Управление
if (!players[0].controls) players[0].controls = { up: false, left: false, right: false };
players[0].controls.up = joystick.y < -15;
players[0].controls.left = joystick.x < -15;
players[0].controls.right = joystick.x > 15;
}
});

document.addEventListener('touchend', () => {
joystick.active = false;
joystick.x = 0;
joystick.y = 0;
joystickElem.style.transform = 'translate(0, 0)';
if (players[0] && players[0].controls) {
players[0].controls.up = false;
players[0].controls.left = false;
players[0].controls.right = false;
}
});

document.getElementById('shoot-btn').addEventListener('touchstart', (e) => {
if (players[0]) shoot(players[0]);
e.preventDefault();
});

// === ОБНОВЛЕНИЕ ПУЛЬ ===
function updateBullets() {
for (let i = bullets.length - 1; i >= 0; i--) {
const bullet = bullets[i];
bullet.x += Math.cos(bullet.angle) * bullet.speed;
bullet.y += Math.sin(bullet.angle) * bullet.speed;

// Удаление за пределами экрана
if (bullet.x < 0 || bullet.x > canvas.width || bullet.y < 0 || bullet.y > canvas.height) {
bullets.splice(i, 1);
}
}
}

// === ПРОВЕРКА СТОЛКНОВЕНИЙ ===
function checkCollisions() {
// Пули с врагами
for (let i = bullets.length - 1; i >= 0; i--) {
const bullet = bullets[i];

// Пуля игрока попадает во врага
if (!bullet.isEnemy) {
for (let j = enemies.length - 1; j >= 0; j--) {
const enemy = enemies[j];
const dist = Math.hypot(bullet.x - enemy.x, bullet.y - enemy.y);
if (dist < 25) {
enemies.splice(j, 1);
bullets.splice(i, 1);
score++;
document.getElementById('score').textContent = score;
if (enemies.length === 0) {
spawnEnemies(3 + Math.floor(score / 5));
}
break;
}
}
}
// Пуля врага попадает в игрока
else if (players.length > 0) {
const player = players[0];
const dist = Math.hypot(bullet.x - player.x, bullet.y - player.y);
if (dist < 25) {
player.health -= 20;
bullets.splice(i, 1);
if (player.health <= 0) {
gameOver();
}
}
}
}
}

// === КОНЕЦ ИГРЫ ===
function gameOver() {
gameRunning = false;
saveLeaderboard();
setTimeout(() => {
alert(`Игра окончена!\nВаш счет: ${score}`);
document.location.reload();
}, 500);
}

// Обработка ресайза
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
if (gameRunning) {
generateMap();
render();
}
});

// Автофокус на поле ввода
window.onload = () => {
document.getElementById('nickname-input').focus();
updateLeaderboard();
};
</script>
</body>
</html>
Автор: Anonymous