refactor(ui&logic): 调整UI边距与英雄属性计算逻辑

1.  修改ui3.plist.meta里两个UI元素的border边距配置
2.  重构HeroBoxComp的属性增量计算逻辑,修正属性加成只统计临时增益而非等级成长
3.  优化HeroSkillDesc的富文本样式,新增数值描边效果统一UI视觉风格
This commit is contained in:
pan
2026-08-11 10:52:23 +08:00
parent 4eba1c8ad8
commit de763efb0f
4 changed files with 591 additions and 615 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -5958,10 +5958,10 @@
"height": 61,
"rawWidth": 54,
"rawHeight": 61,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"borderTop": 28,
"borderBottom": 33,
"borderLeft": 27,
"borderRight": 27,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
@@ -6006,8 +6006,8 @@
"rawHeight": 24,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"borderLeft": 25,
"borderRight": 25,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,

View File

@@ -1,33 +1,33 @@
/**
* @file HeroSkillDesc.ts
* @description 英雄技能/能力描述的【唯一文字表达标准层】
*
* 设计目标:
* heroSet / SkillSet 中的 info / infos 仅作"基础备注",不再承担面板展示。
* 所有功能性描述(触发技能、驻场光环、复活、等级额外属性加成)统一由本模块
* 依据结构化配置动态拼出,数值永远来自 mergeSkillParams 合并后的实时配置,
* 杜绝"改数值忘改文案"的漂移。
*
* 输出(双格式,供 UI 按需取用):
* - buildSkillDesc(source) → 纯文本Label 用),数值无颜色
* - buildSkillDescRich(source) → 富文本RichText 用BBCode技能名蓝色、数值绿色高亮
* 两者共用同一套结构化行构建逻辑,仅渲染阶段区分,保证纯/富文本内容严格一致。
*
* 通用性(纯配置驱动):
* 1. 触发框架按触发类型遍历SkillSet + overrides不动
* 2. 效果文案:查 SkillDescSet 结构化模板渲染
* 3. 目标/属性名由本模块统一语义化target/hit_count、ATTR_NAME
*
* 数据源(入参抽象 ISkillDescSource
* heroInfo静态 1 级配置)与 HeroAttrsComp运行时应用 evolve 后的当前等级配置)
* 字段结构一致天然都满足。HeroBoxComp 传 model 显示当前等级;卡池/商店传 heroInfo 显示 1 级。
*
* 依赖:
* - heroSet :SkillTriggerName、SkillTriggerType、TriggerGrouped/LvFieldEntry/LvReviveEntry/LvBonusEntry 及 resolver
* - SkillSet :SkillSet、mergeSkillParams、SkillOverrides、SkillKind、TGroup
* - SkillDescSet:结构化描述模板
* - FieldSkillSet驻场技能、BuffList计时 buff 默认值兜底、Attrs属性枚举
*/
/**
* @file HeroSkillDesc.ts
* @description 英雄技能/能力描述的【唯一文字表达标准层】
*
* 设计目标:
* heroSet / SkillSet 中的 info / infos 仅作"基础备注",不再承担面板展示。
* 所有功能性描述(触发技能、驻场光环、复活、等级额外属性加成)统一由本模块
* 依据结构化配置动态拼出,数值永远来自 mergeSkillParams 合并后的实时配置,
* 杜绝"改数值忘改文案"的漂移。
*
* 输出(双格式,供 UI 按需取用):
* - buildSkillDesc(source) → 纯文本Label 用),数值无颜色
* - buildSkillDescRich(source) → 富文本RichText 用BBCode技能名蓝色、数值绿色高亮
* 两者共用同一套结构化行构建逻辑,仅渲染阶段区分,保证纯/富文本内容严格一致。
*
* 通用性(纯配置驱动):
* 1. 触发框架按触发类型遍历SkillSet + overrides不动
* 2. 效果文案:查 SkillDescSet 结构化模板渲染
* 3. 目标/属性名由本模块统一语义化target/hit_count、ATTR_NAME
*
* 数据源(入参抽象 ISkillDescSource
* heroInfo静态 1 级配置)与 HeroAttrsComp运行时应用 evolve 后的当前等级配置)
* 字段结构一致天然都满足。HeroBoxComp 传 model 显示当前等级;卡池/商店传 heroInfo 显示 1 级。
*
* 依赖:
* - heroSet :SkillTriggerName、SkillTriggerType、TriggerGrouped/LvFieldEntry/LvReviveEntry/LvBonusEntry 及 resolver
* - SkillSet :SkillSet、mergeSkillParams、SkillOverrides、SkillKind、TGroup
* - SkillDescSet:结构化描述模板
* - FieldSkillSet驻场技能、BuffList计时 buff 默认值兜底、Attrs属性枚举
*/
import { SkillTriggerName, SkillTriggerType, TriggerGrouped, LvTriggerEntry, LvFieldEntry, LvReviveEntry, LvBonusEntry, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv, getTriggerSkillName, HGrowType, calcGrowBonus } from "./heroSet";
import { FieldSkillSet, mergeSkillParams, SkillConfig, SkillKind, SkillOverrides, SkillSet, TGroup } from "./SkillSet";
import { FieldDescSet, FieldValueFmt, SkillDescSet } from "./SkillDescSet";
@@ -99,6 +99,18 @@ const ATTR_NAME: Partial<Record<Attrs, string>> = {
/** 富文本数值高亮色(鲜亮绿色,深色面板上高亮醒目) */
const RICH_VALUE_COLOR = "#3CE66E";
/** 数值描边色(深绿,衬托亮绿填充,与技能名橙填充+深褐描边同一设计思路) */
const RICH_VALUE_OUTLINE = "#145A32";
/**
* 数值富文本片段:亮绿填充 + 深绿描边。
* 高亮数值明度高,深色描边增强边缘对比、提升深色面板上的锐度。
* @param text 数值文本(含单位,如 "15%"、"4次"
*/
function valueRich(text: string | number): string {
return `<outline color=${RICH_VALUE_OUTLINE} width=1><color=${RICH_VALUE_COLOR}>${text}</color></outline>`;
}
/** 富文本技能名高亮色(蜜桃橙,暖色系,与面板绿/蓝/红/黄/紫均不冲突) */
const RICH_NAME_COLOR = "#F8C471";
@@ -498,14 +510,14 @@ export function buildSingleSkillRich(skillUuid: number, overrides?: SkillOverrid
const skill = mergeSkillParams(base, overrides);
// SkillConfig 未定义 cd 字段,冷却时间由外部运行时数据传入;计时模式展示持续时间
const dur = skill.buff_duration ?? (skill.timed_buff_id !== undefined ? BuffList[skill.timed_buff_id]?.duration : undefined) ?? 0;
const cdTag = dur > 0 ? `<color=${RICH_VALUE_COLOR}>持续:${dur}s</color>` : "";
const cdTag = dur > 0 ? valueRich(`持续:${dur}s`) : "";
const nameTag = `${skillNameRich(skill.name)} Lv.${lv}`;
// 复用结构化效果行head 为空避免重复触发类型前缀
const line = buildEffectLine("", skill);
const prefix = line.prefix.replace(SKILL_NAME_PATTERN, (m, g1) => skillNameRich(g1));
const effect = line.value
? `${prefix}<color=${RICH_VALUE_COLOR}>${line.value}</color>${line.suffix}`
? `${prefix}${valueRich(line.value)}${line.suffix}`
: `${prefix}${line.suffix}`;
return cdTag ? `${nameTag} ${cdTag}\n${effect}` : `${nameTag}\n${effect}`;
@@ -525,7 +537,7 @@ export function buildEquipRich(fieldUuids: number[], showName: boolean = false):
if (!line) continue;
const prefix = line.prefix.replace(SKILL_NAME_PATTERN, (m, g1) => skillNameRich(g1));
const text = line.value
? `${prefix}<color=${RICH_VALUE_COLOR}>${line.value}</color>${line.suffix}`
? `${prefix}${valueRich(line.value)}${line.suffix}`
: `${prefix}${line.suffix}`;
lines.push(text);
}
@@ -553,7 +565,7 @@ export function buildSkillDescMoreRich(source: ISkillDescSource): string {
const effectRich = (line: ISkillLine): string => {
const prefix = line.prefix.replace(SKILL_NAME_PATTERN, (m, g1) => skillNameRich(g1));
const text = line.value
? `${prefix}<color=${RICH_VALUE_COLOR}>${line.value}</color>${line.suffix}`
? `${prefix}${valueRich(line.value)}${line.suffix}`
: `${prefix}${line.suffix}`;
return highlightDuration(text);
};
@@ -584,7 +596,7 @@ export function buildSkillDescMoreRich(source: ISkillDescSource): string {
const curSLv = cur.s_lv ?? (curIdx + 1);
// 触发次数直接写为富文本(数字绿色高亮),如 "受伤3次"
const curTrig = needCount
? `${trigName}<color=${RICH_VALUE_COLOR}>${cur.t_num}</color>`
? `${trigName}${valueRich(cur.t_num)}`
: trigName;
// 技能名 + 档位数字后缀(如 "不屈壁垒1"),与光环命名口径一致
const curHead = `${skillName}${curSLv}」: ${curTrig}, `;
@@ -608,7 +620,7 @@ export function buildSkillDescMoreRich(source: ISkillDescSource): string {
const revive = resolveReviveByLv(source[SkillTriggerType.Revive] as LvReviveEntry[] | undefined, lv);
if (revive) {
const reviveName = getTriggerSkillName(SkillTriggerType.Revive, revive.s_uuid);
lines.push(`${skillNameRich(reviveName)}: 每回合拥有<color=${RICH_VALUE_COLOR}>${revive.r_num}</color>次重生机会, 死亡后以<color=${RICH_VALUE_COLOR}>${Math.round(revive.upr * 100)}%</color>血量原地重生`);
lines.push(`${skillNameRich(reviveName)}: 每回合拥有${valueRich(revive.r_num)}次重生机会, 死亡后以${valueRich(`${Math.round(revive.upr * 100)}%`)}血量原地重生`);
}
return lines.join("\n");
@@ -620,7 +632,7 @@ export function buildSkillDescMoreRich(source: ISkillDescSource): string {
* 持续时间对药水/计时 buff 同样关键,这里补充高亮,保证视觉一致。
*/
function highlightDuration(text: string): string {
return text.replace(DURATION_PATTERN, `持续<color=${RICH_VALUE_COLOR}>$1</color>`);
return text.replace(DURATION_PATTERN, `持续${valueRich("$1")}`);
}
/**
@@ -634,7 +646,7 @@ function renderLineRich(line: ISkillLine): string {
// 未来档数值灰色追加在当前高亮数值后("15%/20%/30%"suffix 仍位于整段数值之后
const next = line.nextValues?.length ? `<color=${RICH_LOCKED_COLOR}>/${line.nextValues.join("/")}</color>` : "";
return line.value
? `${prefix}<color=${RICH_VALUE_COLOR}>${line.value}</color>${next}${line.suffix}`
? `${prefix}${valueRich(line.value)}${next}${line.suffix}`
: `${prefix}${next}${line.suffix}`;
}
@@ -646,7 +658,7 @@ function renderLineRich(line: ISkillLine): string {
function buildChanceSuffix(skill: SkillConfig): string {
const items: string[] = [];
const push = (val: number | undefined, label: string) => {
if (val && val > 0) items.push(`附加<color=${RICH_VALUE_COLOR}>${val}</color>%${label}`);
if (val && val > 0) items.push(`附加${valueRich(val)}%${label}`);
};
push(skill.crt, "暴击");
push(skill.frz, "冰冻");
@@ -699,8 +711,8 @@ export function buildCardDescRich(card: CardConfig): string {
return rich;
}
const rhythm = `<color=${RICH_VALUE_COLOR}>${tInv}</color>秒召唤<color=${RICH_VALUE_COLOR}>${(card.overrides?.num ?? 0) + 1}</color>${skillNameRich(skill.name)}攻击敌人`;
const times = `, 共<color=${RICH_VALUE_COLOR}>${tTimes}</color>`;
const rhythm = `${valueRich(tInv)}秒召唤${valueRich((card.overrides?.num ?? 0) + 1)}${skillNameRich(skill.name)}攻击敌人`;
const times = `, 共${valueRich(tTimes)}`;
return `${rhythm}, ${rich}${times}`;
}

View File

@@ -557,13 +557,19 @@ export class HeroBoxComp extends CCComp {
}
// ---- AP / HP ----
// plus附加值= 最终值 - 当前等级基础值;基础值 = 1 级原始 base + 等级成长 + ap/hp_bonus 一次性奖励。
// Why: base_ap/base_hp 是不含任何等级增长的原始值,直接用 final-base 会把升级带来的
// 属性增长也计入增量。这里补齐等级增长作为基准,使 plus 仅反映 buff/光环等临时增益。
const lv = Math.max(1, Math.floor(this.model.lv ?? 1));
const grow = hero ? calcGrowBonus(hero, lv) : { ap: 0, hp: 0 };
const finalAp = Math.max(0, Math.floor(this.model.getFinalAp()));
const baseAp = Math.max(0, Math.floor(this.model.base_ap ?? 0));
this.setStatRow(this.ap_all_label, this.ap_plus_label, finalAp, finalAp - baseAp);
const lvBaseAp = Math.max(0, Math.floor((this.model.base_ap ?? 0) + grow.ap + HeroBoxComp.sumBonus(hero?.ap_bonus, lv)));
this.setStatRow(this.ap_all_label, this.ap_plus_label, finalAp, finalAp - lvBaseAp);
const finalHp = Math.max(0, Math.floor(this.model.getFinalHpMax()));
const baseHp = Math.max(0, Math.floor(this.model.base_hp ?? 0));
this.setStatRow(this.hp_all_label, this.hp_plus_label, finalHp, finalHp - baseHp);
const lvBaseHp = Math.max(0, Math.floor((this.model.base_hp ?? 0) + grow.hp + HeroBoxComp.sumBonus(hero?.hp_bonus, lv)));
this.setStatRow(this.hp_all_label, this.hp_plus_label, finalHp, finalHp - lvBaseHp);
// ---- 图标UUID 变化时重新加载首帧动画) ----
if (hero && heroUuid !== this.iconHeroUuid) {