Python
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Шутер</title>
<style>
body { margin:0; background:#111; overflow:hidden; font-family:Arial; color:#fff; }
canvas { display:block; margin:0 auto; background:#2a2a2a; cursor:crosshair; }
#info { position:fixed; top:10px; left:10px; font-size:14px; line-height:1.5;
background:rgba(0,0,0,.5); padding:8px 12px; border-radius:6px; }
#gameover { position:fixed; top:50%; left:50%; transform:translate(-50%,-50%);
font-size:48px; display:none; text-shadow:2px 2px 4px #000; }
</style>
</head>
<body>
<div id="info">
<b>WASD</b> — движение<br>
<b>Мышь</b> — прицел и стрельба<br>
<b>1-4 / колесо</b> — смена оружия<br>
<b>R</b> — перезарядка<br>
<b>Space</b> — спавн бота
</div>
<div id="gameover">GAME OVER — нажми F5</div>
<canvas id="c" width="1000" height="700"></canvas>
<script>
const cvs = document.getElementById('c');
const ctx = cvs.getContext('2d');
const W = cvs.width, H = cvs.height;
// ======== ОРУЖИЕ ========
const WEAPONS = [
{ name:'Пистолет', dmg:20, rpm:300, spread:0.04, speed:14, mag:12, reload:1.0, bullets:1, color:'#ffeb3b' },
{ name:'Дробовик', dmg:15, rpm:90, spread:0.25, speed:13, mag:6, reload:1.8, bullets:6, color:'#ff9800' },
{ name:'Автомат', dmg:12, rpm:600, spread:0.08, speed:16, mag:30, reload:1.5, bullets:1, color:'#4caf50' },
{ name:'Снайперка', dmg:90, rpm:60, spread:0.005,speed:22, mag:5, reload:2.2, bullets:1, color:'#2196f3' },
];
// ======== ВВОД ========
const keys = {};
const mouse = { x:W/2, y:H/2, down:false };
addEventListener('keydown', e => {
keys[e.key.toLowerCase()] = true;
if (e.key >= '1' && e.key <= '4') player.switchWeapon(+e.key - 1);
if (e.key.toLowerCase() === 'r') player.reload();
if (e.key === ' ') spawnBot();
});
addEventListener('keyup', e => keys[e.key.toLowerCase()] = false);
cvs.addEventListener('mousemove', e => {
const r = cvs.getBoundingClientRect();
mouse.x = e.clientX - r.left;
mouse.y = e.clientY - r.top;
});
cvs.addEventListener('mousedown', () => mouse.down = true);
cvs.addEventListener('mouseup', () => mouse.down = false);
cvs.addEventListener('wheel', e => {
const dir = e.deltaY > 0 ? 1 : -1;
player.switchWeapon((player.weaponIdx + dir + WEAPONS.length) % WEAPONS.length);
});
// ======== КЛАССЫ ========
class Entity {
constructor(x,y,r,color,hp){
this.x=x; this.y=y; this.r=r; this.color=color;
this.hp=hp; this.maxHp=hp; this.angle=0; this.vx=0; this.vy=0;
}
draw(){
ctx.save();
ctx.translate(this.x,this.y);
ctx.rotate(this.angle);
ctx.fillStyle = this.color;
ctx.beginPath(); ctx.arc(0,0,this.r,0,Math.PI*2); ctx.fill();
// ствол
ctx.fillStyle = '#222';
ctx.fillRect(this.r-2, -3, this.r+6, 6);
ctx.restore();
// полоска HP
const w = this.r*2;
ctx.fillStyle = '#400';
ctx.fillRect(this.x-w/2, this.y-this.r-10, w, 4);
ctx.fillStyle = '#0f0';
ctx.fillRect(this.x-w/2, this.y-this.r-10, w*(this.hp/this.maxHp), 4);
}
damage(d){
this.hp -= d;
if (this.hp <= 0) this.die();
}
die(){ /* переопределяется */ }
move(){
this.x += this.vx; this.y += this.vy;
this.x = Math.max(this.r, Math.min(W-this.r, this.x));
this.y = Math.max(this.r, Math.min(H-this.r, this.y));
}
}
class Player extends Entity {
constructor(){
super(W/2, H/2, 16, '#4fc3f7', 100);
this.speed = 3.2;
this.weaponIdx = 0;
this.weapons = WEAPONS.map(w => ({ ...w, ammo:w.mag, reserve:w.mag*3, lastShot:0, reloading:false, reloadEnd:0 }));
}
get weapon(){ return this.weapons[this.weaponIdx]; }
switchWeapon(i){ this.weaponIdx = i; }
reload(){
const w = this.weapon;
if (w.reloading || w.ammo === w.mag || w.reserve <= 0) return;
w.reloading = true;
w.reloadEnd = performance.now() + w.reload*1000;
}
update(){
let dx=0, dy=0;
if (keys['w']) dy -= 1;
if (keys['s']) dy += 1;
if (keys['a']) dx -= 1;
if (keys['d']) dx += 1;
const len = Math.hypot(dx,dy);
if (len){ dx/=len; dy/=len; }
this.vx = dx*this.speed; this.vy = dy*this.speed;
this.move();
this.angle = Math.atan2(mouse.y-this.y, mouse.x-this.x);
const w = this.weapon;
if (w.reloading){
if (performance.now() >= w.reloadEnd){
const need = w.mag - w.ammo;
const take = Math.min(need, w.reserve);
w.ammo += take; w.reserve -= take;
w.reloading = false;
}
}
if (mouse.down && !w.reloading){
const interval = 60000 / w.rpm;
if (performance.now() - w.lastShot >= interval && w.ammo > 0){
this.shoot();
w.lastShot = performance.now();
} else if (w.ammo === 0) this.reload();
}
}
shoot(){
const w = this.weapon;
w.ammo--;
for (let i=0;i<w.bullets;i++){
const a = this.angle + (Math.random()-0.5)*w.spread*2;
bullets.push(new Bullet(this.x+Math.cos(this.angle)*this.r,
this.y+Math.sin(this.angle)*this.r,
a, w.speed, w.dmg, w.color, this));
}
// отдача
this.x -= Math.cos(this.angle)*1.5;
this.y -= Math.sin(this.angle)*1.5;
}
die(){ gameOver(); }
}
class Bot extends Entity {
constructor(x,y){
super(x,y,15,'#ef5350', 40 + Math.random()*30);
this.speed = 1.2 + Math.random()*0.8;
this.lastShot = 0;
this.shootInterval = 800 + Math.random()*1200;
this.accuracy = 0.08 + Math.random()*0.12;
this.state = 'chase';
}
update(){
const dx = player.x - this.x;
const dy = player.y - this.y;
const dist = Math.hypot(dx,dy);
this.angle = Math.atan2(dy,dx);
// ИИ: приближаемся, но держим дистанцию
let targetDist = 220;
let moveX = 0, moveY = 0;
if (dist > targetDist + 30){
moveX = dx/dist; moveY = dy/dist;
} else if (dist < targetDist - 30){
moveX = -dx/dist; moveY = -dy/dist;
} else {
// стрейф
moveX = -dy/dist; moveY = dx/dist;
if (Math.random() < 0.01) this.strafeDir = -this.strafeDir || 1;
moveX *= (this.strafeDir || 1); moveY *= (this.strafeDir || 1);
}
this.vx = moveX * this.speed;
this.vy = moveY * this.speed;
this.move();
// стрельба
if (dist < 500 && performance.now() - this.lastShot > this.shootInterval){
this.lastShot = performance.now();
const a = this.angle + (Math.random()-0.5)*this.accuracy*2;
bullets.push(new Bullet(this.x+Math.cos(this.angle)*this.r,
this.y+Math.sin(this.angle)*this.r,
a, 9, 8, '#ff5252', this));
}
}
die(){
score += 10;
const idx = bots.indexOf(this);
if (idx >= 0) bots.splice(idx,1);
// эффект
for (let i=0;i<10;i++) particles.push(new Particle(this.x,this.y,'#ef5350'));
}
}
class Bullet {
constructor(x,y,angle,speed,dmg,color,owner){
this.x=x; this.y=y;
this.vx = Math.cos(angle)*speed;
this.vy = Math.sin(angle)*speed;
this.dmg = dmg; this.color = color; this.owner = owner;
this.life = 80;
}
update(){
this.x += this.vx; this.y += this.vy;
this.life--;
if (this.x<0||this.x>W||this.y<0||this.y>H) this.life = 0;
}
draw(){
ctx.strokeStyle = this.color;
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.lineTo(this.x - this.vx, this.y - this.vy);
ctx.stroke();
}
}
class Particle {
constructor(x,y,color){
this.x=x; this.y=y; this.color=color;
this.vx = (Math.random()-0.5)*4;
this.vy = (Math.random()-0.5)*4;
this.life = 30;
}
update(){ this.x+=this.vx; this.y+=this.vy; this.life--; }
draw(){
ctx.fillStyle = this.color;
ctx.globalAlpha = this.life/30;
ctx.fillRect(this.x-2, this.y-2, 4, 4);
ctx.globalAlpha = 1;
}
}
// ======== ИГРА ========
const player = new Player();
const bots = [];
const bullets = [];
const particles = [];
let score = 0;
let gameOverFlag = false;
function spawnBot(){
// спавн по краю
const side = Math.floor(Math.random()*4);
let x,y;
if (side===0){ x=Math.random()*W; y=20; }
else if (side===1){ x=Math.random()*W; y=H-20; }
else if (side===2){ x=20; y=Math.random()*H; }
else { x=W-20; y=Math.random()*H; }
bots.push(new Bot(x,y));
}
for (let i=0;i<4;i++) spawnBot();
function gameOver(){
gameOverFlag = true;
document.getElementById('gameover').style.display = 'block';
}
// ======== ЦИКЛ ========
function loop(){
if (!gameOverFlag){
// фон — сетка
ctx.fillStyle = '#2a2a2a';
ctx.fillRect(0,0,W,H);
ctx.strokeStyle = '#333';
ctx.lineWidth = 1;
for (let x=0;x<W;x+=40){
ctx.beginPath(); ctx.moveTo(x,0); ctx.lineTo(x,H); ctx.stroke();
}
for (let y=0;y<H;y+=40){
ctx.beginPath(); ctx.moveTo(0,y); ctx.lineTo(W,y); ctx.stroke();
}
player.update();
bots.forEach(b => b.update());
bullets.forEach(b => b.update());
particles.forEach(p => p.update());
// коллизии пуль
for (let i=bullets.length-1; i>=0; i--){
const b = bullets[i];
if (b.life<=0){ bullets.splice(i,1); continue; }
if (b.owner === player){
for (const bot of bots){
if (Math.hypot(b.x-bot.x, b.y-bot.y) < bot.r){
bot.damage(b.dmg);
b.life = 0;
for (let k=0;k<4;k++) particles.push(new Particle(b.x,b.y,'#ff5252'));
break;
}
}
} else {
if (Math.hypot(b.x-player.x, b.y-player.y) < player.r){
player.damage(b.dmg);
b.life = 0;
for (let k=0;k<4;k++) particles.push(new Particle(b.x,b.y,'#4fc3f7'));
}
}
}
// спавн ботов
if (bots.length < 3 + Math.floor(score/50)) spawnBot();
// отрисовка
particles.forEach(p => p.draw());
bots.forEach(b => b.draw());
player.draw();
bullets.forEach(b => b.draw());
// прицел
ctx.strokeStyle = '#fff';
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.arc(mouse.x, mouse.y, 10, 0, Math.PI*2);
ctx.moveTo(mouse.x-15, mouse.y); ctx.lineTo(mouse.x-5, mouse.y);
ctx.moveTo(mouse.x+5, mouse.y); ctx.lineTo(mouse.x+15, mouse.y);
ctx.moveTo(mouse.x, mouse.y-15); ctx.lineTo(mouse.x, mouse.y-5);
ctx.moveTo(mouse.x, mouse.y+5); ctx.lineTo(mouse.x, mouse.y+15);
ctx.stroke();
// HUD
ctx.fillStyle = 'rgba(0,0,0,.6)';
ctx.fillRect(10, H-80, 280, 70);
ctx.fillStyle = '#fff';
ctx.font = 'bold 18px Arial';
const w = player.weapon;
ctx.fillText(`${w.name}`, 20, H-55);
ctx.font = '16px Arial';
ctx.fillText(`Патроны: ${w.ammo} / ${w.reserve}${w.reloading?' [перезарядка]':''}`, 20, H-32);
ctx.fillText(`Счёт: ${score} Ботов: ${bots.length}`, 20, H-14);
// HP бар сверху
ctx.fillStyle = '#400';
ctx.fillRect(W-220, 15, 200, 20);
ctx.fillStyle = '#0f0';
ctx.fillRect(W-220, 15, 200*(player.hp/player.maxHp), 20);
ctx.strokeStyle = '#fff';
ctx.strokeRect(W-220, 15, 200, 20);
ctx.fillStyle = '#fff';
ctx.font = 'bold 14px Arial';
ctx.fillText(`HP: ${Math.max(0,Math.ceil(player.hp))}`, W-215, 30);
}
requestAnimationFrame(loop);
}
loop();
</script>
</body>
</html>шутер
Sign in to react
Dreamy aulikhan.kakemovАулихан Какемов
Uploaded Jun 21, 2026