"use strict"; /* ================= 背景:星空与星云 ================= */ const STARS=[[],[],[]]; for(let i=0;i<3;i++)for(let j=0;j<26-i*6;j++)STARS[i].push({x:rnd(W),y:rnd(H),s:(0.35+i*0.4),sz:i===2?2:1,col:i===2?pick(["#aab8d8","#8fa8d8","#c0a8c8"]):"#3f4c74"}); const NEB=["#131c3f","#33102a","#0f2a1c","#101a33","#2a1030","#330f0f"]; let nebY=0,planet=null; function drawBG(dt){ ctx.fillStyle="#05070f";ctx.fillRect(0,0,W,H); const stage=G?G.stage-1:0,ncol=NEB[clamp(stage,0,5)]; nebY=(nebY+0.08*dt)%160; for(let k=0;k<2;k++){ const g=ctx.createRadialGradient(W*(k?0.75:0.25),nebY+k*160-160,10,W*(k?0.75:0.25),nebY+k*160-160,130); g.addColorStop(0,ncol);g.addColorStop(1,"transparent"); ctx.globalAlpha=0.38;ctx.fillStyle=g;ctx.fillRect(0,0,W,H);ctx.globalAlpha=1; } for(let i=0;i<3;i++)for(const s of STARS[i]){ s.y+=s.s*dt*(G&&G.odActive>0?2:1);if(s.y>H){s.y=-2;s.x=rnd(W);} ctx.fillStyle=s.col;ctx.fillRect(s.x,s.y,s.sz,s.sz); } if(planet){ planet.y+=0.12*dt; if(planet.y>H+40){planet=null;} else{ ctx.globalAlpha=0.5;ctx.fillStyle=planet.col; ctx.beginPath();ctx.arc(planet.x,planet.y,planet.r,0,TAU);ctx.fill(); ctx.globalAlpha=0.25;ctx.fillStyle="#fff"; ctx.fillRect(planet.x-planet.r,planet.y-2,planet.r*2,1); ctx.fillRect(planet.x-planet.r+3,planet.y+4,planet.r*2-6,1); ctx.globalAlpha=1; }} else if(Math.random()<0.001)planet={x:rnd(40,W-40),y:-40,r:rndi(14,26),col:pick(["#4a5a8c","#8c4a5a","#4a8c6e"])}; } /* ================= Boss 程序化像素绘制 ================= */ function px(x,y,w,h,col){ctx.fillStyle=col;ctx.fillRect(Math.round(x),Math.round(y),w,h);} function poly(pts,col){ctx.fillStyle=col;ctx.beginPath();ctx.moveTo(pts[0],pts[1]);for(let i=2;i0){const prog=1-b.transT/70; ox=rnd(-1.6,1.6);oy=rnd(-1.2,1.2); if(b.idx===1||b.idx===3||b.idx===4||b.idx===6||b.idx===7){mp=prog;if(prog>0.75)flashA=(1-prog)*1.6;} else{sc=prog<0.5?1-0.16*(prog*2):0.84+0.16*((prog-0.5)*2); if(prog<0.5)ph2=b.phase-1; flashA=prog<0.5?prog*0.8:(1-prog)*0.8;}} ctx.save();ctx.translate(Math.round(b.x+ox),Math.round(b.y+oy)); if(sc!==1)ctx.scale(sc,sc); const fl=b.flash>0?"#ffffff":null,t=b.t; if(b.idx===0){ // 巡逻巡洋舰 px(-22,-10,44,20,"#3a4460");px(-18,-14,36,6,"#5b6b8c");px(-14,10,28,5,"#2a3450"); px(-24,-6,6,12,"#5b6b8c");px(18,-6,6,12,"#5b6b8c"); px(-25,4,4,6,fl||"#e0393e");px(21,4,4,6,fl||"#e0393e"); px(-8,-18,16,8,"#5b6b8c");px(-4,-20,8,4,"#3a4460"); px(-6,-4,12,10,fl||"#ff9a3d");px(-3,-2,6,6,"#fff4d9"); for(let i=-2;i<=2;i++)px(i*8-1,12,2,3,t%40<20?"#ff5566":"#7a1622"); px(-22,-10,44,2,"#8c9ab8"); if(ph2>=1){px(-2,-27,4,8,"#5b6b8c");px(-1,-29,2,3,t%20<10?"#ff5566":"#7a1622");} // 二阶段顶部炮塔 }else if(b.idx===1){ // 太空原种:虫蛹↔蝠鲼(变身:裂皮→变粉→红眼→展翅) if(mp!==null){if(mp<0.5)drawPupa(t,fl,mp*2);else drawManta(t,fl,(mp-0.5)*2);} else if(ph2===0)drawPupa(t,fl,0);else drawManta(t,fl,1); }else if(b.idx===2){ // 歼灭者 for(let i=0;i<6;i++){const a=i/6*TAU+t*0.01; px(Math.cos(a)*19-3,Math.sin(a)*19-3,6,6,"#5b3a8c");} px(-16,-12,32,24,"#2a1f4a");px(-12,-16,24,6,"#4a3a7a");px(-12,10,24,6,"#4a3a7a"); px(-20,-4,6,8,"#4a3a7a");px(14,-4,6,8,"#4a3a7a"); px(-6,-6,12,12,fl||"#b05ce8");px(-3,-3,6,6,"#f0d6ff"); for(let i=0;i<3+ph2;i++){const a=t*0.02+i*TAU/(3+ph2); px(Math.cos(a)*26-2,Math.sin(a)*26-2,4,4,t%30<15?"#b05ce8":"#5c2a80");} }else if(b.idx===3){ // 暗黑突击:小体型高机动,翼展角15°/45°/70°随阶段展开 const SW=[15,45,70]; let swp=SW[Math.min(ph2,2)]; if(mp!==null){const s0=SW[Math.max(0,b.phase-1)],s1=SW[Math.min(b.phase,2)];swp=s0+(s1-s0)*mp;} ctx.scale(0.34,0.34); drawAssault(t,fl,swp,Math.min(ph2,2),mp); }else if(b.idx===4){ // 激光钢琴:全屏宽半碟要塞(二阶段裂壳露红核) drawPiano(b,t,fl,mp!==null?mp:(ph2>=1?1:0)); }else if(b.idx===5){ // 母巢核心 for(let i=0;i<8;i++){const a=i/8*TAU+t*0.005; px(Math.cos(a)*24-2,Math.sin(a)*24-2,5,5,"#8c5b2a");} px(-18,-14,36,28,"#4a3410");px(-14,-18,28,8,"#6e4f1a");px(-14,10,28,8,"#6e4f1a"); px(-22,-8,5,16,"#6e4f1a");px(17,-8,5,16,"#6e4f1a"); const pl=8+Math.sin(t*0.1)*2; px(-pl/2-3,-pl/2-3,pl+6,pl+6,fl||"#ff9a3d");px(-pl/2,-pl/2,pl,pl,"#fff4d9"); px(-2,-22,4,6,"#ffcf5c");px(-2,16,4,6,"#ffcf5c"); }else if(b.idx===6){ // 夺权者X:巨型母舰(二阶段抛侧舱→拉长钻石机身+僚机) if(ph2===0||mp!==null){ // P1巨舰主体(变身时收缩淡出) const hullA=mp!==null?1-mp:1,hullS=mp!==null?1-mp*0.25:1; if(hullA>0.02){ctx.save();ctx.scale(hullS,hullS);ctx.globalAlpha=hullA; poly([0,46,-14,8,-22,-30,-9,-42,9,-42,22,-30,14,8],"#d8e4f0"); poly([0,46,14,8,22,-30,9,-42,0,-42],"#9fb0c6"); poly([-9,-42,-34,-52,-26,-28,-9,-34],"#aab8cc");poly([9,-42,34,-52,26,-28,9,-34],"#aab8cc"); circ(0,-16,8,fl||"#43b8ff");circ(0,-16,4,"#bfefff"); px(-2,-40,4,18,"#43b8ff");px(-2,-4,4,30,"#43b8ff"); ctx.restore();ctx.globalAlpha=1;}} if(ph2===0||mp!==null){ // 侧舱(变身时抛离飞走) const jt=mp||0; if(jt<0.99)for(const sd of[-1,1]){ctx.save(); ctx.translate(sd*(55+jt*90),jt*60);ctx.rotate(sd*jt*0.7);ctx.globalAlpha=1-jt*0.8; px(-11,-22,22,44,"#aab8cc");px(-11,-22,22,4,"#43b8ff"); px(-8,18,16,6,"#8a9ab8"); for(let i=0;i<3;i++)px(-3,-10+i*8,6,4,fl||"#ff3355"); ctx.restore();ctx.globalAlpha=1;}} if(ph2>=1){ // 拉长钻石机身+机翼(二阶段显现) const di=mp!==null?mp:1; ctx.save();ctx.globalAlpha=di;const ds=0.7+0.3*di;ctx.scale(ds,ds); poly([0,58,-13,8,-8,-38,0,-48,8,-38,13,8],"#e8f4ff"); poly([0,58,13,8,8,-38,0,-48],"#9fb0c6"); poly([-8,-36,-36,-48,-28,-26,-8,-30],"#c0cddd");poly([8,-36,36,-48,28,-26,8,-30],"#c0cddd"); circ(0,-14,8,fl||"#43b8ff");circ(0,-14,4,"#bfefff"); px(-2,-36,4,18,"#43b8ff");px(-2,0,4,34,"#43b8ff"); ctx.restore();ctx.globalAlpha=1;} }else{ // 暗黑魔碟:核心+圆环;二阶段圆环前段裂成犄角+赤瞳激光;三阶段爆甲出刺+炮台 circ(0,0,20,"#1a1030");circ(0,0,15,"#12081c"); if(ph2===0&&mp===null){ // 一阶段:完整圆环绕核心缓转 ctx.strokeStyle="#55336e";ctx.lineWidth=13; ctx.beginPath();ctx.arc(0,0,30,0,TAU);ctx.stroke(); ctx.strokeStyle="#6e4490";ctx.lineWidth=5; ctx.beginPath();ctx.arc(0,0,30,0,TAU);ctx.stroke(); for(let i=0;i<4;i++){const a=i*Math.PI/2+t*0.012; px(Math.cos(a)*30-2,Math.sin(a)*30-2,4,4,"#c86bff");} px(-6,-4,12,8,"#2a1a3a"); }else{ if(mp!==null&&b.phase===1){ // 变形中:圆环残留快速淡出 const ra=1-clamp(mp/0.35,0,1); if(ra>0){ctx.globalAlpha=ra;ctx.strokeStyle="#55336e";ctx.lineWidth=13; ctx.beginPath();ctx.arc(0,0,30,0,TAU);ctx.stroke();ctx.globalAlpha=1;}} const ha=mp!==null?clamp((mp-0.25)/0.6,0,1):1; // 犄角展开度 if(ha>0){ctx.globalAlpha=ha; for(const sd of[-1,1]){ctx.save();ctx.rotate((1-ha)*sd*1.1); poly([sd*10,4,sd*30,-26,sd*46,-32,sd*40,-6,sd*20,12],"#55336e"); poly([sd*16,2,sd*32,-20,sd*40,-24,sd*34,-4,sd*20,8],"#6e4490"); if(ph2>=2){ // 三阶段爆甲:犄角外壳小刺+红色炮台 for(let k=0;k<3;k++)poly([sd*(25+k*7),-13-k*6,sd*(29+k*7),-22-k*6,sd*(33+k*7),-12-k*6],"#2a1a3a"); px(sd*41-3,-36,7,6,fl||"#ff3355");} ctx.restore();} ctx.globalAlpha=1;} const eyeA=mp!==null?clamp((mp-0.45)/0.5,0,1):1; // 核心赤瞳 if(eyeA>0){ctx.globalAlpha=eyeA; px(-11,-5,7,6,fl||"#ff3355");px(4,-5,7,6,fl||"#ff3355"); px(-9,-3,3,2,"#ffdfe5");px(6,-3,3,2,"#ffdfe5"); ctx.globalAlpha=1;} } } if(flashA>0){ctx.globalAlpha=flashA;ctx.strokeStyle="#fff"; ctx.beginPath();ctx.arc(0,0,b.r+6,0,TAU);ctx.stroke();ctx.globalAlpha=1;} if(b.flash>0)b.flash--; ctx.restore(); // 冲撞预警线(暗黑突击) if(b.dashSt&&b.dashSt.ph==="tele"&&G.time%4<2){ ctx.strokeStyle="#ff3355";ctx.globalAlpha=0.6; if(ctx.setLineDash)ctx.setLineDash([4,3]); ctx.beginPath();ctx.moveTo(b.x,b.y);ctx.lineTo(G.p.x,G.p.y);ctx.stroke(); if(ctx.setLineDash)ctx.setLineDash([]);ctx.globalAlpha=1;} // 多管血条:一管蓝/二管红/三管橙,当前管铺在下一管之上,见底锁血转场 const w=160,def2=BOSSES[b.idx],thr=def2.phases===2?[0.5]:[0.66,0.33]; const TUBEC=["#43b8ff","#e0393e","#ff9a3d"]; const ratio=clamp(b.hp/b.maxhp,0,1),bx=(W-w)/2; px(bx-2,8,w+4,8,"#0d1226"); px(bx,10,w,4,b.phase0&&G.time%8<4){ctx.strokeStyle="#fff";ctx.lineWidth=1;ctx.strokeRect(bx-2.5,7.5,w+5,9);} ctx.font="6px 'Press Start 2P',monospace";ctx.fillStyle="#fff";ctx.textAlign="center"; ctx.fillText(b.transT>0?b.name:(b.name+" "+Math.ceil(pc*100)+"%"),W/2,22); } /* ---- Boss 分体绘制助手 ---- */ function drawPupa(t,fl,cr){ // 太空原种·虫蛹(cr=裂变粉化进度0~1) ctx.rotate(Math.sin(t*0.05)*0.05); const body=[0,-40,11,-37,15,-28,13,-14,11,0,8,14,5,26,2,34,0,36,-2,34,-5,26,-8,14,-11,0,-13,-14,-15,-28,-11,-37]; poly(body,"#c9d6e2"); poly([0,-40,11,-37,15,-28,13,-14,11,0,8,14,5,26,2,34,0,36],"#9fb0c2"); if(cr>0){ctx.globalAlpha=cr*0.85;poly(body,"#f0a0c0");ctx.globalAlpha=1;} // 身体变粉 for(let i=0;i<5;i++){const wy=-24+i*11,ww=13-i*1.8;px(-ww,wy,ww*2,2,"#8498ac");} if(cr>0.25){px(-10,-18,3,14,"#8a2a3a");px(7,-8,3,12,"#8a2a3a");} // 表皮裂开 if(cr>0.55){px(-4,-30,3,10,"#8a2a3a");px(-13,-2,3,9,"#8a2a3a");} circ(0,-34,10,"#dde7f0");px(-9,-37,18,3,"#9fb0c2"); px(-6,-35,4,4,fl||"#43e8ff");px(2,-35,4,4,fl||"#43e8ff"); // 青眼 if(cr>0.3){ctx.globalAlpha=Math.min(1,(cr-0.3)/0.5); // 眼睛变红 px(-6,-35,4,4,"#ff3355");px(2,-35,4,4,"#ff3355");ctx.globalAlpha=1;} px(-12,-44,3,7,"#9fb0c2");px(9,-44,3,7,"#9fb0c2"); px(-19,-16,5,12,"#7fd8ff");px(14,-16,5,12,"#7fd8ff"); } function drawManta(t,fl,un){ // 太空原种·蝠鲼(un=展翅进度0~1) const ws=0.15+0.85*un; ctx.save();ctx.scale(ws,1+Math.sin(t*0.07)*0.04*un); // 翅膀自背后展开 poly([0,-20,-26,-14,-52,-4,-76,6,-58,12,-34,10,-16,16,0,20],"#c86aa8"); poly([0,-20,26,-14,52,-4,76,6,58,12,34,10,16,16,0,20],"#c86aa8"); poly([0,-18,-24,-12,-50,-3,-72,7,-56,9,-30,2,0,-8],"#e89cc4"); poly([0,-18,24,-12,50,-3,72,7,56,9,30,2,0,-8],"#e89cc4"); if(un>0.6)for(const sd of[-1,1])for(let i=0;i<3;i++)px(sd*(44+i*10)-1,6+i*2,2,2,"#7dff8a"); ctx.restore(); ctx.globalAlpha=Math.min(1,un*1.6); poly([0,28,-7,8,-8,-8,-5,-18,5,-18,8,-8,7,8],"#8a3a70"); circ(0,-18,9,"#e8d5e0"); px(-5,-19,3,4,"#3a1030");px(2,-19,3,4,"#3a1030"); px(-5,-18,3,3,fl||"#ff3355");px(2,-18,3,3,fl||"#ff3355"); // 红瞳 px(-13,-26,3,8,"#7dff8a");px(10,-26,3,8,"#7dff8a"); for(let i=0;i<4;i++)px(-9+i*5,0,3,5,fl||"#ff3355"); ctx.globalAlpha=1; } function drawAssault(t,fl,swp,ph,mp){ // 暗黑突击(swp=翼展角;炮管/推进器随阶段增多) const sw=swp*Math.PI/180; for(const sd of[-1,1]){ const tx=sd*(8+52*Math.sin(sw)),ty=14-52*Math.cos(sw); poly([sd*4,6,tx,ty,tx-sd*9,ty+11,sd*4,20],ph>=2?"#c01a2e":"#a01224"); poly([tx-sd*2,ty-6,tx+sd*3,ty-14,tx+sd*7,ty-3],"#6e0c1a");} poly([0,38,-5,6,-3,-26,3,-26,5,6],"#d4233a"); px(-2,8,4,8,fl||"#ffd23d"); const guns=[[16,4],[-16,4],[30,-4],[-30,-4],[42,-10],[-42,-10]]; const gn=ph>=2?6:ph>=1?4:2,pgn=ph===2?4:ph===1?2:0; for(let i=0;i=pgn)gl=Math.max(1,8*mp); // 新炮管生长 px(guns[i][0]-1.5,guns[i][1],3,gl,"#3a0a10");px(guns[i][0]-1,guns[i][1]+gl-2,2,2,"#ff9a3d");} const eng=[[7,-26],[-7,-26],[0,-30],[14,-22]]; const en=ph>=2?4:ph>=1?3:2,pen=ph===2?3:ph===1?2:0; for(let i=0;i=pen)el=Math.max(1,el*mp); // 新推进器 px(eng[i][0]-2,eng[i][1],4,3,"#5a0a14"); px(eng[i][0]-1.5,eng[i][1]-3-el,3,el,t%6<3?"#ff9a3d":"#ffcf5c");} if(ph>=1){poly([-52,-14,-24,-3,-22,-1,-50,-11],"#ffdfe5");poly([52,-14,24,-3,22,-1,50,-11],"#ffdfe5");} if(ph>=2){px(-4,-16,8,12,fl||"#ff3355");px(-2,-14,4,8,"#ffdfe5");} } function drawPiano(b,t,fl,split){ // 激光钢琴(split=裂壳进度0~1) const ks=b.keys,n=ks?ks.n:21; px(-(n-1)*4.75-5,6,(n-1)*9.5+10,19,"#141821"); // 键槽 for(let i=0;i=0)); px(kx-4,8,8,14,hot?(t%6<3?"#e8fbff":"#43e8ff"):"#d8e4f0"); px(kx-4,20,8,2,hot?"#43e8ff":"#8a9ab8");} const off=split*30; // 两半穹顶向左右裂开 for(const sd of[-1,1]){ctx.save();ctx.translate(sd*off,0); const q0=sd<0?Math.PI:Math.PI*1.5,q1=sd<0?Math.PI*1.5:Math.PI*2; ctx.fillStyle="#3a4460";ctx.beginPath();ctx.ellipse(0,6,118,34,0,q0,q1);ctx.lineTo(0,6);ctx.closePath();ctx.fill(); ctx.fillStyle="#55648a";ctx.beginPath();ctx.ellipse(0,6,92,26,0,q0,q1);ctx.lineTo(0,6);ctx.closePath();ctx.fill(); ctx.fillStyle="#6e7f9e";ctx.beginPath();ctx.ellipse(0,6,60,17,0,q0,q1);ctx.lineTo(0,6);ctx.closePath();ctx.fill(); ctx.restore();} if(split>0){ctx.globalAlpha=split; // 红色核心 const cr=10+Math.sin(t*0.12)*2; circ(0,-4,cr+4,"#7a0e1e");circ(0,-4,cr,fl||"#ff3355");circ(0,-4,cr*0.45,"#ffdfe5"); ctx.globalAlpha=1;} if(split<1){ctx.globalAlpha=1-split; // 一阶段青色核心窗 px(-7,-14,14,9,fl||"#43e8ff");px(-4,-12,8,5,"#bfefff"); ctx.globalAlpha=1;} px(-10-off,-32,20,10,"#3a4460");px(-6-off,-37,12,5,"#55648a"); // 舰桥随左半壳 if(split<0.4)for(let i=0;i<7;i++){const a=Math.PI+(i+0.5)/7*Math.PI; px(Math.cos(a)*110-1.5,6+Math.sin(a)*30-1.5,3,3,"#9fb4d8");} } /* ---- Boss光束与浮游炮渲染 ---- */ function drawBossFx(){ for(const f of G.funnels){ // 银钉无人机 poly([f.x,f.y-5,f.x+4,f.y+3,f.x-4,f.y+3],"#cfe4ff"); px(f.x-1,f.y-1,2,3,"#43b8ff"); if(f.flashT>0){ctx.globalAlpha=0.6;px(f.x-3,f.y+3,6,3,"#ffffff");ctx.globalAlpha=1;} } for(const bm of G.bbeams){ if(bm.tele>0){if(G.time%4<2){ctx.globalAlpha=0.5;ctx.strokeStyle=bm.col;ctx.lineWidth=1; ctx.beginPath();ctx.moveTo(bm.x1,bm.y1);ctx.lineTo(bm.x2,bm.y2);ctx.stroke();ctx.globalAlpha=1;}} else{ctx.globalCompositeOperation="lighter"; ctx.globalAlpha=clamp(bm.life/10,0,1)*0.85; ctx.strokeStyle=bm.col;ctx.lineWidth=bm.w; ctx.beginPath();ctx.moveTo(bm.x1,bm.y1);ctx.lineTo(bm.x2,bm.y2);ctx.stroke(); ctx.strokeStyle="#fff";ctx.lineWidth=Math.max(1,bm.w/3); ctx.beginPath();ctx.moveTo(bm.x1,bm.y1);ctx.lineTo(bm.x2,bm.y2);ctx.stroke(); ctx.globalAlpha=1;ctx.globalCompositeOperation="source-over";} } } /* ================= 世界渲染 ================= */ function drawSprite(spr,x,y){ctx.drawImage(spr,Math.round(x-spr.width/2),Math.round(y-spr.height/2));} function drawSeg(x1,y1,x2,y2,w,col,alpha){ const d=Math.hypot(x2-x1,y2-y1)||1,steps=Math.max(1,Math.ceil(d/2)); ctx.globalAlpha=alpha;ctx.fillStyle=col; for(let i=0;i<=steps;i++){const t2=i/steps; ctx.fillRect(Math.round(lerp(x1,x2,t2)-w/2),Math.round(lerp(y1,y2,t2)-w/2),w,w);} ctx.globalAlpha=1; } function render(){ ctx.save(); if(G&&G.shake>0){ctx.translate(rnd(-G.shake,G.shake)*0.5,rnd(-G.shake,G.shake)*0.5);} drawBG(1); if(G){ // 拾取物 for(const k of G.picks){const bob=Math.sin(k.t*0.1)*1.5;drawSprite(SPR[k.spr],k.x,k.y+bob);} // 敌机 for(const e of G.enemies){ if(e.dead)continue; ctx.save(); if(e.beh==="spin"){ctx.translate(e.x,e.y);ctx.rotate(e.rot);ctx.drawImage(e.spr,-e.dw/2,-e.dh/2,e.dw,e.dh);} else ctx.drawImage(e.spr,e.x-e.dw/2,e.y-e.dh/2,e.dw,e.dh); // 亚像素绘制,移动丝滑 ctx.restore(); // 冻结标记:四角冰晶+雪花粒子(不再用矩形染色) if(e.slow>0){const cx2=Math.round(e.x-e.dw/2),cy2=Math.round(e.y-e.dh/2); px(cx2,cy2,2,2,"#7fd8ff");px(cx2+e.dw-2,cy2,2,2,"#7fd8ff"); px(cx2,cy2+e.dh-2,2,2,"#7fd8ff");px(cx2+e.dw-2,cy2+e.dh-2,2,2,"#7fd8ff"); if(G.time%8===0)spawnPart(e.x+rnd(-4,4),e.y-e.dh/2,"#bfeaff",1);} // 中毒标记:绿色气泡 if(e.dot&&G.time%10===0)spawnPart(e.x+rnd(-3,3),e.y,"#9dff5c",1); // 彻底冻结:冰壳轮廓+冰凌(描边而非实心,避免误认血条) if(e.frozen>0){ctx.strokeStyle="#bfeaff";ctx.globalAlpha=0.9; ctx.strokeRect(Math.round(e.x-e.dw/2)-1.5,Math.round(e.y-e.dh/2)-1.5,e.dw+3,e.dh+3);ctx.globalAlpha=1; px(e.x-e.dw/2-2,e.y-1,2,2,"#fff");px(e.x+e.dw/2,e.y-3,2,2,"#fff");px(e.x-1,e.y-e.dh/2-3,2,2,"#fff");} // 寄生电浆标记:紫色水蛙脉动 if(e.leech&&G.time%8<4)px(e.x-2,e.y-2,4,4,"#b05ce8"); if(e.flash>0){ctx.globalAlpha=0.7;ctx.globalCompositeOperation="lighter";ctx.drawImage(e.spr,Math.round(e.x-e.dw/2),Math.round(e.y-e.dh/2),e.dw,e.dh);ctx.globalAlpha=1;ctx.globalCompositeOperation="source-over";} // 精英标记:头顶金色小皇冠 if(e.elite){const bob=Math.sin(G.time*0.1)*1.5; px(e.x-4,e.y-e.dh/2-5+bob,2,3,"#ffd23d");px(e.x-1,e.y-e.dh/2-7+bob,2,5,"#ffd23d");px(e.x+2,e.y-e.dh/2-5+bob,2,3,"#ffd23d");} // 受击迷你血条(真实扣减可见;图鉴演示中常驻显示) if((e.hpt>0&&e.hp0.4?"#5cff9d":"#ff5566");} } drawBoss();drawBossFx(); // 敌弹(圆形光晕烘焙在精灵内) for(const b of G.eb){ if(b.scale&&b.scale!==1){const sp=SPR[b.spr]; // 成长弹(花弹) ctx.save();ctx.translate(Math.round(b.x),Math.round(b.y));ctx.scale(b.scale,b.scale); ctx.drawImage(sp,-Math.round(sp.width/2),-Math.round(sp.height/2));ctx.restore();} else drawSprite(SPR[b.spr],b.x,b.y);} // 玩家子弹 for(const b of G.pb){ if(b.spr){ctx.save();ctx.translate(b.x,b.y); if(b.spin)ctx.rotate(G.time*0.35); else if(b.homing||b.rot)ctx.rotate(Math.atan2(b.vy,b.vx)+Math.PI/2); else if(b.t==="b")ctx.rotate(G.time*0.3); const bsc=b.scale||1,bspr=SPR[b.spr]; ctx.drawImage(bspr,-bspr.width*bsc/2,-bspr.height*bsc/2,bspr.width*bsc,bspr.height*bsc);ctx.restore();} else if(b.t==="f"){px(b.x-1.5,b.y-1.5,3,3,pick(["#ff9a3d","#ff5566","#ffd23d"]));} else if(b.t==="wv"){ // 波动炮:剑气弧光 const rr=b.r; ctx.globalCompositeOperation="lighter"; ctx.globalAlpha=0.75;ctx.strokeStyle="#43b8ff";ctx.lineWidth=4; ctx.beginPath();ctx.arc(b.x,b.y+rr*0.55,rr,Math.PI+0.55,TAU-0.55);ctx.stroke(); ctx.globalAlpha=0.9;ctx.strokeStyle="#bfe8ff";ctx.lineWidth=2; ctx.beginPath();ctx.arc(b.x,b.y+rr*0.55,rr*0.78,Math.PI+0.5,TAU-0.5);ctx.stroke(); ctx.globalAlpha=1;ctx.strokeStyle="#ffffff";ctx.lineWidth=1; ctx.beginPath();ctx.arc(b.x,b.y+rr*0.55,rr*0.58,Math.PI+0.45,TAU-0.45);ctx.stroke(); ctx.globalCompositeOperation="source-over";} else if(b.t==="rg"){ctx.globalAlpha=0.3;px(b.x-3.5,b.y-11,7,22,"#b98cff");ctx.globalAlpha=1; px(b.x-2,b.y-8,4,14,"#b98cff");px(b.x-1,b.y-8,2,14,"#fff");} else{px(b.x-1,b.y-3,2,5,"#7fd8ff");px(b.x-1,b.y-3,1,3,"#fff");} } // 激光束 / 电弧(线段式) for(const l of G.lasers){ drawSeg(l.x1,l.y1,l.x2,l.y2,l.w,l.col,0.4); drawSeg(l.x1,l.y1,l.x2,l.y2,Math.max(1,Math.round(l.w*0.4)),"#ffffff",0.9); l.life--; } G.lasers=G.lasers.filter(l=>l.life>0); for(const z of G.zaps){ for(let i=0;i0&&G.time%6<3)){ drawSprite(SPR[SHIPS[G.ship].spr],p.x,p.y); // 尾焰 const fl=G.time%8<4?3:5; px(p.x-1,p.y+7,2,fl,"#ff9a3d");px(p.x-0.5,p.y+7,1,fl-1,"#fff4d9"); if(G.odActive>0){ctx.globalAlpha=0.4;px(p.x-8,p.y-4,1,12,"#41e6ff");px(p.x+7,p.y-4,1,12,"#41e6ff");ctx.globalAlpha=1;} if(G.shield>0){ctx.strokeStyle="#43e8ff";ctx.globalAlpha=0.5+Math.sin(G.time*0.1)*0.2; ctx.beginPath();ctx.arc(p.x,p.y,11,0,TAU);ctx.stroke();ctx.globalAlpha=1;} if(p.focus){px(p.x-1,p.y-1,3,3,"#ff3355");px(p.x-0.5,p.y-0.5,2,2,"#fff"); ctx.strokeStyle="rgba(255,51,85,.5)";ctx.beginPath();ctx.arc(p.x,p.y,6,0,TAU);ctx.stroke();} } // 粒子与浮字(spark 粒子渲染为线段) for(const q of G.parts){ctx.globalAlpha=clamp(q.life/20,0,1); if(q.spark){ctx.strokeStyle=q.col;ctx.beginPath();ctx.moveTo(q.x,q.y);ctx.lineTo(q.x-q.vx*2.2,q.y-q.vy*2.2);ctx.stroke();} else px(q.x,q.y,q.sz,q.sz,q.col); ctx.globalAlpha=1;} ctx.textAlign="center"; for(const t of G.texts){ctx.globalAlpha=clamp(t.life/30,0,1);ctx.font="6px 'Press Start 2P',monospace";ctx.fillStyle=t.col;ctx.fillText(t.t,t.x,t.y);ctx.globalAlpha=1;} if(!G.demoSim)drawHUD(); // 横幅 if(G.bannerT>0){ const a=clamp(G.bannerT/30,0,1); ctx.globalAlpha=Math.min(1,a); ctx.font="bold 13px 'Microsoft YaHei',monospace"; ctx.fillStyle=G.banner.col;ctx.textAlign="center"; ctx.fillText(G.banner.t,W/2,H*0.38); ctx.font="7px 'Press Start 2P',monospace";ctx.fillStyle="#7f8cb8"; if(G.mutator&&G.bannerT>60)ctx.fillText(MUTS[G.mutator].ds,W/2,H*0.38+16); ctx.globalAlpha=1; } if(G.flash>0){ctx.globalAlpha=G.flash/18;ctx.fillStyle="#fff";ctx.fillRect(0,0,W,H);ctx.globalAlpha=1;} } ctx.restore(); } function drawHUD(){ ctx.textAlign="left";ctx.font="7px 'Press Start 2P',monospace"; ctx.fillStyle="#dfe8ff";ctx.fillText("SC "+G.score,5,10); ctx.fillStyle="#ffcf5c";ctx.textAlign="right";ctx.fillText("✦"+G.coins,W-5,10); ctx.textAlign="left"; ctx.fillStyle="#7f8cb8"; ctx.fillText(G.mode==="endless"?"WAVE "+G.wave:G.mode==="rush"?"BOSS RUSH":"STAGE "+G.stage+"-"+G.wave,5,20); if(G.combo>4){ctx.fillStyle="#ff9a3d";ctx.textAlign="right"; ctx.fillText("x"+(1+Math.min(2,G.combo*0.02)).toFixed(1)+" COMBO "+G.combo,W-5,20);ctx.textAlign="left";} // 结构值血条(绿→红)+ 备用机体 + 炸弹 const hpw=62,pc2=clamp(G.hp/G.maxHp,0,1); px(4,H-12,hpw+2,6,"#0d1226"); px(5,H-11,hpw,4,"#3a1020"); px(5,H-11,hpw*pc2,4,pc2>0.5?"#5cff9d":pc2>0.25?"#ffcf5c":"#ff5566"); for(let i=0;i0?"#fff":"#ff9a3d"); if(G.odActive>0){ctx.fillStyle="#ff9a3d";ctx.textAlign="center";ctx.fillText("暴走 OVERDRIVE",W/2,30);ctx.textAlign="left";} // 主武器装备栏图标 for(let i=0;i0){G.hitstop--;return;} if(G.shake>0)G.shake*=0.85;if(G.shake<0.3)G.shake=0; if(G.flash>0)G.flash--; if(G.bannerT>0)G.bannerT--; if(G.comboT>0){G.comboT--;if(G.comboT===0)G.combo=0;} updatePlayer(); if(G.demoSim)demoSpawn();else updateSpawner(); for(const e of G.enemies){e.uid=e.uid||_uid++;updateEnemy(e);} G.enemies=G.enemies.filter(e=>!e.dead); updateBoss(); updateBullets(); collide(); for(const q of G.parts){ if(q.suck){q.vx+=(q.suck.x-q.x)*0.09;q.vy+=(q.suck.y-q.y)*0.09;} else{q.vx*=0.95;q.vy*=0.95;} if(q.grav)q.vy+=q.grav; q.x+=q.vx;q.y+=q.vy;q.life--;} G.parts=G.parts.filter(q=>q.life>0); for(const t of G.texts){t.y-=0.5;t.life--;} G.texts=G.texts.filter(t=>t.life>0); } let last=0,acc=0; function loop(ts){ requestAnimationFrame(loop); const dt=Math.min(100,ts-last);last=ts; MUS.tick(); if(UI==="play"||UI==="demoSim"){ acc+=dt; let steps=0; while(acc>=1000/60&&steps<4){update();acc-=1000/60;steps++;} if(steps===4)acc=0; } render(); } requestAnimationFrame(loop);