|
|
|
|
@ -245,17 +245,17 @@ const GLYPH_IDLE_ANIM = '待机'; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 视觉通道:字形 + 可选武器挂载。 |
|
|
|
|
* 组合英雄激活后常驻显示当前装备武器;五行数字仅攻击显示武器。 |
|
|
|
|
* 组合英雄 / 五行数字激活后常驻显示武器(格子左侧);五行数字不播字形「攻击」。 |
|
|
|
|
*/ |
|
|
|
|
export class SkillVisualChannel { |
|
|
|
|
private _skin: HeroSkin | null = null; |
|
|
|
|
/** 五行数字节点 Spine(无 HeroSkin 时播攻击/待机) */ |
|
|
|
|
/** 五行数字节点 Spine(无 HeroSkin 时播待机;不播攻击) */ |
|
|
|
|
private _glyphAnim: sp.Skeleton | null = null; |
|
|
|
|
private readonly _weapon = new HeroWeaponMount(); |
|
|
|
|
private _parent: Node | null = null; |
|
|
|
|
private _heroId = 0; |
|
|
|
|
private _useWeapon = false; |
|
|
|
|
/** 五行数字:仅攻击显示武器,收招后 1 秒无出手再藏 */ |
|
|
|
|
/** 五行数字:武器常驻;弓/出手帧走五行路径;数字不播「攻击」 */ |
|
|
|
|
private _attackOnlyWeapon = false; |
|
|
|
|
/** 五行武器本地 X;null 用 WEAPON_LEFT_OFFSET_PX */ |
|
|
|
|
private _weaponLocalX: number | null = null; |
|
|
|
|
@ -303,7 +303,7 @@ export class SkillVisualChannel { |
|
|
|
|
return this._useWeapon; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** 五行数字:仅攻击显示武器 */ |
|
|
|
|
/** 五行数字通道(武器常驻、不播数字攻击) */ |
|
|
|
|
get attackOnlyWeapon(): boolean { |
|
|
|
|
return this._attackOnlyWeapon; |
|
|
|
|
} |
|
|
|
|
@ -314,7 +314,7 @@ export class SkillVisualChannel { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 五行数字:只在攻击时显示武器。 |
|
|
|
|
* 标记五行数字通道。 |
|
|
|
|
* @param on 是否开启 |
|
|
|
|
*/ |
|
|
|
|
setAttackOnlyWeapon(on: boolean): void { |
|
|
|
|
@ -322,7 +322,7 @@ export class SkillVisualChannel { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 绑定五行数字 Spine(播「攻击」/「待机」)。 |
|
|
|
|
* 绑定五行数字 Spine(仅待机;不播攻击)。 |
|
|
|
|
* @param anim 数字节点 Skeleton |
|
|
|
|
*/ |
|
|
|
|
setGlyphAnim(anim: sp.Skeleton | null): void { |
|
|
|
|
@ -428,18 +428,14 @@ export class SkillVisualChannel { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 激活待机:组合英雄显示武器;五行数字藏武器并播数字「待机」。 |
|
|
|
|
* 激活待机:显示武器;五行数字播「待机」(不播攻击)。 |
|
|
|
|
* 武器释放中 / 连击窗内跳过,避免掐断出手。 |
|
|
|
|
*/ |
|
|
|
|
playIdle(): void { |
|
|
|
|
// 定格窗内禁止切待机动画:否则 Spine 末帧会被待机姿覆盖,武器角也像被拧回
|
|
|
|
|
if (this._weaponCasting || this._restHoldActive) return; |
|
|
|
|
if (this._attackOnlyWeapon) { |
|
|
|
|
this._playGlyphIdle(); |
|
|
|
|
this._weapon.setVisible(false); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
this._skin?.playIdleAnim(); |
|
|
|
|
if (this._attackOnlyWeapon) this._playGlyphIdle(); |
|
|
|
|
else this._skin?.playIdleAnim(); |
|
|
|
|
if (this._useWeapon) this.showIdleWeapon(); |
|
|
|
|
else this._weapon.setVisible(false); |
|
|
|
|
} |
|
|
|
|
@ -449,7 +445,6 @@ export class SkillVisualChannel { |
|
|
|
|
* @returns 是否已显示待机武器 |
|
|
|
|
*/ |
|
|
|
|
tryShowIdleWeapon(): boolean { |
|
|
|
|
if (this._attackOnlyWeapon) return false; |
|
|
|
|
if (!this._useWeapon || this._weaponCasting || this._restHoldActive || this._weaponFlies.length > 0) return false; |
|
|
|
|
return this.showIdleWeapon(); |
|
|
|
|
} |
|
|
|
|
@ -516,14 +511,15 @@ export class SkillVisualChannel { |
|
|
|
|
this._cancelWeaponAim(); |
|
|
|
|
this._cancelWeaponOrbit(); |
|
|
|
|
this._weapon.hideBowAtk(true); |
|
|
|
|
if (this._useWeapon && !this._attackOnlyWeapon) this._weapon.setVisible(true); |
|
|
|
|
if (this._useWeapon) this._weapon.setVisible(true); |
|
|
|
|
else this._weapon.setVisible(false); |
|
|
|
|
if (this._caster != null) getBowAtkHost().cancelCaster(this._caster); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 释放表现:字形播「攻击」;有武器时武器播 Skill.animation(缺省「攻击」)。 |
|
|
|
|
* 跟随武器时等字形与武器都播完再收招。玉帝飞剑:不播原武器攻击,直接隐藏。 |
|
|
|
|
* 释放表现:组合字形播「攻击」;五行数字不播字形攻击。 |
|
|
|
|
* 有武器时武器播 Skill.animation(缺省「攻击」)。跟随武器时等字形与武器都播完再收招。 |
|
|
|
|
* 玉帝飞剑:不播原武器攻击,直接隐藏。 |
|
|
|
|
* @returns 本段释放动画倍速(供命中延迟换算;主动技能为 1) |
|
|
|
|
*/ |
|
|
|
|
playCastAnim(params: CastAnimParams): number { |
|
|
|
|
@ -543,6 +539,7 @@ export class SkillVisualChannel { |
|
|
|
|
console.warn('[SkillVisualChannel] Skill.animation 为空', params.caster); |
|
|
|
|
} |
|
|
|
|
const follow = params.followWeapon && this._useWeapon && this.ensureWeapon(); |
|
|
|
|
const playGlyph = !this._attackOnlyWeapon; |
|
|
|
|
if (follow) { |
|
|
|
|
this._cancelRestHold(); |
|
|
|
|
this._cancelWeaponAim(); |
|
|
|
|
@ -577,7 +574,7 @@ export class SkillVisualChannel { |
|
|
|
|
} |
|
|
|
|
if (orbitStaff) this._startWukongStaffOrbit(params.caster, weaponAnim, animScale); |
|
|
|
|
if (params.holdCasting) { |
|
|
|
|
this._playGlyphAttack(animScale, null); |
|
|
|
|
if (playGlyph) this._playGlyphAttack(animScale, null); |
|
|
|
|
if (params.throwWeapon) { |
|
|
|
|
this._onFollowCastAllComplete(); |
|
|
|
|
return animScale; |
|
|
|
|
@ -597,7 +594,7 @@ export class SkillVisualChannel { |
|
|
|
|
} |
|
|
|
|
return animScale; |
|
|
|
|
} |
|
|
|
|
if (this._hasGlyphCastAnim()) { |
|
|
|
|
if (playGlyph && this._hasGlyphCastAnim()) { |
|
|
|
|
this._castAnimLeft++; |
|
|
|
|
this._playGlyphAttack(animScale, this._onCastAnimPartComplete); |
|
|
|
|
} |
|
|
|
|
@ -614,8 +611,12 @@ export class SkillVisualChannel { |
|
|
|
|
} |
|
|
|
|
return animScale; |
|
|
|
|
} |
|
|
|
|
this._playGlyphAttack(animScale, params.holdCasting ? null : params.onAnimComplete); |
|
|
|
|
if (!this._hasGlyphCastAnim() && !params.holdCasting) params.onAnimComplete(); |
|
|
|
|
if (playGlyph) { |
|
|
|
|
this._playGlyphAttack(animScale, params.holdCasting ? null : params.onAnimComplete); |
|
|
|
|
if (!this._hasGlyphCastAnim() && !params.holdCasting) params.onAnimComplete(); |
|
|
|
|
} else if (!params.holdCasting) { |
|
|
|
|
params.onAnimComplete(); |
|
|
|
|
} |
|
|
|
|
return animScale; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -626,10 +627,6 @@ export class SkillVisualChannel { |
|
|
|
|
this._castAnimLeft = 0; |
|
|
|
|
this._cancelWeaponOrbit(); |
|
|
|
|
this._cancelRestHold(); |
|
|
|
|
if (this._attackOnlyWeapon) { |
|
|
|
|
this._enterAttackOnlyRestHold(); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
if (this._shouldTweenRestAim()) { |
|
|
|
|
// 弓/枪:先连击窗保持当前角,超时再回待机
|
|
|
|
|
this._enterWeaponRestHold(1); |
|
|
|
|
@ -670,64 +667,6 @@ export class SkillVisualChannel { |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 五行数字:攻击结束后保持武器可见。 |
|
|
|
|
* 弓/枪:定格攻击末帧并保持当前朝向,1 秒无下一击再插值回待机角后藏武器。 |
|
|
|
|
* 刀/木等:可立刻复位;超时藏武器并播数字「待机」。 |
|
|
|
|
*/ |
|
|
|
|
private _enterAttackOnlyRestHold(): void { |
|
|
|
|
this._cancelRestHold(); |
|
|
|
|
this._weapon.setArrowVisible(false); |
|
|
|
|
this.syncWeaponPivot(this._caster, true); |
|
|
|
|
this._weapon.setVisible(true); |
|
|
|
|
// 弓/枪:连击窗内保持攻击姿与瞄准角(对齐组合弓/枪 _enterWeaponRestHold)
|
|
|
|
|
if (this._shouldTweenRestAim()) { |
|
|
|
|
const skin = this._weapon.skinAnim; |
|
|
|
|
if (skin?.isValid) { |
|
|
|
|
BattleSpinePlayback.clearPendingComplete(skin); |
|
|
|
|
skin.timeScale = 0; |
|
|
|
|
} |
|
|
|
|
this._restHoldActive = true; |
|
|
|
|
this._restHoldAspd = 1; |
|
|
|
|
const seq = ++this._restHoldSeq; |
|
|
|
|
schedulePresentation(WEAPON_REST_HOLD_SEC, () => { |
|
|
|
|
if (seq !== this._restHoldSeq) return; |
|
|
|
|
if (this._weaponCasting) return; |
|
|
|
|
this._restHoldActive = false; |
|
|
|
|
if (skin?.isValid) skin.timeScale = 1; |
|
|
|
|
this._restAttackOnlyWeaponAim(); |
|
|
|
|
}); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
if (!this._isStaffWeapon()) this._weapon.resetAim(); |
|
|
|
|
this._weapon.playIdle(); |
|
|
|
|
this._restHoldActive = true; |
|
|
|
|
const seq = ++this._restHoldSeq; |
|
|
|
|
schedulePresentation(WEAPON_REST_HOLD_SEC, () => { |
|
|
|
|
if (seq !== this._restHoldSeq) return; |
|
|
|
|
if (this._weaponCasting) return; |
|
|
|
|
this._restHoldActive = false; |
|
|
|
|
this._weapon.setVisible(false); |
|
|
|
|
this._playGlyphIdle(); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 五行弓/枪:连击窗结束,从当前瞄准角插值回待机角后再藏武器。 |
|
|
|
|
*/ |
|
|
|
|
private _restAttackOnlyWeaponAim(): void { |
|
|
|
|
const duration = WEAPON_AIM_SEC / Math.max(0.01, this._restHoldAspd > 0 ? this._restHoldAspd : 1); |
|
|
|
|
this._tweenWeaponAim(WEAPON_REST_ANGLE, duration, this._onAttackOnlyAimRestCompleteBound); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** 五行弓/枪拧回待机角后:藏武器并播数字待机 */ |
|
|
|
|
private readonly _onAttackOnlyAimRestCompleteBound = (): void => { |
|
|
|
|
if (this._weaponCasting) return; |
|
|
|
|
this._weapon.resetAim(); |
|
|
|
|
this._weapon.setVisible(false); |
|
|
|
|
this._playGlyphIdle(); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** 是否有字形释放 Spine */ |
|
|
|
|
private _hasGlyphCastAnim(): boolean { |
|
|
|
|
return !!(this._skin?.anim?.isValid || this._resolveGlyphAnim()?.isValid); |
|
|
|
|
@ -786,7 +725,7 @@ export class SkillVisualChannel { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 播字形「攻击」:优先 HeroSkin,否则五行数字 Spine。 |
|
|
|
|
* 播字形「攻击」:HeroSkin(组合英雄);五行数字由 playCastAnim 跳过。 |
|
|
|
|
* @param timeScale 倍速 |
|
|
|
|
* @param onComplete 播完;null 不等待 |
|
|
|
|
*/ |
|
|
|
|
@ -867,20 +806,16 @@ export class SkillVisualChannel { |
|
|
|
|
if (!params) return; |
|
|
|
|
if (!params.holdCasting) { |
|
|
|
|
this._weaponCasting = false; |
|
|
|
|
if (this._attackOnlyWeapon) { |
|
|
|
|
this._enterAttackOnlyRestHold(); |
|
|
|
|
const aspd = Math.max(0.01, SkillPresentationEnv.getFinalAttackSpeed(params.caster)); |
|
|
|
|
const restScale = params.animTimeScale != null && params.animTimeScale > 0 ? params.animTimeScale : aspd; |
|
|
|
|
if (this._isBowWeapon() && this._weapon.bowAtkShowing) { |
|
|
|
|
this._weapon.freezeBowDraw(); |
|
|
|
|
// 弓与枪相同:连击窗保持当前角,超时再回待机
|
|
|
|
|
this._enterWeaponRestHold(restScale); |
|
|
|
|
} else if (this._shouldTweenRestAim()) { |
|
|
|
|
this._enterWeaponRestHold(restScale); |
|
|
|
|
} else { |
|
|
|
|
const aspd = Math.max(0.01, SkillPresentationEnv.getFinalAttackSpeed(params.caster)); |
|
|
|
|
const restScale = params.animTimeScale != null && params.animTimeScale > 0 ? params.animTimeScale : aspd; |
|
|
|
|
if (this._isBowWeapon() && this._weapon.bowAtkShowing) { |
|
|
|
|
this._weapon.freezeBowDraw(); |
|
|
|
|
// 弓与枪相同:连击窗保持当前角,超时再回待机
|
|
|
|
|
this._enterWeaponRestHold(restScale); |
|
|
|
|
} else if (this._shouldTweenRestAim()) { |
|
|
|
|
this._enterWeaponRestHold(restScale); |
|
|
|
|
} else { |
|
|
|
|
this._weapon.playIdle(); |
|
|
|
|
} |
|
|
|
|
this._weapon.playIdle(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
params.onAnimComplete(); |
|
|
|
|
@ -897,9 +832,13 @@ export class SkillVisualChannel { |
|
|
|
|
const parent = this._parent; |
|
|
|
|
if (!root?.isValid || !parent?.isValid) return false; |
|
|
|
|
const id = caster ?? this._caster; |
|
|
|
|
// 五行数字:挂在字块本地,按 TextGrid.wPosX
|
|
|
|
|
// 五行数字:挂在字块本地,按 TextGrid.wPosX(须靠左为负)
|
|
|
|
|
if (this._weapon.weaponId <= 0) { |
|
|
|
|
const x = this._weaponLocalX != null ? this._weaponLocalX : -WEAPON_LEFT_OFFSET_PX; |
|
|
|
|
let x = this._weaponLocalX != null ? this._weaponLocalX : -WEAPON_LEFT_OFFSET_PX; |
|
|
|
|
if (!(x < 0)) { |
|
|
|
|
console.error('[SkillVisualChannel] 五行武器本地 X 须靠左', this._heroId, x); |
|
|
|
|
x = -WEAPON_LEFT_OFFSET_PX; |
|
|
|
|
} |
|
|
|
|
root.setPosition(x, 0, 0); |
|
|
|
|
root.angle = 0; |
|
|
|
|
if (this._weapon.bowAtkShowing) this._weapon.syncBowAtkPivot(); |
|
|
|
|
|