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/battle/view/GridUpgradeSpineEffect.ts

239 lines
7.6 KiB

import { director, Director, Node, Sorting2D, Tween, UIOpacity, UITransform, Vec3, sp, tween } from 'cc';
import { GBundle, GPath } from '../../game/ConfigRes';
import { BattleSpinePlayback } from '../presentation/BattleSpinePlayback';
import { DeviceProfile } from './DeviceProfile';
const SPINE_NODE_NAME = 'spine';
const FALL_SPINE_NODE_NAME = 'fallSpine';
const OPEN_SPINE_NODE_NAME = 'openSpine';
const UPGRADE_ANIM = '升级效果';
/** 文字落到空格时的放置效果 */
export const FALL_ANIM = 'animation';
/** 铲开挖未开启格 */
export const OPEN_ANIM = 'animation';
/** levelUpText 预制体目录 */
const LEVEL_UP_TEXT_PATH = GPath.ObstaclePrefab('');
/** levelUpText 预制体名 */
const LEVEL_UP_TEXT_NAME = 'levelUpText';
/** 升级飘字上浮像素 */
const LEVEL_UP_TEXT_FLOAT_PX = 40;
/** 升级飘字时长(秒) */
const LEVEL_UP_TEXT_DURATION_SEC = 1.0;
/** 升级飘字相对锚点上移 */
const LEVEL_UP_TEXT_LIFT_PX = 20;
/** 升级飘字 Sorting2D 层(monsterUp,压过英雄皮) */
const LEVEL_UP_TEXT_SORT_LAYER = 4;
/** 升级飘字 Sorting2D order */
const LEVEL_UP_TEXT_SORT_ORDER = 10;
/**
* 英雄 / 单字升级飘字(battleMap/prefab/障碍物/levelUpText)。
*/
export class LevelUpTextFloater {
/**
* 在飞行层按世界坐标播放「升 级」飘字。
* @param parent 挂载父节点(通常为 ui_fly)
* @param worldPos 锚点世界坐标
*/
static play(parent: Node | null, worldPos: Readonly<Vec3>): void {
if (!DeviceProfile.enableLevelUpTextFloater) return;
if (!parent?.isValid) {
console.log('[LevelUpTextFloater] 挂载层缺失');
return;
}
let node: Node | null = null;
try {
node = gg.res.getNode(LEVEL_UP_TEXT_PATH, LEVEL_UP_TEXT_NAME, GBundle.BattleMap);
} catch (e) {
console.log('[LevelUpTextFloater] 预制体加载异常', String(e));
return;
}
if (!node?.isValid) {
console.log('[LevelUpTextFloater] levelUpText 预制体缺失');
return;
}
node.parent = parent;
node.layer = parent.layer;
node.active = true;
node.setSiblingIndex(parent.children.length - 1);
const parentUt = parent.getComponent(UITransform);
if (!parentUt) {
console.log('[LevelUpTextFloater] 挂载层缺少 UITransform');
LevelUpTextFloater._release(node);
return;
}
const local = parentUt.convertToNodeSpaceAR(new Vec3(worldPos.x, worldPos.y + LEVEL_UP_TEXT_LIFT_PX, worldPos.z));
node.setPosition(local.x, local.y, local.z);
node.setScale(1, 1, 1);
let sorting = node.getComponent(Sorting2D);
if (!sorting) sorting = node.addComponent(Sorting2D);
sorting.sortingLayer = LEVEL_UP_TEXT_SORT_LAYER;
sorting.sortingOrder = LEVEL_UP_TEXT_SORT_ORDER;
const opacity = node.getComponent(UIOpacity) ?? node.addComponent(UIOpacity);
opacity.opacity = 255;
Tween.stopAllByTarget(node);
Tween.stopAllByTarget(opacity);
const endY = local.y + LEVEL_UP_TEXT_FLOAT_PX;
tween(node)
.to(LEVEL_UP_TEXT_DURATION_SEC, { position: new Vec3(local.x, endY, local.z) }, { easing: 'cubicOut' })
.call(() => LevelUpTextFloater._release(node))
.start();
tween(opacity)
.delay(LEVEL_UP_TEXT_DURATION_SEC * 0.35)
.to(LEVEL_UP_TEXT_DURATION_SEC * 0.65, { opacity: 0 })
.start();
}
/** 回收飘字节点 */
private static _release(node: Node | null): void {
if (!node?.isValid) return;
Tween.stopAllByTarget(node);
const opacity = node.getComponent(UIOpacity);
if (opacity) Tween.stopAllByTarget(opacity);
if (node['resKey']) gg.res.putNode(node);
else node.destroy();
}
}
function playNamedSpine(
host: Node | null,
spineNodeName: string,
animName: string,
onComplete?: () => void,
): void {
if (!host?.isValid) {
onComplete?.();
return;
}
const spineNode = host.getChildByName(spineNodeName);
if (!spineNode?.isValid) {
onComplete?.();
return;
}
const spine = spineNode.getComponent(sp.Skeleton);
if (!spine?.isValid || !spine.skeletonData) {
onComplete?.();
return;
}
spineNode.active = true;
spineNode.setSiblingIndex(host.children.length - 1);
spine.enabled = true;
spine.paused = false;
BattleSpinePlayback.resetTracks(spine);
let settled = false;
let hideTimer = 0;
const finish = (): void => {
if (settled) return;
settled = true;
clearTimeout(hideTimer);
if (spineNode?.isValid) spineNode.active = false;
onComplete?.();
};
const ok = BattleSpinePlayback.play(spine, animName, {
loop: false,
onComplete: finish,
});
if (ok) {
hideTimer = setTimeout(finish, 2500);
} else {
finish();
}
}
function hideNamedSpine(host: Node | null, spineNodeName: string): void {
if (!host?.isValid) return;
const spineNode = host.getChildByName(spineNodeName);
if (!spineNode?.isValid) return;
const spine = spineNode.getComponent(sp.Skeleton);
if (spine?.isValid) {
BattleSpinePlayback.clearPendingComplete(spine);
BattleSpinePlayback.resetTracks(spine);
}
spineNode.active = false;
}
/** 合成/升级时播放 mapGrid 上的 Spine 特效 */
export function playGridUpgradeSpine(
host: Node | null,
animName = UPGRADE_ANIM,
onComplete?: () => void,
): void {
if (!DeviceProfile.enableGridUpgradeSpine) {
onComplete?.();
return;
}
playNamedSpine(host, SPINE_NODE_NAME, animName, onComplete);
}
/** 下一帧播放,避免 rebuild / 节点挂载同一帧内 Spine 未就绪或被覆盖 */
export function playGridUpgradeSpineDeferred(
host: Node | null,
animName = UPGRADE_ANIM,
onComplete?: () => void,
): void {
if (!host?.isValid) {
onComplete?.();
return;
}
director.once(Director.EVENT_AFTER_UPDATE, () => {
if (host?.isValid) playGridUpgradeSpine(host, animName, onComplete);
else onComplete?.();
});
}
export function hideGridUpgradeSpine(host: Node | null): void {
hideNamedSpine(host, SPINE_NODE_NAME);
}
/** 文字落到空 mapGrid 时播放 fallSpine「animation」,播完自动隐藏 */
export function playGridFallSpine(host: Node | null, onComplete?: () => void): void {
playNamedSpine(host, FALL_SPINE_NODE_NAME, FALL_ANIM, onComplete);
}
export function playGridFallSpineDeferred(host: Node | null, onComplete?: () => void): void {
if (!host?.isValid) {
onComplete?.();
return;
}
director.once(Director.EVENT_AFTER_UPDATE, () => {
if (host?.isValid) playGridFallSpine(host, onComplete);
else onComplete?.();
});
}
export function hideGridFallSpine(host: Node | null): void {
hideNamedSpine(host, FALL_SPINE_NODE_NAME);
}
/** 铲开挖未开启格时播放 openSpine「animation」,播完自动隐藏 */
export function playGridOpenSpine(host: Node | null, onComplete?: () => void): void {
playNamedSpine(host, OPEN_SPINE_NODE_NAME, OPEN_ANIM, onComplete);
}
export function playGridOpenSpineDeferred(host: Node | null, onComplete?: () => void): void {
if (!host?.isValid) {
onComplete?.();
return;
}
director.once(Director.EVENT_AFTER_UPDATE, () => {
if (host?.isValid) playGridOpenSpine(host, onComplete);
else onComplete?.();
});
}
export function hideGridOpenSpine(host: Node | null): void {
hideNamedSpine(host, OPEN_SPINE_NODE_NAME);
}