- 太空原种:蛹体保持同一多边形,翅膀折叠藏于身后逐渐展开,裂纹逐条生长,眼青->红、侧囊青->绿渐变(删除drawPupa/drawManta双套形体切换) - 暗黑魔碟:圆环=左右两个半环,变形为两半环裂口扩张各自收缩成犄角;眼窝常存赤瞳点亮;三阶段爆甲=壳板带轨迹旋飞飞离+铆钉连续伸长成刺+炮台基座伸出炮管
512 lines
29 KiB
JavaScript
512 lines
29 KiB
JavaScript
"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;i<pts.length;i+=2)ctx.lineTo(pts[i],pts[i+1]);ctx.closePath();ctx.fill();}
|
||
function circ(x,y,r,col){ctx.fillStyle=col;ctx.beginPath();ctx.arc(x,y,r,0,TAU);ctx.fill();}
|
||
function drawBoss(){
|
||
const b=G.boss;if(!b)return;
|
||
// 变形动画:五个新Boss播专属变身(mp=进度);其余通用收缩展开 + 白光衰减
|
||
let ph2=b.phase,sc=1,ox=0,oy=0,flashA=0,mp=null;
|
||
if(b.transT>0){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){ // 太空原种:同一躯体连续变形——裂皮→粉化→红眼→身后展翅(无部件凭空出现)
|
||
drawProgenitor(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*(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{ // 暗黑魔碟:核心+圆环;变形=圆环前后裂开、两半各自收缩成犄角(同一几何体连续演化);爆甲=壳板飞离、铆钉伸长成刺
|
||
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 sB=Math.PI/2-open*0.55,sT=Math.PI/2-open*0.35; // 半环跨幅:前端裂口扩得更快
|
||
for(const sd of[-1,1]){
|
||
const a0=sd<0?Math.PI-sB:-sT,a1=sd<0?Math.PI+sT:sB; // 左半环[π-sB,π+sT] 右半环[-sT,sB]
|
||
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();
|
||
for(const ea of[a0,a1]){ctx.save(); // 裂口断茬(随弧端移动)
|
||
ctx.translate(Math.cos(ea)*R,Math.sin(ea)*R);ctx.rotate(ea+Math.PI/2);
|
||
px(-6,-8,12,4,"#2a1a3a");px(-3,-11,6,3,"#2a1a3a");ctx.restore();}
|
||
for(let k=0;k<3;k++){const aa=a0+(0.32+k*0.18)*(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.32+k*0.36)*(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");}
|
||
}
|
||
}
|
||
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.phase<thr.length?(TUBEC[b.phase+1]||"#3a1020"):"#3a1020"); // 底:下一管或暗底
|
||
const hi=b.phase===0?1:thr[b.phase-1],lo=b.phase<thr.length?thr[b.phase]:0;
|
||
const pc=clamp((ratio-lo)/(hi-lo),0,1);
|
||
px(bx,10,w*pc,4,TUBEC[b.phase]||b.col); // 当前管
|
||
for(const tt of thr)px(bx+Math.round(w*tt)-1,9,2,6,"#0d1226"); // 管界刻痕
|
||
if(b.transT>0&&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(t,fl,tf){ // 太空原种:同一躯体连续演化(tf 0=虫蛹→1=完全体),无部件凭空出现
|
||
ctx.rotate(Math.sin(t*0.05)*0.05);
|
||
const wE=Math.min(1,tf*1.2); // 展翅进度
|
||
// —— 翅膀:折叠时收在躯体正后方(被身体完全遮住),自背后向两侧展开 ——
|
||
ctx.save();
|
||
ctx.scale(0.05+0.95*wE,(0.55+0.45*wE)*(1+Math.sin(t*0.07)*0.04*wE));
|
||
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");
|
||
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();
|
||
// —— 躯体:蛹壳保持同一多边形,粉色血肉晕染全身 ——
|
||
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(tf>0){ctx.globalAlpha=tf*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");}
|
||
const crks=[[-10,-18,14,0.12],[7,-8,12,0.28],[-4,-30,10,0.44],[-13,-2,9,0.58]]; // 裂纹逐条生长(延长,非新增)
|
||
for(const ck of crks){const cl=Math.max(0,Math.min(1,(tf-ck[3])/0.3))*ck[2];
|
||
if(cl>0.5)px(ck[0],ck[1],3,cl,"#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(tf>0.3){ctx.globalAlpha=Math.min(1,(tf-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"); // 侧囊:青→荧绿渐变
|
||
if(tf>0.5){ctx.globalAlpha=(tf-0.5)*2;px(-19,-16,5,12,"#7dff8a");px(14,-16,5,12,"#7dff8a");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<gn;i++){let gl=8;if(mp!==null&&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<en;i++){let el=5+(t%4<2?2:0);if(mp!==null&&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<n;i++){const kx=-(n-1)*4.75+i*9.5;
|
||
const hot=ks&&(ks.hot===i||(ks.hotList&&ks.hotList.indexOf(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.hp<e.maxhp)||G.demoSim){const w2=e.dw,fr=clamp(e.hp/e.maxhp,0,1);
|
||
px(e.x-w2/2,e.y-e.dh/2-3,w2,2,"#3a1020");
|
||
px(e.x-w2/2,e.y-e.dh/2-3,w2*fr,2,fr>0.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;i<z.pts.length-1;i++){
|
||
drawSeg(z.pts[i][0],z.pts[i][1],z.pts[i+1][0],z.pts[i+1][1],1,z.col,0.9);
|
||
drawSeg(z.pts[i][0],z.pts[i][1],z.pts[i+1][0],z.pts[i+1][1],1,"#ffffff",0.5);}
|
||
}
|
||
// 区域效果 / 新星环 / 水雷(按类型差异化)
|
||
for(const z of G.zones){
|
||
if(z.kind==="plasma"){ // 电浆区:去除圆形区域,只保留内部窜动的电弧
|
||
if(G.time%4===0)zapVisual(z.x+rnd(-z.r*0.7,z.r*0.7),z.y+rnd(-z.r*0.7,z.r*0.7),z.x+rnd(-z.r*0.7,z.r*0.7),z.y+rnd(-z.r*0.7,z.r*0.7),pick(["#43e8ff","#bfeaff"]));
|
||
if(G.time%9===0)G.parts.push({x:z.x+rnd(-z.r/2,z.r/2),y:z.y+rnd(-z.r/2,z.r/2),vx:0,vy:-0.2,life:10,col:"#43e8ff",sz:1});
|
||
continue;
|
||
}
|
||
ctx.globalAlpha=0.16;ctx.fillStyle=z.col;
|
||
ctx.beginPath();ctx.arc(z.x,z.y,z.r,0,TAU);ctx.fill();
|
||
ctx.globalAlpha=0.5+Math.sin(G.time*0.1)*0.2;ctx.strokeStyle=z.col;
|
||
ctx.beginPath();ctx.arc(z.x,z.y,z.r,0,TAU);ctx.stroke();ctx.globalAlpha=1;
|
||
if(z.slow){ // 时间力场:逆时针旋转刻度
|
||
ctx.strokeStyle="#cfeaff";ctx.globalAlpha=0.6;
|
||
for(let i=0;i<6;i++){const a=-G.time*0.02+i*TAU/6;
|
||
ctx.beginPath();ctx.moveTo(z.x+Math.cos(a)*(z.r-6),z.y+Math.sin(a)*(z.r-6));
|
||
ctx.lineTo(z.x+Math.cos(a)*z.r,z.y+Math.sin(a)*z.r);ctx.stroke();}
|
||
ctx.globalAlpha=1;
|
||
}else if(z.pull){ // 引力:内旋纹
|
||
for(let i=0;i<3;i++){const a=G.time*0.08+i*TAU/3;
|
||
px(z.x+Math.cos(a)*z.r*0.6-1,z.y+Math.sin(a)*z.r*0.6-1,2,2,"#b05ce8");}}
|
||
}
|
||
// 热诱弹
|
||
for(const f of G.flares)drawSprite(SPR.flare,f.x,f.y);
|
||
for(const n of G.novas){
|
||
ctx.globalAlpha=clamp(n.life/22,0,1)*0.8;ctx.strokeStyle=n.col;
|
||
ctx.beginPath();ctx.arc(n.x,n.y,Math.max(1,n.r),0,TAU);ctx.stroke();ctx.globalAlpha=1;
|
||
}
|
||
// 圣光十字:逐渐变长 + 原地旋转90°
|
||
for(const hc of G.holies){
|
||
const hrot=(hc.t/36)*Math.PI/2,hL=hc.len*Math.min(1,hc.t/12),hA=clamp(hc.life/18,0,1);
|
||
for(let k=0;k<4;k++){const ang=hrot+k*Math.PI/2,hdx=Math.cos(ang),hdy=Math.sin(ang);
|
||
drawSeg(hc.x,hc.y,hc.x+hdx*hL,hc.y+hdy*hL,4,"#ffcf5c",0.4*hA);
|
||
drawSeg(hc.x,hc.y,hc.x+hdx*hL,hc.y+hdy*hL,2,"#fff4d9",0.9*hA);}
|
||
}
|
||
// 僚机 / 光刃 / 卫星 / 浮游单位
|
||
if(G.p.dead<=0){
|
||
for(const key of G.droneLoadout){const S=G.dst[key];
|
||
if(S&&S.x!==undefined){const dspr=SPR[DRONES[key].spr],dsc=DRONES[key].big||1;
|
||
if(dsc!==1)ctx.drawImage(dspr,Math.round(S.x-dspr.width*dsc/2),Math.round(S.y-dspr.height*dsc/2),Math.round(dspr.width*dsc),Math.round(dspr.height*dsc));
|
||
else drawSprite(dspr,S.x,S.y);}}
|
||
if(G.subLoadout.includes("orbit")){const lv=G.subs.orbit;
|
||
for(let i=0;i<lv;i++){const a=G.p.orbA+i*(TAU/lv);drawSprite(SPR.blade,G.p.x+Math.cos(a)*26,G.p.y+Math.sin(a)*26);}}
|
||
if(G.subLoadout.includes("sats")){const n=1+G.subs.sats;
|
||
for(let i=0;i<n;i++){const a=G.p.satA+i*(TAU/n);drawSprite(SPR.sat,G.p.x+Math.cos(a)*20,G.p.y+Math.sin(a)*20);}}
|
||
const skS=G.sst.seeker;if(skS&&skS.x!==undefined&&G.subLoadout.includes("seeker"))drawSprite(SPR.dGun2,skS.x,skS.y);
|
||
const bdS=G.sst.beamdrone;if(bdS&&bdS.x!==undefined&&G.subLoadout.includes("beamdrone"))drawSprite(SPR.dBeam,bdS.x,bdS.y);
|
||
|
||
if(G.subLoadout.includes("thorns"))for(let i=0;i<4;i++){const a=G.time*0.04+i*TAU/4;
|
||
px(G.p.x+Math.cos(a)*13-1,G.p.y+Math.sin(a)*13-1,3,3,"#c8d8ff");}
|
||
}
|
||
// 玩家
|
||
const p=G.p;
|
||
if(p.dead<=0&&!(p.inv>0&&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;i<G.stat.maxLives;i++){ctx.globalAlpha=i<G.lives?1:0.2;ctx.drawImage(SPR.heart,5+i*9,H-21);}
|
||
ctx.globalAlpha=1;
|
||
// 经验条
|
||
px(0,H-3,W,3,"#0d1226");px(0,H-3,W*clamp(G.xp/G.xpNeed,0,1),3,"#43e8ff");
|
||
ctx.fillStyle="#43e8ff";ctx.font="6px 'Press Start 2P',monospace";
|
||
ctx.fillText("LV"+G.level,W-24,H-5);
|
||
// 超频条
|
||
const odw=54;
|
||
px(W-odw-4,H-16,odw,4,"#0d1226");
|
||
px(W-odw-4,H-16,odw*clamp(G.od/100,0,1),4,G.odActive>0?"#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;i<G.loadout.length;i++){
|
||
const w=WEAPONS[G.loadout[i]],img=w.icon?ICONS[w.icon]:SPR[w.spr];
|
||
if(img)ctx.drawImage(img,W-10-i*9,H-28,7,7);
|
||
}
|
||
}
|
||
/* ================= 主循环 ================= */
|
||
function update(){
|
||
G.time++;
|
||
if(G.hitstop>0){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);
|