danielbegemkulov2-sudo opened a new issue, #132:
URL: https://github.com/apache/cordova-fetch/issues/132
# Feature Request
## Motivation Behind Feature
<!-- Why should this feature be implemented? What problem does it solve? -->
## Feature Description
<!--
Describe your feature request in detail
Please provide any code examples or screenshots of what this feature would
look like
Are there any drawbacks? Will this break anything for existing users?
-->
## Alternatives or Workarounds
<!--
Describe alternatives or workarounds you are currently using
Are there ways to do this with existing functionality?
--><!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Прототип: Top-Down Shooter</title>
<style>
html,body{height:100%;margin:0;background:#111;color:#eee;font-family:system-ui,Segoe
UI,Roboto,Arial}
#gameWrap{display:flex;gap:16px;align-items:flex-start;padding:12px}
canvas{background:linear-gradient(#0b1720,#061018);border-radius:8px;box-shadow:0
6px 20px rgba(0,0,0,.6)}
#ui{min-width:200px}
button{display:inline-block;margin-top:8px;padding:10px
14px;border-radius:8px;border:none;background:#2b7;background-color:#1565c0;color:#fff;cursor:pointer}
.muted{opacity:.8;font-size:13px}
#score{font-size:18px;margin-bottom:8px}
</style>
</head>
<body>
<div id="gameWrap">
<canvas id="game" width="900" height="600"></canvas>
<div id="ui">
<div id="score">Счёт: 0</div>
<div id="hp">HP: 100</div>
<div id="wave">Волна: 0</div>
<div class="muted">Управление: WASD — движение, мышь — прицел, ЛКМ —
стрелять</div>
<button id="startBtn">Начать / Перезапустить</button>
<div style="margin-top:12px" class="muted">Прототип:
однопользовательская аркада. Хочешь добавить: оружия, апгрейды, карты,
мобилки?</div>
</div>
</div>
<script>
(() => {
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d', {alpha:false});
let W = canvas.width, H = canvas.height;
// UI
const scoreEl = document.getElementById('score');
const hpEl = document.getElementById('hp');
const waveEl = document.getElementById('wave');
const startBtn = document.getElementById('startBtn');
// Game state
let mouse = {x: W/2, y: H/2, down:false};
let keys = {};
let running = false;
let player, bullets, enemies, spawnTimer, score, wave, gameOver;
function reset() {
player = {x: W/2, y: H/2, r:14, speed: 220, hp:100, fireRate: 0.18,
lastShot:0};
bullets = [];
enemies = [];
spawnTimer = 0;
score = 0;
wave = 0;
gameOver = false;
updateUI();
}
function updateUI(){
scoreEl.textContent = 'Счёт: ' + Math.floor(score);
hpEl.textContent = 'HP: ' + Math.max(0, Math.floor(player.hp));
waveEl.textContent = 'Волна: ' + wave;
}
// Input
window.addEventListener('mousemove', e => {
const rect = canvas.getBoundingClientRect();
mouse.x = (e.clientX - rect.left) * (canvas.width / rect.width);
mouse.y = (e.clientY - rect.top) * (canvas.height / rect.height);
});
window.addEventListener('mousedown', () => mouse.down = true);
window.addEventListener('mouseup', () => mouse.down = false);
window.addEventListener('keydown', e => keys[e.key.toLowerCase()] = true);
window.addEventListener('keyup', e => keys[e.key.toLowerCase()] = false);
// Resize support (optional)
// Keep canvas fixed size for simplicity.
// Game logic
function spawnWave() {
wave++;
const count = 4 + wave * 2;
for (let i=0;i<count;i++){
const side = Math.floor(Math.random()*4);
let x,y;
if (side===0){ x = Math.random()*W; y = -30; }
else if (side===1){ x = Math.random()*W; y = H+30; }
else if (side===2){ x = -30; y = Math.random()*H; }
else { x = W+30; y = Math.random()*H; }
const speed = 40 + Math.random()*40 + wave*4;
enemies.push({x,y,r:14 + Math.random()*10, speed, hp: 10 + wave*2,
hitFlash:0});
}
}
function angleTo(a,b){ return Math.atan2(b.y-a.y, b.x-a.x); }
function update(dt){
if (!running) return;
if (gameOver) return;
// Player movement
let vx=0, vy=0;
if (keys['w']||keys['arrowup']) vy -= 1;
if (keys['s']||keys['arrowdown']) vy += 1;
if (keys['a']||keys['arrowleft']) vx -= 1;
if (keys['d']||keys['arrowright']) vx += 1;
const len = Math.hypot(vx,vy) || 1;
player.x += (vx/len) * player.speed * dt;
player.y += (vy/len) * player.speed * dt;
// clamp
player.x = Math.max(10, Math.min(W-10, player.x));
player.y = Math.max(10, Math.min(H-10, player.y));
// Shooting
player.lastShot += dt;
if (mouse.down && player.lastShot >= player.fireRate){
player.lastShot = 0;
const ang = Math.atan2(mouse.y - player.y, mouse.x - player.x);
const speed = 520;
bullets.push({x:player.x + Math.cos(ang)*(player.r+8), y:player.y +
Math.sin(ang)*(player.r+8), vx:Math.cos(ang)*speed, vy:Math.sin(ang)*speed,
r:3, life:1.8});
}
// Bullets
for (let i=bullets.length-1;i>=0;i--){
const b = bullets[i];
b.x += b.vx * dt;
b.y += b.vy * dt;
b.life -= dt;
if (b.life <= 0 || b.x < -50 || b.x > W+50 || b.y < -50 || b.y > H+50)
bullets.splice(i,1);
}
// Enemies
for (let i=enemies.length-1;i>=0;i--){
const e = enemies[i];
// move toward player
const ang = Math.atan2(player.y - e.y, player.x - e.x);
e.x += Math.cos(ang) * e.speed * dt;
e.y += Math.sin(ang) * e.speed * dt;
// collision with player
const dx = e.x - player.x, dy = e.y - player.y;
const dist = Math.hypot(dx,dy);
if (dist < e.r + player.r){
// damage
player.hp -= 15 * dt; // continuous damage while touching
e.hp -= 999; // enemy dies on contact (for fast gameplay)
e.hitFlash = 0.2;
}
// hit by bullets
for (let j=bullets.length-1;j>=0;j--){
const b = bullets[j];
const dx2 = e.x - b.x, dy2 = e.y - b.y;
if (Math.hypot(dx2,dy2) < e.r + b.r){
e.hp -= 10; // bullet damage
bullets.splice(j,1);
e.hitFlash = 0.12;
}
}
if (e.hp <= 0){
score += 10 + wave*2;
enemies.splice(i,1);
} else {
e.hitFlash = Math.max(0, e.hitFlash - dt);
}
}
// Spawn waves when all enemies cleared
if (enemies.length === 0) {
spawnTimer += dt;
if (spawnTimer > 1.1) { spawnTimer = 0; spawnWave(); }
}
// Check player death
if (player.hp <= 0){
player.hp = 0;
gameOver = true;
}
updateUI();
}
// Render
function draw(){
// background
ctx.clearRect(0,0,W,H);
// subtle vignette-ish
ctx.fillStyle = '#071018';
ctx.fillRect(0,0,W,H);
// draw bullets
for (const b of bullets){
ctx.beginPath();
ctx.arc(b.x,b.y,b.r,0,Math.PI*2);
ctx.fillStyle = '#ffd';
ctx.fill();
}
// draw enemies
for (const e of enemies){
ctx.save();
ctx.translate(e.x,e.y);
// body
ctx.beginPath();
ctx.arc(0,0,e.r,0,Math.PI*2);
ctx.fillStyle = e.hitFlash > 0 ? '#ff9' : '#ff5252';
ctx.fill();
// simple eyes
ctx.fillStyle = '#330000';
ctx.fillRect(-e.r/3, -e.r/6, e.r/3, e.r/6);
ctx.fillRect(e.r/6, -e.r/6, e.r/3, e.r/6);
ctx.restore();
}
// player
// draw direction aim
const ang = Math.atan2(mouse.y - player.y, mouse.x - player.x);
ctx.save();
ctx.translate(player.x, player.y);
ctx.rotate(ang);
// body
ctx.beginPath();
ctx.arc(0,0,player.r,0,Math.PI*2);
ctx.fillStyle = '#6cf';
ctx.fill();
// gun
ctx.fillStyle = '#0b3';
ctx.fillRect(6, -4, player.r+8, 8);
ctx.restore();
// HUD overlay
if (gameOver){
ctx.fillStyle = 'rgba(0,0,0,0.6)';
ctx.fillRect(0,0,W,H);
ctx.fillStyle = '#fff';
ctx.textAlign = 'center';
ctx.font = '40px system-ui,Segoe UI,Roboto';
ctx.fillText('Игра окончена', W/2, H/2 - 20);
ctx.font = '20px system-ui,Segoe UI,Roboto';
ctx.fillText('Нажми "Начать / Перезапустить"', W/2, H/2 + 18);
}
}
// Main loop
let last = performance.now();
function loop(ts){
const dt = Math.min(0.04, (ts - last)/1000); // clamp dt
last = ts;
update(dt);
draw();
requestAnimationFrame(loop);
}
// Start game
startBtn.addEventListener('click', () => {
reset();
running = true;
player.lastShot = 0.2;
last = performance.now();
});
// init
reset();
running = true;
spawnWave();
requestAnimationFrame(loop);
// small touch support: start shooting on touch
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
mouse.down = true;
const rect = canvas.getBoundingClientRect();
const t = e.touches[0];
mouse.x = (t.clientX - rect.left) * (canvas.width / rect.width);
mouse.y = (t.clientY - rect.top) * (canvas.height / rect.height);
}, {passive:false});
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
const rect = canvas.getBoundingClientRect();
const t = e.touches[0];
mouse.x = (t.clientX - rect.left) * (canvas.width / rect.width);
mouse.y = (t.clientY - rect.top) * (canvas.height / rect.height);
}, {passive:false});
canvas.addEventListener('touchend', (e) => { mouse.down = false; },
{passive:false});
})();
</script>
</body>
</html><!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Прототип: Top-Down Shooter</title>
<style>
html,body{height:100%;margin:0;background:#111;color:#eee;font-family:system-ui,Segoe
UI,Roboto,Arial}
#gameWrap{display:flex;gap:16px;align-items:flex-start;padding:12px}
canvas{background:linear-gradient(#0b1720,#061018);border-radius:8px;box-shadow:0
6px 20px rgba(0,0,0,.6)}
#ui{min-width:200px}
button{display:inline-block;margin-top:8px;padding:10px
14px;border-radius:8px;border:none;background:#2b7;background-color:#1565c0;color:#fff;cursor:pointer}
.muted{opacity:.8;font-size:13px}
#score{font-size:18px;margin-bottom:8px}
</style>
</head>
<body>
<div id="gameWrap">
<canvas id="game" width="900" height="600"></canvas>
<div id="ui">
<div id="score">Счёт: 0</div>
<div id="hp">HP: 100</div>
<div id="wave">Волна: 0</div>
<div class="muted">Управление: WASD — движение, мышь — прицел, ЛКМ —
стрелять</div>
<button id="startBtn">Начать / Перезапустить</button>
<div style="margin-top:12px" class="muted">Прототип:
однопользовательская аркада. Хочешь добавить: оружия, апгрейды, карты,
мобилки?</div>
</div>
</div>
<script>
(() => {
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d', {alpha:false});
let W = canvas.width, H = canvas.height;
// UI
const scoreEl = document.getElementById('score');
const hpEl = document.getElementById('hp');
const waveEl = document.getElementById('wave');
const startBtn = document.getElementById('startBtn');
// Game state
let mouse = {x: W/2, y: H/2, down:false};
let keys = {};
let running = false;
let player, bullets, enemies, spawnTimer, score, wave, gameOver;
function reset() {
player = {x: W/2, y: H/2, r:14, speed: 220, hp:100, fireRate: 0.18,
lastShot:0};
bullets = [];
enemies = [];
spawnTimer = 0;
score = 0;
wave = 0;
gameOver = false;
updateUI();
}
function updateUI(){
scoreEl.textContent = 'Счёт: ' + Math.floor(score);
hpEl.textContent = 'HP: ' + Math.max(0, Math.floor(player.hp));
waveEl.textContent = 'Волна: ' + wave;
}
// Input
window.addEventListener('mousemove', e => {
const rect = canvas.getBoundingClientRect();
mouse.x = (e.clientX - rect.left) * (canvas.width / rect.width);
mouse.y = (e.clientY - rect.top) * (canvas.height / rect.height);
});
window.addEventListener('mousedown', () => mouse.down = true);
window.addEventListener('mouseup', () => mouse.down = false);
window.addEventListener('keydown', e => keys[e.key.toLowerCase()] = true);
window.addEventListener('keyup', e => keys[e.key.toLowerCase()] = false);
// Resize support (optional)
// Keep canvas fixed size for simplicity.
// Game logic
function spawnWave() {
wave++;
const count = 4 + wave * 2;
for (let i=0;i<count;i++){
const side = Math.floor(Math.random()*4);
let x,y;
if (side===0){ x = Math.random()*W; y = -30; }
else if (side===1){ x = Math.random()*W; y = H+30; }
else if (side===2){ x = -30; y = Math.random()*H; }
else { x = W+30; y = Math.random()*H; }
const speed = 40 + Math.random()*40 + wave*4;
enemies.push({x,y,r:14 + Math.random()*10, speed, hp: 10 + wave*2,
hitFlash:0});
}
}
function angleTo(a,b){ return Math.atan2(b.y-a.y, b.x-a.x); }
function update(dt){
if (!running) return;
if (gameOver) return;
// Player movement
let vx=0, vy=0;
if (keys['w']||keys['arrowup']) vy -= 1;
if (keys['s']||keys['arrowdown']) vy += 1;
if (keys['a']||keys['arrowleft']) vx -= 1;
if (keys['d']||keys['arrowright']) vx += 1;
const len = Math.hypot(vx,vy) || 1;
player.x += (vx/len) * player.speed * dt;
player.y += (vy/len) * player.speed * dt;
// clamp
player.x = Math.max(10, Math.min(W-10, player.x));
player.y = Math.max(10, Math.min(H-10, player.y));
// Shooting
player.lastShot += dt;
if (mouse.down && player.lastShot >= player.fireRate){
player.lastShot = 0;
const ang = Math.atan2(mouse.y - player.y, mouse.x - player.x);
const speed = 520;
bullets.push({x:player.x + Math.cos(ang)*(player.r+8), y:player.y +
Math.sin(ang)*(player.r+8), vx:Math.cos(ang)*speed, vy:Math.sin(ang)*speed,
r:3, life:1.8});
}
// Bullets
for (let i=bullets.length-1;i>=0;i--){
const b = bullets[i];
b.x += b.vx * dt;
b.y += b.vy * dt;
b.life -= dt;
if (b.life <= 0 || b.x < -50 || b.x > W+50 || b.y < -50 || b.y > H+50)
bullets.splice(i,1);
}
// Enemies
for (let i=enemies.length-1;i>=0;i--){
const e = enemies[i];
// move toward player
const ang = Math.atan2(player.y - e.y, player.x - e.x);
e.x += Math.cos(ang) * e.speed * dt;
e.y += Math.sin(ang) * e.speed * dt;
// collision with player
const dx = e.x - player.x, dy = e.y - player.y;
const dist = Math.hypot(dx,dy);
if (dist < e.r + player.r){
// damage
player.hp -= 15 * dt; // continuous damage while touching
e.hp -= 999; // enemy dies on contact (for fast gameplay)
e.hitFlash = 0.2;
}
// hit by bullets
for (let j=bullets.length-1;j>=0;j--){
const b = bullets[j];
const dx2 = e.x - b.x, dy2 = e.y - b.y;
if (Math.hypot(dx2,dy2) < e.r + b.r){
e.hp -= 10; // bullet damage
bullets.splice(j,1);
e.hitFlash = 0.12;
}
}
if (e.hp <= 0){
score += 10 + wave*2;
enemies.splice(i,1);
} else {
e.hitFlash = Math.max(0, e.hitFlash - dt);
}
}
// Spawn waves when all enemies cleared
if (enemies.length === 0) {
spawnTimer += dt;
if (spawnTimer > 1.1) { spawnTimer = 0; spawnWave(); }
}
// Check player death
if (player.hp <= 0){
player.hp = 0;
gameOver = true;
}
updateUI();
}
// Render
function draw(){
// background
ctx.clearRect(0,0,W,H);
// subtle vignette-ish
ctx.fillStyle = '#071018';
ctx.fillRect(0,0,W,H);
// draw bullets
for (const b of bullets){
ctx.beginPath();
ctx.arc(b.x,b.y,b.r,0,Math.PI*2);
ctx.fillStyle = '#ffd';
ctx.fill();
}
// draw enemies
for (const e of enemies){
ctx.save();
ctx.translate(e.x,e.y);
// body
ctx.beginPath();
ctx.arc(0,0,e.r,0,Math.PI*2);
ctx.fillStyle = e.hitFlash > 0 ? '#ff9' : '#ff5252';
ctx.fill();
// simple eyes
ctx.fillStyle = '#330000';
ctx.fillRect(-e.r/3, -e.r/6, e.r/3, e.r/6);
ctx.fillRect(e.r/6, -e.r/6, e.r/3, e.r/6);
ctx.restore();
}
// player
// draw direction aim
const ang = Math.atan2(mouse.y - player.y, mouse.x - player.x);
ctx.save();
ctx.translate(player.x, player.y);
ctx.rotate(ang);
// body
ctx.beginPath();
ctx.arc(0,0,player.r,0,Math.PI*2);
ctx.fillStyle = '#6cf';
ctx.fill();
// gun
ctx.fillStyle = '#0b3';
ctx.fillRect(6, -4, player.r+8, 8);
ctx.restore();
// HUD overlay
if (gameOver){
ctx.fillStyle = 'rgba(0,0,0,0.6)';
ctx.fillRect(0,0,W,H);
ctx.fillStyle = '#fff';
ctx.textAlign = 'center';
ctx.font = '40px system-ui,Segoe UI,Roboto';
ctx.fillText('Игра окончена', W/2, H/2 - 20);
ctx.font = '20px system-ui,Segoe UI,Roboto';
ctx.fillText('Нажми "Начать / Перезапустить"', W/2, H/2 + 18);
}
}
// Main loop
let last = performance.now();
function loop(ts){
const dt = Math.min(0.04, (ts - last)/1000); // clamp dt
last = ts;
update(dt);
draw();
requestAnimationFrame(loop);
}
// Start game
startBtn.addEventListener('click', () => {
reset();
running = true;
player.lastShot = 0.2;
last = performance.now();
});
// init
reset();
running = true;
spawnWave();
requestAnimationFrame(loop);
// small touch support: start shooting on touch
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
mouse.down = true;
const rect = canvas.getBoundingClientRect();
const t = e.touches[0];
mouse.x = (t.clientX - rect.left) * (canvas.width / rect.width);
mouse.y = (t.clientY - rect.top) * (canvas.height / rect.height);
}, {passive:false});
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
const rect = canvas.getBoundingClientRect();
const t = e.touches[0];
mouse.x = (t.clientX - rect.left) * (canvas.width / rect.width);
mouse.y = (t.clientY - rect.top) * (canvas.height / rect.height);
}, {passive:false});
canvas.addEventListener('touchend', (e) => { mouse.down = false; },
{passive:false});
})();
</script>
</body>
</html><!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Прототип: Top-Down Shooter</title>
<style>
html,body{height:100%;margin:0;background:#111;color:#eee;font-family:system-ui,Segoe
UI,Roboto,Arial}
#gameWrap{display:flex;gap:16px;align-items:flex-start;padding:12px}
canvas{background:linear-gradient(#0b1720,#061018);border-radius:8px;box-shadow:0
6px 20px rgba(0,0,0,.6)}
#ui{min-width:200px}
button{display:inline-block;margin-top:8px;padding:10px
14px;border-radius:8px;border:none;background:#2b7;background-color:#1565c0;color:#fff;cursor:pointer}
.muted{opacity:.8;font-size:13px}
#score{font-size:18px;margin-bottom:8px}
</style>
</head>
<body>
<div id="gameWrap">
<canvas id="game" width="900" height="600"></canvas>
<div id="ui">
<div id="score">Счёт: 0</div>
<div id="hp">HP: 100</div>
<div id="wave">Волна: 0</div>
<div class="muted">Управление: WASD — движение, мышь — прицел, ЛКМ —
стрелять</div>
<button id="startBtn">Начать / Перезапустить</button>
<div style="margin-top:12px" class="muted">Прототип:
однопользовательская аркада. Хочешь добавить: оружия, апгрейды, карты,
мобилки?</div>
</div>
</div>
<script>
(() => {
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d', {alpha:false});
let W = canvas.width, H = canvas.height;
// UI
const scoreEl = document.getElementById('score');
const hpEl = document.getElementById('hp');
const waveEl = document.getElementById('wave');
const startBtn = document.getElementById('startBtn');
// Game state
let mouse = {x: W/2, y: H/2, down:false};
let keys = {};
let running = false;
let player, bullets, enemies, spawnTimer, score, wave, gameOver;
function reset() {
player = {x: W/2, y: H/2, r:14, speed: 220, hp:100, fireRate: 0.18,
lastShot:0};
bullets = [];
enemies = [];
spawnTimer = 0;
score = 0;
wave = 0;
gameOver = false;
updateUI();
}
function updateUI(){
scoreEl.textContent = 'Счёт: ' + Math.floor(score);
hpEl.textContent = 'HP: ' + Math.max(0, Math.floor(player.hp));
waveEl.textContent = 'Волна: ' + wave;
}
// Input
window.addEventListener('mousemove', e => {
const rect = canvas.getBoundingClientRect();
mouse.x = (e.clientX - rect.left) * (canvas.width / rect.width);
mouse.y = (e.clientY - rect.top) * (canvas.height / rect.height);
});
window.addEventListener('mousedown', () => mouse.down = true);
window.addEventListener('mouseup', () => mouse.down = false);
window.addEventListener('keydown', e => keys[e.key.toLowerCase()] = true);
window.addEventListener('keyup', e => keys[e.key.toLowerCase()] = false);
// Resize support (optional)
// Keep canvas fixed size for simplicity.
// Game logic
function spawnWave() {
wave++;
const count = 4 + wave * 2;
for (let i=0;i<count;i++){
const side = Math.floor(Math.random()*4);
let x,y;
if (side===0){ x = Math.random()*W; y = -30; }
else if (side===1){ x = Math.random()*W; y = H+30; }
else if (side===2){ x = -30; y = Math.random()*H; }
else { x = W+30; y = Math.random()*H; }
const speed = 40 + Math.random()*40 + wave*4;
enemies.push({x,y,r:14 + Math.random()*10, speed, hp: 10 + wave*2,
hitFlash:0});
}
}
function angleTo(a,b){ return Math.atan2(b.y-a.y, b.x-a.x); }
function update(dt){
if (!running) return;
if (gameOver) return;
// Player movement
let vx=0, vy=0;
if (keys['w']||keys['arrowup']) vy -= 1;
if (keys['s']||keys['arrowdown']) vy += 1;
if (keys['a']||keys['arrowleft']) vx -= 1;
if (keys['d']||keys['arrowright']) vx += 1;
const len = Math.hypot(vx,vy) || 1;
player.x += (vx/len) * player.speed * dt;
player.y += (vy/len) * player.speed * dt;
// clamp
player.x = Math.max(10, Math.min(W-10, player.x));
player.y = Math.max(10, Math.min(H-10, player.y));
// Shooting
player.lastShot += dt;
if (mouse.down && player.lastShot >= player.fireRate){
player.lastShot = 0;
const ang = Math.atan2(mouse.y - player.y, mouse.x - player.x);
const speed = 520;
bullets.push({x:player.x + Math.cos(ang)*(player.r+8), y:player.y +
Math.sin(ang)*(player.r+8), vx:Math.cos(ang)*speed, vy:Math.sin(ang)*speed,
r:3, life:1.8});
}
// Bullets
for (let i=bullets.length-1;i>=0;i--){
const b = bullets[i];
b.x += b.vx * dt;
b.y += b.vy * dt;
b.life -= dt;
if (b.life <= 0 || b.x < -50 || b.x > W+50 || b.y < -50 || b.y > H+50)
bullets.splice(i,1);
}
// Enemies
for (let i=enemies.length-1;i>=0;i--){
const e = enemies[i];
// move toward player
const ang = Math.atan2(player.y - e.y, player.x - e.x);
e.x += Math.cos(ang) * e.speed * dt;
e.y += Math.sin(ang) * e.speed * dt;
// collision with player
const dx = e.x - player.x, dy = e.y - player.y;
const dist = Math.hypot(dx,dy);
if (dist < e.r + player.r){
// damage
player.hp -= 15 * dt; // continuous damage while touching
e.hp -= 999; // enemy dies on contact (for fast gameplay)
e.hitFlash = 0.2;
}
// hit by bullets
for (let j=bullets.length-1;j>=0;j--){
const b = bullets[j];
const dx2 = e.x - b.x, dy2 = e.y - b.y;
if (Math.hypot(dx2,dy2) < e.r + b.r){
e.hp -= 10; // bullet damage
bullets.splice(j,1);
e.hitFlash = 0.12;
}
}
if (e.hp <= 0){
score += 10 + wave*2;
enemies.splice(i,1);
} else {
e.hitFlash = Math.max(0, e.hitFlash - dt);
}
}
// Spawn waves when all enemies cleared
if (enemies.length === 0) {
spawnTimer += dt;
if (spawnTimer > 1.1) { spawnTimer = 0; spawnWave(); }
}
// Check player death
if (player.hp <= 0){
player.hp = 0;
gameOver = true;
}
updateUI();
}
// Render
function draw(){
// background
ctx.clearRect(0,0,W,H);
// subtle vignette-ish
ctx.fillStyle = '#071018';
ctx.fillRect(0,0,W,H);
// draw bullets
for (const b of bullets){
ctx.beginPath();
ctx.arc(b.x,b.y,b.r,0,Math.PI*2);
ctx.fillStyle = '#ffd';
ctx.fill();
}
// draw enemies
for (const e of enemies){
ctx.save();
ctx.translate(e.x,e.y);
// body
ctx.beginPath();
ctx.arc(0,0,e.r,0,Math.PI*2);
ctx.fillStyle = e.hitFlash > 0 ? '#ff9' : '#ff5252';
ctx.fill();
// simple eyes
ctx.fillStyle = '#330000';
ctx.fillRect(-e.r/3, -e.r/6, e.r/3, e.r/6);
ctx.fillRect(e.r/6, -e.r/6, e.r/3, e.r/6);
ctx.restore();
}
// player
// draw direction aim
const ang = Math.atan2(mouse.y - player.y, mouse.x - player.x);
ctx.save();
ctx.translate(player.x, player.y);
ctx.rotate(ang);
// body
ctx.beginPath();
ctx.arc(0,0,player.r,0,Math.PI*2);
ctx.fillStyle = '#6cf';
ctx.fill();
// gun
ctx.fillStyle = '#0b3';
ctx.fillRect(6, -4, player.r+8, 8);
ctx.restore();
// HUD overlay
if (gameOver){
ctx.fillStyle = 'rgba(0,0,0,0.6)';
ctx.fillRect(0,0,W,H);
ctx.fillStyle = '#fff';
ctx.textAlign = 'center';
ctx.font = '40px system-ui,Segoe UI,Roboto';
ctx.fillText('Игра окончена', W/2, H/2 - 20);
ctx.font = '20px system-ui,Segoe UI,Roboto';
ctx.fillText('Нажми "Начать / Перезапустить"', W/2, H/2 + 18);
}
}
// Main loop
let last = performance.now();
function loop(ts){
const dt = Math.min(0.04, (ts - last)/1000); // clamp dt
last = ts;
update(dt);
draw();
requestAnimationFrame(loop);
}
// Start game
startBtn.addEventListener('click', () => {
reset();
running = true;
player.lastShot = 0.2;
last = performance.now();
});
// init
reset();
running = true;
spawnWave();
requestAnimationFrame(loop);
// small touch support: start shooting on touch
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
mouse.down = true;
const rect = canvas.getBoundingClientRect();
const t = e.touches[0];
mouse.x = (t.clientX - rect.left) * (canvas.width / rect.width);
mouse.y = (t.clientY - rect.top) * (canvas.height / rect.height);
}, {passive:false});
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
const rect = canvas.getBoundingClientRect();
const t = e.touches[0];
mouse.x = (t.clientX - rect.left) * (canvas.width / rect.width);
mouse.y = (t.clientY - rect.top) * (canvas.height / rect.height);
}, {passive:false});
canvas.addEventListener('touchend', (e) => { mouse.down = false; },
{passive:false});
})();
</script>
</body>
</html>
###
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]