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.
762 lines
14 KiB
762 lines
14 KiB
|
3 weeks ago
|
import { Node, Tween, tween, UITransform, v3, Vec3 } from 'cc';
|
||
|
|
import { mapKeysToArray } from '../../../mx/tools/MembershipSet';
|
||
|
|
import type { TextGrid } from './TextGrid';
|
||
|
|
import { BattleAudio } from '../audio/BattleAudio';
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/** 槽位间延迟(右→左依次抛物线入场) */
|
||
|
|
|
||
|
|
const SLOT_STAGGER = 0.2;
|
||
|
|
|
||
|
|
/** 抛物线飞行时长(秒) */
|
||
|
|
|
||
|
|
const FLY_DUR = 0.48;
|
||
|
|
|
||
|
|
/** 相对按钮再往上拱的像素 */
|
||
|
|
|
||
|
|
const APEX_LIFT = 300;
|
||
|
|
|
||
|
|
/** 落地弹一下 */
|
||
|
|
|
||
|
|
const LAND_SQUASH = 0.1;
|
||
|
|
|
||
|
|
const LAND_SETTLE = 0.12;
|
||
|
|
|
||
|
|
/** 单格抛物线入场可选参数 */
|
||
|
|
export interface BagSlotEntranceFlightOpts {
|
||
|
|
/** 飞行期间挂载层(如 ui_fly),保证在 btnItem 等 UI 之上 */
|
||
|
|
flightLayer?: Node | null;
|
||
|
|
/** 落点槽位;与 flightLayer 同时传入时终点为该槽世界坐标 */
|
||
|
|
homeSlot?: Node | null;
|
||
|
|
}
|
||
|
|
|
||
|
|
function lerp(a: number, b: number, t: number): number {
|
||
|
|
|
||
|
|
return a + (b - a) * t;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
function hideEntranceTrailSafe(grid: TextGrid): void {
|
||
|
|
|
||
|
|
if (grid?.isValid && grid.node?.isValid) grid.hideEntranceTrail();
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/** 入场进度代理:禁止 tween(node),避免 setData/resetNodeTransform 掐掉 onComplete */
|
||
|
|
|
||
|
|
type EntranceMotion = { fly: number; land: number };
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
|
||
|
|
* 背包刷新出场:从砚台世界坐标抛物线落入槽位(右→左)。
|
||
|
|
|
||
|
|
* 全程只 tween 代理对象;节点变换仅在 onUpdate 写入。
|
||
|
|
|
||
|
|
* 每格必达 onDone(播完 / 节点失效 / stopGrid),由 finished 计数触发总 onComplete。
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
export class BagSlotEntranceAnimator {
|
||
|
|
|
||
|
|
private static readonly _motions = new WeakMap<TextGrid, EntranceMotion>();
|
||
|
|
|
||
|
|
private static _activeComplete: (() => void) | null = null;
|
||
|
|
|
||
|
|
private static readonly _gridDone = new WeakMap<TextGrid, () => void>();
|
||
|
|
|
||
|
|
/** 打断上一轮 play 后,旧 gridDone/settle 须失效 */
|
||
|
|
|
||
|
|
private static _playGen = 0;
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 该字块是否仍在入场抛物线或落地 squash 中。
|
||
|
|
* 落地并 snap 回家后为 false,可立即拖动。
|
||
|
|
*/
|
||
|
|
static isPlaying(grid: TextGrid | null | undefined): boolean {
|
||
|
|
if (!grid) return false;
|
||
|
|
return this._motions.has(grid) || this._gridDone.has(grid);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 背包刷新入场:右→左依次抛物线落入槽位。
|
||
|
|
* @param onSlotDone 单格落地后回调(可立即开拖 / 刷可合成)
|
||
|
|
*/
|
||
|
|
static play(slots: Map<number, TextGrid>, _bagList?: Node | null, onComplete?: () => void, fromWorld?: Readonly<Vec3> | null, onSlotDone?: (grid: TextGrid) => void): void {
|
||
|
|
|
||
|
|
// settle=false:开场只清残留 tween,勿触发上一轮 onComplete(否则会 snap/flush 打乱本轮)
|
||
|
|
|
||
|
|
this.stopAll(slots, _bagList, false);
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
const indices = mapKeysToArray(slots).sort((a, b) => b - a);
|
||
|
|
|
||
|
|
if (indices.length === 0) {
|
||
|
|
|
||
|
|
onComplete?.();
|
||
|
|
|
||
|
|
return;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
const sessionGen = this._playGen;
|
||
|
|
|
||
|
|
let finished = 0;
|
||
|
|
|
||
|
|
const total = indices.length;
|
||
|
|
|
||
|
|
let settled = false;
|
||
|
|
|
||
|
|
const settle = (): void => {
|
||
|
|
|
||
|
|
if (settled || this._playGen !== sessionGen) return;
|
||
|
|
|
||
|
|
settled = true;
|
||
|
|
|
||
|
|
if (this._activeComplete === settle) this._activeComplete = null;
|
||
|
|
|
||
|
|
onComplete?.();
|
||
|
|
|
||
|
|
};
|
||
|
|
|
||
|
|
this._activeComplete = settle;
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
for (let order = 0; order < indices.length; order++) {
|
||
|
|
|
||
|
|
const slotIndex = indices[order];
|
||
|
|
|
||
|
|
const grid = slots.get(slotIndex);
|
||
|
|
|
||
|
|
if (!grid?.node?.isValid) {
|
||
|
|
|
||
|
|
finished++;
|
||
|
|
|
||
|
|
if (finished >= total) settle();
|
||
|
|
|
||
|
|
continue;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
let fired = false;
|
||
|
|
|
||
|
|
const gridDone = (): void => {
|
||
|
|
|
||
|
|
if (fired || this._playGen !== sessionGen) return;
|
||
|
|
|
||
|
|
fired = true;
|
||
|
|
|
||
|
|
this._gridDone.delete(grid);
|
||
|
|
|
||
|
|
onSlotDone?.(grid);
|
||
|
|
|
||
|
|
finished++;
|
||
|
|
|
||
|
|
if (finished >= total) settle();
|
||
|
|
|
||
|
|
};
|
||
|
|
|
||
|
|
this._gridDone.set(grid, gridDone);
|
||
|
|
|
||
|
|
if (fromWorld) {
|
||
|
|
|
||
|
|
this._playParabolaFrom(grid, fromWorld, order, gridDone);
|
||
|
|
|
||
|
|
} else {
|
||
|
|
|
||
|
|
this._playFallback(grid, slotIndex, order, total, gridDone);
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/** 单格从世界坐标抛物线落入当前槽位(与砚台出字同轨迹) */
|
||
|
|
|
||
|
|
static playSingleFromWorld(
|
||
|
|
|
||
|
|
grid: TextGrid,
|
||
|
|
|
||
|
|
fromWorld: Readonly<Vec3>,
|
||
|
|
|
||
|
|
appearOrder: number,
|
||
|
|
|
||
|
|
onDone: () => void,
|
||
|
|
|
||
|
|
opts?: BagSlotEntranceFlightOpts,
|
||
|
|
|
||
|
|
): void {
|
||
|
|
|
||
|
|
if (!grid?.node?.isValid) {
|
||
|
|
|
||
|
|
onDone();
|
||
|
|
|
||
|
|
return;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
let fired = false;
|
||
|
|
|
||
|
|
const gridDone = (): void => {
|
||
|
|
|
||
|
|
if (fired) return;
|
||
|
|
|
||
|
|
fired = true;
|
||
|
|
|
||
|
|
this._gridDone.delete(grid);
|
||
|
|
|
||
|
|
onDone();
|
||
|
|
|
||
|
|
};
|
||
|
|
|
||
|
|
this._gridDone.set(grid, gridDone);
|
||
|
|
|
||
|
|
this._playParabolaFrom(grid, fromWorld, appearOrder, gridDone, opts);
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
private static _playParabolaFrom(
|
||
|
|
|
||
|
|
grid: TextGrid,
|
||
|
|
|
||
|
|
fromWorld: Readonly<Vec3>,
|
||
|
|
|
||
|
|
appearOrder: number,
|
||
|
|
|
||
|
|
onDone: () => void,
|
||
|
|
|
||
|
|
opts?: BagSlotEntranceFlightOpts,
|
||
|
|
|
||
|
|
): void {
|
||
|
|
|
||
|
|
const node = grid.node;
|
||
|
|
|
||
|
|
this._stopMotion(grid);
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
const homeSlot = opts?.homeSlot ?? node.parent;
|
||
|
|
|
||
|
|
const flightLayer = opts?.flightLayer ?? null;
|
||
|
|
|
||
|
|
const spaceNode = flightLayer?.isValid ? flightLayer : homeSlot;
|
||
|
|
|
||
|
|
const spaceUt = spaceNode?.getComponent(UITransform);
|
||
|
|
|
||
|
|
if (!homeSlot?.isValid || !spaceNode?.isValid || !spaceUt) {
|
||
|
|
|
||
|
|
this._snapHome(grid);
|
||
|
|
|
||
|
|
onDone();
|
||
|
|
|
||
|
|
return;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
if (flightLayer?.isValid && node.parent !== flightLayer) {
|
||
|
|
|
||
|
|
if (!flightLayer.active) {
|
||
|
|
|
||
|
|
flightLayer.active = true;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
node.parent = flightLayer;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
const start = spaceUt.convertToNodeSpaceAR(new Vec3(fromWorld.x, fromWorld.y, fromWorld.z));
|
||
|
|
|
||
|
|
const end = flightLayer?.isValid
|
||
|
|
|
||
|
|
? spaceUt.convertToNodeSpaceAR(homeSlot.worldPosition)
|
||
|
|
|
||
|
|
: v3(0, 0, 0);
|
||
|
|
|
||
|
|
const p0x = start.x;
|
||
|
|
|
||
|
|
const p0y = start.y;
|
||
|
|
|
||
|
|
const p1x = (start.x + end.x) * 0.5;
|
||
|
|
|
||
|
|
const p1y = start.y + APEX_LIFT;
|
||
|
|
|
||
|
|
const p2x = end.x;
|
||
|
|
|
||
|
|
const p2y = end.y;
|
||
|
|
|
||
|
|
const side = start.x >= end.x ? 1 : -1;
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
node.setPosition(start);
|
||
|
|
|
||
|
|
node.angle = -18 * side;
|
||
|
|
|
||
|
|
node.setScale(0.2, 0.2, 1);
|
||
|
|
|
||
|
|
hideEntranceTrailSafe(grid);
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
const motion: EntranceMotion = { fly: 0, land: 0 };
|
||
|
|
|
||
|
|
this._motions.set(grid, motion);
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
tween(motion)
|
||
|
|
|
||
|
|
.delay(appearOrder * SLOT_STAGGER)
|
||
|
|
|
||
|
|
.call(() => {
|
||
|
|
|
||
|
|
if (!this._assertEntranceAlive(grid, node, onDone)) return;
|
||
|
|
|
||
|
|
try {
|
||
|
|
|
||
|
|
grid.showEntranceTrail();
|
||
|
|
|
||
|
|
} catch (e) {
|
||
|
|
|
||
|
|
console.log('[BagSlotEntranceAnimator] showEntranceTrail failed', String(e));
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
BattleAudio.playInkstoneSpawn();
|
||
|
|
|
||
|
|
})
|
||
|
|
|
||
|
|
.to(FLY_DUR, { fly: 1 }, {
|
||
|
|
|
||
|
|
easing: 'quadIn',
|
||
|
|
|
||
|
|
onUpdate: () => {
|
||
|
|
|
||
|
|
if (!this._assertEntranceAlive(grid, node, onDone)) return;
|
||
|
|
|
||
|
|
const t = motion.fly;
|
||
|
|
|
||
|
|
const u = 1 - t;
|
||
|
|
|
||
|
|
node.setPosition(
|
||
|
|
|
||
|
|
u * u * p0x + 2 * u * t * p1x + t * t * p2x,
|
||
|
|
|
||
|
|
u * u * p0y + 2 * u * t * p1y + t * t * p2y,
|
||
|
|
|
||
|
|
0,
|
||
|
|
|
||
|
|
);
|
||
|
|
|
||
|
|
const s = lerp(0.2, 1, t);
|
||
|
|
|
||
|
|
node.setScale(s, s * lerp(1, 0.85, t * t), 1);
|
||
|
|
|
||
|
|
node.angle = lerp(-18 * side, 0, t);
|
||
|
|
|
||
|
|
},
|
||
|
|
|
||
|
|
})
|
||
|
|
|
||
|
|
.call(() => {
|
||
|
|
|
||
|
|
if (!this._assertEntranceAlive(grid, node, onDone)) return;
|
||
|
|
|
||
|
|
node.setPosition(end);
|
||
|
|
|
||
|
|
node.angle = 0;
|
||
|
|
|
||
|
|
node.setScale(1.15, 0.75, 1);
|
||
|
|
|
||
|
|
motion.land = 0;
|
||
|
|
|
||
|
|
})
|
||
|
|
|
||
|
|
.to(LAND_SQUASH, { land: 1 }, {
|
||
|
|
|
||
|
|
easing: 'quadOut',
|
||
|
|
|
||
|
|
onUpdate: () => {
|
||
|
|
|
||
|
|
if (!this._assertEntranceAlive(grid, node, onDone)) return;
|
||
|
|
|
||
|
|
const t = motion.land;
|
||
|
|
|
||
|
|
node.setScale(lerp(1.15, 0.85, t), lerp(0.75, 1.2, t), 1);
|
||
|
|
|
||
|
|
},
|
||
|
|
|
||
|
|
})
|
||
|
|
|
||
|
|
.call(() => {
|
||
|
|
|
||
|
|
if (!this._assertEntranceAlive(grid, node, onDone)) return;
|
||
|
|
|
||
|
|
motion.land = 0;
|
||
|
|
|
||
|
|
})
|
||
|
|
|
||
|
|
.to(LAND_SETTLE, { land: 1 }, {
|
||
|
|
|
||
|
|
easing: 'quadOut',
|
||
|
|
|
||
|
|
onUpdate: () => {
|
||
|
|
|
||
|
|
if (!this._assertEntranceAlive(grid, node, onDone)) return;
|
||
|
|
|
||
|
|
const t = motion.land;
|
||
|
|
|
||
|
|
node.setScale(lerp(0.85, 1, t), lerp(1.2, 1, t), 1);
|
||
|
|
|
||
|
|
},
|
||
|
|
|
||
|
|
})
|
||
|
|
|
||
|
|
.call(() => {
|
||
|
|
|
||
|
|
this._motions.delete(grid);
|
||
|
|
|
||
|
|
if (flightLayer?.isValid && homeSlot?.isValid && node.parent !== homeSlot) {
|
||
|
|
|
||
|
|
node.parent = homeSlot;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
this._snapHome(grid);
|
||
|
|
|
||
|
|
onDone();
|
||
|
|
|
||
|
|
})
|
||
|
|
|
||
|
|
.start();
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
private static _playFallback(
|
||
|
|
|
||
|
|
grid: TextGrid,
|
||
|
|
|
||
|
|
slotIndex: number,
|
||
|
|
|
||
|
|
appearOrder: number,
|
||
|
|
|
||
|
|
total: number,
|
||
|
|
|
||
|
|
onDone: () => void,
|
||
|
|
|
||
|
|
): void {
|
||
|
|
|
||
|
|
const node = grid.node;
|
||
|
|
|
||
|
|
this._stopMotion(grid);
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
const home = v3(0, 0, 0);
|
||
|
|
|
||
|
|
const center = (total - 1) / 2;
|
||
|
|
|
||
|
|
const peakY = 90 + APEX_LIFT + Math.abs(slotIndex - center) * 10;
|
||
|
|
|
||
|
|
const start = v3(0, peakY, 0);
|
||
|
|
|
||
|
|
const p0x = start.x;
|
||
|
|
|
||
|
|
const p0y = start.y;
|
||
|
|
|
||
|
|
const p1x = 0;
|
||
|
|
|
||
|
|
const p1y = start.y;
|
||
|
|
|
||
|
|
const p2x = home.x;
|
||
|
|
|
||
|
|
const p2y = home.y;
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
node.setPosition(start);
|
||
|
|
|
||
|
|
node.angle = 0;
|
||
|
|
|
||
|
|
node.setScale(0.2, 0.2, 1);
|
||
|
|
|
||
|
|
hideEntranceTrailSafe(grid);
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
const motion: EntranceMotion = { fly: 0, land: 0 };
|
||
|
|
|
||
|
|
this._motions.set(grid, motion);
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
tween(motion)
|
||
|
|
|
||
|
|
.delay(appearOrder * SLOT_STAGGER)
|
||
|
|
|
||
|
|
.call(() => {
|
||
|
|
|
||
|
|
if (!this._assertEntranceAlive(grid, node, onDone)) return;
|
||
|
|
|
||
|
|
try {
|
||
|
|
|
||
|
|
grid.showEntranceTrail();
|
||
|
|
|
||
|
|
} catch (e) {
|
||
|
|
|
||
|
|
console.log('[BagSlotEntranceAnimator] showEntranceTrail failed', String(e));
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
BattleAudio.playInkstoneSpawn();
|
||
|
|
|
||
|
|
})
|
||
|
|
|
||
|
|
.to(FLY_DUR, { fly: 1 }, {
|
||
|
|
|
||
|
|
easing: 'quadIn',
|
||
|
|
|
||
|
|
onUpdate: () => {
|
||
|
|
|
||
|
|
if (!this._assertEntranceAlive(grid, node, onDone)) return;
|
||
|
|
|
||
|
|
const t = motion.fly;
|
||
|
|
|
||
|
|
const u = 1 - t;
|
||
|
|
|
||
|
|
node.setPosition(
|
||
|
|
|
||
|
|
u * u * p0x + 2 * u * t * p1x + t * t * p2x,
|
||
|
|
|
||
|
|
u * u * p0y + 2 * u * t * p1y + t * t * p2y,
|
||
|
|
|
||
|
|
0,
|
||
|
|
|
||
|
|
);
|
||
|
|
|
||
|
|
const s = lerp(0.2, 1, t);
|
||
|
|
|
||
|
|
node.setScale(s, s * lerp(1, 0.85, t * t), 1);
|
||
|
|
|
||
|
|
},
|
||
|
|
|
||
|
|
})
|
||
|
|
|
||
|
|
.call(() => {
|
||
|
|
|
||
|
|
if (!this._assertEntranceAlive(grid, node, onDone)) return;
|
||
|
|
|
||
|
|
node.setPosition(home);
|
||
|
|
|
||
|
|
node.setScale(1.15, 0.75, 1);
|
||
|
|
|
||
|
|
motion.land = 0;
|
||
|
|
|
||
|
|
})
|
||
|
|
|
||
|
|
.to(LAND_SQUASH, { land: 1 }, {
|
||
|
|
|
||
|
|
easing: 'quadOut',
|
||
|
|
|
||
|
|
onUpdate: () => {
|
||
|
|
|
||
|
|
if (!this._assertEntranceAlive(grid, node, onDone)) return;
|
||
|
|
|
||
|
|
const t = motion.land;
|
||
|
|
|
||
|
|
node.setScale(lerp(1.15, 0.85, t), lerp(0.75, 1.2, t), 1);
|
||
|
|
|
||
|
|
},
|
||
|
|
|
||
|
|
})
|
||
|
|
|
||
|
|
.call(() => {
|
||
|
|
|
||
|
|
if (!this._assertEntranceAlive(grid, node, onDone)) return;
|
||
|
|
|
||
|
|
motion.land = 0;
|
||
|
|
|
||
|
|
})
|
||
|
|
|
||
|
|
.to(LAND_SETTLE, { land: 1 }, {
|
||
|
|
|
||
|
|
easing: 'quadOut',
|
||
|
|
|
||
|
|
onUpdate: () => {
|
||
|
|
|
||
|
|
if (!this._assertEntranceAlive(grid, node, onDone)) return;
|
||
|
|
|
||
|
|
const t = motion.land;
|
||
|
|
|
||
|
|
node.setScale(lerp(0.85, 1, t), lerp(1.2, 1, t), 1);
|
||
|
|
|
||
|
|
},
|
||
|
|
|
||
|
|
})
|
||
|
|
|
||
|
|
.call(() => {
|
||
|
|
|
||
|
|
this._motions.delete(grid);
|
||
|
|
|
||
|
|
this._snapHome(grid);
|
||
|
|
|
||
|
|
onDone();
|
||
|
|
|
||
|
|
})
|
||
|
|
|
||
|
|
.start();
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
|
||
|
|
* 入场中节点被回收/销毁:停掉剩余链并计完成,避免 finished 永远凑不齐。
|
||
|
|
|
||
|
|
* @returns 仍可继续播
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
private static _assertEntranceAlive(grid: TextGrid, node: Node, onDone: () => void): boolean {
|
||
|
|
|
||
|
|
if (grid.isValid && node.isValid) return true;
|
||
|
|
|
||
|
|
this._stopMotion(grid);
|
||
|
|
|
||
|
|
hideEntranceTrailSafe(grid);
|
||
|
|
|
||
|
|
onDone();
|
||
|
|
|
||
|
|
return false;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
private static _snapHome(grid: TextGrid): void {
|
||
|
|
|
||
|
|
hideEntranceTrailSafe(grid);
|
||
|
|
|
||
|
|
const node = grid.node;
|
||
|
|
|
||
|
|
if (!node?.isValid) return;
|
||
|
|
|
||
|
|
node.setPosition(0, 0, 0);
|
||
|
|
|
||
|
|
node.angle = 0;
|
||
|
|
|
||
|
|
node.setScale(1, 1, 1);
|
||
|
|
|
||
|
|
node.active = true;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
private static _stopMotion(grid: TextGrid): void {
|
||
|
|
|
||
|
|
const motion = this._motions.get(grid);
|
||
|
|
|
||
|
|
if (motion) {
|
||
|
|
|
||
|
|
Tween.stopAllByTarget(motion);
|
||
|
|
|
||
|
|
this._motions.delete(grid);
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
|
||
|
|
* 单格停入场。
|
||
|
|
|
||
|
|
* @param invokeDone false 时只停 tween(rebuild 换场,由 stopAll 作废 session)
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
static stopGrid(grid: TextGrid | null | undefined, invokeDone = true): void {
|
||
|
|
|
||
|
|
if (!grid) return;
|
||
|
|
|
||
|
|
const pendingDone = this._gridDone.get(grid);
|
||
|
|
|
||
|
|
if (!invokeDone) this._gridDone.delete(grid);
|
||
|
|
|
||
|
|
this._stopMotion(grid);
|
||
|
|
|
||
|
|
if (grid.node?.isValid) {
|
||
|
|
|
||
|
|
this._snapHome(grid);
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
if (invokeDone) pendingDone?.();
|
||
|
|
|
||
|
|
else this._gridDone.delete(grid);
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
|
||
|
|
* 停止全部入场。
|
||
|
|
|
||
|
|
* @param settle true 时触发 onComplete
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
static stopAll(slots: Map<number, TextGrid>, bagList?: Node | null, settle = true): void {
|
||
|
|
|
||
|
|
this._playGen++;
|
||
|
|
|
||
|
|
for (const grid of slots.values()) {
|
||
|
|
|
||
|
|
this.stopGrid(grid, false);
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
if (bagList?.isValid) {
|
||
|
|
|
||
|
|
bagList.setScale(1, 1, 1);
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
const complete = this._activeComplete;
|
||
|
|
|
||
|
|
this._activeComplete = null;
|
||
|
|
|
||
|
|
if (settle) complete?.();
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|