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.
121 lines
4.1 KiB
121 lines
4.1 KiB
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);
|
|
}
|
|
}
|
|
|