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.
187 lines
6.6 KiB
187 lines
6.6 KiB
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;
|
|
/** 当前攻效对应的播控序号;>0 且 multi=true 表示多段收招判定 */
|
|
private _atkFxSeq = 0;
|
|
/** 本次攻效是否多段(atk_type=16) */
|
|
private _atkFxMultiHit = false;
|
|
/** 本次攻效是否环形刀光(200005):龙影由公用 type=10 host 发射,收招等攻效播完 */
|
|
private _atkFxRing = false;
|
|
|
|
detachPooledFx(): void {
|
|
this._disposeAtk();
|
|
super.detachPooledFx();
|
|
}
|
|
|
|
onIdle(): void {
|
|
if (this._fxPlaying) return;
|
|
this._hideAtk();
|
|
super.onIdle();
|
|
}
|
|
|
|
abortPresentation(): void {
|
|
this._fxPlaying = false;
|
|
this._atkFxRing = false;
|
|
this._hideAtk();
|
|
super.abortPresentation();
|
|
}
|
|
|
|
/** skillId Presenter:普攻与技能同一套攻效 */
|
|
presentNormAttack(ctx: SkillPresentationContext): void {
|
|
this._presentGunFx(ctx);
|
|
}
|
|
|
|
/** atk_type=16 扇形多段:同一套攻效,命中按段间隔逐段结算 */
|
|
presentFanMultiHit(ctx: SkillPresentationContext): void {
|
|
this._presentGunFx(ctx, true);
|
|
}
|
|
|
|
/** 200005 环形刀光攻效:龙影由公用 skill type=10 host 发射,这里只播攻效 */
|
|
presentRingAttack(ctx: SkillPresentationContext): void {
|
|
this._presentGunFx(ctx, false, true);
|
|
}
|
|
|
|
/** 主动技能与普攻共用预制体(表暂无 act_skill,预留入口) */
|
|
presentSkill(ctx: SkillPresentationContext): void {
|
|
this._presentGunFx(ctx);
|
|
}
|
|
|
|
private _presentGunFx(ctx: SkillPresentationContext, multiHit = false, ring = false): 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);
|
|
this._atkFxSeq = seq;
|
|
this._atkFxMultiHit = multiHit;
|
|
this._atkFxRing = ring;
|
|
if (ring) {
|
|
// 字形「攻击」动画先于攻效播完:挂起收招,等攻效播完再收招
|
|
this.holdPresentationUntilFxComplete();
|
|
}
|
|
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);
|
|
if (ring) this.markStaffSmashAnimsComplete(seq);
|
|
}
|
|
if (ring) {
|
|
// 龙影由公用 skill type=10 host 发射:不安排常规收招 hit,攻效播完收招
|
|
return;
|
|
}
|
|
const hitDelay = PRES_HIT_AT_BASE / Math.max(0.01, aspd);
|
|
if (multiHit) this.schedulePresentationMultiHit(seq, ctx, hitDelay);
|
|
else this.schedulePresentationCast(seq, hitDelay);
|
|
}
|
|
|
|
private readonly _onAtkFxCompleteBound = (): void => {
|
|
this._fxPlaying = false;
|
|
this._hideAtk();
|
|
if (!this.isValid) return;
|
|
const seq = this._atkFxSeq;
|
|
if (this._atkFxRing && seq > 0 && this.isPresentationCast(seq)) {
|
|
// 环形刀光:龙影由公用 host 发射,攻效播完即收招
|
|
this.markStaffSmashAnimsComplete(seq);
|
|
return;
|
|
}
|
|
if (this._atkFxMultiHit && seq > 0 && this.isPresentationCast(seq)) {
|
|
// 多段:攻效播完即解锁收招,不等字形攻击动画(可能已被待机打断)
|
|
this.markStaffSmashAnimsComplete(seq);
|
|
return;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|