feat(hero-ui): 新增普攻lv2强化档独立展示功能

实现英雄图鉴中普攻lv2强化档的单独展示逻辑:
1. 为技能档位渲染添加minLv参数,支持裁剪低档位
2. 新增普攻lv2专属模式,图标/名称取lv1,弹窗仅展示lv2+档位
3. 自动为符合条件的普攻强化档生成独立技能图标
4. 适配等级显示偏移,配置Lv2显示为Lv1
This commit is contained in:
pan
2026-09-04 15:25:35 +08:00
parent c48d497bd8
commit 22e91fc99d
4 changed files with 68 additions and 22 deletions

View File

@@ -542,9 +542,11 @@ function buildTierLines(tiers: ISkillTierInfo[], triggerLabel: string): HeroSkil
* @param heroUuid 英雄 uuid(HeroInfo key) * @param heroUuid 英雄 uuid(HeroInfo key)
* @param skillUuid 技能 uuid(skills key / 触发组 s_uuid / FieldSkillSet uuid / 复活 SkillSet uuid) * @param skillUuid 技能 uuid(skills key / 触发组 s_uuid / FieldSkillSet uuid / 复活 SkillSet uuid)
* @param slot 技能所属槽位(可空):指定时仅在该槽位查找;缺省按上述顺序自动定位 * @param slot 技能所属槽位(可空):指定时仅在该槽位查找;缺省按上述顺序自动定位
* @param minLv 最低展示档(默认 1):传 2 时裁剪掉 sLv<2 的档,仅保留 lv2+ 档位
* (普攻强化档作为独立技能展示的场景,如 heroSet 普攻 lv2 档单独出图标)
* @returns 档位行数组;技能不属于该英雄时返回 [] * @returns 档位行数组;技能不属于该英雄时返回 []
*/ */
export function buildHeroSkillTierLines(heroUuid: number, skillUuid: number, slot?: SkillTriggerType): HeroSkillTierLine[] { export function buildHeroSkillTierLines(heroUuid: number, skillUuid: number, slot?: SkillTriggerType, minLv: number = 1): HeroSkillTierLine[] {
const hero = HeroInfo[heroUuid]; const hero = HeroInfo[heroUuid];
if (!hero) return []; if (!hero) return [];
@@ -577,12 +579,13 @@ export function buildHeroSkillTierLines(heroUuid: number, skillUuid: number, slo
// 光环路径(无 ISkillTierInfo 中间结构,直接生成 HeroSkillTierLine) // 光环路径(无 ISkillTierInfo 中间结构,直接生成 HeroSkillTierLine)
if (tiers.length === 0) { if (tiers.length === 0) {
const fieldLines = collectFieldTierLines(hero, skillUuid); const fieldLines = collectFieldTierLines(hero, skillUuid);
if (fieldLines.length > 0) return fieldLines; if (fieldLines.length > 0) return minLv > 1 ? fieldLines.filter(l => l.sLv >= minLv) : fieldLines;
// 复活已纳入 dead 触发组,通过 dead 路径处理 // 复活已纳入 dead 触发组,通过 dead 路径处理
return []; return [];
} }
return buildTierLines(tiers, triggerLabel); const lines = buildTierLines(tiers, triggerLabel);
return minLv > 1 ? lines.filter(l => l.sLv >= minLv) : lines;
} }
/** /**

View File

@@ -22,7 +22,7 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp"; import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
import { oops } from "db://oops-framework/core/Oops"; import { oops } from "db://oops-framework/core/Oops";
import { mLogger } from "../common/Logger"; import { mLogger } from "../common/Logger";
import { HeroInfo, HeroList, SkillTriggerType, LvFieldEntry, LvTriggerEntry, TriggerGrouped, getHeroDisplayStats } from "../common/config/heroSet"; import { HeroInfo, HeroList, SkillTriggerType, LvFieldEntry, LvTriggerEntry, TriggerGrouped, HSkillInfo, getHeroDisplayStats } from "../common/config/heroSet";
import { buildHeroSkillDesc } from "../common/HeroSkillText"; import { buildHeroSkillDesc } from "../common/HeroSkillText";
import { GameEvent } from "../common/config/GameEvent"; import { GameEvent } from "../common/config/GameEvent";
import { HERO_UPGRADE_NEED_FRAGMENTS, RARITY_BG_COLORS } from "../common/config/GameSet"; import { HERO_UPGRADE_NEED_FRAGMENTS, RARITY_BG_COLORS } from "../common/config/GameSet";
@@ -524,6 +524,9 @@ export class HerosListComp extends CCComp {
this.addSkillIcon(hSkill.uuid, sLv, false, false) this.addSkillIcon(hSkill.uuid, sLv, false, false)
}) })
// ---- 普攻 lv2 强化档:作为独立技能图标(名称/图标取 lv1,弹窗只展示 lv2 说明) ----
this.addNormalLv2Icon(Object.values(hero.skills)[0], lv)
// ---- 触发技能:每个触发槽位整组一个图标,按 PANEL_TRIGGER_TYPES 顺序追加 ---- // ---- 触发技能:每个触发槽位整组一个图标,按 PANEL_TRIGGER_TYPES 顺序追加 ----
for (const type of HerosListComp.PANEL_TRIGGER_TYPES) { for (const type of HerosListComp.PANEL_TRIGGER_TYPES) {
if (type === SkillTriggerType.Field) { if (type === SkillTriggerType.Field) {
@@ -536,21 +539,23 @@ export class HerosListComp extends CCComp {
/** /**
* 实例化技能小图标并初始化 * 实例化技能小图标并初始化
* @param s_uuid 技能 UUID(SkillSet 或 FieldSkillSet;整组模式下取组内首个,仅用于图标) * @param s_uuid 技能 UUID(SkillSet 或 FieldSkillSet;整组模式下取组内首个,仅用于图标)
* @param s_lv 当前等级对应的技能等级(locked 时为解锁预览档) * @param s_lv 当前等级对应的技能等级(locked 时为解锁预览档)
* @param locked 当前等级未激活 → 显示锁定遮罩 * @param locked 当前等级未激活 → 显示锁定遮罩
* @param isField true 表示驻场光环词条(图标取 FieldSkillSet) * @param isField true 表示驻场光环词条(图标取 FieldSkillSet)
* @param slot 技能所属槽位(普攻/大招传 undefined;触发/光环传对应类型, * @param slot 技能所属槽位(普攻/大招传 undefined;触发/光环传对应类型,
* 同一底座不同槽位视为不同技能,弹窗按槽位取档;复活已纳入 dead) * 同一底座不同槽位视为不同技能,弹窗按槽位取档;复活已纳入 dead)
* @param isGroup 整组模式:该图标代表整个触发槽位,组名取组内首个自定义名, * @param isGroup 整组模式:该图标代表整个触发槽位,组名取组内首个自定义名,
* 弹窗按整组合并渲染(组内多技能/词条统一说明) * 弹窗按整组合并渲染(组内多技能/词条统一说明)
* @param normalLv2 普攻 lv2 专属模式:该图标代表普攻 lv2 强化档,名称/图标取 lv1,
* 弹窗只展示 lv2 档位说明
*/ */
private addSkillIcon(s_uuid: number, s_lv: number, locked: boolean, isField: boolean, slot?: SkillTriggerType, isGroup: boolean = false) { private addSkillIcon(s_uuid: number, s_lv: number, locked: boolean, isField: boolean, slot?: SkillTriggerType, isGroup: boolean = false, normalLv2: boolean = false) {
const node = instantiate(this.skill_icon_prefab) const node = instantiate(this.skill_icon_prefab)
node.name = `skill_${s_uuid}` node.name = `skill_${s_uuid}`
const comp = node.getComponent(SSkillBoxComp) || node.addComponent(SSkillBoxComp) const comp = node.getComponent(SSkillBoxComp) || node.addComponent(SSkillBoxComp)
// 携带所属英雄与槽位,点击弹窗按英雄技能分档描述渲染 // 携带所属英雄与槽位,点击弹窗按英雄技能分档描述渲染
comp.initHeroSkill(s_uuid, s_lv, locked, isField, this.huuid, slot, isGroup) comp.initHeroSkill(s_uuid, s_lv, locked, isField, this.huuid, slot, isGroup, normalLv2)
this.skills_node.addChild(node) this.skills_node.addChild(node)
} }
@@ -598,6 +603,22 @@ export class HerosListComp extends CCComp {
/** 复活技能已纳入 dead 触发组,通过 dead 配置展示(revive_hp / revive_count),无需单独处理 */ /** 复活技能已纳入 dead 触发组,通过 dead 配置展示(revive_hp / revive_count),无需单独处理 */
/**
* 普攻 lv2+ 强化档作为独立技能图标:
* 普攻(skills[0])lv_entries 存在档时单独出图标,名称/图标取 lv1(s_uuid 相同),
* 等级显示偏移一档(配置 Lv2 → Lv1,SSkillBoxComp 统一处理),弹窗按 hero_normal_lv2
* 模式只展示 lv2+ 档位说明;激活判定按档位的英雄等级(lv 阈值)。
* Why: 普攻 lv2 档是独立成长机制(如附加暴击/冰冻),需让玩家明确感知其存在与解锁条件。
*/
private addNormalLv2Icon(normal: HSkillInfo | undefined, lv: number) {
const entry = normal?.lv_entries?.[0]
if (!normal || !entry) return
// 节点命名区分 lv2 档(普攻与触发槽位可能同底座,uuid 重名会互相干扰)
this.addSkillIcon(normal.uuid, entry.s_lv ?? 2, lv < entry.lv, false, undefined, false, true)
const icon = this.skills_node.children[this.skills_node.children.length - 1]
if (icon) icon.name = `skill_${normal.uuid}_lv2`
}
// ======================== 英雄动画 ======================== // ======================== 英雄动画 ========================
private updateHeroAnimation(uuid: number) { private updateHeroAnimation(uuid: number) {

View File

@@ -48,6 +48,9 @@ export class SSkillBoxComp extends CCComp {
/** 整组模式(英雄图鉴):true 时该图标代表整个触发槽位(atking/dead/fstart/atked/call/field), /** 整组模式(英雄图鉴):true 时该图标代表整个触发槽位(atking/dead/fstart/atked/call/field),
* 组内多技能/词条合并为一个名称与一套说明展示,弹窗按整组取档 */ * 组内多技能/词条合并为一个名称与一套说明展示,弹窗按整组取档 */
private hero_group: boolean = false; private hero_group: boolean = false;
/** 普攻 lv2 专属模式(英雄图鉴):true 时该图标代表普攻 lv2 强化档,
* 名称/图标取 lv1,弹窗只展示 lv2 档位说明(裁剪 lv1 档) */
private hero_normal_lv2: boolean = false;
/** 卡牌等级 */ /** 卡牌等级 */
private rarity: number = 1; private rarity: number = 1;
/** 卡牌自定义图标ID(优先级最高,未定义则按 trigger_type 自动取) */ /** 卡牌自定义图标ID(优先级最高,未定义则按 trigger_type 自动取) */
@@ -89,6 +92,8 @@ export class SSkillBoxComp extends CCComp {
hero_slot: this.hero_uuid ? this.hero_slot : undefined, hero_slot: this.hero_uuid ? this.hero_slot : undefined,
// 整组模式:弹窗按整个触发槽位合并渲染(组内多技能/词条统一说明) // 整组模式:弹窗按整个触发槽位合并渲染(组内多技能/词条统一说明)
hero_group: this.hero_uuid ? this.hero_group : undefined, hero_group: this.hero_uuid ? this.hero_group : undefined,
// 普攻 lv2 专属模式:弹窗只展示 lv2 档位说明(名称/图标取 lv1)
hero_normal_lv2: this.hero_uuid ? this.hero_normal_lv2 : undefined,
}); });
} }
@@ -115,13 +120,18 @@ export class SSkillBoxComp extends CCComp {
* @param slot 技能所属槽位(普攻/大招传 undefined;触发/光环传对应类型,复活已纳入 dead) * @param slot 技能所属槽位(普攻/大招传 undefined;触发/光环传对应类型,复活已纳入 dead)
* @param isGroup 整组模式:该图标代表整个触发槽位,组名取组内首个自定义名, * @param isGroup 整组模式:该图标代表整个触发槽位,组名取组内首个自定义名,
* 弹窗按整组合并渲染(组内多技能/词条统一说明) * 弹窗按整组合并渲染(组内多技能/词条统一说明)
*/ * @param normalLv2 普攻 lv2+ 强化档专属模式:名称/图标取 lv1,弹窗只展示 lv2+ 档位说明;
initHeroSkill(s_uuid: number, s_lv: number, locked: boolean, isField: boolean = false, heroUuid: number = 0, slot?: SkillTriggerType, isGroup: boolean = false) { * 等级显示偏移一档(配置 Lv2 → 显示 Lv1,Lv3 → 显示 Lv2),强化档从 1 起计
*/
initHeroSkill(s_uuid: number, s_lv: number, locked: boolean, isField: boolean = false, heroUuid: number = 0, slot?: SkillTriggerType, isGroup: boolean = false, normalLv2: boolean = false) {
this.s_uuid = s_uuid; this.s_uuid = s_uuid;
this.hero_uuid = heroUuid; this.hero_uuid = heroUuid;
this.hero_slot = slot; this.hero_slot = slot;
this.hero_group = isGroup; this.hero_group = isGroup;
this.rarity = s_lv; this.hero_normal_lv2 = normalLv2;
// 普攻 lv2+ 强化档:等级显示偏移一档(配置 Lv2 显示 Lv1,Lv3 显示 Lv2),
// 强化档作为独立于普攻的技能存在,等级从 1 起计
this.rarity = normalLv2 ? Math.max(1, s_lv - 1) : s_lv;
this.hero_icon_source = isField ? "field" : "skill"; this.hero_icon_source = isField ? "field" : "skill";
this.updateUI(); this.updateUI();
this.setLocked(locked); this.setLocked(locked);

View File

@@ -64,6 +64,9 @@ export interface SInfoParams {
/** 触发组整体模式(英雄图鉴):true 时按 hero_slot 整组渲染, /** 触发组整体模式(英雄图鉴):true 时按 hero_slot 整组渲染,
* 组内多技能/词条合并为一套分档说明统一展示(每个触发槽位视为一个技能) */ * 组内多技能/词条合并为一套分档说明统一展示(每个触发槽位视为一个技能) */
hero_group?: boolean; hero_group?: boolean;
/** 普攻 lv2+ 强化档专属模式(英雄图鉴):名称/图标取 lv1,只展示 lv2+ 档位说明
* (裁剪 lv1 档,隐藏 2/3 级面板);等级显示偏移一档(配置 Lv2 → 显示 Lv1,Lv3 → Lv2) */
hero_normal_lv2?: boolean;
} }
/** 等级提升信息(用于 2 级 / 3 级面板) */ /** 等级提升信息(用于 2 级 / 3 级面板) */
@@ -256,15 +259,16 @@ export class SinfoComp extends CCComp {
private renderHeroSkill(p: SInfoParams): boolean { private renderHeroSkill(p: SInfoParams): boolean {
const tiers = (p.hero_group && p.hero_slot !== undefined) const tiers = (p.hero_group && p.hero_slot !== undefined)
? buildHeroGroupTierLines(p.hero_uuid!, p.hero_slot) ? buildHeroGroupTierLines(p.hero_uuid!, p.hero_slot)
: buildHeroSkillTierLines(p.hero_uuid!, p.uuid, p.hero_slot); : buildHeroSkillTierLines(p.hero_uuid!, p.uuid, p.hero_slot, p.hero_normal_lv2 ? 2 : 1);
if (tiers.length === 0) return false; if (tiers.length === 0) return false;
// 图标 / 名称 / 当前技能等级与通用逻辑一致(skill_lv 传的就是当前生效 s_lv) // 图标 / 名称 / 当前技能等级与通用逻辑一致(skill_lv 传的就是当前生效 s_lv;
// 普攻 lv2+ 强化档等级偏移一档显示:配置 Lv2 → Lv1,Lv3 → Lv2)
this.renderIcon(p); this.renderIcon(p);
this.setLabel(this.name_label, this.resolveName(p)); this.setLabel(this.name_label, this.resolveName(p));
this.setLabel(this.lv_label, `Lv.${p.skill_lv}`); this.setLabel(this.lv_label, `Lv.${p.hero_normal_lv2 ? Math.max(1, p.skill_lv - 1) : p.skill_lv}`);
// 技能 1 级:完整能力描述 // 主说明:普攻 lv2 专属模式只展示 lv2 档位文本(裁剪后首档即 lv2),否则 slv1 完整能力
if (this.main_info && this.main_info.isValid) { if (this.main_info && this.main_info.isValid) {
this.main_info.string = tiers[0].text || "暂无描述"; this.main_info.string = tiers[0].text || "暂无描述";
} }
@@ -272,9 +276,17 @@ export class SinfoComp extends CCComp {
// 英雄当前局外等级:激活判定基准 // 英雄当前局外等级:激活判定基准
const heroLv = Math.max(1, smc.getHeroOuterLv(p.hero_uuid!)); const heroLv = Math.max(1, smc.getHeroOuterLv(p.hero_uuid!));
// 未激活图标背景:1 级档位未解锁时显示 // 未激活图标背景:首档未解锁时显示
this.updateInactiveBg(heroLv >= tiers[0].heroLv); this.updateInactiveBg(heroLv >= tiers[0].heroLv);
// 普攻 lv2 专属模式:等级/档位面板均由该档接管,隐藏 2/3 级面板(只展示 lv2 描述)
if (p.hero_normal_lv2) {
this.setPanelActive(this.lv2_node, false);
this.setPanelActive(this.lv3_node, false);
this.renderLvPanelFromTier(tiers[0], heroLv, this.main_status, null);
return true;
}
// 技能 1 级:激活状态与 2/3 级面板同一表示方式(描述已在 main_info 展示,此处仅渲染状态) // 技能 1 级:激活状态与 2/3 级面板同一表示方式(描述已在 main_info 展示,此处仅渲染状态)
this.renderLvPanelFromTier(tiers[0], heroLv, this.main_status, null); this.renderLvPanelFromTier(tiers[0], heroLv, this.main_status, null);