消消方块阵换皮表情
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.

249 lines
9.4 KiB

1 week ago
import { _decorator, Collider2D, Component, Mat4, Node, Vec3, isValid } from 'cc';
import { tetriNode } from './tetriNode';
1 week ago
import { ROLE_HAND_LAYER } from './roleHand';
1 week ago
const { ccclass, property } = _decorator;
/**
* ui_roleLayer lateUpdate followTarget
* update
* {@link setFollowTarget} onLoad tetriNode
* `destroy` followTarget `!isValid`
*
* battleScene cardUI_2D ui_roleLayerUI_2D visibility
* layer ui_roleLayer
* ui_roleLayer Node.group Collider2D.group/
*/
@ccclass('role')
export class role extends Component {
@property({
type: Node,
tooltip: '可选:编辑器里指定;运行时优先用 setFollowTarget()',
})
followTarget: Node | null = null;
private _carrierTetri: tetriNode | null = null;
private _movedToRoleLayer = false;
/** 落稳挂层时,角色世界位置在 followTarget 空间下的局部点(用于每帧跟旋转) */
private readonly _offsetInTargetSpace = new Vec3();
private readonly _tmpWorldPos = new Vec3();
private readonly _matInv = new Mat4();
/** followTarget 世界坐标合理范围(玩法区约 ±2500,超出视为方块已滑出/掉落失控) */
private static readonly _MAX_FOLLOW_WORLD_X = 2500;
private static readonly _MAX_FOLLOW_WORLD_Y = 50000;
private _isFollowTargetWorldSane(target: Node): boolean {
const wp = target.worldPosition;
return (
Math.abs(wp.x) <= role._MAX_FOLLOW_WORLD_X
&& Math.abs(wp.y) <= role._MAX_FOLLOW_WORLD_Y
&& wp.y >= -1200
&& Number.isFinite(wp.x)
&& Number.isFinite(wp.y)
);
}
/**
* / tetriNode
* tetriNode
*/
public setFollowTarget(node: Node | null): void {
this.followTarget = node?.isValid ? node : null;
this._refreshCarrierTetri();
}
private _refreshCarrierTetri(): void {
const t = this.followTarget;
this._carrierTetri =
t?.getComponent(tetriNode) ?? t?.getComponentInChildren(tetriNode) ?? null;
}
1 week ago
/** 与子节点统一 layer,避免 UI 相机不画 battleScene 等;hand 为 Sprite 保持 UI_2D */
1 week ago
private _applyLayerRecursive(n: Node, layer: number): void {
1 week ago
if (n.name === 'hand') {
n.layer = ROLE_HAND_LAYER;
return;
}
1 week ago
n.layer = layer;
const ch = n.children;
for (let i = 0; i < ch.length; i++) {
const c = ch[i];
if (c?.isValid) this._applyLayerRecursive(c, layer);
}
}
/** 从参考节点取 group(编辑器分组 / 部分版本 Node 上仍有 group 字段) */
private _getRefNodeGroup(ref: Node): unknown {
return (ref as any).group;
}
/** 参考层上用于碰撞的 group(取 ref 子树里第一个 Collider2D;空层则无) */
private _getRefColliderGroup(ref: Node): number | undefined {
const cols = ref.getComponentsInChildren(Collider2D) ?? [];
for (let i = 0; i < cols.length; i++) {
const col = cols[i];
if (!col?.isValid) continue;
const g = (col as any).group;
if (typeof g === 'number') return g;
}
return undefined;
}
/** 角色子树与 ui_roleLayer 对齐 Node.group + Collider2D.group */
private _applyGroupRecursiveFromRef(root: Node, ref: Node): void {
const ng = this._getRefNodeGroup(ref);
const cg = this._getRefColliderGroup(ref);
const hasNg = ng !== undefined && ng !== null && (typeof ng === 'number' || typeof ng === 'string');
const walk = (n: Node): void => {
if (!n?.isValid) return;
if (hasNg) (n as any).group = ng;
if (cg !== undefined) {
const cols = n.getComponents(Collider2D) ?? [];
for (let i = 0; i < cols.length; i++) {
const c = cols[i];
if (c?.isValid) (c as any).group = cg;
}
}
const ch = n.children;
for (let i = 0; i < ch.length; i++) walk(ch[i]);
};
walk(root);
}
/** 仍在方块子树时:与直接父节点 layer 一致(card→战场 reparent 后也会自动跟上) */
private _syncLayerToParent(): void {
if (this._movedToRoleLayer) return;
const p = this.node.parent;
if (!p?.isValid) return;
if (this.node.layer === p.layer) return;
this._applyLayerRecursive(this.node, p.layer);
}
onLoad(): void {
if (!this.followTarget?.isValid) {
let cur: Node | null = this.node.parent;
while (cur) {
const tn = cur.getComponent(tetriNode);
if (tn) {
this.followTarget = cur;
this._carrierTetri = tn;
break;
}
cur = cur.parent;
}
} else {
this._refreshCarrierTetri();
}
}
/**
* ui_roleLayer
* reparent roleLayer destroy
*/
public static destroyRoleLayerFollowersOf(blockRoot: Node | null): void {
if (!blockRoot) return;
const layer = gg?.game?.CurentBattle?.ui_roleLayer;
if (!layer?.isValid) return;
const list = layer.getComponentsInChildren(role);
for (let i = 0; i < list.length; i++) {
const comp = list[i];
if (!comp?.node?.isValid) continue;
const ft = comp.followTarget;
if (!ft) continue;
if (!ft.isValid) {
comp.node.destroy();
continue;
}
let cur: Node | null = ft;
while (cur) {
if (cur === blockRoot) {
comp.node.destroy();
break;
}
cur = cur.parent;
}
}
}
/**
* ui_roleLayerfollowTarget onDestroy
*/
public static pruneOrphanRoleLayer(): void {
const layer = gg?.game?.CurentBattle?.ui_roleLayer;
if (!layer?.isValid) return;
const list = layer.getComponentsInChildren(role);
for (let i = 0; i < list.length; i++) {
const comp = list[i];
if (!comp?.node?.isValid) continue;
const ft = comp.followTarget;
if (!ft || !ft.isValid) {
comp.node.destroy();
}
}
}
private _destroySelf(): void {
if (isValid(this.node)) this.node.destroy();
}
/**
* lateUpdate RigidBody2D
* update
*/
lateUpdate(): void {
const target = this.followTarget;
if (target != null && !target.isValid) {
this._destroySelf();
return;
}
const battle = gg?.game?.CurentBattle;
// 暂停时仍同步位置(预警/弹窗叠层下物理或外力仍可能挪块),避免角色留在旧坐标
if (battle && (battle.IsEdit || battle.IsGameOver)) {
return;
}
if (!target?.isValid) {
return;
}
if (!this._isFollowTargetWorldSane(target)) {
console.warn(
'[role] followTarget 世界坐标异常,销毁角色',
target.name,
target.worldPosition,
'role=',
this.node.name,
);
this._destroySelf();
return;
}
this._syncLayerToParent();
const tn =
this._carrierTetri ??
target.getComponent(tetriNode) ??
target.getComponentInChildren(tetriNode);
if (tn?.isSettled && !this._movedToRoleLayer) {
const layer = gg?.game?.CurentBattle?.ui_roleLayer;
if (layer?.isValid) {
Mat4.invert(this._matInv, target.worldMatrix);
const wp = this.node.worldPosition;
Vec3.transformMat4(this._offsetInTargetSpace, wp, this._matInv);
this.node.setParent(layer, true);
this._applyLayerRecursive(this.node, layer.layer);
this._applyGroupRecursiveFromRef(this.node, layer);
this._movedToRoleLayer = true;
}
}
if (this._movedToRoleLayer && target.isValid) {
Vec3.transformMat4(this._tmpWorldPos, this._offsetInTargetSpace, target.worldMatrix);
this.node.setWorldPosition(this._tmpWorldPos);
this.node.worldRotation = target.worldRotation;
}
}
}