"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||b.idx===8||b.idx===9){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){ // 太空原种·装甲蛾兽:三角翼+蜈蚣腹+四红复眼(变形=背甲滑开裂粉肉/腹节伸长/翼膜转红) drawProgenitor(b,t,fl,mp!==null?mp:(ph2===0?0: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*(37+jt*95),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 if(b.idx===8){ // 黑石要塞:厚重箭头臂+五棱金字塔核心(俯视五芒星堡,缓慢旋转);纹路充能=主炮前摇 const op=mp!==null?mp:(ph2>=1?1:0); const chg=Math.max(b.warpChg||0,ph2>=1?0.55:0); // P2纹路常亮半格,主炮充能满亮 ctx.save();ctx.rotate(t*0.0032); // 整体缓慢旋转 for(let k=0;k<5;k++){ctx.save();ctx.rotate(k*TAU/5); // —— 五支厚重箭头臂 —— px(-2,-30,4,22,"#173d2a"); // 壳下亚空间光芯(常存,裂开才见) for(const sd of[-1,1]){ctx.save();ctx.translate(sd*op*2,0); // 二阶段臂板沿中线微开 poly([0,-50,sd*16,-32,sd*9,-26,sd*6,-6,0,-6],"#10151f"); // 下层臂板:宽肩后掠箭头剪影 poly([0,-47,sd*12.5,-31,sd*7,-25,sd*4.5,-8,0,-8],"#1b2434"); // 上层臂板(厚度层次) poly([0,-47,sd*12.5,-31,sd*7,-25,0,-30],sd<0?"#33436a":"#232f47"); // 箭头斜面受光(左亮右暗) px(sd*2-1,-22,2,12,"#0a0e16"); // 板缝 ctx.restore();} px(-1,-26,2,18,"#123324"); // 中央能量纹路(暗纹常存) if(chg>0){const vl=18*chg;px(-1,-8-vl,2,vl,"#5cff9d");} // 亮纹从臂根向箭头蔓延(充能前摇) px(-1.5,-50,3,3,fl||"#5cff9d"); // 箭头尖绿晶 ctx.restore();} for(let k=0;k<5;k++){ctx.save();ctx.rotate((k+0.5)*TAU/5); // 臂间小堡 poly([0,-15,6.5,-5,-6.5,-5],"#141b29");px(-1,-11,2,2,"#1f6a44"); ctx.restore();} poly([0,-20,12,-16,19,-6,19,6,12,16,0,20,-12,16,-19,6,-19,-6,-12,-16],"#10151f"); // 外环堡垒·下层 poly([0,-17,10,-13,16,-5,16,5,10,13,0,17,-10,13,-16,5,-16,-5,-10,-13],"#1b2434"); // 外环堡垒·上层 for(let i=0;i<10;i++){const a2=i*TAU/10;px(Math.sin(a2)*17.5-1,-Math.cos(a2)*17.5-1,2,2,t%40<20?"#3d4f73":"#24304a");} // 环上窗火 for(let k=0;k<5;k++){const a1=k*TAU/5-TAU/20,a2=k*TAU/5+TAU/20; // 五棱金字塔(俯视棱面交替受光) ctx.fillStyle=k%2?"#33436a":"#1b2434"; ctx.beginPath();ctx.moveTo(0,0);ctx.lineTo(Math.sin(a1)*13,-Math.cos(a1)*13);ctx.lineTo(Math.sin(a2)*13,-Math.cos(a2)*13);ctx.closePath();ctx.fill();} if(op>0){ctx.strokeStyle=`rgba(92,255,157,${op*0.7})`;ctx.lineWidth=1; // 二阶段棱缝透绿 for(let k=0;k<5;k++){const a=k*TAU/5+TAU/20;ctx.beginPath();ctx.moveTo(0,0);ctx.lineTo(Math.sin(a)*13,-Math.cos(a)*13);ctx.stroke();}} if(chg>0)circ(0,0,5+chg*3.5,`rgba(92,255,157,${chg*0.3})`); // 充能光晕 circ(0,0,4.5,"#0a2418");circ(0,0,3,fl||"#5cff9d");circ(0,0,1.3,"#d6ffe8"); // 亚空间核心 ctx.restore(); }else if(b.idx===9){ // 泰伦母舰:锥形生物舰(头下尾上);P2头背甲壳裂开露眼+伸出触手;P3触手间展开环齿嘴 let op2,op3; // 变形期 mp=0→1 连续驱动(phase在变形开始即+1,故mp优先于ph2) if(ph2===0){op2=0;op3=0;} // P1:甲壳闭合 else if(ph2===1){op2=mp!==null?mp:1;op3=0;} // P1→P2变形:甲壳滑开+触手伸出+眼渐显 else{op2=1;op3=mp!==null?mp:1;} // P2→P3变形:甲壳翻开+嘴展开+触手加粗 ctx.save();ctx.scale(1,1+Math.sin(t*0.05)*0.015); // 呼吸起伏 for(const sd of[-1,0,1]){const sw=Math.sin(t*0.055+sd*1.8)*3; // 尾须缓摆 poly([sd*8-2.5,-36,sd*8+2.5,-36,sd*11+sw,-52,sd*9+sw-1.5,-52],"#3d2f4a"); px(sd*11+sw-1,-54,2,3,"#e8dcc0");} poly([0,-44,18,-36,24,-18,20,2,12,18,6,32,0,38,-6,32,-12,18,-20,2,-24,-18,-18,-36],"#241a30"); // 锥形舰体·底 poly([0,-42,15,-34,20,-18,17,0,10,16,5,30,0,35,-5,30,-10,16,-17,0,-20,-18,-15,-34],"#3d2f4a"); // 中层甲壳 poly([0,-40,6,-30,7,-12,5,4,3,16,0,22,-3,16,-5,4,-7,-12,-6,-30],"#1a1426"); // 背脊中线甲 for(let i=0;i<3;i++){const yy=-38+i*7,ww=15-i*3; // 橄榄甲片叠瓦 poly([0,yy-4,ww,yy+1,ww-3,yy+5,-(ww-3),yy+5,-ww,yy+1],i%2?"#5a6a32":"#4a5628");} for(const sd of[-1,1])for(let i=0;i<3;i++){const yy=-30+i*12,xx=sd*(20-i*3); // 侧棘(骨白) poly([xx,yy,xx+sd*7,yy+3,xx,yy+6],"#e8dcc0");} for(const sd of[-1,1]){poly([sd*4,8,sd*13,4,sd*11,18,sd*6,26],"#b87a6a"); // 两侧粉肉 for(let i=0;i<3;i++)px(sd*(7+i*2)-1,9+i*5,2,4,"#8a5a4a");} // 肋纹 ctx.globalAlpha=0.35+0.3*Math.sin(t*0.09);px(-1,-34,2,44,"#7dff8a");ctx.globalAlpha=1; // 脉络搏动 if(op2>0){ctx.globalAlpha=op2; // 壳下组织:粉肉+大眼(瞳孔追玩家,P3变橙红) circ(0,20,8,"#b87a6a"); const ex=clamp((G.p.x-b.x)*0.02,-1.5,1.5),ey=clamp((G.p.y-b.y)*0.015,-1,2); circ(0,20,5.5,ph2>=2?"#e85c3a":"#b8e85c"); ctx.fillStyle="#0d0d14";ctx.fillRect(ex-1,17+ey,2.5,6); circ(ex-1.5,18.5+ey,0.8,"#e8ffd0"); ctx.globalAlpha=1;} for(const sd of[-1,1]){ctx.save();ctx.translate(sd*(op2*6+op3*5),-op3*3); // 头背甲壳两瓣:P2滑开/P3翻开 poly([0,20,sd*9,14,sd*8,28,sd*3,36,0,34],"#241a30"); poly([0,22,sd*6,17,sd*5,27,sd*2,32,0,31],"#3d2f4a"); ctx.restore();} if(op2>0){ // 触手:2长(角度制扇形摆荡,与判定同源)+4小 for(const sd of[-1,1]){ const pts=tentSegs(b,sd,op2),gl=pts[3].glow||0,sweeping=b.tent&&b.tent.side===sd; if(sweeping&&b.tent.act>17){b.tent.trail=b.tent.trail||[];b.tent.trail.push(pts.map(p=>({x:p.x,y:p.y})));if(b.tent.trail.length>10)b.tent.trail.shift();} // 扫击记录残影 else if(sweeping&&b.tent.trail&&b.tent.trail.length)b.tent.trail.shift(); if(sweeping&&b.tent.trail){const tr=b.tent.trail; for(let hi=0;hi=0;i--){const p=pts[i],f=i/6,r=Math.max(0.5,3.2*(1-f*0.45)*(1+op3*0.3)); if(gl>0.15){ctx.globalAlpha=gl*0.28;circ(p.x,p.y,r+2,"#a8ff5c");ctx.globalAlpha=1;} circ(p.x,p.y,r,i%2?"#7a4a6a":"#5a3a5a");} const p1=pts[5],p2=pts[6],ta=Math.atan2(p2.y-p1.y,p2.x-p1.x); // 骨白刀尖:朝向跟随末端切线 ctx.save();ctx.translate(p2.x,p2.y);ctx.rotate(ta-Math.PI/2); poly([-2.5,-1.5,2.5,-1.5,0,6],"#e8dcc0"); ctx.restore(); } if(b.tent&&b.tent.warn>0&&G.time%3===0){const pts2=tentSegs(b,b.tent.side,op2); // 前摇聚能粒子 G.parts.push({x:b.x+pts2[4].x+rnd(-24,24),y:b.y+pts2[4].y+rnd(-14,24),vx:0,vy:0,life:18,col:"#a8ff5c",sz:1,suck:{x:b.x+pts2[4].x,y:b.y+pts2[4].y}});} for(const sd of[-1,1]){const tentS=(x0,y0,len,wd,phs)=>{const sw2=Math.sin(t*0.06+phs)*3; // 4条小触手 for(let i2=0;i2<3;i2++){const f=i2/3;circ(x0+sw2*f*f*2,y0+len*f,wd*(1-f*0.4)*(1+op3*0.3),i2%2?"#5a3a5a":"#7a4a6a");}}; tentS(sd*15*op2,16,12*op2,1.8,sd*2.9+1);tentS(sd*4*op2,32,14*op2,1.6,sd*4.1+2);} } if(op3>0){const mw=9*op3; // 环齿嘴(P3展开,利齿缓慢旋动) circ(0,30,mw,"#4a1a1a");circ(0,30,mw*0.55,"#2a0d0d"); for(let i=0;i<8;i++){const a=i*TAU/8+t*0.02,tx3=Math.cos(a)*mw*0.82,ty3=Math.sin(a)*mw*0.82; poly([tx3-1.5,30+ty3,tx3+1.5,30+ty3,tx3*0.6,30+ty3*0.6],"#e8dcc0");}} ctx.restore(); }else{ // 暗黑魔碟:核心+圆环;变形=圆环前后裂开、两半各自收缩成犄角(同一几何体连续演化);爆甲=壳板飞离、铆钉伸长成刺 let open,fo,eyeG; if(b.phase===0){open=0;fo=0;eyeG=0;} else if(b.phase===1){open=mp!==null?mp:1;fo=0;eyeG=mp!==null?mp:1;} else{open=1;fo=mp!==null?mp:1;eyeG=1;} const R=30+open*3; // 外径微扩 circ(0,0,20,"#1a1030");circ(0,0,15,"#12081c"); px(-11,-5,7,6,"#241430");px(4,-5,7,6,"#241430"); // 眼窝常存 if(eyeG>0){ctx.globalAlpha=eyeG; // 赤瞳点亮(非凭空出现) px(-10,-4,5,4,fl||"#ff3355");px(5,-4,5,4,fl||"#ff3355"); px(-9,-3,2,1,"#ffdfe5");px(6,-3,2,1,"#ffdfe5"); ctx.globalAlpha=1;} const rot=open*0.55; // 外壳下边分开,以顶部为轴外旋,下端打开 for(const sd of[-1,1]){ctx.save(); ctx.translate(0,-R);ctx.rotate(-sd*rot);ctx.translate(0,R); // 绕顶端轴旋转:下端向外打开 const a0=sd<0?Math.PI/2:-Math.PI/2,a1=sd<0?Math.PI*1.5:Math.PI/2; // 左半环/右半环 ctx.strokeStyle="#55336e";ctx.lineWidth=13; ctx.beginPath();ctx.arc(0,0,R,a0,a1);ctx.stroke(); ctx.strokeStyle="#6e4490";ctx.lineWidth=5; ctx.beginPath();ctx.arc(0,0,R,a0,a1);ctx.stroke(); const hL=3+open*26; // 半环下端伸长变尖:圆环下半部分延续为朝下的锥刺(同一几何体) poly([-6.5,R,6.5,R,2.5,R+hL*0.55,0,R+hL,-2.5,R+hL*0.55],"#55336e"); poly([-3.5,R,3.5,R,1.2,R+hL*0.5,0,R+hL*0.8,-1.2,R+hL*0.5],"#6e4490"); for(let k=0;k<3;k++){const aa=a0+(0.35+k*0.2)*(a1-a0); const rx=Math.cos(aa),ry=Math.sin(aa),tx2=-ry,ty2=rx; const bl=2+9*fo; // 铆钉→尖刺连续伸长 poly([rx*(R+5)+tx2*2.5,ry*(R+5)+ty2*2.5, rx*(R+5)-tx2*2.5,ry*(R+5)-ty2*2.5, rx*(R+5+bl),ry*(R+5+bl)],fo>0.6?"#c86bff":"#2a1a3a"); if(fo<0.99){ctx.save(); // 外壳板:爆甲时旋转飞离(有运动轨迹,非消失) ctx.translate(rx*(R+9+fo*22)+tx2*fo*sd*9,ry*(R+9+fo*22)+ty2*fo*sd*9); ctx.rotate(aa+Math.PI/2+fo*sd*1.7);ctx.globalAlpha=1-fo; px(-5,-3,10,6,"#55336e");px(-5,-3,10,2,"#6e4490"); ctx.restore();ctx.globalAlpha=1;}} {const ta=a0+0.5*(a1-a0),bl2=2+6*fo; // 红色炮台:基座常存,爆甲后炮管伸出点亮 ctx.save();ctx.translate(Math.cos(ta)*(R+2),Math.sin(ta)*(R+2));ctx.rotate(ta+Math.PI/2); px(-2.5,-3,5,4,"#2a1a3a"); px(-1.5,-3-bl2,3,bl2,fo>0.4?(fl||"#ff3355"):"#3a2248"); ctx.restore();} for(let k=0;k<2;k++){const aa2=a0+(0.35+k*0.3)*(a1-a0)+Math.sin(t*0.012+k*2)*0.05; // 环面光点 px(Math.cos(aa2)*R-2,Math.sin(aa2)*R-2,4,4,"#c86bff");} ctx.restore();} circ(0,-R,4,"#6e4490"); // 顶部铰链节 } 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 drawProgenitor(b,t,fl,tf){ // 太空原种·装甲虫兽(tf 0=装甲态→1=血肉态):P1小翅芽→P2骨膜巨翼;无复眼(大颚+感器红缝=虫形压迫感);摆尾衔接于躯体之下(尾根被加长的身体覆住,无缝) ctx.rotate(Math.sin(t*0.05)*0.04); // 悬停微摇 const wS=0.22+0.78*tf; // 翼展:P1小翅芽→P2全展(同一对翼连续缩放) // —— 骨膜翼一对:3根骨刺撑起膜面(P1灰甲小翼→P2血红膜+暗红骨,同一几何体罩色) —— for(const sd of[-1,1]){ctx.save(); ctx.translate(sd*7,-8);ctx.scale(sd*wS,1+Math.sin(t*0.07)*0.03); // 呼吸+镜像 poly([0,-4,14,-16,34,-16,54,-10,48,0,34,6,16,4,0,2],"#c9d6e2"); // 膜面主甲 poly([0,-4,14,-16,34,-16,54,-10,40,-8,18,-8,0,-1],"#e8f0f6"); // 上缘受光 poly([0,2,16,4,34,6,48,0,36,0,14,2],"#8498ac"); // 下缘暗面 for(const tp of[[14,-15],[34,-15],[54,-9]])drawSeg(2,-2,tp[0],tp[1],2,"#8498ac",1); // 3根骨刺(辐射) if(tf>0){ctx.globalAlpha=tf*0.85; poly([0,-4,14,-16,34,-16,54,-10,48,0,34,6,16,4,0,2],"#c02030"); // P2膜面转血红(同面罩色) for(const tp of[[14,-15],[34,-15],[54,-9]])drawSeg(2,-2,tp[0],tp[1],2,"#7a1622",tf); // P2骨刺转暗红 ctx.globalAlpha=1;} ctx.restore();} // —— 摆尾:先画在躯体下层(尾根被加长的身体覆住,无缝衔接),5节逐节相位摆动;P2节间血红肉显露+整体伸长 —— const segH=5.2+tf*1.6,gap=11; for(let i=4;i>=0;i--){const sw=Math.sin(t*0.055+i*0.7)*(1.2+i),sy=gap+i*segH,hw=9-i*1.5; if(tf>0&&i<4){ctx.globalAlpha=tf*0.85;px(sw-hw+2,sy+segH-2,(hw-2)*2,2,"#c8202e");ctx.globalAlpha=1;} // 节间血肉环 poly([sw-hw,sy,sw+hw,sy,sw+hw-2,sy+segH,sw-hw+2,sy+segH],i%2?"#b8c8d8":"#c9d6e2"); // 尾节甲片 px(sw-hw,sy,3,segH,"#e8f0f6");px(sw+hw-3,sy,3,segH,"#8498ac"); // 左亮右暗 for(const sd of[-1,1])poly([sw+sd*(hw+1),sy+1,sw+sd*(hw+4),sy+2.5,sw+sd*(hw+1),sy+4],"#e8dcc0"); // 节侧骨刺 } const swT=Math.sin(t*0.055+2.8)*5.2,tipY=gap+4*segH; // 尾末生殖腔(花弹发射口,与enemies.js同源公式) circ(swT,tipY+2,2.6+Math.sin(t*0.11)*0.6,fl||(tf>0.5?"#ff2a3c":"#ff5566")); circ(swT,tipY+2,1.1,"#ffdfe5"); // —— 壳下血肉(常存于背甲之下,滑开才见;血红,非粉) —— if(tf>0){ctx.globalAlpha=tf*0.9; poly([0,-36,9,-29,11,-14,8,2,5,16,0,20,-5,16,-8,2,-11,-14,-9,-29],"#c8202e"); px(-2,-30,4,46,"#e8393a");ctx.globalAlpha=1;} // —— 背甲两瓣:沿背脊中线裂开,P2向外滑开(同一多边形平移) —— for(const sd of[-1,1]){ctx.save();ctx.translate(sd*tf*7,0); poly([0,-40,sd*11,-37,sd*15,-28,sd*13,-12,sd*10,4,sd*6,14,0,18],sd<0?"#c9d6e2":"#9fb0c2"); poly([0,-40,sd*11,-37,sd*15,-28,sd*7,-26,sd*5,-36],"#e8f0f6"); // 受光斜面 for(let i=0;i<4;i++)px(sd*(4+i*2.8)-1,-24+i*9,2,5,"#5a6c80"); // 甲片板缝 const crk=Math.max(0,Math.min(1,(tf-0.15)/0.4))*10; // 裂纹随tf生长(延长,非新增) if(crk>0.5)px(sd*6-1,-18,2,crk,"#8a2a3a"); ctx.restore();} // —— 头部:圆顶甲+锯齿犄角+下弯大颚(无复眼=虫形压迫感)+一线感器红缝 —— circ(0,-34,10,"#dde7f0");px(-9,-36,18,3,"#9fb0c2"); for(const sd of[-1,1]){poly([sd*6,-40,sd*15,-49,sd*12,-51,sd*4,-43],"#8498ac"); poly([sd*10,-44,sd*13,-48,sd*11,-43],"#5a6c80"); // 犄角锯齿棱 px(sd*9-1,-46,2,4,"#7dff8a");} // 犄角绿纹 px(-5,-33,10,1.6,t%30<15?"#ff3355":"#7a1622"); // 感器红缝(明灭) for(const sd of[-1,1]){poly([sd*3,-26,sd*9,-22,sd*8,-14,sd*5,-13,sd*2,-20],"#e8dcc0"); // 下弯大颚(骨白) px(sd*5,-16,2,3,"#8a6a4a");} // 颚尖暗色 if(b.eyeGlow>0){ctx.globalAlpha=b.eyeGlow/20; // 击发余辉:大颚+感器缝闪白 px(-8,-22,4,3,"#fff");px(4,-22,4,3,"#fff");px(-5,-33,10,1.6,"#fff");ctx.globalAlpha=1;} // —— 头侧青色管囊(P2转荧绿) —— for(const sd of[-1,1]){px(sd*15-2.5,-18,5,12,tf>0.5?"#7dff8a":(t%40<20?"#7fd8ff":"#43b8ff")); px(sd*15-1.5,-20,3,3,"#55648a");} } 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)*5-5,6,(n-1)*10+10,19,"#141821"); // 键槽(键距10统一,不再因非整数取整成对) 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");} if(ks&&ks.hotT>0&&ks.hotT0){const pr=1-ks.hotT/ks.hotT0; // 蓄力前摇:即将发射的键下方圆形蓄力 const list=ks.hotList||(ks.hot>=0?[ks.hot]:[]); for(const kh of list){const kx=-(n-1)*5+kh*10; ctx.globalAlpha=0.3+0.55*pr;ctx.strokeStyle="#43e8ff";ctx.lineWidth=1.5; ctx.beginPath();ctx.arc(kx,28,2+3.5*pr,0,TAU);ctx.stroke(); ctx.globalAlpha=0.22*pr;ctx.fillStyle="#43e8ff"; ctx.beginPath();ctx.arc(kx,28,2+3.5*pr,0,TAU);ctx.fill(); ctx.globalAlpha=1;}} 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;} for(const sd of[-1,1]){ctx.save();ctx.translate(sd*off,0); // 舰桥从中线剖开,各随各的半壳 px(sd<0?-10:0,-32,10,10,"#3a4460"); px(sd<0?-6:0,-37,6,5,"#55648a"); for(let i=0;i<7;i++){const a=Math.PI+(i+0.5)/7*Math.PI; // 壳沿铆钉也随半壳 if((sd<0)!==(a0){ctx.globalAlpha=0.6;px(f.x-3,f.y+3,6,3,"#ffffff");ctx.globalAlpha=1;} } function drawFunnelsBack(){ // 转到Boss上半部(背后)的无人机先画,被机体遮挡 for(const f of G.funnels)if(Math.sin(f.sa)<-0.15)drawFunnel(f); } function drawBossFx(){ for(const f of G.funnels)if(Math.sin(f.sa)>=-0.15)drawFunnel(f); // 下半部(身前)后画 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");} } drawFunnelsBack();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); if(b.rift>0)ctx.globalAlpha=0.5+0.4*Math.sin(G.time*0.35); // 裂隙弹预警闪烁 ctx.drawImage(sp,-Math.round(sp.width/2),-Math.round(sp.height/2));ctx.restore();} else if(b.spr==="dartS"||b.spr==="dartB"||b.spr==="dartP"||b.spr==="shardB"||b.spr==="spikeB"){const sp=SPR[b.spr]; // 长条弹:按飞行方向偏转 ctx.save();ctx.translate(Math.round(b.x),Math.round(b.y)); ctx.rotate(Math.atan2(b.vy,b.vx)-Math.PI/2); 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);