Files
pixelheros/assets/script/game/hero/HeroViewComp.ts
panw 0a8f2056c3 refactor(game): 优化属性卡牌配置与UI更新逻辑
- 将AttrSet中的`note`字段统一重命名为`name`,提高字段语义清晰度
- 在CardSet中修正属性卡和药水卡名称的获取逻辑,使用新的`name`字段
- 调整HeroViewComp的health方法,增加血量变化小于等于99时不显示特效的优化
- 将HeroAttrsSystem中的自然回复计时器从1秒改为5秒,并更新相关注释
- 移除HeroViewComp中已注释的冗余UI更新代码,保持代码整洁
2026-02-04 16:23:09 +08:00

574 lines
20 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 { 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";
import { HeroSpine } from "./HeroSpine";
import { BoxSet, FacSet } from "../common/config/GameSet";
import { smc } from "../common/SingletonModuleComp";
import { EAnmConf, SkillSet,} from "../common/config/SkillSet";
import { oops } from "db://oops-framework/core/Oops";
import { GameEvent } from "../common/config/GameEvent";
import { TooltipTypes } from "../common/config/GameSet";
import { Attrs, } from "../common/config/HeroAttrs";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { Tooltip } from "../skill/Tooltip";
import { timedCom } from "../skill/timedCom";
import { HeroInfo, HType } from "../common/config/heroSet";
import { Timer } from "db://oops-framework/core/common/timer/Timer";
const { ccclass, property } = _decorator;
/** 角色显示组件 */
export interface BuffInfo {
value: number;
remainTime?: number;
}
@ccclass('HeroViewComp') // 定义Cocos Creator 组件
@ecs.register('HeroView', false) // 定义ECS 组件
export class HeroViewComp extends CCComp {
@property({ tooltip: "是否启用调试日志" })
private debugMode: boolean = false; // 是否启用调试模式
// ==================== View 层属性(表现相关)====================
as: HeroSpine = null!
status:String = "idle"
scale: number = 1; // 显示方向
box_group:number = BoxSet.HERO; // 碰撞组
realDeadTime:number=2
deadCD:number=0
monDeadTime:number=0.5
// 血条显示相关
lastBarUpdateTime:number = 0; // 最后一次血条/蓝条/护盾更新时间
// ==================== UI 节点引用 ====================
private top_node: Node = null!;
// ==================== 直接访问 HeroAttrsComp ====================
get model() {
// 🔥 修复添加安全检查防止ent为null时的访问异常
if (!this.ent) {
mLogger.warn(this.debugMode, 'HeroViewComp', "[HeroViewComp] ent is null, returning null for model");
return null;
}
return this.ent.get(HeroAttrsComp);
}
private damageQueue: Array<{
damage: number,
isCrit: boolean,
delay: number,
anm:string,
}> = [];
private isProcessingDamage: boolean = false;
private damageInterval: number = 0.01; // 伤害数字显示间隔
onLoad() {
this.as = this.getComponent(HeroSpine);
const collider = this.node.getComponent(BoxCollider2D);
this.scheduleOnce(()=>{
if (collider) {
collider.enabled = true; // 先禁
collider.group = this.box_group; // 设置为英雄组
}
},0.1)
// let anm = this.node.getChildByName("anm")
// anm.setScale(anm.scale.x*0.8,anm.scale.y*0.8);
}
/** 视图层逻辑代码分离演示 */
start () {
this.init();
}
/** 初始化/重置视图状态 */
init() {
this.status = "idle";
this.deadCD = 0;
this.lastBarUpdateTime = 0;
this.as.idle()
// 初始化 UI 节点
this.initUINodes();
/** 方向 */
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 = false
this.top_node.getChildByName("shield").active = false;
// 初始隐藏血条(有更新时才显示)
this.top_node.active = false;
// 🔥 重置血条 UI 显示状态
if (this.model) {
this.hp_show();
}
}
/** 初始化 UI 节点引用 */
private initUINodes() {
this.top_node = this.node.getChildByName("top");
let hp_y = this.node.getComponent(UITransform).height+10;
this.top_node.setPosition(0, hp_y, 0);
smc.updateHeroInfo(this.model)
}
/**
* View 层每帧更新
* 注意:数据更新逻辑已移到 HeroAttrSystem这里只负责显示
*/
update(dt: number){
if(!smc.mission.play ) return;
if(smc.mission.pause) return
// 🔥 修复添加安全检查防止在实体销毁过程中访问null的model
if(!this.ent) return;
if (!this.model) return;
if(this.model.is_dead){
this.deadCD+=dt
if(this.deadCD>=this.realDeadTime){
this.deadCD=0
this.realDead()
}
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(); // 伤害数字显示队列
// ✅ 按需更新 UI脏标签模式- 只在属性变化时更新
if (this.model.dirty_hp) {
this.hp_show();
this.model.dirty_hp = false;
}
// if (this.model.dirty_mp) {
// this.mp_show();
// this.model.dirty_mp = false;
// }
if (this.model.dirty_shield) {
this.show_shield(this.model.shield, this.model.Attrs[Attrs.SHIELD_MAX]);
this.model.dirty_shield = false;
}
}
/** 显示护盾 */
private show_shield(shield: number = 0, shield_max: number = 0) {
this.lastBarUpdateTime = Date.now() / 1000;
if(!this.top_node.active) return
let shield_progress = shield / shield_max;
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);
}
/** 显示血量 */
private hp_show() {
this.lastBarUpdateTime = Date.now() / 1000;
// 不再基于血量是否满来决定显示状态,只更新进度条
let hp=this.model.hp;
let hp_max=this.model.Attrs[Attrs.HP_MAX];
// mLogger.log(this.debugMode, 'HeroViewComp', "hp_show",hp,hp_max)
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
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 mp_show() {
this.lastBarUpdateTime = Date.now() / 1000;
if(!this.top_node.active) return
let mp=this.model.mp;
let mp_max=this.model.Attrs[Attrs.MP_MAX];
mLogger.log(this.debugMode, 'HeroViewComp', "mp_show",mp,mp_max)
this.top_node.getChildByName("mp").getComponent(ProgressBar).progress = mp / mp_max;
this.scheduleOnce(() => {
this.top_node.getChildByName("mp").getChildByName("mpb").getComponent(ProgressBar).progress = mp / mp_max;
}, 0.15);
}
/** 升级特效 */
private lv_up() {
var path = "game/skill/buff/buff_lvup";
var prefab: Prefab = oops.res.get(path, Prefab)!;
var node = instantiate(prefab);
node.parent = this.node;
}
/** 攻击力提升特效 */
private ap_up() {
var path = "game/skill/buff/buff_apup";
var prefab: Prefab = oops.res.get(path, Prefab)!;
var node = instantiate(prefab);
node.parent = this.node;
}
/** 显示 Buff 特效 */
private show_do_buff(name: string) {
var path = "game/skill/buff/" + name;
var prefab: Prefab = oops.res.get(path, Prefab)!;
var node = instantiate(prefab);
let pos = v3(this.node.position.x, this.node.position.y + 20, this.node.position.z);
node.parent = this.node.parent;
node.setPosition(pos);
}
/** 受击特效 */
private in_atked(anm: string = "atked", scale: number = 1) {
this.as.do_atked()
// var path = "game/skill/end/" + anm;
// var prefab: Prefab = oops.res.get(path, Prefab)!;
// var node = instantiate(prefab);
// node.setScale(node.scale.x * scale, node.scale.y);
// node.setPosition(this.node.position.x, this.node.position.y+50, this.node.position.z);
// node.parent = this.node.parent;
}
/** 冰冻特效 */
private in_iced(t: number = 1, ap: number = 0) {
var path = "game/skill/buff/buff_iced";
var prefab: Prefab = oops.res.get(path, Prefab)!;
var node = instantiate(prefab);
node.getComponent(timedCom).time = t;
node.getComponent(timedCom).ap = ap;
node.parent = this.node;
}
/** 眩晕特效 */
private in_yun(t: number = 1, ap: number = 0) {
var path = "game/skill/buff/buff_yun";
var prefab: Prefab = oops.res.get(path, Prefab)!;
var node = instantiate(prefab);
let height = this.node.getComponent(UITransform).height;
node.setPosition(v3(0, height));
node.getComponent(timedCom).time = t;
node.getComponent(timedCom).ap = ap;
node.parent = this.node;
}
/** 技能提示 */
private tooltip(type: number = 1, value: string = "", s_uuid: number = 1001, y: number = 50) {
let pos = v3(0, 60);
pos.y = pos.y + y;
Tooltip.load(pos, type, value, s_uuid, this.node);
}
/** 血量提示(伤害数字) */
private hp_tip(type: number = 1, value: string = "", s_uuid: number = 1001, y: number = 0) {
let x = this.node.position.x;
// 获取怪物高度的一半,定位到中心点
let halfHeight = 0;
const transform = this.node.getComponent(UITransform);
if (transform) {
halfHeight = transform.height / 2;
}
// 起点设为怪物中心位置 + 20偏移
let ny = this.node.position.y + halfHeight + 20;
let pos = v3(x, ny, 0);
Tooltip.load(pos, type, value, s_uuid, this.node.parent);
}
/** 护盾吸收提示 */
shield_tip(absorbed: number) {
this.hp_tip(TooltipTypes.life, absorbed.toFixed(0));
}
/** 治疗特效 */
private heathed() {
var path = "game/skill/buff/heathed";
var prefab: Prefab = oops.res.get(path, Prefab)!;
var node = instantiate(prefab);
node.parent = this.node;
}
// 注意BaseUp 逻辑已移到 HeroAttrSystem.update()
// 注意updateTemporaryBuffsDebuffs 逻辑已移到 HeroAttrSystem.update()
get isActive() {
return this.ent.has(HeroViewComp) && this.node?.isValid;
}
/** 状态切换(动画) */
status_change(type:string){
if(this.status === type) return;
this.status = type;
if(this.model.is_dead || this.model.is_reviving) return
if(type === "idle"){
this.as.idle();
} else if(type === "move"){
this.as.move();
}
}
add_shield(shield:number){
// 护盾数据更新由 Model 层处理,这里只负责视图表现
if(this.model && this.model.shield>0) this.show_shield(this.model.shield, this.model.Attrs[Attrs.SHIELD_MAX]);
}
health(hp: number = 0) {
// ✅ 仅显示特效和提示,不调用 hp_show()
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;
}
alive(){
// 重置复活标记 - 必须最先重置否则status_change会被拦截
this.model.is_reviving = false;
this.model.is_dead=false
this.model.is_count_dead=false
this.as.do_buff();
this.status_change("idle");
this.model.hp =this.model.Attrs[Attrs.HP_MAX]*50/100;
this.top_node.active=false
this.lastBarUpdateTime=0
// 恢复怪物行动
if (this.model.is_master) {
smc.mission.stop_mon_action = false;
mLogger.log(this.debugMode, 'HeroViewComp', "[HeroViewComp] Hero revived, resuming monster action");
}
}
/**
* 调度复活逻辑
* @param delay 延迟时间(秒)
*/
scheduleRevive(delay: number) {
this.scheduleOnce(() => {
this.alive();
}, delay);
}
/**
* 死亡视图表现
* 由 HeroAtkSystem 调用,只负责视觉效果和事件通知
*/
do_dead(){
// 添加安全检查
if (!this.model) return;
// 防止重复触发
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){
this.realDeadTime = this.monDeadTime;
}
// 播放死亡特效
this.as.dead();
}
realDead(){
// 🔥 修复添加model安全检查防止实体销毁过程中的空指针异常
if (!this.model) {
mLogger.warn(this.debugMode, 'HeroViewComp', "[HeroViewComp] realDead called but model is null, skipping");
return;
}
if(this.model.fac === FacSet.HERO){
// 英雄死亡:延迟触发死亡事件
// 🔥 只有主角死亡才触发游戏结束判定
if (this.model.is_master) return
}
// 🔥 方案B治理性措施 - 在销毁实体前先禁用碰撞体,从源头减少"尸体"参与碰撞
const collider = this.getComponent(Collider2D);
if (collider) {
collider.enabled = false;
}
// 根据阵营触发不同事件
if(this.model.fac === FacSet.MON){
oops.message.dispatchEvent(GameEvent.MonDead, {
uuid: this.model.hero_uuid,
lv: this.model.lv,
is_boss: this.model.is_boss,
is_elite: this.model.is_big_boss, // 暂时映射 is_big_boss 为 elite或者由 MissionComp 二次判断
position: this.node.position
});
}
this.ent.destroy();
}
do_atked(damage:number,isCrit:boolean,s_uuid:number,isBack:boolean=false){
// 受到攻击时显示血条,并更新最后更新时间
this.top_node.active = true;
this.lastBarUpdateTime = Date.now() / 1000;
if (damage <= 0) return;
// 视图层表现
let SConf=SkillSet[s_uuid]
if (isBack) this.back()
this.showDamage(damage, isCrit, SConf.DAnm);
}
private isBackingUp: boolean = false; // 🔥 添加后退状态标记
//后退
back(){
// 🔥 防止重复调用后退动画
if (this.isBackingUp) return;
this.isBackingUp = true; // 🔥 设置后退状态
if(this.model.fac==FacSet.MON) {
let tx=this.node.position.x+5
if(tx > 320) tx=320
tween(this.node)
.to(0.1, { position:v3(tx,this.node.position.y,0)})
.call(() => {
this.isBackingUp = false; // 🔥 动画完成后重置状态
})
.start()
}
if(this.model.fac==FacSet.HERO) {
let tx=this.node.position.x-5
if(tx < -320) tx=-320
tween(this.node)
.to(0.1, { position:v3(tx,this.node.position.y,0)})
.call(() => {
this.isBackingUp = false; // 🔥 动画完成后重置状态
})
.start()
}
}
// 伤害计算和战斗逻辑已迁移到 HeroBattleSystem
playSkillEffect(skill_id:number) {
let skill = SkillSet[skill_id]
mLogger.log(this.debugMode, 'HeroViewComp', '[heroview] skill_id'+skill_id,skill)
if (!skill) return;
switch(skill.act){
case "max":
this.as.max()
break
case "atk":
this.as.atk()
break
case "buff":
this.as.buff()
break
}
}
/** 显示伤害数字 */
showDamage(damage: number, isCrit: boolean,DAnm:number) {
let anm=EAnmConf[DAnm].path // DAnm和EAnm共用设定数组
this.damageQueue.push({
damage,
isCrit,
delay: this.damageInterval,
anm
});
}
/** 处理伤害队列 */
private processDamageQueue() {
if (this.isProcessingDamage || this.damageQueue.length === 0) return;
this.isProcessingDamage = true;
const damageInfo = this.damageQueue.shift()!;
this.showDamageImmediate(damageInfo.damage, damageInfo.isCrit,damageInfo.anm);
// 设置延时处理下一个伤
this.scheduleOnce(() => {
this.isProcessingDamage = false;
}, this.damageInterval);
}
/** 立即显示伤害效果 */
private showDamageImmediate(damage: number, isCrit: boolean, anm:string="atked") {
if (!this.model) return;
this.hp_show();
this.in_atked(anm, this.model.fac==FacSet.HERO?1:-1);
if (isCrit) {
this.hp_tip(TooltipTypes.crit, damage.toFixed(0));
} else {
this.hp_tip(TooltipTypes.life, damage.toFixed(0));
}
}
reset() {
// 清理残留的定时器和缓动
this.unscheduleAllCallbacks();
Tween.stopAllByTarget(this.node);
// 清理碰撞器事件监听
const collider = this.getComponent(Collider2D);
if (collider) {
collider.off(Contact2DType.BEGIN_CONTACT);
}
this.deadCD=0
this.lastBarUpdateTime=0
// 清理伤害队列
this.damageQueue.length = 0;
this.isProcessingDamage = false;
// 节点生命周期由 Monster 对象池管理,此处不再销毁
// if (this.node && this.node.isValid) {
// this.node.destroy();
// }
}
}