33 lines
6.2 KiB
HTML
33 lines
6.2 KiB
HTML
<!doctype html>
|
|
<html lang="ru">
|
|
<head>
|
|
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>Asterion Patrol — Demo Game</title>
|
|
<style>
|
|
*{box-sizing:border-box} body{margin:0;min-height:100vh;display:grid;place-items:center;background:#030817;color:#e1f5ff;font-family:Inter,system-ui,sans-serif;overflow:hidden}
|
|
.shell{position:relative;width:min(100vw,1100px);aspect-ratio:16/9;box-shadow:0 30px 90px #000;border:1px solid #71e9ff42;background:#071128}
|
|
canvas{display:block;width:100%;height:100%;image-rendering:pixelated;cursor:crosshair}.hud{position:absolute;inset:0;pointer-events:none;padding:20px;display:flex;justify-content:space-between;text-shadow:0 2px 8px #000;font:700 13px ui-monospace,monospace;letter-spacing:.08em}.hud b{color:#75edff}.tip{position:absolute;left:20px;bottom:17px;margin:0;color:#b7cce1;font-size:11px;line-height:1.5;text-shadow:0 2px 5px #000}.tip kbd{padding:2px 5px;border:1px solid #80dbef66;border-radius:4px;background:#061a32;font-family:inherit}.overlay{position:absolute;inset:0;display:grid;place-items:center;text-align:center;background:#030916aa;backdrop-filter:blur(3px)}.overlay.hidden{display:none}.panel{padding:26px 34px;border:1px solid #76eaff55;border-radius:12px;background:#07172bd9;box-shadow:0 0 50px #31c9ff30}.panel h1{margin:0 0 8px;font-size:28px;letter-spacing:-.04em}.panel p{margin:0;color:#b4cce0;font-size:13px}.panel button{margin-top:18px;padding:10px 16px;border:0;border-radius:6px;background:#69eaff;color:#031221;font-weight:800;cursor:pointer}@media(max-width:640px){.shell{width:100vw}.hud{padding:12px;font-size:10px}.tip{left:12px;bottom:10px;font-size:9px}}
|
|
</style>
|
|
</head>
|
|
<body><section class="shell"><canvas id="game" width="960" height="540" aria-label="Asterion Patrol game"></canvas>
|
|
<div class="hud"><div>ASTERION PATROL<br><b id="score">SCORE 00000</b></div><div id="status">HULL <b>■■■</b></div></div>
|
|
<p class="tip"><kbd>WASD</kbd>/<kbd>←↑→↓</kbd> move · <kbd>SPACE</kbd> or click fire · <kbd>P</kbd> pause</p>
|
|
<div id="overlay" class="overlay"><div class="panel"><h1>ASTERION PATROL</h1><p>Defend the nebula corridor.</p><button id="start">Launch mission</button></div></div>
|
|
</section>
|
|
<script>
|
|
const canvas=document.querySelector('#game'), ctx=canvas.getContext('2d'); ctx.imageSmoothingEnabled=false;
|
|
const scoreEl=document.querySelector('#score'), statusEl=document.querySelector('#status'), overlay=document.querySelector('#overlay'), start=document.querySelector('#start');
|
|
const load=src=>{const i=new Image();i.src=src;return i};
|
|
const bg=load('assets/space-nebula.png'), playerImg=load('output/spaceships/fleet_catalog_01.png'), enemyImgs=[load('output/spaceships/fleet_catalog_05.png'),load('output/spaceships/fleet_catalog_09.png'),load('output/spaceships/fleet_catalog_17.png')], impact=load('assets/plasma-impact.png');
|
|
let keys={}, bullets=[], enemies=[], bursts=[], player, score, last=0, spawn=0, running=false, paused=false;
|
|
function reset(){player={x:480,y:445,r:24,hp:3,cool:0};bullets=[];enemies=[];bursts=[];score=0;spawn=0;scoreEl.textContent='SCORE 00000';statusEl.innerHTML='HULL <b>■■■</b>'}
|
|
function fire(){if(!running||paused||player.cool>0)return;player.cool=.18;bullets.push({x:player.x,y:player.y-34,vy:-520});}
|
|
function spawnEnemy(){const img=enemyImgs[Math.floor(Math.random()*enemyImgs.length)];enemies.push({x:55+Math.random()*850,y:-45,v:45+Math.random()*45,r:23,img,hp:1,wiggle:Math.random()*6});}
|
|
function hit(x,y){bursts.push({x,y,t:0});}
|
|
function update(dt){if(!running||paused)return;const speed=260;let dx=(keys.ArrowRight||keys.d?1:0)-(keys.ArrowLeft||keys.a?1:0),dy=(keys.ArrowDown||keys.s?1:0)-(keys.ArrowUp||keys.w?1:0);if(dx||dy){const l=Math.hypot(dx,dy);player.x+=dx/l*speed*dt;player.y+=dy/l*speed*dt}player.x=Math.max(28,Math.min(932,player.x));player.y=Math.max(70,Math.min(500,player.y));player.cool=Math.max(0,player.cool-dt);spawn-=dt;if(spawn<=0){spawn=.65+Math.random()*.55;spawnEnemy()}bullets.forEach(b=>b.y+=b.vy*dt);bullets=bullets.filter(b=>b.y>-15);enemies.forEach(e=>{e.y+=e.v*dt;e.x+=Math.sin(performance.now()/700+e.wiggle)*20*dt});for(const b of bullets){for(const e of enemies){if(!e.dead&&Math.hypot(b.x-e.x,b.y-e.y)<e.r+5){e.dead=true;b.dead=true;score+=125;hit(e.x,e.y)}}}enemies=enemies.filter(e=>{if(e.dead)return false;if(e.y>575){player.hp--;hit(player.x,player.y);return false}return true});bullets=bullets.filter(b=>!b.dead);bursts.forEach(b=>b.t+=dt);bursts=bursts.filter(b=>b.t<.64);scoreEl.textContent='SCORE '+String(score).padStart(5,'0');statusEl.innerHTML='HULL <b>'+ '■'.repeat(Math.max(0,player.hp))+'□'.repeat(3-Math.max(0,player.hp))+'</b>';if(player.hp<=0){running=false;overlay.classList.remove('hidden');overlay.querySelector('h1').textContent='MISSION LOST';overlay.querySelector('p').textContent='Score: '+score;start.textContent='Retry mission'}}
|
|
function ship(img,x,y,size,rotation=0){ctx.save();ctx.translate(x,y);ctx.rotate(rotation);ctx.drawImage(img,-size/2,-size/2,size,size);ctx.restore()}
|
|
function draw(){ctx.clearRect(0,0,960,540);if(bg.complete){ctx.drawImage(bg,0,0,960,540)}else{ctx.fillStyle='#071128';ctx.fillRect(0,0,960,540)}ctx.fillStyle='rgba(4,12,30,.38)';ctx.fillRect(0,0,960,540);for(const b of bullets){ctx.fillStyle='#7effff';ctx.fillRect(b.x-2,b.y-12,4,16);ctx.fillStyle='#fff';ctx.fillRect(b.x-1,b.y-10,2,10)}enemies.forEach(e=>ship(e.img,e.x,e.y,66,Math.PI));if(player)ship(playerImg,player.x,player.y,76);bursts.forEach(b=>{if(!impact.complete)return;const frame=Math.min(3,Math.floor(b.t/.16)), fw=impact.naturalWidth/4, fh=impact.naturalHeight;ctx.save();ctx.globalCompositeOperation='screen';ctx.drawImage(impact,frame*fw,0,fw,fh,b.x-47,b.y-47,94,94);ctx.restore()})}
|
|
function frame(t){const dt=Math.min(.033,(t-last)/1000||0);last=t;update(dt);draw();requestAnimationFrame(frame)}requestAnimationFrame(frame);
|
|
window.addEventListener('keydown',e=>{keys[e.key]=true;if(e.code==='Space'){e.preventDefault();fire()}if(e.key.toLowerCase()==='p'&&running){paused=!paused}});window.addEventListener('keyup',e=>keys[e.key]=false);canvas.addEventListener('pointerdown',fire);start.addEventListener('click',()=>{reset();running=true;paused=false;overlay.classList.add('hidden')});
|
|
</script></body></html>
|