1. 调整近战默认攻击距离从150改为100 2. 将击退相关属性从概率改为击退距离增量,同步更新所有配置、UI显示和技能描述 3. 移除旧的击退概率判定逻辑,改为直接应用击退距离增量 4. 优化受击击退的处理流程,统一参数传递
247 lines
10 KiB
TypeScript
247 lines
10 KiB
TypeScript
/**
|
||
* @file HeroMoreCom.ts
|
||
* @description 英雄信息弹窗组件(UI 视图层)
|
||
*
|
||
* 职责:
|
||
* 1. 展示单个英雄的次级属性(攻速/击晕/暴击/爆伤/冰冻/击退/穿透/风怒)与技能描述,
|
||
* 展示内容由原 HeroBoxComp 分离而来,计算口径与其完全一致。
|
||
* 2. 由 HeroBoxComp 长按英雄盒时同步实例化(instantiate more.prefab)并调用 init(eid)。
|
||
* 3. 属性受 buff/光环影响实时变化,打开期间按固定间隔降频刷新;英雄死亡/被卖出自动关闭。
|
||
* 4. 点击弹窗内任意位置(含全屏透明遮罩)或 HeroBoxComp 放开手指时由调用方 destroy。
|
||
*
|
||
* 依赖:
|
||
* - HeroAttrsComp —— 英雄属性数据模型
|
||
* - HeroSkillDesc —— 技能描述富文本标准层
|
||
*/
|
||
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 { buildSkillDescMoreRich } from "../common/config/HeroSkillDesc";
|
||
|
||
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);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 初始化并渲染英雄信息(由 HeroBoxComp 在 instantiate 后同步调用)。
|
||
* @param eid 英雄 ECS 实体 ID;实体不存在时立即销毁
|
||
*/
|
||
public init(eid: number) {
|
||
this.refreshElapsed = 0;
|
||
this.skillDescCache = "";
|
||
|
||
if (!eid) {
|
||
this.closeSelf();
|
||
return;
|
||
}
|
||
// 按 eid 查找英雄实体(与 HInfoComp 口径一致)
|
||
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 行次级属性(总值 + 附加值)+ 技能描述富文本。
|
||
* 附加 = 最终生效值 - 基础配置值,与原 HeroBoxComp 口径一致。
|
||
*/
|
||
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.crit_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 = buildSkillDescMoreRich(this.model);
|
||
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 = "";
|
||
}
|
||
}
|
||
}
|
||
|
||
// ======================== 交互 ========================
|
||
|
||
/** 点击面板任意处(含遮罩)关闭(备用交互,主路径由 HeroBoxComp 放开手指 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();
|
||
}
|
||
}
|
||
}
|