import { Node, Tween, tween } from 'cc'; type LeanProxy = { angle: number; sx: number; sy: number }; const PROXY_KEY = '_hitLeanProxy'; /** * 受击后仰(搞笑向):按来击反方向旋转后仰 + 压扁/拉伸。 * hitDir = 击退方向(目标 - 来源),单位任意。 * 注:部分运行时无 UISkew,故用 angle 表达斜切感。 */ export function playHitLeanBack(target: Node, hitDirX: number, hitDirY = 0): void { if (!target?.isValid) return; const len = Math.hypot(hitDirX, hitDirY); let nx = len > 1e-3 ? hitDirX / len : 0; let ny = len > 1e-3 ? hitDirY / len : 0; if (Math.abs(nx) < 0.08 && Math.abs(ny) < 0.08) { nx = 1; ny = 0; } // 击退向右 → 身体顺时针后仰(负 angle 在 2D UI 常见朝向需实测;取 -nx) const peakAngle = -nx * (18 + Math.abs(nx) * 10); // 约 ±18°~28° const squashX = 1.22 + Math.abs(nx) * 0.08; const squashY = 0.68 - Math.abs(ny) * 0.06; const stretchX = 0.86; const stretchY = 1.18; const old = (target as Node & { [PROXY_KEY]?: LeanProxy })[PROXY_KEY]; if (old) Tween.stopAllByTarget(old); Tween.stopAllByTarget(target); const proxy: LeanProxy = { angle: 0, sx: 1, sy: 1 }; (target as Node & { [PROXY_KEY]?: LeanProxy })[PROXY_KEY] = proxy; target.angle = 0; target.setScale(1, 1, 1); const apply = (): void => { if (!target.isValid) return; target.angle = proxy.angle; target.setScale(proxy.sx, proxy.sy, 1); }; tween(proxy) .to(0.045, { angle: peakAngle * 0.4, sx: squashX, sy: squashY }, { easing: 'quadOut', onUpdate: apply, }) .to(0.09, { angle: peakAngle, sx: stretchX, sy: stretchY }, { easing: 'backOut', onUpdate: apply, }) .to(0.18, { angle: 0, sx: 1, sy: 1 }, { easing: 'elasticOut', onUpdate: apply, }) .call(() => { if (!target.isValid) return; target.angle = 0; target.setScale(1, 1, 1); }) .start(); }