四Boss修正:魔碟犄角重做/钢琴免疫bug+节奏/原种翅膀下移/夺权者侧舱贴体+无人机阵型

- 暗黑魔碟:犄角改为朝下尖锥——外壳下边分开、以顶部为轴外旋、下端打开并伸长变尖(半环与锥刺同一几何体)
- 激光钢琴:修复爬升到y=52后永远满足入场免疫(y<ty-1)导致不掉血的bug(hurtBoss改用landed锁);攻击节奏全面提速(开局空窗40tick->8+mainT 36、单键预警7->3、激光tele12->7、模式间隔压缩)
- 太空原种:翅膀下移10px
- 夺权者X:侧舱±55->±37贴主机体(弹幕发射点同步);无人机P1=2架/P2=4架(修正原反);散弹重做=无人机脱离环绕摆阵型(横队垂直向下/两翼外张扇形/集火瞄准)4轮齐射后归位
- 无人机弹dartS改2×12蓝白细长精灵;无人机环绕遮挡分层(上半部先画被机体压住)
This commit is contained in:
JOJO 2026-08-05 21:05:38 +08:00
parent 45017fbd3f
commit 6895132500
3 changed files with 73 additions and 43 deletions

View File

@ -166,24 +166,46 @@ function shuffled(n){const a=[];for(let i=0;i<n;i++)a.push(i);for(let i=n-1;i>0;
function bossBeam(x1,y1,x2,y2,w,col,tele,dur,dmg,src){ // Boss光束tele帧预警→dur帧伤害src={b,ox,oy,followX2}时发射点跟随Boss
G.bbeams.push({x1,y1,x2,y2,w,col,tele,life:dur,dmg:dmg||20,cd:0,src:src||null});
}
function spawnFunnels(b,n){ // 夺权者X银钉无人机(环绕僚机,瞄准/散射轮换
function spawnFunnels(b,n){ // 夺权者X蓝白长钉无人机(环绕僚机;定期脱离环绕摆阵型齐射
for(let i=0;i<n;i++)
G.funnels.push({x:b.x,y:b.y,sa:i/n*TAU+0.5,t:rndi(0,40),fireT:rndi(50,110)+i*18,mode:i%2?"aim":"scatter",flashT:0});
G.funnels.push({x:b.x,y:b.y,sa:i/n*TAU+0.5,t:rndi(0,40),fireT:rndi(50,110)+i*18,slot:i,flashT:0});
}
function updateFunnels(){
const b=G.boss;
const b=G.boss,n=G.funnels.length;
if(b&&!b.dead&&n){
if(b.formT===undefined)b.formT=rndi(340,520);
if(b.form){b.form.t++;if(b.form.t>170)b.form=null;} // 阵型演出40帧入位→4轮齐射→归位
else if((b.laserT||0)<=0&&--b.formT<=0){ // 巨宽激光期间不排阵
b.form={pat:rndi(0,2),t:0};
G.funnels.forEach((f,i)=>{f.slot=i;});
b.formT=rndi(480,660);
}
}
for(const f of G.funnels){
f.t++;if(f.flashT>0)f.flashT--;
if(b&&!b.dead){f.sa+=0.014;
f.x+=(b.x+Math.cos(f.sa)*46-f.x)*0.09;
f.y+=(b.y+16+Math.sin(f.sa)*20-f.y)*0.09;}
if(b&&b.laserT>0)continue; // 巨宽核心激光期间僚机停火(仍环绕)
if(b&&!b.dead){
f.sa+=0.014;
const ringX=b.x+Math.cos(f.sa)*46,ringY=b.y+16+Math.sin(f.sa)*20;
if(b.form){ // 脱离环绕摆阵型:横队垂直向下 / 两翼外张扇形 / 集火瞄准
const fm2=b.form,i=f.slot||0,nn=Math.max(1,n-1);
let tx,ty,fa;
if(fm2.pat===0){tx=b.x+(i-nn/2)*26;ty=b.y-30;fa=Math.PI/2;}
else if(fm2.pat===1){const sd=i%2?1:-1;tx=b.x+sd*(36+Math.floor(i/2)*16);ty=b.y+6+Math.floor(i/2)*8;fa=Math.PI/2+sd*0.55;}
else{tx=b.x+(i-nn/2)*18;ty=b.y+26;fa=null;}
if(fm2.t<150){f.x+=(tx-f.x)*0.11;f.y+=(ty-f.y)*0.11;}
else{f.x+=(ringX-f.x)*0.09;f.y+=(ringY-f.y)*0.09;} // 归位回环绕
if((b.laserT||0)>0)continue;
if(fm2.t>=40&&fm2.t<140&&fm2.t%26===0){
const a2=fa===null?Math.atan2(aimPt().y-f.y,aimPt().x-f.x):fa;
ebul(f.x,f.y,a2,4.6,"dartS",14);f.flashT=5;}
}else{
f.x+=(ringX-f.x)*0.09;f.y+=(ringY-f.y)*0.09;
if((b.laserT||0)>0)continue; // 巨宽核心激光期间停火(仍环绕)
f.fireT--;
if(f.fireT<=0){ // 银色高弹速长钉:瞄准单发 / 三发散射 轮换
const ap=aimPt(),a=Math.atan2(ap.y-f.y,ap.x-f.x);
if(f.mode==="aim"){ebul(f.x,f.y,a,4.8,"dartS",15);f.mode="scatter";f.fireT=rndi(64,96);}
else{for(let k=-1;k<=1;k++)ebul(f.x,f.y,a+k*0.24,3.9,"dartS",13);f.mode="aim";f.fireT=rndi(100,140);}
f.flashT=5;}
if(f.fireT<=0){const a=Math.atan2(aimPt().y-f.y,aimPt().x-f.x);
ebul(f.x,f.y,a,4.8,"dartS",15);f.fireT=rndi(70,120);f.flashT=5;}
}
}
}
G.funnels=G.funnels.filter(f=>!f.dead);
}
@ -196,6 +218,7 @@ function startBoss(){
G.boss={idx,x:W/2,y:-60,ty:64,hp:b.hp*hpM,maxhp:b.hp*hpM,r:(b.rr?b.rr[0]:b.r),ry:(b.ryy?b.ryy[0]:undefined),t:0,phase:0,transT:0,
col:b.col,name:b.name,sub:b.sub,fireM:BAL.fireMul(n)*(G.diff===1?BAL.hard.fire:1),dead:false,
mainT:100,minorT:9999,minorSet:[],rerollT:340,ax:W/2,ay:64,mt:0,landed:false};
if(b.main==="piano")G.boss.mainT=36; // 激光钢琴:缩短开局空窗
G.bossN++;SFX.warn();MUS.start(true);
banner("WARNING — "+b.name,"#ff5566",150);
}
@ -210,7 +233,7 @@ function updateBoss(){
let np=0;for(let i=0;i<thr.length;i++)if(ratio<=thr[i])np=i+1;
if(np>b.phase){b.phase=np;b.transT=70;b.r=(def.rr&&def.rr[np])||b.r;b.ry=(def.ryy&&def.ryy[np])||undefined;
G.eb.length=0;G.bbeams.length=0;b.keys=null;SFX.warn();G.shake=Math.max(G.shake,5);
if(b.idx===6){G.funnels.length=0;if(np===1){spawnFunnels(b,2);b.plT=Math.round(300/b.fireM);}} // 夺权者X二阶段换为2架僚机
if(b.idx===6){G.funnels.length=0;b.form=null;if(np===1){spawnFunnels(b,4);b.plT=Math.round(300/b.fireM);}} // 夺权者X二阶段增至4架僚机
banner(b.name+" — 第"+["一","二","三"][np]+"形态","#ff5566",90);}
if(b.transT>0){b.transT--;
if(b.transT===0){explodeFx(b.x,b.y,"dark",1.3);G.shake=Math.max(G.shake,6);
@ -275,12 +298,12 @@ function bossMain(b,def,fm){
case "assault": // 暗黑突击:启动冲撞(锚点记当前位置,冲撞归来后正弦从此继续)
b.dashSt={ph:"tele",t:52};b.ax=b.x;b.ay=b.y;b.mainT=9999;break;
case "piano": { // 激光钢琴21键四种模式轮换——顺扫(逼走位)/反扫/乱序/齐射
if(!b.keys)b.keys={n:21,seq:[],i:0,t:40,hot:-1,hotT:0,hotList:null,mode:3};
if(!b.keys)b.keys={n:21,seq:[],i:0,t:8,hot:-1,hotT:0,hotList:null,mode:3};
const ks=b.keys,n=ks.n;
if(ks.hotT>0){ks.hotT--;
if(ks.hotT===0){const list=ks.hotList||[ks.hot];
for(const kh of list){if(kh<0)continue;const kx=-(n-1)*4.75+kh*9.5;
bossBeam(b.x+kx,b.y+18,b.x+kx,H+8,4,"#43e8ff",12,20,18,{b,ox:kx,oy:18,followX2:true});}
bossBeam(b.x+kx,b.y+18,b.x+kx,H+8,4,"#43e8ff",7,22,18,{b,ox:kx,oy:18,followX2:true});}
ks.hotList=null;ks.hot=-1;SFX.eshoot();}}
ks.t--;
if(ks.t<=0&&ks.hotT<=0){
@ -290,10 +313,10 @@ function bossMain(b,def,fm){
else if(ks.mode===1){ks.seq=[];for(let i=n-1;i>=0;i--)ks.seq.push(i);}
else if(ks.mode===2){ks.seq=shuffled(n);}
else{ks.seq=[];for(let i=0;i<6+ph*2;i++)ks.seq.push(-1);} // -1=齐射标记
ks.i=0;ks.t=Math.round((ph?36:56)/fm);
ks.i=0;ks.t=Math.round((ph?24:40)/fm);
}else{const cur=ks.seq[ks.i++];
if(cur===-1){const pool=shuffled(n);ks.hotList=pool.slice(0,4+ph*2);ks.hotT=16;ks.t=Math.round((ph?34:50)/fm);}
else{ks.hot=cur;ks.hotT=7;ks.t=Math.round((ph?3:5)/fm);} // 扫射模式:快速连发
if(cur===-1){const pool=shuffled(n);ks.hotList=pool.slice(0,4+ph*2);ks.hotT=9;ks.t=Math.round((ph?22:34)/fm);}
else{ks.hot=cur;ks.hotT=3;ks.t=Math.round((ph?2:3)/fm);} // 扫射模式:快速连发
}
}
b.mainT=6;break;}
@ -301,10 +324,10 @@ function bossMain(b,def,fm){
for(let i=0;i<10+ph*4;i++)ebul(b.x,b.y,i/(10+ph*4)*TAU+b.t*0.03,1.6+0.2*ph,"orbO");
if(b.t%240<3)for(let i=0;i<1+ph;i++)spawnEnemy(ph>=2?"kami":"mini",b.x+rnd(-30,30),b.y+8,false);
b.mainT=Math.round(Math.max(34,70-8*ph)/fm);break;
case "usurper": // 夺权者X银钉无人机P1四架/P2两架僚机P2周期性巨宽核心激光压制其他攻击
case "usurper": // 夺权者X蓝白长钉无人机P1两架/P2四架僚机P2周期性巨宽核心激光压制其他攻击
if(ph===0){
if(!b.fn0){b.fn0=1;spawnFunnels(b,4);}
for(const sd of[-1,1]){const px2=b.x+sd*55,py2=b.y-4,a6=Math.atan2(ap2.y-py2,ap2.x-px2);
if(!b.fn0){b.fn0=1;spawnFunnels(b,2);}
for(const sd of[-1,1]){const px2=b.x+sd*37,py2=b.y-4,a6=Math.atan2(ap2.y-py2,ap2.x-px2);
ebul(px2,py2,a6,4.4,"dartS",15);}
b.mainT=Math.round(46/fm);
}else{
@ -350,7 +373,7 @@ function bossMinor(b,mk,fm){
}
}
function hurtBoss(dmg){
const b=G.boss;if(!b||b.dead||b.transT>0||b.y<b.ty-1)return; // 入场未到位/变形转场期间不掉血
const b=G.boss;if(!b||b.dead||b.transT>0||!b.landed)return; // 入场未到位/变形转场期间不掉血landed锁防站桩Boss爬升后永远满足 y<ty 而免疫)
const def=BOSSES[b.idx],thr=def.phases===2?[0.5]:[0.66,0.33];
const floor=b.phase<thr.length?thr[b.phase]:0; // 当前血管的底
b.hp-=dmg;b.flash=2;

View File

@ -95,7 +95,7 @@ function drawBoss(){
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;
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");
@ -121,17 +121,18 @@ function drawBoss(){
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]
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();
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 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");
@ -145,9 +146,10 @@ function drawBoss(){
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; // 环面光点
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;}
@ -177,8 +179,9 @@ function drawBoss(){
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.translate(0,10);
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");
@ -249,12 +252,16 @@ function drawPiano(b,t,fl,split){ // 激光钢琴split=裂壳进度0~1
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){ // 银钉无人机
function drawFunnel(f){ // 蓝白长钉无人机(单架)
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;}
}
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;}}
@ -312,7 +319,7 @@ function render(){
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();
drawFunnelsBack();drawBoss();drawBossFx();
// 敌弹(圆形光晕烘焙在精灵内)
for(const b of G.eb){
if(b.scale&&b.scale!==1){const sp=SPR[b.spr]; // 成长弹(花弹)

View File

@ -99,14 +99,14 @@ function orb(c1,c2){
".HHCCCHH.",
"...HHH..."],{H:halo,W:"#ffffff",C:c2});
}
const BHALO={orbR:"#ff5566",orbP:"#b05ce8",orbG:"#5cff9d",orbO:"#ff9a3d",dartB:"#3a8cd8",flowr:"#ff8fc8",dartS:"#cfe4ff",dartP:"#b98cff"};
const BHALO={orbR:"#ff5566",orbP:"#b05ce8",orbG:"#5cff9d",orbO:"#ff9a3d",dartB:"#3a8cd8",flowr:"#ff8fc8",dartS:"#5ec4ff",dartP:"#b98cff"};
SPR.dartB=bake(["KCCCK",".CCC.",".CWC.","..C.."],{K:"#0d2433",C:"#3a8cd8",W:"#cfeaff"}); // 蓝色锥形敌弹Boss弹幕
SPR.flowr=bake([
"....P....",".P..P..P.","..PPPPP..",".PPWWWPP.","PPPWWWPPP",
".PPWWWPP.","..PPPPP..",".P..P..P.","....P...."],
{P:"#ff8fc8",W:"#ffffff"}); // 太空原种:花弹(圆形八刺,会逐渐变大)
SPR.dartS=bake(["SWS","SWS","SWS","SWS","SWS","SWS",".S.",".W."],
{S:"#cfe4ff",W:"#ffffff"}); // 夺权者X银色高弹速长钉
SPR.dartS=bake(["WW","WW","CC","CC","CC","CC","CC","CC","CC","CC","CC","CC"],
{W:"#f2fbff",C:"#5ec4ff"}); // 夺权者X蓝白细长钉2×12
SPR.dartP=bake(["KPPPK",".PPP.",".PWP.","..P.."],{K:"#2a1040",P:"#b98cff",W:"#ead9ff"}); // 暗黑魔碟:紫锥弹
SPR.orbR=orb("#ffe9e9","#ff5566");SPR.orbP=orb("#f4e9ff","#b05ce8");
SPR.orbG=orb("#e9ffe9","#5cff9d");SPR.orbO=orb("#fff4d9","#ff9a3d");