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: issues-unsubscr...@cordova.apache.org.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org --------------------------------------------------------------------- To unsubscribe, e-mail: issues-unsubscr...@cordova.apache.org For additional commands, e-mail: issues-h...@cordova.apache.org