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.
164 lines
5.8 KiB
164 lines
5.8 KiB
import { instantiate, Node, Tween, tween, UIOpacity, Vec3 } from 'cc';
|
|
import type { AiActiveSkillCastFxInfo } from '../../battle/ai/runtime/AiActiveSkillHost';
|
|
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 {
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|