激光钢琴重设计:美术大改=4层同心装甲环带穹顶要塞+像素琴键(黑键纹样)+6小炮塔+左右大炮塔(左紫右蓝)+雷达+发光纹路蓄力蔓延+裂壳露红色管线网红核(内层滑更远阶梯裂口);攻击小改=新增第5模式滑奏(顺扫立刻反扫)/P2乱序双键和音/topRain改炮塔轮流开火/ring删改pedal踏板重音慢速大弹。绘制迁入新文件js/boss_art.js。仅语法检查通过,未运行验证
This commit is contained in:
parent
a7da7e1031
commit
b776149ff0
@ -132,6 +132,7 @@
|
||||
<script src="js/enemies.js"></script>
|
||||
<script src="js/combat.js"></script>
|
||||
<script src="js/render.js"></script>
|
||||
<script src="js/boss_art.js"></script>
|
||||
<script src="js/ui.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
78
star-raid/js/boss_art.js
Normal file
78
star-raid/js/boss_art.js
Normal file
@ -0,0 +1,78 @@
|
||||
"use strict";
|
||||
/* ================= Boss 美术库(大改重设计) =================
|
||||
激光钢琴 drawPiano / 夺权者X drawUsurper / 暗黑魔碟 drawSaucer
|
||||
设计标准见 .astrion/memory/boss_design_standard.md:
|
||||
大剪影+3~4层配色递进+多层结构+受光逻辑+发光焦点+微动效;变形=同一几何体连续演化 */
|
||||
/* ---- 激光钢琴:穹顶琴键要塞(split=裂壳进度0~1) ----
|
||||
4层同心装甲环带+像素琴键炮口带;变形=环带沿中线阶梯滑开(内层滑更远)露红色管线网+红核 */
|
||||
function drawPiano(b,t,fl,split){
|
||||
const ks=b.keys,n=ks?ks.n:21;
|
||||
// —— 红色管线网(壳下常存,裂壳才见;锯齿线非圆) ——
|
||||
if(split>0){ctx.globalAlpha=split*0.85;
|
||||
for(let k=0;k<5;k++){const lx=-40+k*20;
|
||||
px(lx,-14,1,10,"#ff3355");px(lx,-5,4,1,"#ff3355");px(lx+3,-4,1,8,"#ff3355");}
|
||||
ctx.globalAlpha=1;}
|
||||
// —— 红色核心(裂壳显现,搏动;收在穹顶基线之上) ——
|
||||
if(split>0){ctx.globalAlpha=split;
|
||||
const cr=8+Math.sin(t*0.12)*1.5;
|
||||
circ(0,-8,cr+4,"#7a0e1e");circ(0,-8,cr,fl||"#ff3355");circ(0,-8,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;}
|
||||
// —— 穹顶4层装甲环带(沿中线剖半;内层滑得更远=阶梯裂口) ——
|
||||
const BANDS=[[118,34,"#10151f"],[98,28,"#1b2434"],[74,21,"#33436a"],[50,14,"#55648a"]];
|
||||
for(let bi=0;bi<4;bi++){const off=split*(16+bi*6),rx=BANDS[bi][0],ry=BANDS[bi][1];
|
||||
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=BANDS[bi][2];ctx.beginPath();ctx.ellipse(0,6,rx,ry,0,q0,q1);ctx.lineTo(0,6);ctx.closePath();ctx.fill();
|
||||
ctx.restore();}}
|
||||
// —— 外环窗火+壳沿铆钉(随最外半壳滑开) ——
|
||||
for(const sd of[-1,1]){ctx.save();ctx.translate(sd*split*16,0);
|
||||
for(let k=0;k<4;k++)px(sd*(20+k*22)-2,-2,4,3,t%40<20?"#3d5a73":"#24304a"); // 窗火明暗
|
||||
for(let i=0;i<7;i++){const a=Math.PI+(i+0.5)/7*Math.PI; // 壳沿铆钉
|
||||
if((sd<0)!==(a<Math.PI*1.5))continue;
|
||||
px(Math.cos(a)*110-1.5,6+Math.sin(a)*30-1.5,3,3,"#9fb4d8");}
|
||||
ctx.restore();}
|
||||
// —— 6座穹顶小炮塔(topRain炮位,与enemies.js同源:x=-75+ti*30,P2随第3环带滑开28) ——
|
||||
{const off2=split*28;
|
||||
for(let ti=0;ti<6;ti++){const sd2=ti<3?-1:1,tx=-75+ti*30+sd2*off2;
|
||||
circ(tx,-4,3.5,"#33436a");circ(tx-1,-5,1.5,"#55648a");
|
||||
px(tx-1,-2,2,5,b.turrT>0&&b.turrI===ti?"#e8fbff":"#1b2434");}}
|
||||
// —— 左右大炮塔(踏板重音炮位;左紫右蓝;P2随最外环带滑开16) ——
|
||||
{const off0=split*16;
|
||||
for(const sd of[-1,1]){const px2=sd*(112+off0);
|
||||
circ(px2,2,5.5,"#33436a");circ(px2-1.5,0,2.5,"#55648a");
|
||||
px(px2-1.5,4,3,8,"#1b2434");
|
||||
px(px2-1,10,2,3,b.pedT>0?"#ffffff":(sd<0?"#b98cff":"#43e8ff"));}}
|
||||
// —— 舰桥+雷达(随半壳滑开) ——
|
||||
for(const sd of[-1,1]){ctx.save();ctx.translate(sd*split*30,0);
|
||||
px(sd<0?-10:0,-32,10,10,"#1b2434");
|
||||
px(sd<0?-6:0,-37,6,5,"#33436a");
|
||||
ctx.restore();}
|
||||
ctx.save();ctx.translate(-split*30,0); // 雷达随左半壳
|
||||
circ(0,-34,4,"#33436a");
|
||||
drawSeg(0,-34,Math.cos(t*0.05)*8,-34+Math.sin(t*0.05)*8,1,"#43e8ff",0.55); // 雷达扫面
|
||||
px(-1,-38,2,2,t%30<15?"#ff3355":"#7a1622");
|
||||
ctx.restore();
|
||||
// —— 发光纹路(暗纹常存,蓄力时向键带点亮蔓延) ——
|
||||
{const pr=(ks&&ks.hotT>0&&ks.hotT0)?1-ks.hotT/ks.hotT0:0;
|
||||
for(let k=0;k<5;k++){const tx2=-60+k*30;
|
||||
px(tx2,-8,1,12,"#134152");
|
||||
if(pr>0){ctx.globalAlpha=pr*0.7;px(tx2,-8,1,12,"#43e8ff");ctx.globalAlpha=1;}}}
|
||||
// —— 琴键炮口带(21键像素钢琴:白键+黑键+键缝;键位公式 -(n-1)*5+k*10 与enemies.js同步) ——
|
||||
px(-(n-1)*5-5,6,(n-1)*10+10,19,"#0a0e16"); // 键槽
|
||||
for(let i=0;i<n;i++){const kx=-(n-1)*5+i*10;
|
||||
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");
|
||||
if(i<n-1&&[0,1,3,4,5].indexOf(i%7)>=0)px(kx+3,8,4,7,"#101318");} // 黑键(钢琴纹样)
|
||||
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;}}
|
||||
}
|
||||
@ -192,7 +192,7 @@ const BOSSES=[
|
||||
{name:"太空原种",sub:"SPACE SPAWN",hp:1350,col:"#ff8fc8",r:20,rr:[20,34],ryy:[26,18],wide:1,main:"spawn",minors:["wiggle","spikeSnipe","sporeArc","tentBeam","mawSnipe","wingRain"],phases:2},
|
||||
{name:"歼灭者",sub:"ANNIHILATOR",hp:1700,col:"#b05ce8",r:22,main:"annihilate",minors:["aimSpread","spawnMine"],phases:3},
|
||||
{name:"暗黑突击",sub:"DARK ASSAULT",hp:2100,col:"#ff3355",r:9,rr:[9,10,11],ryy:[5,6,7],main:"assault",minors:["rain","aimSpread"],phases:3},
|
||||
{name:"激光钢琴",sub:"LASER PIANO",hp:2500,col:"#43e8ff",r:26,rr:[100,100],ryy:[30,30],main:"piano",minors:["topRain","ring"],phases:2,static:1},
|
||||
{name:"激光钢琴",sub:"LASER PIANO",hp:2500,col:"#43e8ff",r:26,rr:[100,100],ryy:[30,30],main:"piano",minors:["topRain","pedal"],phases:2,static:1},
|
||||
{name:"母巢核心",sub:"HIVE CORE",hp:2800,col:"#ff9a3d",r:22,main:"hive",minors:["spawnMini","aimSpread","ring"],phases:3},
|
||||
{name:"夺权者X",sub:"USURPER X",hp:3700,col:"#e8f4ff",r:24,rr:[52,26],ryy:[34,40],wide:1,main:"usurper",minors:["chain","pillar","aimSpread"],phases:2},
|
||||
{name:"暗黑魔碟",sub:"DARK SAUCER",hp:4500,col:"#8a5cff",r:24,rr:[30,32,34],main:"saucer",minors:["ringBig","coneFan","crossEye","dartSpiral"],phases:3},
|
||||
@ -302,7 +302,7 @@ function updateBoss(){
|
||||
else{b.x=W/2+Math.sin(b.mt*0.013)*(def.wide?44:70);b.y=b.ty+Math.sin(b.mt*0.011)*(def.wide?8:12);} // 通用游走提速+上下浮动(钢琴站桩除外)
|
||||
b.mt++;
|
||||
const fm=b.fireM*(1+0.22*b.phase);
|
||||
if(b.fnT>0)b.fnT--;if(b.plT>0)b.plT--;if(b.eyeT>0)b.eyeT--;if(b.hornT>0)b.hornT--;if(b.laserT>0)b.laserT--;if(b.eyeGlow>0)b.eyeGlow--;if(b.rainT>0)b.rainT--; // 帧级技能计时器
|
||||
if(b.fnT>0)b.fnT--;if(b.plT>0)b.plT--;if(b.eyeT>0)b.eyeT--;if(b.hornT>0)b.hornT--;if(b.laserT>0)b.laserT--;if(b.eyeGlow>0)b.eyeGlow--;if(b.rainT>0)b.rainT--;if(b.turrT>0)b.turrT--;if(b.pedT>0)b.pedT--; // 帧级技能计时器
|
||||
if(b.idx===1&&b.rainT>0&&b.rainT<94&&b.rainT%3===0){ // 原种红雨:前摇16帧双翼高频抖动后,翼展范围内红光点随机洒落
|
||||
const span=46*(0.22+0.78*(b.phase>=1?1:0.62));
|
||||
ebul(b.x+rnd(-span,span),b.y+rnd(-10,2),Math.PI/2+rnd(-0.08,0.08),rnd(2.2,3.4),"orbR",12);}
|
||||
@ -385,17 +385,18 @@ function bossMain(b,def,fm){
|
||||
ks.hotList=null;ks.hot=-1;SFX.eshoot();}}
|
||||
ks.t--;
|
||||
if(ks.t<=0&&ks.hotT<=0){
|
||||
if(ks.i>=ks.seq.length){ // 布下一局:轮换模式
|
||||
ks.mode=(ks.mode+1)%4;
|
||||
if(ks.i>=ks.seq.length){ // 布下一局:轮换模式(5种)
|
||||
ks.mode=(ks.mode+1)%5;
|
||||
if(ks.mode===0){ks.seq=[];for(let i=0;i<n-3;i++)ks.seq.push(i);} // 顺扫止于18号:最右3键永远安全
|
||||
else if(ks.mode===1){ks.seq=[];for(let i=n-1;i>=3;i--)ks.seq.push(i);} // 反扫止于3号:最左3键永远安全
|
||||
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=齐射标记
|
||||
else if(ks.mode===3){ks.seq=[];for(let i=0;i<6+ph*2;i++)ks.seq.push(-1);} // -1=齐射标记
|
||||
else{ks.seq=[];for(let i=0;i<n-3;i++)ks.seq.push(i);for(let i=n-4;i>=0;i--)ks.seq.push(i);} // 滑奏:顺扫到尾立刻反扫回来
|
||||
ks.i=0;ks.t=Math.round((ph?14:22)/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=9;ks.hotT0=9;ks.t=Math.round((ph?21:30)/fm);} // 齐射:慢50%
|
||||
else if(ks.mode<=1){ks.hot=cur;ks.hotT=1;ks.hotT0=1;ks.t=Math.round((ph?2:3)/fm);} // 顺/反扫:前摇快50%
|
||||
else{ks.hot=cur;ks.hotT=3;ks.hotT0=3;ks.t=Math.round((ph?3:5)/fm);} // 乱序:慢50%
|
||||
else if(ks.mode<=1||ks.mode===4){ks.hot=cur;ks.hotT=1;ks.hotT0=1;ks.t=Math.round((ph?2:3)/fm);} // 顺/反扫/滑奏:前摇快50%
|
||||
else{if(ph>=1){ks.hotList=[cur,ks.i<ks.seq.length?ks.seq[ks.i++]:cur];ks.hot=-1;}else{ks.hot=cur;}ks.hotT=3;ks.hotT0=3;ks.t=Math.round((ph?3:5)/fm);} // 乱序:P2一次点2键(和音)
|
||||
}
|
||||
}
|
||||
b.mainT=4;break;}
|
||||
@ -466,7 +467,16 @@ function bossMinor(b,mk,fm){
|
||||
case "spawnMine": spawnEnemy("mine",b.x,b.y+10,false);b.minorT=Math.round(280/fm);break;
|
||||
case "aimSpread": for(let i=-2;i<=2;i++)ebul(b.x,b.y+12,a0+i*0.19,2.5,"orbO",15);b.minorT=Math.round(140/fm);break;
|
||||
case "rain": for(let i=0;i<5;i++)ebul(rnd(20,W-20),-6,Math.PI/2+rnd(-0.1,0.1),2.2,"orbO",14);b.minorT=Math.round(125/fm);break;
|
||||
case "topRain": for(let i=0;i<4;i++)ebul(rnd(30,W-30),-6,Math.PI/2+rnd(-0.15,0.15),1.9,"orbG",13);b.minorT=Math.round(150/fm);break;
|
||||
case "topRain": { // 钢琴:6座穹顶小炮塔轮流开火(炮位与drawPiano同源;P2随环带滑开28)
|
||||
const off2=b.phase>=1?28:0,ti=rndi(0,5),tx=b.x+(-75+ti*30)+(ti<3?-1:1)*off2;
|
||||
b.turrI=ti;b.turrT=8;
|
||||
for(let i=0;i<3;i++)ebul(tx+rnd(-3,3),b.y-2,Math.PI/2+rnd(-0.12,0.12),2.0,"orbG",13);
|
||||
b.minorT=Math.round(150/fm);break;}
|
||||
case "pedal": { // 钢琴:踏板重音——左右大炮塔慢速大弹(左紫右蓝;P2随外环滑开16;炮位与drawPiano同源)
|
||||
const off0=b.phase>=1?16:0;
|
||||
for(const sd of[-1,1]){const bb=ebul(b.x+sd*(112+off0),b.y+10,Math.PI/2+sd*0.08,1.1,sd<0?"orbP":"dartB",18);
|
||||
bb.scale=2.4;bb.r=6;}
|
||||
b.pedT=8;b.minorT=Math.round(280/fm);break;}
|
||||
case "chain": for(let arm=0;arm<2;arm++)for(let i=0;i<4;i++)ebul(b.x,b.y+8,b.t*0.09+arm*Math.PI+i*0.11,1.7,"orbO",14);b.minorT=Math.round(44/fm);break;
|
||||
case "pillar": {const px2=rnd(40,W-40);bossBeam(px2,-6,px2,H+8,4,"#b98cff",26,26,18);b.minorT=Math.round(220/fm);break;}
|
||||
case "eyeBeam": bossBeam(b.x,b.y+6,ap2.x,H+8,4,"#ff3355",22,24,20);b.minorT=Math.round(180/fm);break;
|
||||
|
||||
@ -337,43 +337,6 @@ function drawAssault(t,fl,swp,ph,mp){ // 暗黑突击(swp=翼展角;炮管/
|
||||
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<n;i++){const kx=-(n-1)*5+i*10;
|
||||
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");}
|
||||
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)!==(a<Math.PI*1.5))continue;
|
||||
px(Math.cos(a)*110-1.5,6+Math.sin(a)*30-1.5,3,3,"#9fb4d8");}
|
||||
ctx.restore();}
|
||||
}
|
||||
/* ---- Boss光束与浮游炮渲染 ---- */
|
||||
function drawFunnel(f){ // 蓝白长钉无人机(单架)
|
||||
poly([f.x,f.y-5,f.x+4,f.y+3,f.x-4,f.y+3],"#cfe4ff");
|
||||
|
||||
Loading…
Reference in New Issue
Block a user