feat(英雄): 优化血条和冷却UI表现
- 移除血条2秒自动隐藏逻辑,改为常显并根据血量状态调整透明度 - 新增血条受击抖动效果,提升打击感 - 增加技能冷却进度条显示功能 - 统一血条激活状态管理,通过透明度变化区分活跃/空闲状态 - 修复复活后血条显示异常问题
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user