Робот-стрелок: Боты + Игрок
by HyperRider36647 lines19.5 KB
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Робот-стрелок: Боты + Игрок</title>
<style>
body {
margin: 0;
background: #0a0e1a;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-family: 'Courier New', monospace;
color: #fff;
overflow: hidden;
}
#game {
position: relative;
width: 900px; height: 600px;
background: radial-gradient(circle at 50% 50%, #1a2238 0%, #0a0e1a 100%);
border: 3px solid #2a3a5a;
border-radius: 8px;
overflow: hidden;
}
#game::before {
content: "";
position: absolute; inset: 0;
background-image:
linear-gradient(rgba(80,160,255,0.08) 1px, transparent 1px),
linear-gradient(90deg, rgba(80,160,255,0.08) 1px, transparent 1px);
background-size: 40px 40px;
pointer-events: none;
}
/* Робот (общий стиль) */
.robot {
position: absolute;
width: 46px; height: 46px;
z-index: 50;
transition: none;
}
.robot-body {
position: absolute; inset: 0;
border-radius: 8px;
border: 3px solid;
box-sizing: border-box;
}
.robot-eye {
position: absolute;
top: 12px;
width: 12px; height: 12px;
background: #fff;
border-radius: 50%;
box-shadow: 0 0 8px #fff;
}
.robot-eye.l { left: 8px; }
.robot-eye.r { right: 8px; }
.robot-eye::after {
content: "";
position: absolute;
top: 3px; left: 3px;
width: 6px; height: 6px;
background: #0a0e1a;
border-radius: 50%;
}
/* Игрок 1 — синий */
.p1 .robot-body { background: linear-gradient(135deg,#4fc3f7,#0288d1); border-color:#01579b; }
.p1 { filter: drop-shadow(0 0 10px #4fc3f7); }
/* Игрок 2 — зелёный */
.p2 .robot-body { background: linear-gradient(135deg,#81c784,#2e7d32); border-color:#1b5e20; }
.p2 { filter: drop-shadow(0 0 10px #81c784); }
.p2 .robot-eye::after { background: #0a0e1a; }
/* Бот — красный */
.bot .robot-body { background: linear-gradient(135deg,#ff6b6b,#b71c1c); border-color:#7f0000; }
.bot { filter: drop-shadow(0 0 8px #ff4444); z-index: 40; }
.bot .robot-eye { background: #ffeb3b; box-shadow: 0 0 6px #ffeb3b; }
/* Полоска HP над роботом */
.hpbar {
position: absolute;
top: -10px; left: 0;
width: 46px; height: 5px;
background: #333;
border-radius: 3px;
overflow: hidden;
}
.hpbar > div {
height: 100%;
background: #4caf50;
transition: width 0.15s;
}
/* Пули */
.bullet {
position: absolute;
width: 10px; height: 10px;
border-radius: 50%;
z-index: 30;
}
.bullet.bp1 { background:#4fc3f7; box-shadow: 0 0 12px #4fc3f7, 0 0 4px #fff; }
.bullet.bp2 { background:#81c784; box-shadow: 0 0 12px #81c784, 0 0 4px #fff; }
.bullet.bot { background:#ff5252; box-shadow: 0 0 12px #ff5252; }
/* Взрыв */
.boom {
position: absolute;
width: 60px; height: 60px;
border-radius: 50%;
background: radial-gradient(circle,#fff 0%,#ffeb3b 30%,#ff5252 60%,transparent 100%);
pointer-events: none;
z-index: 60;
animation: boomAnim 0.4s ease-out forwards;
}
@keyframes boomAnim {
0% { transform: scale(0.2); opacity: 1; }
100% { transform: scale(1.8); opacity: 0; }
}
/* HUD */
.hud {
position: absolute;
top: 12px; left: 12px;
display: flex; gap: 18px;
font-size: 14px;
z-index: 100;
background: rgba(0,0,0,0.55);
padding: 8px 16px;
border-radius: 8px;
border: 1px solid #2a3a5a;
}
.hud b { color: #4fc3f7; }
.hud .p2stat b { color: #81c784; }
/* Экран старта/конца */
.overlay {
position: absolute; inset: 0;
background: rgba(0,0,0,0.88);
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 200;
text-align: center;
padding: 20px;
}
.overlay.show { display: flex; }
.overlay h1 {
font-size: 38px;
margin: 0 0 14px;
color: #4fc3f7;
text-shadow: 0 0 20px #4fc3f7;
}
.overlay p { font-size: 16px; margin: 6px 0; color: #ccc; line-height: 1.7; }
.overlay .btn-row { display: flex; gap: 14px; margin-top: 22px; flex-wrap: wrap; justify-content: center; }
.overlay button {
padding: 12px 28px;
font-size: 16px;
background: #4fc3f7;
border: none;
border-radius: 8px;
cursor: pointer;
color: #0a0e1a;
font-weight: bold;
font-family: inherit;
transition: transform 0.1s, background 0.2s;
}
.overlay button:hover { background: #81d4fa; transform: scale(1.05); }
.overlay button.green { background: #81c784; }
.overlay button.green:hover { background: #a5d6a7; }
.overlay .keys {
display: flex; gap: 40px; margin-top: 20px; text-align: left;
flex-wrap: wrap; justify-content: center;
}
.overlay .keys div {
background: rgba(255,255,255,0.06);
padding: 14px 22px;
border-radius: 10px;
border: 1px solid rgba(255,255,255,0.1);
}
.overlay .keys h3 { margin: 0 0 8px; font-size: 15px; }
.overlay .keys kbd {
background: #1a2238;
border: 1px solid #2a3a5a;
border-radius: 4px;
padding: 2px 7px;
font-size: 13px;
color: #4fc3f7;
}
</style>
</head>
<body>
<div id="game">
<div class="hud">
<span>❤️ P1: <b id="hp1">100</b></span>
<span>☠️ Ботов убито: <b id="kills">0</b></span>
<span>🌊 Волна: <b id="wave">1</b></span>
<span class="p2stat" id="p2hud" style="display:none">💚 P2: <b id="hp2">100</b></span>
</div>
<div class="overlay show" id="overlay">
<h1 id="ovTitle">🤖 РОБОТ-СТРЕЛОК</h1>
<p id="ovText">Выбери режим:</p>
<div class="btn-row">
<button onclick="startGame('solo')">🎯 ОДИН ПРОТИВ БОТОВ</button>
<button class="green" onclick="startGame('pvp')">👥 ДВА ИГРОКА (PvP)</button>
</div>
<div class="keys" id="keysInfo">
<div>
<h3 style="color:#4fc3f7">Игрок 1</h3>
<kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd> — движение<br>
<kbd>Пробел</kbd> / <kbd>ЛКМ</kbd> — стрельба
</div>
</div>
</div>
</div>
<script>
const game = document.getElementById('game');
const overlay = document.getElementById('overlay');
const ovTitle = document.getElementById('ovTitle');
const ovText = document.getElementById('ovText');
const hp1El = document.getElementById('hp1');
const hp2El = document.getElementById('hp2');
const p2hud = document.getElementById('p2hud');
const killsEl = document.getElementById('kills');
const waveEl = document.getElementById('wave');
const keysInfo = document.getElementById('keysInfo');
const W = 900, H = 600;
const R_SIZE = 46;
// ==== Игроки ====
let p1 = null, p2 = null; // {el, x, y, hp, speed, alive, lastShot}
let enemies = []; // {el, x, y, hp, speed, lastShot, alive, hpbar}
let bullets = []; // {el, x, y, vx, vy, from, owner}
let running = false;
let mode = 'solo';
let kills = 0, wave = 1, spawnTimer = 0;
let lastTime = 0;
let gameTime = 0;
let mouse = { x: W/2, y: H/2 };
let keys = {};
// ==== Создание робота ====
function makeRobot(cls, x, y) {
const el = document.createElement('div');
el.className = 'robot ' + cls;
el.innerHTML = `
<div class="robot-body"></div>
<div class="robot-eye l"></div>
<div class="robot-eye r"></div>
<div class="hpbar"><div style="width:100%"></div></div>
`;
el.style.left = x + 'px';
el.style.top = y + 'px';
game.appendChild(el);
return el;
}
function updateHpBar(robot) {
const bar = robot.el.querySelector('.hpbar > div');
if (bar) bar.style.width = Math.max(0, robot.hp) + '%';
}
// ==== Запуск ====
function startGame(m) {
mode = m;
overlay.classList.remove('show');
// очистка
game.querySelectorAll('.robot, .bullet, .boom').forEach(e => e.remove());
p1 = p2 = null;
enemies = []; bullets = [];
kills = 0; wave = 1; spawnTimer = 0; gameTime = 0;
p1 = {
el: makeRobot('p1', 150, H/2 - R_SIZE/2),
x: 150, y: H/2 - R_SIZE/2,
hp: 100, speed: 4.2, alive: true, lastShot: 0, facing: {x:1,y:0}
};
p1.el.style.left = p1.x + 'px';
p1.el.style.top = p1.y + 'px';
if (mode === 'pvp') {
p2 = {
el: makeRobot('p2', W - 150 - R_SIZE, H/2 - R_SIZE/2),
x: W - 150 - R_SIZE, y: H/2 - R_SIZE/2,
hp: 100, speed: 4.2, alive: true, lastShot: 0
};
p2.el.style.left = p2.x + 'px';
p2.el.style.top = p2.y + 'px';
p2hud.style.display = 'inline';
} else {
p2hud.style.display = 'none';
}
hp1El.textContent = 100;
hp2El.textContent = 100;
killsEl.textContent = 0;
waveEl.textContent = 1;
running = true;
lastTime = performance.now();
requestAnimationFrame(loop);
}
// ==== Управление ====
document.addEventListener('keydown', e => {
const k = e.key.toLowerCase();
keys[k] = true;
if (e.code === 'Space') { e.preventDefault(); shootHuman(p1, mouse.x, mouse.y); }
if (k === 'enter') shootHuman(p2, null, null); // P2 стреляет вперёд
// P2 стрельба — Ctrl или стрелка вниз? Лучше: стрелять автоматически по стрелке направления
});
document.addEventListener('keyup', e => { keys[e.key.toLowerCase()] = false; });
game.addEventListener('mousemove', e => {
const r = game.getBoundingClientRect();
mouse.x = e.clientX - r.left;
mouse.y = e.clientY - r.top;
});
game.addEventListener('mousedown', e => {
if (!running) return;
if (mode === 'solo' || (mode === 'pvp' && e.button === 0)) {
shootHuman(p1, mouse.x, mouse.y);
}
});
game.addEventListener('contextmenu', e => e.preventDefault());
// ==== Стрельба ====
function shootHuman(shooter, tx, ty) {
if (!running || !shooter || !shooter.alive) return;
const now = performance.now();
if (now - shooter.lastShot < 140) return;
shooter.lastShot = now;
let dx, dy;
const cx = shooter.x + R_SIZE/2;
const cy = shooter.y + R_SIZE/2;
if (shooter === p1) {
dx = tx - cx; dy = ty - cy;
const len = Math.hypot(dx, dy) || 1;
dx /= len; dy /= len;
} else {
// P2 стреляет в направлении движения или вправо
dx = 1; dy = 0;
if (keys['arrowup']) { dx = 0; dy = -1; }
if (keys['arrowdown']) { dx = 0; dy = 1; }
if (keys['arrowleft']) { dx = -1; dy = 0; }
if (keys['arrowright']) { dx = 1; dy = 0; }
}
const speed = 11;
spawnBullet(cx, cy, dx*speed, dy*speed,
shooter === p1 ? 'bp1' : 'bp2',
shooter === p1 ? 'p1' : 'p2');
}
function spawnBullet(x, y, vx, vy, cls, owner) {
const el = document.createElement('div');
el.className = 'bullet ' + cls;
el.style.left = x + 'px';
el.style.top = y + 'px';
game.appendChild(el);
bullets.push({ el, x, y, vx, vy, cls, owner, life: 0 });
}
// ==== Спавн ботов ====
function spawnBot() {
const side = Math.floor(Math.random() * 4);
let x, y;
const pad = 50;
if (side === 0) { x = Math.random() * (W - R_SIZE); y = -pad; }
else if (side === 1) { x = Math.random() * (W - R_SIZE); y = H + pad; }
else if (side === 2) { x = -pad; y = Math.random() * (H - R_SIZE); }
else { x = W + pad; y = Math.random() * (H - R_SIZE); }
const el = makeRobot('bot', x, y);
enemies.push({
el, x, y, hp: 3, alive: true,
speed: 1.3 + Math.random() * 0.7 + wave * 0.12,
lastShot: performance.now() + 500 + Math.random() * 1000,
baseCooldown: Math.max(400, 1300 - wave * 60)
});
}
// ==== Взрыв ====
function boom(x, y) {
const b = document.createElement('div');
b.className = 'boom';
b.style.left = (x - 30) + 'px';
b.style.top = (y - 30) + 'px';
game.appendChild(b);
setTimeout(() => b.remove(), 400);
}
// ==== Цикл ====
function loop(now) {
if (!running) return;
const dt = Math.min(50, now - lastTime);
lastTime = now;
gameTime += dt;
updateHuman(p1, 'p1', dt);
if (p2) updateHuman(p2, 'p2', dt);
updateBots(dt, now);
updateBullets(dt);
updateSpawning(dt);
// Проверка конца
if ((!p1 || !p1.alive) && (!p2 || !p2.alive || mode === 'solo')) {
endGame();
return;
}
requestAnimationFrame(loop);
}
function updateHuman(h, id, dt) {
if (!h.alive) return;
let dx = 0, dy = 0;
if (id === 'p1') {
if (keys['w'] || keys['ц']) dy -= 1;
if (keys['s'] || keys['ы']) dy += 1;
if (keys['a'] || keys['ф']) dx -= 1;
if (keys['d'] || keys['в']) dx += 1;
} else {
if (keys['arrowup']) dy -= 1;
if (keys['arrowdown']) dy += 1;
if (keys['arrowleft']) dx -= 1;
if (keys['arrowright']) dx += 1;
}
if (dx && dy) { dx *= 0.707; dy *= 0.707; }
h.x += dx * h.speed * (dt/16.67);
h.y += dy * h.speed * (dt/16.67);
h.x = Math.max(0, Math.min(W - R_SIZE, h.x));
h.y = Math.max(0, Math.min(H - R_SIZE, h.y));
h.el.style.left = h.x + 'px';
h.el.style.top = h.y + 'px';
}
function updateBots(dt, now) {
// ближайшая цель
const targets = [];
if (p1 && p1.alive) targets.push(p1);
if (p2 && p2.alive) targets.push(p2);
for (const e of enemies) {
if (!e.alive) continue;
if (targets.length === 0) continue;
// ближайший живой игрок
let target = targets[0];
let bestD = Infinity;
for (const t of targets) {
const d = Math.hypot(t.x - e.x, t.y - e.y);
if (d < bestD) { bestD = d; target = t; }
}
const cx = e.x + R_SIZE/2;
const cy = e.y + R_SIZE/2;
const tcx = target.x + R_SIZE/2;
const tcy = target.y + R_SIZE/2;
const ang = Math.atan2(tcy - cy, tcx - cx);
// движение к цели
const step = e.speed * (dt/16.67);
e.x += Math.cos(ang) * step;
e.y += Math.sin(ang) * step;
e.x = Math.max(-20, Math.min(W - R_SIZE + 20, e.x));
e.y = Math.max(-20, Math.min(H - R_SIZE + 20, e.y));
e.el.style.left = e.x + 'px';
e.el.style.top = e.y + 'px';
// стрельба
if (now - e.lastShot > e.baseCooldown && bestD < 500) {
e.lastShot = now;
spawnBullet(cx, cy,
Math.cos(ang) * 6.5,
Math.sin(ang) * 6.5,
'bot', 'bot');
}
}
}
function updateBullets(dt) {
for (let i = bullets.length - 1; i >= 0; i--) {
const b = bullets[i];
b.x += b.vx * (dt/16.67);
b.y += b.vy * (dt/16.67);
b.life += dt;
b.el.style.left = b.x + 'px';
b.el.style.top = b.y + 'px';
// за границей / старая пуля
if (b.x < -20 || b.x > W + 20 || b.y < -20 || b.y > H + 20 || b.life > 3000) {
b.el.remove(); bullets.splice(i, 1); continue;
}
// попадание
let hit = false;
if (b.owner === 'p1' || b.owner === 'p2') {
// по ботам
for (const e of enemies) {
if (!e.alive) continue;
if (b.x > e.x && b.x < e.x + R_SIZE &&
b.y > e.y && b.y < e.y + R_SIZE) {
e.hp -= 1;
updateHpBar(e);
hit = true;
if (e.hp <= 0) {
e.alive = false;
boom(e.x + R_SIZE/2, e.y + R_SIZE/2);
e.el.remove();
kills++;
killsEl.textContent = kills;
}
break;
}
}
// PvP: пули P1 бьют P2 и наоборот
if (!hit && mode === 'pvp') {
const other = b.owner === 'p1' ? p2 : p1;
if (other && other.alive &&
b.x > other.x && b.x < other.x + R_SIZE &&
b.y > other.y && b.y < other.y + R_SIZE) {
other.hp -= 8;
updateHpBar(other);
updatePlayerHpUI();
hit = true;
if (other.hp <= 0) {
other.alive = false;
boom(other.x + R_SIZE/2, other.y + R_SIZE/2);
other.el.style.opacity = '0.3';
}
}
}
} else if (b.owner === 'bot') {
// по игрокам
for (const pl of [p1, p2]) {
if (!pl || !pl.alive) continue;
if (b.x > pl.x && b.x < pl.x + R_SIZE &&
b.y > pl.y && b.y < pl.y + R_SIZE) {
pl.hp -= 7;
updateHpBar(pl);
updatePlayerHpUI();
hit = true;
if (pl.hp <= 0) {
pl.alive = false;
boom(pl.x + R_SIZE/2, pl.y + R_SIZE/2);
pl.el.style.opacity = '0.3';
}
break;
}
}
}
if (hit) {
b.el.remove();
bullets.splice(i, 1);
}
}
}
function updatePlayerHpUI() {
hp1El.textContent = Math.max(0, Math.round(p1 ? p1.hp : 0));
if (p2) hp2El.textContent = Math.max(0, Math.round(p2.hp));
}
function updateSpawning(dt) {
// волна растёт со временем
const newWave = 1 + Math.floor(gameTime / 15000);
if (newWave !== wave) {
wave = newWave;
waveEl.textContent = wave;
}
spawnTimer -= dt;
const aliveBots = enemies.filter(e => e.alive).length;
const maxBots = Math.min(2 + wave, 10);
if (spawnTimer <= 0 && aliveBots < maxBots) {
spawnBot();
spawnTimer = Math.max(400, 1800 - wave * 120);
}
}
function endGame() {
running = false;
// определяем результат
let title = '', text = '';
if (mode === 'solo') {
title = '☠️ ТЫ ПАЛ В БОЮ';
text = `Ботов убито: ${kills}<br>Волна: ${wave}`;
} else {
// PvP — кто выжил
const a1 = p1 && p1.alive;
const a2 = p2 && p2.alive;
if (a1 && !a2) title = '🏆 ПОБЕДИЛ ИГРОК 1 (синий)!';
else if (!a1 && a2) title = '🏆 ПОБЕДИЛ ИГРОК 2 (зелёный)!';
else title = '💀 НИЧЬЯ — оба пали';
text = `Ботов убито: ${kills}<br>Волна: ${wave}`;
}
ovTitle.innerHTML = title;
ovText.innerHTML = text;
// кнопки
const btnRow = overlay.querySelector('.btn-row');
btnRow.innerHTML = `
<button onclick="startGame('solo')">🎯 ОДИН ПРОТИВ БОТОВ</button>
<button class="green" onclick="startGame('pvp')">👥 ДВА ИГРОКА</button>
`;
// инфо о клавишах
keysInfo.innerHTML = `
<div>
<h3 style="color:#4fc3f7">Игрок 1</h3>
<kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd> — движение<br>
<kbd>Пробел</kbd> / <kbd>ЛКМ</kbd> — стрельба в курсор
</div>
<div>
<h3 style="color:#81c784">Игрок 2 (PvP)</h3>
<kbd>↑</kbd><kbd>↓</kbd><kbd>←</kbd><kbd>→</kbd> — движение<br>
<kbd>Enter</kbd> — стрельба в направлении движения
</div>
`;
overlay.classList.add('show');
}
// Запускаем анимацию HUD раз в кадр (даже до старта)
function idleLoop() {
if (running) requestAnimationFrame(loop);
else requestAnimationFrame(idleLoop);
}
// Запускаем цикл только когда игра стартует — но HUD обновим в loop
requestAnimationFrame(idleLoop);
</script>
</body>
</html>Game Source: Робот-стрелок: Боты + Игрок
Creator: HyperRider36
Libraries: none
Complexity: complex (647 lines, 19.5 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: game-hyperrider36-mvte2fpf" to link back to the original. Then publish at arcadelab.ai/publish.