1. 从HeroTopBarComp中移除等级Label组件、lvShow方法以及等级显示调用 2. 从HeroViewComp的update逻辑中移除等级脏数据监听 3. 调整英雄顶栏预制体的布局参数并隐藏等级节点
174 lines
6.4 KiB
TypeScript
174 lines
6.4 KiB
TypeScript
import { _decorator, 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)
|
||
* - 透明度激活 / 闲置
|
||
* - 受击抖动动画
|
||
*
|
||
* 数据流: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: 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 = false;
|
||
this.node.active = true;
|
||
|
||
this.setTopBarOpacity(false);
|
||
this.hpShow();
|
||
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 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);
|
||
}
|
||
}
|
||
}
|