消消方块阵换皮表情
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

265 lines
9.4 KiB

1 week ago
import { _decorator, Collider2D, Component, Contact2DType, director, IPhysics2DContact, PolygonCollider2D, v3, Vec2, Vec3 } from 'cc';
import { tantantan } from './tantantan';
const { ccclass, property } = _decorator;
@ccclass('tantantanzidan')
export class tantantanzidan extends Component {
@property({ tooltip: '反射调试打印(复现问题时打开)' })
debugReflect: boolean = false;
isdie:boolean = false;
tantantanObj:tantantan = null;
private _lastReflectFrame = -999999;
private _lastReflectNormal: Vec3 = v3(0, 0, 0);
private _reflectCooldownUntilFrame = -999999;
private _closestPointOnSegment(p: Vec3, a: Vec3, b: Vec3): Vec3 {
const abx = b.x - a.x;
const aby = b.y - a.y;
const apx = p.x - a.x;
const apy = p.y - a.y;
const abLen2 = abx * abx + aby * aby;
if (abLen2 < 0.000001) return a.clone();
let t = (apx * abx + apy * aby) / abLen2;
if (t < 0) t = 0;
else if (t > 1) t = 1;
return v3(a.x + abx * t, a.y + aby * t, 0);
}
/** contact 为空时:用多边形最近边求法线与接触点(世界坐标) */
private _estimateNormalFromPolygon(otherCollider: Collider2D, bulletWorld: Vec3): { n: Vec3; cp: Vec3 } | null {
const poly = otherCollider as any as PolygonCollider2D;
if (!poly || !Array.isArray((poly as any).points) || (poly as any).points.length < 2) return null;
const ptsLocal: Vec2[] = (poly as any).points;
const wm = otherCollider.node.worldMatrix;
const ptsWorld: Vec3[] = ptsLocal.map((p) => {
const v = v3(p.x, p.y, 0);
Vec3.transformMat4(v, v, wm);
return v;
});
let bestDist2 = Number.POSITIVE_INFINITY;
let bestA: Vec3 | null = null;
let bestB: Vec3 | null = null;
let bestCP: Vec3 | null = null;
for (let i = 0; i < ptsWorld.length; i++) {
const a = ptsWorld[i];
const b = ptsWorld[(i + 1) % ptsWorld.length];
const cp = this._closestPointOnSegment(bulletWorld, a, b);
const dx = bulletWorld.x - cp.x;
const dy = bulletWorld.y - cp.y;
const dist2 = dx * dx + dy * dy;
if (dist2 < bestDist2) {
bestDist2 = dist2;
bestA = a;
bestB = b;
bestCP = cp;
}
}
if (!bestA || !bestB || !bestCP) return null;
// 边向量
const sx = bestB.x - bestA.x;
const sy = bestB.y - bestA.y;
if (sx * sx + sy * sy < 0.000001) return null;
// 两个法线候选
const n1 = v3(-sy, sx, 0);
const n2 = v3(sy, -sx, 0);
n1.normalize();
n2.normalize();
// 选指向子弹的那个(cp -> bullet)
const toB = v3(bulletWorld.x - bestCP.x, bulletWorld.y - bestCP.y, 0);
if (toB.lengthSqr() < 0.000001) return { n: n1, cp: bestCP };
const d1 = toB.x * n1.x + toB.y * n1.y;
const d2 = toB.x * n2.x + toB.y * n2.y;
return { n: (d1 >= d2 ? n1 : n2), cp: bestCP };
}
private _reflectIfNeeded(otherCollider: Collider2D, contact?: IPhysics2DContact | null) {
if (!this.tantantanObj) return;
const frame = director.getTotalFrames();
if (frame < this._reflectCooldownUntilFrame) return;
// 入射方向(世界)
const d0 = this.tantantanObj.flyDir?.clone() ?? v3(0, 1, 0);
d0.z = 0;
if (d0.lengthSqr() < 0.000001) d0.set(0, 1, 0);
const d = d0.normalize();
const bwp = this.node.worldPosition;
// 法线/接触点(世界):优先用 manifold;没有就用多边形估算
let n: Vec3 | null = null;
let cpWorld: Vec3 | null = null;
const manifold = contact?.getWorldManifold?.();
const nn: Vec2 | undefined = manifold ? (manifold as any)?.normal : undefined;
const pts: Vec2[] | undefined = manifold ? (manifold as any)?.points : undefined;
if (nn) {
n = v3(nn.x, nn.y, 0);
if (n.lengthSqr() > 0.000001) n.normalize();
}
if (pts && pts.length > 0) {
cpWorld = v3(pts[0].x, pts[0].y, 0);
}
if (!n || n.lengthSqr() < 0.000001 || !cpWorld) {
const est = this._estimateNormalFromPolygon(otherCollider, bwp);
if (est) {
n = est.n;
cpWorld = est.cp;
}
}
if (!n || !cpWorld) {
if (this.debugReflect) {
console.log('[reflect-skip] no contact/manifold; no polygon estimate', {
frame,
otherName: otherCollider.node?.name,
otherTag: otherCollider.tag,
});
}
return;
}
// 矫正法线方向:确保 n 指向子弹(cp -> bullet)
const toBullet = v3(bwp.x - cpWorld.x, bwp.y - cpWorld.y, 0);
if (toBullet.lengthSqr() > 0.000001) {
if (toBullet.x * n.x + toBullet.y * n.y < 0) {
n.x = -n.x;
n.y = -n.y;
}
}
// 只有在“确实撞向墙面”时才反射:d·n < 0
const dot = d.x * n.x + d.y * n.y;
if (dot >= -0.0001) {
if (this.debugReflect) {
console.log('[reflect-skip]', {
frame,
dot,
d: { x: d.x, y: d.y },
n: { x: n.x, y: n.y },
bullet: { x: bwp.x, y: bwp.y },
contactPoint: { x: cpWorld.x, y: cpWorld.y },
otherName: otherCollider.node?.name,
});
}
return;
}
// 同帧/同法线去抖(多边形拐角可能同帧触发多次)
if (frame === this._lastReflectFrame) {
const ndot = this._lastReflectNormal.x * n.x + this._lastReflectNormal.y * n.y;
if (ndot > 0.999) return;
}
this._lastReflectFrame = frame;
this._lastReflectNormal.set(n.x, n.y, 0);
// 标准反射:r = d - 2(d·n)n
const r = v3(d.x - 2 * dot * n.x, d.y - 2 * dot * n.y, 0);
if (r.lengthSqr() < 0.000001) return;
r.normalize();
// 更新方向与角度(0°=+Y)
this.tantantanObj.flyDir = r;
const aimAngle = (Math.atan2(r.y, r.x) * 180) / Math.PI;
this.node.angle = this._normalizeAngleDeg(aimAngle - 90);
// 推出墙体,避免持续贴边导致每帧反射
const push = 8;
const bwp2 = this.node.worldPosition;
this.node.setWorldPosition(bwp2.x + n.x * push, bwp2.y + n.y * push, bwp2.z);
// 反射后短冷却 1 帧,避免“贴边抖动”死循环
this._reflectCooldownUntilFrame = frame + 1;
if (this.debugReflect) {
console.log('[reflect]', {
frame,
dot,
d: { x: d.x, y: d.y },
n: { x: n.x, y: n.y },
r: { x: r.x, y: r.y },
bullet: { x: bwp.x, y: bwp.y },
contactPoint: { x: cpWorld.x, y: cpWorld.y },
otherName: otherCollider.node?.name,
});
}
}
private _normalizeAngleDeg(a: number): number {
// normalize to [-180, 180]
a = ((a % 360) + 360) % 360;
if (a > 180) a -= 360;
return a;
}
setData(tanObj:tantantan){
this.tantantanObj = tanObj;
}
//注册弹事件
protected onDisable(): void {
let collider = this.getComponent(Collider2D);
if (collider) {
collider.off(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
}
}
protected onEnable(): void {
let collider = this.getComponent(Collider2D);
if (collider) {
collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
}
}
onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact?: IPhysics2DContact | null){
//selfCollider 子弹
//otherCollider 怪物和door
//怪物要区分人还是鸡哥
if(this.isdie){
return;
}
//console.log('onBeginContact', selfCollider, otherCollider);
if(otherCollider.tag == 1){
// 碰撞到多边形包围盒,反射子弹(手动移动场景下 BEGIN_CONTACT 更可靠)
this._reflectIfNeeded(otherCollider, contact);
return;
}else if(otherCollider.tag == 2){
//打到了鸡哥
//子弹移除
//this.tantantanObj.isZidanFly = false;
this.isdie = true;
// 物理回调里直接 destroy/切换刚体状态会报警告:延迟到下一帧执行
if (selfCollider) selfCollider.enabled = false;
this.scheduleOnce(() => {
if (!this.node?.isValid) return;
//结算成功
this.tantantanObj?.successView();
this.node.destroy();
}, 0);
}else if(otherCollider.tag == 3){
this.isdie = true;
//打到了人物
if (selfCollider) selfCollider.enabled = false;
this.scheduleOnce(() => {
if (!this.node?.isValid) return;
this.tantantanObj.isZidanFly = false;
//子弹移除,继续下一发游戏
this.tantantanObj?.nextGame();
this.node.destroy();
}, 0);
}
}
}