import { _decorator, clamp01, Component, Label, Node, Sprite, UITransform } from 'cc'; import { MonsterType } from '../../manager/MonsterDataManager'; const { ccclass } = _decorator; /** 白色/黄色滞后血条:掉到主条目标的时长(秒) */ const PROGRESS2_DROP_SEC = 0.3; @ccclass('MonsterBlood') export class MonsterBlood extends Component { /** 小怪主血量进度 */ smallMonsterProgress: Node = null; /** 小怪滞后血量进度(主条变化后持续跟随掉落) */ smallMonsterProgress2: Node = null; midBossProgress: Node = null; bossProgress: Node = null; /** Boss 滞后血量进度(主条变化后持续跟随掉落) */ bossProgress2: Node = null; /** Boss 血条总血量数字 */ num: Node = null; /** 滞后条追逐目标 fill;<0 表示未在掉落 */ private _trailTargetFill = -1; /** 滞后条掉落速度(fill/秒) */ private _trailDropSpeed = 0; /** 当前正在掉落的滞后条节点 */ private _trailNode: Node | null = null; protected onLoad(): void { this._ensureNodes(); this.enabled = false; } protected onDisable(): void { this._stopTrailDrop(); } protected update(dt: number): void { const battle = gg.game?.CurentBattle as { TimeScale?: number; IsGamePause?: boolean; IsGameOver?: boolean } | undefined; if (!battle || battle.IsGameOver || battle.IsGamePause) return; const scale = battle.TimeScale ?? 1; if (!(scale > 0)) return; this._tickTrailDrop(dt * scale); } /** 池化复用时 onLoad 可能已跑过,受击刷新前再绑一次子节点 */ private _ensureNodes(): void { if (!this.bossProgress?.isValid) this.bossProgress = this.node.find('bossProgress'); if (!this.bossProgress2?.isValid) this.bossProgress2 = this.node.find('bossProgress2'); if (!this.smallMonsterProgress?.isValid) { this.smallMonsterProgress = this.node.find('smallMonsterProgress'); } if (!this.smallMonsterProgress2?.isValid) { this.smallMonsterProgress2 = this.node.find('smallMonsterProgress2'); } if (!this.midBossProgress?.isValid) this.midBossProgress = this.node.find('midBossProgress'); if (!this.num?.isValid) this.num = this.node.find('num'); } Data: ITableMonster = null; /** * 按当前/最大生命刷新进度。召唤怪与小怪共用「小怪血条底」,禁止走中期条。 * @param hp 当前生命 * @param maxHp 最大生命 * @param shieldHp 护盾当前(仅影响小怪满血是否显示) * @param shieldMaxHp 护盾上限 */ updateBloodBar(hp: number, maxHp: number, shieldHp = 0, shieldMaxHp = 0) { if (!this.Data) return; this._ensureNodes(); const t = this.Data.type; const isSmall = t === MonsterType.SmallMonster; const isSummon = t === MonsterType.SummonMonster; const useSmallBar = isSmall || isSummon; const isBoss = t === MonsterType.Boss; const isMid = !isBoss && !useSmallBar && ( t === MonsterType.SilverBoss || t === MonsterType.GoldenBoss || t === MonsterType.OrangeBoss ); const hasShield = shieldMaxHp > 0 && shieldHp > 0; if (isSmall && !(hp < maxHp || hasShield)) { this._resetSmallProgressFills(); this.node.active = false; return; } this.node.active = true; const ratio = maxHp > 0 ? clamp01(hp / maxHp) : 0; if (this.smallMonsterProgress) { this.smallMonsterProgress.active = useSmallBar; if (useSmallBar) { this._updateFillWithTrail(this.smallMonsterProgress, this.smallMonsterProgress2, ratio); } } if (this.midBossProgress) { this.midBossProgress.active = isMid; if (isMid) this._setFill(this.midBossProgress, ratio); } if (isBoss) { this._updateBossBar(hp, maxHp); } } /** * 主条立即改 fill;滞后条在 0.3s 内持续掉到相同 fill。 * @param main 主进度节点 * @param trail 滞后进度节点 * @param fill 目标填充 0~1 */ private _updateFillWithTrail(main: Node, trail: Node | null, fill: number): void { const mainSp = main.getComponent(Sprite); if (!mainSp) { console.log('[MonsterBlood] 主进度缺少 Sprite', main.name); return; } this._applyFill(mainSp, fill); if (!trail?.isValid) { console.log('[MonsterBlood] 缺少滞后进度', main.name); return; } trail.active = true; const trailSp = trail.getComponent(Sprite); if (!trailSp) { console.log('[MonsterBlood] 滞后进度缺少 Sprite', trail.name); return; } const cur = trailSp.fillRange; if (Math.abs(cur - fill) < 1e-4 || fill >= cur) { this._stopTrailDrop(); this._applyFill(trailSp, fill); return; } this._trailNode = trail; this._trailTargetFill = fill; this._trailDropSpeed = (cur - fill) / PROGRESS2_DROP_SEC; this.enabled = true; } /** 每帧推进滞后条 */ private _tickTrailDrop(dt: number): void { if (!(this._trailTargetFill >= 0)) return; const trail = this._trailNode; if (!trail?.isValid) { this._stopTrailDrop(); return; } const trailSp = trail.getComponent(Sprite); if (!trailSp) { this._stopTrailDrop(); return; } const target = this._trailTargetFill; const next = trailSp.fillRange - this._trailDropSpeed * Math.max(0, dt); if (next <= target) { this._applyFill(trailSp, target); this._stopTrailDrop(); return; } this._applyFill(trailSp, next); } /** 满血隐藏前复位,避免池化脏 fill */ private _resetSmallProgressFills(): void { this._stopTrailDrop(); const mainSp = this.smallMonsterProgress?.isValid ? this.smallMonsterProgress.getComponent(Sprite) : null; if (mainSp) this._applyFill(mainSp, 1); const trailSp = this.smallMonsterProgress2?.isValid ? this.smallMonsterProgress2.getComponent(Sprite) : null; if (trailSp) this._applyFill(trailSp, 1); } /** * 写入 FILLED 进度并刷新渲染。 * TRIMMED + FILLED 会按贴图像素显示、池化后 contentSize 还可能被填满比例挤扁,须 CUSTOM。 * @param sp 进度 Sprite * @param fill 0~1 */ private _applyFill(sp: Sprite, fill: number): void { if (sp.type !== Sprite.Type.FILLED) { sp.type = Sprite.Type.FILLED; sp.fillType = Sprite.FillType.HORIZONTAL; } sp.fillStart = 0; if (sp.sizeMode !== Sprite.SizeMode.CUSTOM) { const frame = sp.spriteFrame; const ut = sp.node.getComponent(UITransform); const w = frame?.rect.width ?? ut?.contentSize.width ?? 0; const h = frame?.rect.height ?? ut?.contentSize.height ?? 0; sp.sizeMode = Sprite.SizeMode.CUSTOM; if (ut && w > 0 && h > 0) ut.setContentSize(w, h); } sp.fillRange = fill; sp.markForUpdateRenderData(); } /** * 节点上的进度 Sprite。 * @param node 进度节点 * @param fill 0~1 */ private _setFill(node: Node, fill: number): void { const sp = node.getComponent(Sprite); if (!sp) { console.log('[MonsterBlood] 进度条缺少 Sprite', node.name); return; } this._applyFill(sp, fill); } /** 停止滞后条追逐 */ private _stopTrailDrop(): void { this._trailTargetFill = -1; this._trailDropSpeed = 0; this._trailNode = null; this.enabled = false; } /** Boss 血条:主条立即扣、bossProgress2 滞后掉落,num 显示总血量数字 */ private _updateBossBar(hp: number, maxHp: number): void { const ratio = maxHp > 0 ? clamp01(hp / maxHp) : 0; if (this.bossProgress?.isValid) { this._updateFillWithTrail(this.bossProgress, this.bossProgress2, ratio); } else { console.log('[MonsterBlood] 缺少 bossProgress'); } if (this.num) { const numLabel = this.num.getComponent(Label); if (numLabel) numLabel.string = String(Math.max(0, Math.ceil(hp))); } } }