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/BattleGame/BattleGameAiActiveSkillFxPr...

168 lines
6.0 KiB

import { instantiate, Node, Tween, tween, UIOpacity, Vec3 } from 'cc';
import type { AiActiveSkillCastFxInfo } from '../../battle/ai/runtime/AiActiveSkillHost';
import { BattleGameSkillDragKind } from '../../manager/battleBuff/BattleBuffActiveSkillUtil';
import type { CocosBagBoardView } from '../../battle/view/CocosBagBoardView';
import type { MapManager } from '../../battle/map/legacy/MapManager';
import type { BattleGameOtherSkillView } from './BattleGameOtherSkillView';
/** 拖拽影子透明度(与玩家 skillDrag 一致) */
const ICON_GHOST_OPACITY = 200;
/** 飞向受体时长(秒) */
const FLY_DURATION_SEC = 0.35;
/** 落地后停留再淡出(秒) */
const LAND_HOLD_SEC = 0.12;
/** 淡出时长(秒) */
const FADE_OUT_SEC = 0.18;
/**
* AI 主动技能施放表现:复制 otherskill 图标飞向单位/基地/受体(对齐玩家拖放落点)。
* 妙手神偷由 BattleGameStealGlyphFxPresenter 播神偷手,不走飞图标。
*/
export class BattleGameAiActiveSkillFxPresenter {
private _otherSkillView: BattleGameOtherSkillView | null = null;
private _bagView: CocosBagBoardView | null = null;
private _map: MapManager | null = null;
private _flyLayer: Node | null = null;
private readonly _activeGhosts: Node[] = [];
private readonly _tmpEnd = new Vec3();
/**
* 绑定 UI / 地图。
* @param otherSkillView 敌方主动栏
* @param bagView 本机背包视图(出怪口 / 迷惑中心)
* @param map 地图
* @param flyLayer 飞行层
*/
bind(
otherSkillView: BattleGameOtherSkillView | null,
bagView: CocosBagBoardView | null,
map: MapManager | null,
flyLayer: Node | null | undefined,
): void {
this.unbind();
this._otherSkillView = otherSkillView;
this._bagView = bagView;
this._map = map;
this._flyLayer = flyLayer?.isValid ? flyLayer : null;
}
/** 注销并清理进行中的飞图标 */
unbind(): void {
this._abortAll();
this._otherSkillView = null;
this._bagView = null;
this._map = null;
this._flyLayer = null;
}
/**
* 播放一次施放飞图标。
* @param info AI 施放载荷
*/
play(info: AiActiveSkillCastFxInfo): void {
if (info.kind === BattleGameSkillDragKind.StealGlyph) {
return;
}
const flyLayer = this._flyLayer;
const icon = this._otherSkillView?.getActiveIconNode(info.slotIndex) ?? null;
if (!flyLayer?.isValid) return;
if (!icon?.isValid) return;
const endWorld = this._resolveEndWorld(info);
if (!endWorld) return;
const ghost = instantiate(icon);
ghost.name = 'aiActiveSkillIconGhost';
ghost.parent = flyLayer;
ghost.setWorldPosition(icon.worldPosition);
ghost.setWorldScale(icon.worldScale);
ghost.setSiblingIndex(flyLayer.children.length - 1);
const opacity = ghost.getComponent(UIOpacity) ?? ghost.addComponent(UIOpacity);
opacity.opacity = ICON_GHOST_OPACITY;
this._activeGhosts.push(ghost);
Tween.stopAllByTarget(ghost);
tween(ghost)
.to(FLY_DURATION_SEC, { worldPosition: endWorld }, { easing: 'quadOut' })
.delay(LAND_HOLD_SEC)
.call(this._onLandFadeStart.bind(this, ghost))
.start();
}
/** 落地后淡出并回收 */
private _onLandFadeStart(ghost: Node): void {
if (!ghost?.isValid) {
this._releaseGhost(ghost);
return;
}
const opacity = ghost.getComponent(UIOpacity);
if (!opacity?.isValid) {
this._releaseGhost(ghost);
return;
}
tween(opacity)
.to(FADE_OUT_SEC, { opacity: 0 })
.call(this._onFadeDone.bind(this, ghost))
.start();
}
/** 淡出结束 */
private _onFadeDone(ghost: Node): void {
this._releaseGhost(ghost);
}
/**
* 解析受体世界坐标。
* @param info 载荷
*/
private _resolveEndWorld(info: AiActiveSkillCastFxInfo): Vec3 | null {
const receptor = info.receptor;
if (receptor.kind === 'map' || receptor.kind === 'steal') {
const node = this._map?.getMapGridByIndex(receptor.gridIndex)?.node;
if (!node?.isValid) return null;
node.getWorldPosition(this._tmpEnd);
return this._tmpEnd.clone();
}
if (receptor.kind === 'spawn') {
const node = this._bagView?.getSpawnPointNode(receptor.spawnPort) ?? null;
if (!node?.isValid) return null;
node.getWorldPosition(this._tmpEnd);
return this._tmpEnd.clone();
}
if (receptor.kind === 'confuse') {
const center = this._bagView?.getConfuseHeroTargetWorldCenter(receptor.target) ?? null;
if (!center) return null;
return center.clone();
}
if (receptor.kind === 'bag') {
// AI 无头背包无字块视图;无法对齐玩家 bag 落点
return null;
}
return null;
}
/** 回收单个飞图标 */
private _releaseGhost(ghost: Node | null): void {
if (!ghost) return;
const idx = this._activeGhosts.indexOf(ghost);
if (idx >= 0) this._activeGhosts.splice(idx, 1);
if (!ghost.isValid) return;
Tween.stopAllByTarget(ghost);
const opacity = ghost.getComponent(UIOpacity);
if (opacity?.isValid) Tween.stopAllByTarget(opacity);
ghost.destroy();
}
/** 中断全部飞图标 */
private _abortAll(): void {
const pending = this._activeGhosts.slice();
this._activeGhosts.length = 0;
for (let i = 0; i < pending.length; i++) {
const ghost = pending[i];
if (!ghost?.isValid) continue;
Tween.stopAllByTarget(ghost);
const opacity = ghost.getComponent(UIOpacity);
if (opacity?.isValid) Tween.stopAllByTarget(opacity);
ghost.destroy();
}
}
}