import { _decorator, Label, ProgressBar, Sprite, UIOpacity, Vec3, v3, tween, Tween, Color, clamp } from "cc"; import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp"; import { HeroAttrsComp } from "./HeroAttrsComp"; import { FacSet, FightSet } from "../common/config/GameSet"; const { ccclass, property } = _decorator; /** * 英雄头顶状态栏组件 * * 挂载在英雄节点下的 "top" 子节点上,独立管理: * - 血条进度(ProgressBar) * - 护盾数值条(ProgressBar,top/shield 节点,显示 shield / SHIELD_MAX) * - 等级数字(Label) * - 透明度激活 / 闲置 * - 受击抖动动画 * * 数据流:HeroViewComp 将 HeroAttrsComp 引用传入,本组件通过 model 读取属性。 * 注意:英雄根节点下的 "shielded" 是护盾特效动画节点,由 HeroViewComp 管理,不在本组件职责内。 */ @ccclass('HeroTopBarComp') @ecs.register('HeroTopBar', false) export class HeroTopBarComp extends CCComp { // ==================== @property 绑定(编辑器拖拽) ==================== @property({ type: ProgressBar, tooltip: "血条进度条" }) private hpProgressBar: ProgressBar = null!; @property({ type: Label, tooltip: "等级数字" }) private lvLabel: Label = null!; @property({ type: ProgressBar, tooltip: "护盾数值条(top 下的 shield 节点)" }) private shieldProgressBar: ProgressBar = null!; @property({ type: Sprite, tooltip: "血条填充 Sprite(用于设置阵营颜色)" }) private hpBarSprite: Sprite = null!; // ==================== 内部引用 ==================== private topOpacity: UIOpacity = null!; private basePos: Vec3 = v3(); // ==================== 配置 ==================== private hp_height: number = 90; private boss_hp_height: number = 160; // ==================== 透明度 ==================== private readonly barIdleOpacity: number = 153; private readonly barActiveOpacity: number = 255; private readonly idleOpacityDelay: number = 0.25; /** 外部赋值:英雄数据模型 */ private _model: HeroAttrsComp = null!; public get model(): HeroAttrsComp { return this._model; } public set model(value: HeroAttrsComp) { this._model = value; } private readonly restoreBarIdleOpacity = () => { this.setTopBarOpacity(false); }; /** * 初始化 / 对象池复用时重置 * @param model 英雄属性组件 * @param direction 显示方向(1 或 -1) */ public init(model: HeroAttrsComp, direction: number = 1): void { this._model = model; // UIOpacity 无 @property 绑定,运行时获取或添加 this.topOpacity = this.node.getComponent(UIOpacity) || this.node.addComponent(UIOpacity); // 方向取 abs,避免对象池复用时 scale 累乘导致血条反转 this.node.setScale(direction * Math.abs(this.node.scale.x), 1 * this.node.scale.y); // 位置 const height = model.is_boss ? this.boss_hp_height : this.hp_height; this.node.setPosition(0, height, 0); this.basePos = this.node.position.clone(); // 血条颜色(英雄阵营为绿色) if (model.fac === FacSet.HERO && this.hpBarSprite) { this.hpBarSprite.color = new Color("#2ECC71"); } // 确保血条等 UI 始终在最上层 this.node.setSiblingIndex(999); // 重置显示状态 this.node.getChildByName("hp")!.active = true; this.node.getChildByName("cd")!.active = false; this.node.getChildByName("lv")!.active = true; this.node.active = true; this.setTopBarOpacity(false); this.hpShow(); this.lvShow(); this.shieldShow(model.shield); } /** 更新血量进度条 */ public hpShow(): void { if (!this._model || !this.hpProgressBar) return; const hp = this._model.hp; const hpMax = this._model.hp_max; let targetProgress = hpMax > 0 ? hp / hpMax : 0; targetProgress = clamp(targetProgress, 0, 1); const prevProgress = this.hpProgressBar.progress; if (targetProgress < prevProgress) { this.activateTopBar(); this.playHpBarShake(); } this.hpProgressBar.progress = targetProgress; if (targetProgress > prevProgress) { this.setTopBarOpacity(false); } } /** 更新护盾数值条(百分比 = shield / SHIELD_MAX) */ public shieldShow(shield: number = 0): void { if (!this.shieldProgressBar) return; const progress = FightSet.SHIELD_MAX > 0 ? shield / FightSet.SHIELD_MAX : 0; this.shieldProgressBar.progress = clamp(progress, 0, 1); // 护盾节点显隐 this.shieldProgressBar.node.active = shield > 0; } /** 更新等级数字 */ public lvShow(): void { if (!this.lvLabel || !this._model) return; this.lvLabel.string = String(this._model.lv); } /** 激活顶栏(高亮 + 延迟恢复闲置透明度) */ public activateTopBar(): void { this.setTopBarOpacity(true); this.unschedule(this.restoreBarIdleOpacity); this.scheduleOnce(this.restoreBarIdleOpacity, this.idleOpacityDelay); } /** 设置顶栏透明度 */ public setTopBarOpacity(isActive: boolean): void { if (!this.node || !this.node.isValid) return; if (this.topOpacity) { this.topOpacity.opacity = isActive ? this.barActiveOpacity : this.barIdleOpacity; } this.node.active = true; } /** 显示顶栏 */ public show(): void { if (this.node && this.node.isValid) this.node.active = true; } /** 隐藏顶栏 */ public hide(): void { if (this.node && this.node.isValid) this.node.active = false; } /** 血条受击抖动动画 */ private playHpBarShake(): void { if (!this.node || !this.node.isValid) return; Tween.stopAllByTarget(this.node); this.node.setPosition(this.basePos); tween(this.node) .by(0.04, { position: v3(-3, 0, 0) }) .by(0.04, { position: v3(6, 0, 0) }) .by(0.04, { position: v3(-5, 0, 0) }) .by(0.04, { position: v3(2, 0, 0) }) .call(() => { this.node.setPosition(this.basePos); }) .start(); } /** 对象池回收 / 组件重置时调用 */ public reset(): void { this.unscheduleAllCallbacks(); if (this.node && this.node.isValid) { Tween.stopAllByTarget(this.node); this.node.setPosition(this.basePos); } } }