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/PanelHome/PanelHomeEmolumentCoinFlyPr...

154 lines
5.4 KiB

3 weeks ago
import { instantiate, Node, Tween, tween, UIOpacity, UITransform, v3, Vec3 } from 'cc';
/** 单次飞行时长(秒) */
const COIN_FLY_SEC = 0.45;
/** 影子透明度 */
const SHADOW_OPACITY = 200;
/** 影子起飞间隔(秒) */
const COIN_SHADOW_STAGGER = 0.04;
/** 每个起点生成金币枚数 */
const COINS_PER_SOURCE = 5;
/** 起点横向散开间距(像素) */
const SPAWN_SPREAD_X = 14;
/** 起点纵向随机偏移(像素) */
const SPAWN_SPREAD_Y = 10;
/** ui_money 下金币 icon 节点名 */
const MONEY_COIN_NODE = '金币';
/** 俸禄金币飞行起点(PopEmolument 领取前记录世界坐标) */
export interface EmolumentCoinFlySource {
/** 起点世界坐标 */
worldPos: Vec3;
}
/**
* 首页俸禄领取:多起点金币影子飞向 ui_money/金币。
*/
export class PanelHomeEmolumentCoinFlyPresenter {
private _flyLayer: Node | null = null;
private _moneyRoot: Node | null = null;
private readonly _shadowNodes: Node[] = [];
/**
* 绑定飞行层与 ui_money。
* @param flyLayer 影子父节点(建议 PanelHome 根节点)
* @param moneyRoot ui_money
*/
bind(flyLayer: Node | null | undefined, moneyRoot: Node | null | undefined): void {
this._flyLayer = flyLayer?.isValid ? flyLayer : null;
this._moneyRoot = moneyRoot?.isValid ? moneyRoot : null;
}
/** 停止飞行并清理影子 */
unbind(): void {
this._stopShadows();
this._flyLayer = null;
this._moneyRoot = null;
}
/**
* 从各起点生成 5 枚影子,飞向 ui_money/金币。
* @param sources 奖励 > 0 的起点列表
* @param onComplete 全部到达或跳过
*/
play(sources: EmolumentCoinFlySource[], onComplete?: () => void): void {
const done = onComplete ?? (() => { });
const flyLayer = this._flyLayer;
const targetCoin = this._resolveMoneyCoin();
if (!flyLayer?.isValid || !targetCoin?.isValid) {
console.log('[PanelHomeEmolumentCoinFly] 飞行参数无效,跳过');
done();
return;
}
if (!sources.length) {
done();
return;
}
this._stopShadows();
gg.audio.playEffect({ name: '结算金币效果' });
const endPos = this._resolveWorldCenter(targetCoin);
const shadowTemplate = targetCoin;
const shadowWorldScale = targetCoin.worldScale.clone();
let spawnedCount = 0;
let arrived = 0;
let shadowIndex = 0;
for (let s = 0; s < sources.length; s++) {
const source = sources[s];
spawnedCount += COINS_PER_SOURCE;
const srcPos = source.worldPos;
for (let i = 0; i < COINS_PER_SOURCE; i++) {
const shadow = instantiate(shadowTemplate);
shadow.name = 'emolumentCoinShadow';
flyLayer.addChild(shadow);
shadow.setSiblingIndex(flyLayer.children.length - 1);
const spread = (i - (COINS_PER_SOURCE - 1) * 0.5) * SPAWN_SPREAD_X;
shadow.setWorldPosition(
srcPos.x + spread,
srcPos.y + (Math.random() - 0.5) * SPAWN_SPREAD_Y,
srcPos.z,
);
shadow.setWorldScale(shadowWorldScale);
shadow.getOrAddComponent(UIOpacity).opacity = SHADOW_OPACITY;
this._shadowNodes.push(shadow);
const delayIndex = shadowIndex;
shadowIndex++;
Tween.stopAllByTarget(shadow);
tween(shadow)
.delay(delayIndex * COIN_SHADOW_STAGGER)
.to(COIN_FLY_SEC, { worldPosition: endPos.clone() }, { easing: 'sineIn' })
.call(() => {
arrived++;
if (shadow.isValid) {
Tween.stopAllByTarget(shadow);
shadow.destroy();
}
const idx = this._shadowNodes.indexOf(shadow);
if (idx >= 0) this._shadowNodes.splice(idx, 1);
if (arrived >= spawnedCount) done();
})
.start();
}
}
if (spawnedCount <= 0) {
done();
}
}
/** ui_money/金币 */
private _resolveMoneyCoin(): Node | null {
const moneyRoot = this._moneyRoot;
if (!moneyRoot?.isValid) {
console.log('[PanelHomeEmolumentCoinFly] ui_money 缺失');
return null;
}
const coin = moneyRoot.getChildByName(MONEY_COIN_NODE);
if (!coin?.isValid) {
console.log('[PanelHomeEmolumentCoinFly] ui_money 缺少', MONEY_COIN_NODE);
return null;
}
return coin;
}
/** 节点中心世界坐标 */
private _resolveWorldCenter(node: Node): Vec3 {
const ut = node.getComponent(UITransform);
if (ut) return ut.convertToWorldSpaceAR(v3(0, 0, 0));
return node.worldPosition.clone();
}
/** 销毁所有影子 */
private _stopShadows(): void {
for (let i = 0; i < this._shadowNodes.length; i++) {
const shadow = this._shadowNodes[i];
if (!shadow?.isValid) continue;
Tween.stopAllByTarget(shadow);
shadow.destroy();
}
this._shadowNodes.length = 0;
}
}