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.
154 lines
5.4 KiB
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;
|
||
|
|
}
|
||
|
|
}
|