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.
AdLike2048/assets/script/ui/PopDailyShopBuy/DailyShopBuySkillFlyPresent...

122 lines
4.1 KiB

3 weeks ago
import { instantiate, Node, Tween, tween, UIOpacity, UITransform, v3, Vec3 } from 'cc';
/** icon 影子飞行时长(秒) */
const ICON_FLY_SEC = 0.45;
/** 技能槽放大倍率 */
const SLOT_PULSE_SCALE = 1.1;
/** 放大缩小循环次数 */
const SLOT_PULSE_TIMES = 2;
/** 单次放大/缩小时长(秒) */
const SLOT_PULSE_HALF_SEC = 0.12;
/** 影子透明度 */
const SHADOW_OPACITY = 200;
/**
* 每日商店购买:货架 icon 影子飞入槽位 + 技能放大缩小。
*/
export class DailyShopBuySkillFlyPresenter {
private _flyLayer: Node | null = null;
private _shadowNode: Node | null = null;
private _pulseNode: Node | null = null;
/** 绑定飞行层(弹窗 ui_center) */
bind(flyLayer: Node | null | undefined): void {
this._flyLayer = flyLayer?.isValid ? flyLayer : null;
}
/** 注销并中断动画 */
unbind(): void {
this._stopShadow();
this._stopPulse();
this._flyLayer = null;
}
/**
* 货架 icon 影子飞到槽位空模板中心。
* @param sourceIcon ui_shopItem 的 icon 节点
* @param targetSlotBase 空模板根节点
* @param onArrived 飞行结束
*/
playIconFly(sourceIcon: Node, targetSlotBase: Node, onArrived: () => void): void {
const flyLayer = this._flyLayer;
if (!sourceIcon?.isValid || !targetSlotBase?.isValid || !flyLayer?.isValid) {
console.log('[DailyShopBuySkillFly] 飞行参数无效,跳过');
onArrived();
return;
}
this._stopShadow();
const shadow = instantiate(sourceIcon);
shadow.name = 'dailyShopBuyIconShadow';
flyLayer.addChild(shadow);
shadow.setSiblingIndex(flyLayer.children.length - 1);
shadow.worldPosition = sourceIcon.worldPosition.clone();
shadow.setWorldScale(sourceIcon.worldScale.clone());
const opacity = shadow.getOrAddComponent(UIOpacity);
opacity.opacity = SHADOW_OPACITY;
const endPos = this._resolveSlotFlyTargetWorld(targetSlotBase);
this._shadowNode = shadow;
tween(shadow)
.to(ICON_FLY_SEC, { worldPosition: endPos }, { easing: 'sineInOut' })
.call(this._onIconFlyArrived.bind(this, onArrived))
.start();
}
/** 技能槽放大 1.1 倍后回到 1,执行 2 次 */
playSlotPulse(skillItem: Node, onDone?: () => void): void {
if (!skillItem?.isValid) {
onDone?.();
return;
}
this._stopPulse();
this._pulseNode = skillItem;
const sx = skillItem.scale.x;
const sy = skillItem.scale.y;
const sz = skillItem.scale.z;
let tw = tween(skillItem);
for (let i = 0; i < SLOT_PULSE_TIMES; i++) {
tw = tw
.to(SLOT_PULSE_HALF_SEC, { scale: v3(sx * SLOT_PULSE_SCALE, sy * SLOT_PULSE_SCALE, sz) }, { easing: 'sineOut' })
.to(SLOT_PULSE_HALF_SEC, { scale: v3(sx, sy, sz) }, { easing: 'sineIn' });
}
tw.call(this._onSlotPulseDone.bind(this, onDone)).start();
}
/** 槽位飞行落点(空模板中心世界坐标) */
private _resolveSlotFlyTargetWorld(targetSlotBase: Node): Vec3 {
const ut = targetSlotBase.getComponent(UITransform);
if (ut) return ut.convertToWorldSpaceAR(v3(0, 0, 0));
return targetSlotBase.worldPosition.clone();
}
/** icon 影子飞行结束 */
private _onIconFlyArrived(onArrived: () => void): void {
this._stopShadow();
onArrived();
}
/** 技能槽脉冲结束 */
private _onSlotPulseDone(onDone?: () => void): void {
this._pulseNode = null;
onDone?.();
}
/** 停止并销毁影子节点 */
private _stopShadow(): void {
const shadow = this._shadowNode;
this._shadowNode = null;
if (!shadow?.isValid) return;
Tween.stopAllByTarget(shadow);
shadow.destroy();
}
/** 停止技能槽脉冲并还原 scale */
private _stopPulse(): void {
const node = this._pulseNode;
this._pulseNode = null;
if (!node?.isValid) return;
Tween.stopAllByTarget(node);
}
}