Files
pixelheros/assets/script/game/map/HeroMoreCom.ts
panFD f0063525da refactor(map): 移除废弃HeroBoxComp相关代码与引用
feat: 新增5名英雄配置数据与服务端配置
style: 更新HeroMoreCom注释与刷新逻辑说明
2026-09-08 08:36:33 +08:00

247 lines
10 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.
/**
* @file HeroMoreCom.ts
* @description 英雄信息弹窗组件(UI 视图层)
*
* 职责:
* 1. 展示单个英雄的次级属性(攻速/击晕/暴击/爆伤/冰冻/击退/穿透/风怒)与技能描述。
* 2. 由调用方同步实例化(instantiate more.prefab)并调用 init(eid)。
* 3. 属性受 buff/光环影响实时变化,打开期间按固定间隔降频刷新;英雄死亡/被卖出自动关闭。
* 4. 点击弹窗内任意位置(含全屏透明遮罩)时由调用方 destroy。
*
* 依赖:
* - HeroAttrsComp —— 英雄属性数据模型
* - HeroSkillText —— 技能描述富文本标准层(局外等级口径)
*/
import { _decorator, Component, Label, NodeEventType, RichText } from "cc";
import { oops } from "db://oops-framework/core/Oops";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { buildHeroSkillDesc } from "../common/HeroSkillText";
const { ccclass, property } = _decorator;
/**
* HeroMoreCom —— 英雄信息弹窗(more.prefab 根节点组件)
*
* 与长按的英雄盒一一绑定(eid),英雄移除后自动关闭。
* 序列化引用(8 行属性 Label + 技能 RichText)直接写在 more.prefab 中。
*/
@ccclass('HeroMoreCom')
export class HeroMoreCom extends Component {
// ======================== 编辑器绑定节点(more.prefab attr_more 下 8 行) ========================
/** 攻速(攻击间隔,秒)总值 */
@property(Label)
private speed_all_label: Label = null;
/** 攻速附加值 */
@property(Label)
private speed_plus_label: Label = null;
/** 击晕率总值(百分点) */
@property(Label)
private stun_all_label: Label = null;
/** 击晕率附加值 */
@property(Label)
private stun_plus_label: Label = null;
/** 暴击率总值(百分点) */
@property(Label)
private crt_all_label: Label = null;
/** 暴击率附加值 */
@property(Label)
private crt_plus_label: Label = null;
/** 暴击伤害总值(额外暴伤,百分点) */
@property(Label)
private crit_dmg_all_label: Label = null;
/** 暴击伤害附加值 */
@property(Label)
private crit_dmg_plus_label: Label = null;
/** 冰冻率总值(百分点) */
@property(Label)
private freeze_all_label: Label = null;
/** 冰冻率附加值 */
@property(Label)
private freeze_plus_label: Label = null;
/** 击退强化总值(击退距离增量) */
@property(Label)
private knockback_all_label: Label = null;
/** 击退强化附加值 */
@property(Label)
private knockback_plus_label: Label = null;
/** 穿透率总值(百分点) */
@property(Label)
private puncture_all_label: Label = null;
/** 穿透率附加值 */
@property(Label)
private puncture_plus_label: Label = null;
// 风怒机制已停用: 风怒率展示字段已停用
// /** 风怒率总值(百分点) */
// @property(Label)
// private wfuny_all_label: Label = null;
// /** 风怒率附加值 */
// @property(Label)
// private wfuny_plus_label: Label = null;
@property(RichText)
private skill_label: RichText = null;
// ======================== 运行时状态 ========================
/** 绑定的英雄属性数据模型引用 */
private model: HeroAttrsComp | null = null;
/** 是否正在关闭(防止重复调用 remove) */
private isClosing: boolean = false;
/** 刷新计时累计(降频刷新实时属性) */
private refreshElapsed: number = 0;
/** 实时刷新间隔(秒) */
private static readonly REFRESH_INTERVAL = 0.25;
/** 技能描述富文本内容缓存(避免降频内重复重解析排版) */
private skillDescCache: string = "";
// ======================== 生命周期 ========================
onLoad() {
// 全屏透明遮罩为根节点子节点,点击任意处触摸事件冒泡至此 → 关闭弹窗(备用交互)
this.node.on(NodeEventType.TOUCH_END, this.onTouchEndClose, this);
}
onDestroy() {
if (this.node && this.node.isValid) {
this.node.off(NodeEventType.TOUCH_END, this.onTouchEndClose, this);
}
}
/**
* 初始化并渲染英雄信息(由调用方在 instantiate 后同步调用)。
* @param eid 英雄 ECS 实体 ID;实体不存在时立即销毁
*/
public init(eid: number) {
this.refreshElapsed = 0;
this.skillDescCache = "";
if (!eid) {
this.closeSelf();
return;
}
// 按 eid 查找英雄实体
let foundModel: HeroAttrsComp | null = null;
ecs.query(ecs.allOf(HeroAttrsComp)).forEach((entity: ecs.Entity) => {
if (entity.eid === eid) {
foundModel = entity.get(HeroAttrsComp);
}
});
if (!foundModel) {
this.closeSelf();
return;
}
this.model = foundModel;
this.refresh();
}
/**
* 帧更新:降频刷新实时属性;英雄实体失效(死亡/卖出)时自动关闭。
*/
update(dt: number) {
if (this.isClosing || !this.model) return;
if (!(this.model as any).ent) {
this.closeSelf();
return;
}
this.refreshElapsed += dt;
if (this.refreshElapsed < HeroMoreCom.REFRESH_INTERVAL) return;
this.refreshElapsed = 0;
this.refresh();
}
// ======================== 显示刷新 ========================
/**
* 刷新显示:8 行次级属性(总值 + 附加值)+ 技能描述富文本。
* 附加 = 最终生效值 - 基础配置值。
*/
private refresh() {
if (!this.model) return;
// ---- 攻速(攻击间隔,秒;速度提升 → 间隔变短,附加值为负) ----
const skillIds = this.model.getSkillIds();
const displaySkillId = skillIds[1] ?? skillIds[0] ?? 0;
const baseCd = displaySkillId ? (this.model.skills[displaySkillId]?.cd ?? 0) : 0;
const finalCd = displaySkillId ? this.model.getEffectiveSkillCd(displaySkillId) : 0;
this.setStatRow(this.speed_all_label, this.speed_plus_label, finalCd, finalCd - baseCd, "s", 1);
// ---- 击晕率 / 暴击率 / 暴击伤害(百分点) ----
const finalStun = this.model.getFinalStunChance();
this.setStatRow(this.stun_all_label, this.stun_plus_label, finalStun, finalStun - (this.model.stun_chance ?? 0), "%");
const finalCrit = this.model.getFinalCritical();
this.setStatRow(this.crt_all_label, this.crt_plus_label, finalCrit, finalCrit - (this.model.critical ?? 0), "%");
const finalCritDmg = this.model.getFinalCritDamage();
this.setStatRow(this.crit_dmg_all_label, this.crit_dmg_plus_label, finalCritDmg, finalCritDmg - (this.model.critical_damage ?? 0), "%");
// ---- 冰冻率 / 击退强化 / 穿透率 ----
const finalFreeze = this.model.getFinalFreezeChance();
this.setStatRow(this.freeze_all_label, this.freeze_plus_label, finalFreeze, finalFreeze - (this.model.freeze_chance ?? 0), "%");
// 击退强化已改为击退距离增量,不再按百分比显示
const finalKnockback = this.model.getFinalKnockbackChance();
this.setStatRow(this.knockback_all_label, this.knockback_plus_label, finalKnockback, finalKnockback - (this.model.knockback_chance ?? 0), "");
const finalPuncture = this.model.getFinalPunctureChance();
this.setStatRow(this.puncture_all_label, this.puncture_plus_label, finalPuncture, finalPuncture - (this.model.puncture_chance ?? 0), "%");
// 风怒机制已停用: 风怒率展示行已移除
// const finalWfuny = this.model.getFinalWindFury();
// this.setStatRow(this.wfuny_all_label, this.wfuny_plus_label, finalWfuny, finalWfuny - (this.model.wfuny ?? 0), "%");
// ---- 技能描述(技能名+技能等级 Lv.x,附升级提示 Lv.x→Lv.y 金色预览) ----
if (this.skill_label && this.skill_label.isValid) {
// 缓存未变则跳过重设,避免降频内 RichText 重复重解析排版
const desc = buildHeroSkillDesc(this.model.hero_uuid);
if (desc !== this.skillDescCache) {
this.skillDescCache = desc;
this.skill_label.string = desc;
}
}
}
/**
* 写入一行属性(总值 + 附加值)。
* 总值始终显示(0 不隐藏);附加值为 0 时隐藏,正/负分别显示 (+x) / (x)。
*
* @param allLabel 总值标签(可为空,空时跳过)
* @param plusLabel 附加值标签(可为空)
* @param total 总数值(最终生效值)
* @param bonus 附加数值(最终值 - 基础值)
* @param suffix 数值后缀(如 "%")
* @param decimals 保留小数位数(默认 0,取整)
*/
private setStatRow(allLabel: Label | null, plusLabel: Label | null, total: number, bonus: number, suffix: string = "", decimals: number = 0) {
const fmt = (v: number) => decimals > 0 ? v.toFixed(decimals) : `${Math.floor(v)}`;
if (allLabel && allLabel.isValid) {
allLabel.string = `${fmt(total)}${suffix}`;
}
if (plusLabel && plusLabel.isValid) {
if (bonus !== 0) {
plusLabel.string = bonus > 0 ? `(+${fmt(bonus)}${suffix})` : `(${fmt(bonus)}${suffix})`;
} else {
plusLabel.string = "";
}
}
}
// ======================== 交互 ========================
/** 点击面板任意处(含遮罩)关闭(备用交互,主路径由调用方 destroy) */
private onTouchEndClose() {
oops.audio.playEffect("music/button");
this.closeSelf();
}
/** 关闭弹窗(同步销毁节点) */
private closeSelf() {
if (this.isClosing) return;
this.isClosing = true;
if (this.node && this.node.isValid) {
this.node.destroy();
}
}
}