feat(英雄): 优化血条和冷却UI表现

- 移除血条2秒自动隐藏逻辑,改为常显并根据血量状态调整透明度
- 新增血条受击抖动效果,提升打击感
- 增加技能冷却进度条显示功能
- 统一血条激活状态管理,通过透明度变化区分活跃/空闲状态
- 修复复活后血条显示异常问题
This commit is contained in:
walkpan
2026-03-18 22:53:29 +08:00
parent 53b1cf2734
commit b2595cd1b4
3 changed files with 203 additions and 1212 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
import { Vec3, _decorator , v3,Collider2D,Contact2DType,Label ,Node,Prefab,instantiate,ProgressBar, Component, Material, Sprite, math, clamp, Game, tween, Tween, Color, BoxCollider2D, UITransform} from "cc";
import { Vec3, _decorator , v3,Collider2D,Contact2DType,Label ,Node,Prefab,instantiate,ProgressBar, Component, Material, Sprite, math, clamp, Game, tween, Tween, Color, BoxCollider2D, UITransform, UIOpacity} 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 { mLogger } from "../common/Logger";
@@ -42,6 +42,14 @@ export class HeroViewComp extends CCComp {
lastBarUpdateTime:number = 0; // 最后一次血条/蓝条/护盾更新时间
// ==================== UI 节点引用 ====================
private top_node: Node = null!;
private topOpacity: UIOpacity = null!;
private hpBarBasePos: Vec3 = v3();
private readonly barIdleOpacity: number = 153;
private readonly barActiveOpacity: number = 255;
private readonly idleOpacityDelay: number = 0.25;
private readonly restoreBarIdleOpacity = () => {
this.setTopBarOpacity(false);
};
// ==================== 直接访问 HeroAttrsComp ====================
get model() {
@@ -91,15 +99,12 @@ export class HeroViewComp extends CCComp {
/** 方向 */
this.node.setScale(this.scale*Math.abs(this.node.scale.x), 1*this.node.scale.y); // 确保 scale.x 为正后再乘方向
this.top_node.setScale(this.scale*this.top_node.scale.x,1*this.top_node.scale.y);
/* 显示角色血*/
this.top_node.getChildByName("hp").active = true;
this.top_node.getChildByName("mp").active = true
this.top_node.getChildByName("cd").active = true
this.top_node.getChildByName("shield").active = false;
// 初始隐藏血条(有更新时才显示)
this.top_node.active = false;
this.top_node.active = true;
this.setTopBarOpacity(false);
// 🔥 重置血条 UI 显示状态
if (this.model) {
@@ -110,6 +115,11 @@ export class HeroViewComp extends CCComp {
/** 初始化 UI 节点引用 */
private initUINodes() {
this.top_node = this.node.getChildByName("top");
this.topOpacity = this.top_node.getComponent(UIOpacity) || this.top_node.addComponent(UIOpacity);
const hpNode = this.top_node.getChildByName("hp");
if (hpNode) {
this.hpBarBasePos = hpNode.position.clone();
}
// let hp_y = this.node.getComponent(UITransform).height+10;
// this.top_node.setPosition(0, hp_y, 0);
}
@@ -136,15 +146,6 @@ export class HeroViewComp extends CCComp {
return
} ;
// 处理血条显示计时2秒无更新则隐藏
if (this.lastBarUpdateTime > 0) {
const timeSinceLastUpdate = Date.now() / 1000 - this.lastBarUpdateTime;
if (timeSinceLastUpdate >= 2) {
this.top_node.active = false;
this.lastBarUpdateTime = 0;
}
}
// ✅ View 层职责:处理表现相关的逻辑
this.processDamageQueue(); // 伤害数字显示队列
@@ -161,7 +162,10 @@ export class HeroViewComp extends CCComp {
}
}
public cd_show(){
this.top_node.getChildByName("cd").getComponent(ProgressBar).progress = this.model.s_cd/this.model.s_cd_max;
}
/** 显示护盾 */
private show_shield(shield: number = 0, shield_max: number = 0) {
this.lastBarUpdateTime = Date.now() / 1000;
@@ -170,9 +174,7 @@ export class HeroViewComp extends CCComp {
this.node.getChildByName("shielded").active = shield > 0;
this.top_node.getChildByName("shield").active = shield > 0;
this.top_node.getChildByName("shield").getComponent(ProgressBar).progress = shield_progress;
this.scheduleOnce(() => {
this.top_node.getChildByName("shield").getChildByName("pb").getComponent(ProgressBar).progress = shield_progress;
}, 0.15);
}
/** 显示血量 */
@@ -186,22 +188,50 @@ export class HeroViewComp extends CCComp {
let targetProgress = hp / hp_max;
let hpNode = this.top_node.getChildByName("hp");
let hpProgressBar = hpNode.getComponent(ProgressBar);
let hpbProgressBar = hpNode.getChildByName("hpb").getComponent(ProgressBar);
if (targetProgress < hpProgressBar.progress) {
// 扣血先扣血hp再跟hpb
this.activateTopBar();
this.playHpBarShake();
hpProgressBar.progress = targetProgress;
this.scheduleOnce(() => {
if(hpbProgressBar && hpbProgressBar.isValid) hpbProgressBar.progress = targetProgress;
}, 0.15);
} else {
// 加血先加底hpb再加血hp
hpbProgressBar.progress = targetProgress;
this.scheduleOnce(() => {
if(hpProgressBar && hpProgressBar.isValid) hpProgressBar.progress = targetProgress;
}, 0.15);
}
}
private isFullHp(): boolean {
if (!this.model) return false;
if (this.model.hp_max <= 0) return false;
return this.model.hp >= this.model.hp_max;
}
private setTopBarOpacity(isActive: boolean) {
if (!this.topOpacity || !this.topOpacity.isValid) return;
if (isActive) {
this.topOpacity.opacity = this.barActiveOpacity;
return;
}
this.topOpacity.opacity = this.isFullHp() ? this.barIdleOpacity : this.barActiveOpacity;
}
private activateTopBar() {
this.setTopBarOpacity(true);
this.unschedule(this.restoreBarIdleOpacity);
this.scheduleOnce(this.restoreBarIdleOpacity, this.idleOpacityDelay);
}
private playHpBarShake() {
const hpNode = this.top_node?.getChildByName("hp");
if (!hpNode || !hpNode.isValid) return;
Tween.stopAllByTarget(hpNode);
hpNode.setPosition(this.hpBarBasePos);
tween(hpNode)
.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(() => {
hpNode.setPosition(this.hpBarBasePos);
})
.start();
}
@@ -348,26 +378,25 @@ export class HeroViewComp extends CCComp {
if(hp<=99) return;
this.heathed();
this.hp_tip(TooltipTypes.health, hp.toFixed(0));
this.top_node.active = true;
this.lastBarUpdateTime = Date.now() / 1000;
}
mp_add(mp: number = 0) {
// ✅ 仅显示提示,不调用 mp_show()
this.hp_tip(TooltipTypes.addmp, mp.toFixed(0));
this.top_node.active = true;
this.lastBarUpdateTime = Date.now() / 1000;
}
playIntervalEffect(attr: Attrs, value: number, s_uuid: number) {
if (!this.node || !this.node.isValid) return;
this.top_node.active = true;
this.lastBarUpdateTime = Date.now() / 1000;
if (attr === Attrs.hp) {
if (value > 0) {
this.heathed();
this.hp_tip(TooltipTypes.health, value.toFixed(0), s_uuid);
} else if (value < 0) {
this.activateTopBar();
this.playHpBarShake();
this.in_atked("atked", this.model?.fac == FacSet.HERO ? 1 : -1);
this.hp_tip(TooltipTypes.life, Math.abs(value).toFixed(0), s_uuid);
}
@@ -398,7 +427,8 @@ export class HeroViewComp extends CCComp {
this.as.do_buff();
this.status_change("idle");
this.model.hp =this.model.hp_max*50/100;
this.top_node.active=false
this.top_node.active=true
this.setTopBarOpacity(false);
this.lastBarUpdateTime=0
}
@@ -424,7 +454,6 @@ export class HeroViewComp extends CCComp {
// 防止重复触发
if(this.model.is_count_dead) return;
this.model.is_count_dead = true; // 防止重复触发,必须存在防止重复调用
this.top_node.active=false
// 怪物使用0.5秒死亡时间英雄使用realDeadTime
if(this.model.fac === FacSet.MON){
@@ -462,8 +491,8 @@ export class HeroViewComp extends CCComp {
}
do_atked(damage:number,isCrit:boolean,s_uuid:number,isBack:boolean=false){
// 受到攻击时显示血条,并更新最后更新时间
this.top_node.active = true;
// 受到攻击时更新最后更新时间
this.activateTopBar();
this.lastBarUpdateTime = Date.now() / 1000;
if (damage <= 0) return;
@@ -573,6 +602,7 @@ export class HeroViewComp extends CCComp {
}
this.deadCD=0
this.lastBarUpdateTime=0
this.unschedule(this.restoreBarIdleOpacity);
// 清理伤害队列
this.damageQueue.length = 0;

View File

@@ -45,6 +45,8 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
if (!heroAttrs || !heroView || !heroView.node) return;
if (heroAttrs.is_dead || heroAttrs.is_reviving || heroAttrs.isStun() || heroAttrs.isFrost()) return;
heroAttrs.updateCD(this.dt);
heroView.cd_show();
const castPlan = this.pickCastSkill(heroAttrs, heroView);
if (!this.hasCastTarget(castPlan)) return;
this.castSkill(castPlan, heroAttrs, heroView);