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/BattleSuccess/BattleSuccessCoinFlyPresent...

116 lines
4.2 KiB

import { instantiate, Node, Tween, tween, UIOpacity, UITransform, v3, Vec3 } from 'cc';
import { GAudioPath } from '../../game/ConfigRes';
/** 单次飞行时长(秒) */
const COIN_FLY_SEC = 0.45;
/** 影子透明度 */
const SHADOW_OPACITY = 200;
/** 影子起飞间隔(秒) */
const COIN_SHADOW_STAGGER = 0.04;
/** 影子最少枚数 */
const MIN_COIN_SHADOW = 3;
/** 影子最多枚数 */
const MAX_COIN_SHADOW = 12;
/**
* 胜利结算:ui_coin 金币 icon 影子飞向 ui_money。
*/
export class BattleSuccessCoinFlyPresenter {
private _flyLayer: Node | null = null;
private _shadowNodes: Node[] = [];
/** 绑定飞行层(ui_center) */
bind(flyLayer: Node | null | undefined): void {
this._flyLayer = flyLayer?.isValid ? flyLayer : null;
}
/** 停止并清理影子 */
unbind(): void {
this._stopShadows();
this._flyLayer = null;
}
/**
* 在 sourceIcon 附近生成多枚影子,飞向 targetCoin。
* @param sourceIcon ui_coin/icon
* @param targetCoin ui_money/金币
* @param coinAmount 本次获得金币(用于计算影子数量)
* @param onComplete 全部到达
*/
play(sourceIcon: Node | null, targetCoin: Node | null, coinAmount: number, onComplete: () => void): void {
const flyLayer = this._flyLayer;
if (!sourceIcon?.isValid || !targetCoin?.isValid || !flyLayer?.isValid) {
console.log('[BattleSuccessCoinFly] 飞行参数无效,跳过');
onComplete();
return;
}
const count = this._shadowCount(coinAmount);
if (count <= 0) {
onComplete();
return;
}
this._stopShadows();
gg.audio.playEffect({ name: '结算金币效果', path: GAudioPath.SfxUi });
const endPos = this._resolveWorldCenter(targetCoin);
const srcPos = sourceIcon.worldPosition.clone();
const srcScale = sourceIcon.worldScale;
const shadowScale = new Vec3(srcScale.x, srcScale.y, srcScale.z);
let arrived = 0;
for (let i = 0; i < count; i++) {
const shadow = instantiate(sourceIcon);
shadow.name = 'coinShadow';
flyLayer.addChild(shadow);
shadow.setSiblingIndex(flyLayer.children.length - 1);
const spread = (i - (count - 1) * 0.5) * 14;
shadow.setWorldPosition(srcPos.x + spread, srcPos.y + (Math.random() - 0.5) * 10, srcPos.z);
shadow.setWorldScale(shadowScale);
const fade = shadow.getOrAddComponent(UIOpacity);
if (fade) fade.opacity = SHADOW_OPACITY;
this._shadowNodes.push(shadow);
Tween.stopAllByTarget(shadow);
tween(shadow)
.delay(i * 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 >= count) onComplete();
})
.start();
}
}
/** 按金币数量估算影子枚数 */
private _shadowCount(amount: number): number {
const n = Math.floor(Number(amount) || 0);
if (n <= 0) return 0;
const scaled = Math.ceil(Math.log10(n + 1) * 3);
return Math.min(MAX_COIN_SHADOW, Math.max(MIN_COIN_SHADOW, scaled));
}
/** 节点中心世界坐标 */
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;
}
}