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.
116 lines
4.2 KiB
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;
|
|
}
|
|
}
|
|
|