diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..1d69658
Binary files /dev/null and b/.DS_Store differ
diff --git a/demo/tentacle-sweep.html b/demo/tentacle-sweep.html
new file mode 100644
index 0000000..82bbd6b
--- /dev/null
+++ b/demo/tentacle-sweep.html
@@ -0,0 +1,136 @@
+
+
+
+
+泰伦触手扫击 · 动作设计 Demo v2
+
+
+
+
+右侧触手:前摇向右后拉蓄力(发光+聚能粒子)→ 从右下向左下 60° 扇形挥鞭(残影轨迹)→ 顿挫收回 · 判定=根部圆心扇形
+
+
+
diff --git a/js/enemies.js b/js/enemies.js
index 4a0380c..4856495 100644
--- a/js/enemies.js
+++ b/js/enemies.js
@@ -36,6 +36,25 @@ function spawnEnemy(type,x,y,elite){
if(e.beh==="spin"){e.vx=rnd(-0.5,0.5);e.rot=rnd(0,TAU);}
G.enemies.push(e);
}
+/* ---- 泰伦触手扫击:角度制扇形动作(demo验证版移植,渲染与判定同源) ---- */
+function tentPose(tn,side,tB){ // 返回{ang(0=正下,side向为正),stretch,glow}
+ const idle=Math.sin(tB*0.06)*0.07*side;
+ if(tn.warn>0){const f=1-tn.warn/50,e=f*f; // 前摇:向扫击反方向后拉蓄力
+ return{ang:idle*(1-f)+side*0.73*e+Math.sin(tB*0.9)*0.012*e,stretch:1+e*0.15,glow:f};}
+ if(tn.act>17){const f=(45-tn.act)/28,e=f*f*f; // 扫击:加速挥出(右下→左下60°扇形)
+ return{ang:side*(0.73-e*1.31),stretch:1.15+e*0.45,glow:1};}
+ if(tn.act>0){return{ang:side*(-0.58)+Math.sin(tB*1.2)*0.05*(tn.act/17),stretch:1.6,glow:1};} // 顿挫
+ const f=1-tn.rec/40,e=1-(1-f)*(1-f); // 收回
+ return{ang:side*(-0.58)+(idle-side*(-0.58))*e,stretch:1.6-0.6*e,glow:1-e};}
+function tentSegs(b,side,opF){ // 触手7节局部坐标(相对Boss中心),逐节延迟形成鞭弧
+ const pts=[];opF=opF===undefined?1:opF;
+ for(let i=0;i<7;i++){
+ let ang=Math.sin((b.t-i*2.2)*0.06)*0.07*side,stretch=1,glow=0;
+ if(b.tent){const s=tentPose(b.tent,side,b.t-i*2.2);ang=s.ang;stretch=s.stretch;glow=s.glow;}
+ const f=i/6,L=40*stretch*f*opF;
+ pts.push({x:side*9+Math.sin(ang)*L,y:24+Math.cos(ang)*L,glow});
+ }
+ return pts;}
/* ---- 敌机行为 ---- */
function updateEnemy(e){
e.t++; if(e.flash>0)e.flash--; if(e.hpt>0)e.hpt--;
@@ -293,13 +312,20 @@ function updateBoss(){
if(b.plT===90){b.warpChg=0;bossBeam(b.x,b.y+6,b.x,H+8,b.phase>=1?24:20,"#5cff9d",10,80,30,{b,ox:0,oy:6,followX2:true,sweep:1,amp:b.phase>=1?70:50});}
}
}
- if(b.idx===10&&b.tent){const tn=b.tent; // 泰伦:触手扫击(预警→横扫扇形,扫至中段判定)
- if(tn.warn>0){tn.warn--;if(tn.warn<=0){tn.act=30;tn.hitDone=false;}}
+ if(b.idx===10&&b.tent){const tn=b.tent; // 泰伦:触手扫击(前摇→横扫→顿挫→收回;触手即碰撞体)
+ if(tn.warn>0){tn.warn--;if(tn.warn<=0){tn.act=45;tn.hitDone=false;}}
else if(tn.act>0){tn.act--;
- if(!tn.hitDone&&tn.act<=15){tn.hitDone=true;
+ if(!tn.hitDone){ // 扫击+顿挫全期:玩家与触手各节线段做点-线段碰撞
const p=G.p;
- if(p.dead<=0&&Math.abs(p.x-(b.x+tn.side*30))<34&&p.y>b.y+10&&p.y0){tn.rec--;if(tn.rec<=0)b.tent=null;}
}
const lck=(b.laserT||0)>0; // 巨宽核心激光期间压制其他攻击
// —— 主攻击 ——
@@ -462,10 +488,10 @@ function bossMinor(b,mk,fm){
case "sporeBloom": // 泰伦:孢子囊投放(慢飘碍走位,死亡酸液环爆)
for(let i=0;i<2+b.phase;i++)spawnEnemy("sporeT",b.x+rnd(-20,20),b.y+rnd(2,16),false);
b.minorT=Math.round(280/fm);break;
- case "tentSweep": // 泰伦:触手扫击(P2+):预警后伸长横扫身前一带
+ case "tentSweep": // 泰伦:触手扫击(P2+):前摇后拉蓄力→60°扇形挥鞭(触手即碰撞体)
if(b.phase<1){b.minorT=80;break;}
- if(!b.tent){b.tent={side:ap2.x0){ctx.globalAlpha=0.13+0.09*Math.sin(t*0.3);ctx.fillStyle="#a8ff5c"; // 触手扫击预警带
- ctx.fillRect(b.tent.side*30-34,10,68,78);ctx.globalAlpha=1;}
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");}
@@ -188,16 +186,27 @@ function drawBoss(){
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小(P2从裂口伸出,P3加粗;扫击侧伸长)
- const tent=(x0,y0,len,wd,phs,tipW,swF,swS)=>{const sw2=Math.sin(t*0.06+phs)*3.5;
- const bx=swF!=null?(swF*2-1)*30*(swS||1):0; // 扫击:尖端横扫偏转
- for(let i2=0;i2<4;i2++){const f=i2/4;
- circ(x0+sw2*f*f*2.2+bx*f*f,y0+len*f,wd*(1-f*0.45)*(1+op3*0.3),i2%2?"#5a3a5a":"#7a4a6a");}
- const fx2=x0+sw2*2.2+bx,fy=y0+len;
- poly([fx2-2*tipW,fy-2,fx2+2*tipW,fy-2,fx2,fy+6],"#e8dcc0");};
- for(const sd of[-1,1]){const swp=b.tent&&b.tent.act>0&&b.tent.side===sd?1-b.tent.act/30:null; // 扫击侧:横扫扇形动画
- tent(sd*9*op2,24,(26+op3*8)*(swp!=null?1.8:1)*op2,3.2,sd*1.7,1,swp,sd);}
- for(const sd of[-1,1]){tent(sd*15*op2,16,12*op2,1.8,sd*2.9+1,0.6,null,0);tent(sd*4*op2,32,14*op2,1.6,sd*4.1+2,0.6,null,0);}
+ 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");