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.

145 lines
4.6 KiB

3 weeks ago
import { _decorator, Node, sp } from 'cc';
import { GBundle } from '../../game/ConfigRes';
import type { SkillPresentationContext } from '../skill/SkillPresentationTypes';
import { PRES_HIT_AT_BASE } from '../presentation/SkillVisualChannel';
import { HeroSkin } from '../view/HeroSkin';
import { BattleSpinePlayback } from '../presentation/BattleSpinePlayback';
import { HeroActor } from './HeroActor';
const { ccclass } = _decorator;
/** battleHero/prefab/skill/英雄棍-攻击 */
const ATK_PREFAB = '英雄棍-攻击';
const ATK_PREFAB_PATH = 'prefab/skill/';
/** 攻效节点名 */
const NODE_ANIM = 'anim';
/** 攻效 Spine 动画 */
const SPINE_ATK_FX = '攻击2';
/**
* 棍:HeroActor 壳;普攻/技能表现共用「英雄棍-攻击」(播攻击2),字形播「攻击」。
*/
@ccclass('Gun')
export class Gun extends HeroActor {
private _atkRoot: Node | null = null;
private _atkAnim: sp.Skeleton | null = null;
/** 攻效「攻击2」播放中;命中结算会 onIdle,勿此时藏攻效 */
private _fxPlaying = false;
detachPooledFx(): void {
this._disposeAtk();
super.detachPooledFx();
}
onIdle(): void {
if (this._fxPlaying) return;
this._hideAtk();
super.onIdle();
}
abortPresentation(): void {
this._fxPlaying = false;
this._hideAtk();
super.abortPresentation();
}
/** skillId Presenter:普攻与技能同一套攻效 */
presentNormAttack(ctx: SkillPresentationContext): void {
this._presentGunFx(ctx);
}
/** 主动技能与普攻共用预制体(表暂无 act_skill,预留入口) */
presentSkill(ctx: SkillPresentationContext): void {
this._presentGunFx(ctx);
}
private _presentGunFx(ctx: SkillPresentationContext): void {
if (!ctx.isAlive || !this.isValid || !this.node?.isValid) {
ctx.finish();
return;
}
this._bindChannel();
this._ensureAtk();
this.syncAtkFxSkin(this._atkAnim);
this._showAtk();
const seq = this.beginPresentationCast(ctx);
const aspd = this.visualChannel.playCastAnim({
caster: ctx.caster,
animation: String(ctx.params.animation ?? ''),
followWeapon: false,
primaryTarget: ctx.targets[0] ?? null,
onAnimComplete: this.onPresentationAnimCompleteBound,
});
if (this._atkAnim?.isValid) {
this._fxPlaying = true;
this.playSpine(this._atkAnim, SPINE_ATK_FX, this._onAtkFxCompleteBound, aspd, false);
} else {
this._fxPlaying = false;
console.log('[Gun] 攻效预制体未加载', ATK_PREFAB);
}
this.schedulePresentationCast(seq, PRES_HIT_AT_BASE / Math.max(0.01, aspd));
}
private readonly _onAtkFxCompleteBound = (): void => {
this._fxPlaying = false;
this._hideAtk();
if (this.isValid) super.onIdle();
};
private _ensureAtk(): void {
if (!this._atkRoot?.isValid) {
const atk = gg.res.getNode(ATK_PREFAB_PATH, ATK_PREFAB, GBundle.BattleHero);
if (!atk?.isValid) {
this._atkRoot = null;
this._atkAnim = null;
return;
}
this._atkRoot = atk;
}
this._attachAtk();
const animNode = this._atkRoot.getChildByName(NODE_ANIM);
this._atkAnim = animNode?.getComponent(sp.Skeleton) ?? null;
if (!this._atkAnim?.isValid) {
console.log('[Gun] 攻效缺少节点', NODE_ANIM);
}
}
/** 挂在英雄展示根上,与「动画」同级 */
private _attachAtk(): void {
const atk = this._atkRoot;
if (!atk?.isValid) return;
if (!this._heroSkin) {
this._heroSkin = this.node.getComponent(HeroSkin);
}
const display = this._heroSkin?.displayNode;
const parent = display?.isValid ? display : this.node;
if (atk.parent !== parent) {
atk.parent = parent;
}
atk.setPosition(0, 0, 0);
}
private _showAtk(): void {
if (this._atkRoot?.isValid) this._atkRoot.active = true;
}
private _hideAtk(): void {
if (this._atkAnim?.isValid) {
BattleSpinePlayback.clearPendingComplete(this._atkAnim);
this._atkAnim.timeScale = 1;
}
if (this._atkRoot?.isValid) this._atkRoot.active = false;
}
private _disposeAtk(): void {
this._fxPlaying = false;
this._hideAtk();
if (this._atkRoot?.isValid) {
gg.res.putNode(this._atkRoot);
}
this._atkRoot = null;
this._atkAnim = null;
}
}