feat(map,hero): 实现英雄技能整组合并展示功能

重构英雄技能展示逻辑,支持将同一触发槽位的多个技能/光环词条合并为一个技能展示:
1. 新增触发组整组分档渲染接口,合并同槽位多技能档位数据
2. 新增组名获取函数,统一使用槽位首个自定义名称作为展示名
3. 调整图鉴面板技能列表,每个触发槽位仅展示一个合并后的图标
4. 升级技能弹窗渲染逻辑,支持整组模式下的合并说明展示
5. 优化注释与代码结构,统一技能展示口径
This commit is contained in:
pan
2026-09-04 14:15:45 +08:00
parent 4e0ef5bccc
commit db26865e03
5 changed files with 319 additions and 107 deletions

View File

@@ -486,19 +486,21 @@ export class HerosListComp extends CCComp {
// ======================== 技能图标列表 ========================
/** 图鉴面板展示的触发技能类型(heroInfo 触发配置:死亡/战斗开始/光环/攻击后/受击) */
/** 图鉴面板展示的触发技能槽位(按触发时机排序:召唤→战斗开始→攻击后→受击后→死亡→常驻光环) */
private static readonly PANEL_TRIGGER_TYPES: SkillTriggerType[] = [
SkillTriggerType.Dead,
SkillTriggerType.Call,
SkillTriggerType.FStart,
SkillTriggerType.Field,
SkillTriggerType.Atking,
SkillTriggerType.Atked,
SkillTriggerType.Dead,
SkillTriggerType.Field,
]
/**
* 刷新当前英雄的技能图标列表:
* 1. 大招:skills[1] 及以后(首个为普攻,跳过)。
* 2. 触发技能:Dead/FStart/Field/Atking/Atked(复活已纳入 dead)。
* 2. 触发技能:每个触发槽位(Call/FStart/Atking/Atked/Dead/Field)整组视为一个技能,
* 只出一个图标,组内多技能/词条合并展示(复活已纳入 dead)。
* 以局外等级(getHeroOuterLv)为当前等级,未达解锁档的显示 locked,
* 已激活的显示当前等级对应的技能等级。
*/
@@ -522,63 +524,76 @@ export class HerosListComp extends CCComp {
this.addSkillIcon(hSkill.uuid, sLv, false, false)
})
// ---- 触发技能:按 PANEL_TRIGGER_TYPES 顺序追加 ----
// ---- 触发技能:每个触发槽位整组一个图标,按 PANEL_TRIGGER_TYPES 顺序追加 ----
for (const type of HerosListComp.PANEL_TRIGGER_TYPES) {
if (type === SkillTriggerType.Field) {
this.addFieldIcons(hero[SkillTriggerType.Field], lv)
this.addFieldGroupIcon(hero[SkillTriggerType.Field], lv)
} else {
this.addTriggerIcons(hero[type] as TriggerGrouped | undefined, lv, type)
this.addTriggerGroupIcon(hero[type] as TriggerGrouped | undefined, lv, type)
}
}
}
/**
* 实例化技能小图标并初始化
* @param s_uuid 技能 UUID(SkillSet 或 FieldSkillSet)
* @param s_lv 当前等级对应的技能等级(locked 时为解锁预览档)
* @param locked 当前等级未激活 → 显示锁定遮罩
* @param isField true 表示驻场光环词条(图标取 FieldSkillSet)
* @param slot 技能所属槽位(普攻/大招传 undefined;触发/光环传对应类型,
* 同一底座不同槽位视为不同技能,弹窗按槽位精确取档;复活已纳入 dead)
* @param s_uuid 技能 UUID(SkillSet 或 FieldSkillSet;整组模式下取组内首个,仅用于图标)
* @param s_lv 当前等级对应的技能等级(locked 时为解锁预览档)
* @param locked 当前等级未激活 → 显示锁定遮罩
* @param isField true 表示驻场光环词条(图标取 FieldSkillSet)
* @param slot 技能所属槽位(普攻/大招传 undefined;触发/光环传对应类型,
* 同一底座不同槽位视为不同技能,弹窗按槽位取档;复活已纳入 dead)
* @param isGroup 整组模式:该图标代表整个触发槽位,组名取组内首个自定义名,
* 弹窗按整组合并渲染(组内多技能/词条统一说明)
*/
private addSkillIcon(s_uuid: number, s_lv: number, locked: boolean, isField: boolean, slot?: SkillTriggerType) {
private addSkillIcon(s_uuid: number, s_lv: number, locked: boolean, isField: boolean, slot?: SkillTriggerType, isGroup: boolean = false) {
const node = instantiate(this.skill_icon_prefab)
node.name = `skill_${s_uuid}`
const comp = node.getComponent(SSkillBoxComp) || node.addComponent(SSkillBoxComp)
// 携带所属英雄与槽位,点击弹窗按英雄技能分档描述渲染
comp.initHeroSkill(s_uuid, s_lv, locked, isField, this.huuid, slot)
comp.initHeroSkill(s_uuid, s_lv, locked, isField, this.huuid, slot, isGroup)
this.skills_node.addChild(node)
}
/**
* 触发技能(dead/fstart/atking/atked):按 s_uuid 分组,取 ≤lv 最大档判定激活;
* 未激活时预览最低档(与 resolveTriggerByLv 同语义)
* @param type 触发槽位(随图标透传,区分与普攻同底座的触发技能)
* 触发技能槽位(call/fstart/atking/atked/dead)整组一个图标:
* 组内多 s_uuid 合并展示,图标/等级取组内首个 s_uuid 的生效档(未激活预览最低档);
* 整组激活判定:组内任一 s_uuid 存在 ≤lv 生效档即视为已激活(与 resolveTriggerByLv 同语义)
* @param type 触发槽位(随图标透传,弹窗按整组取档)
*/
private addTriggerIcons(grouped: TriggerGrouped | undefined, lv: number, type: SkillTriggerType) {
private addTriggerGroupIcon(grouped: TriggerGrouped | undefined, lv: number, type: SkillTriggerType) {
if (!grouped) return
for (const key in grouped) {
const entries = grouped[key]
if (!entries?.length) continue
const sorted = [...entries].sort((a, b) => a.lv - b.lv)
let active: LvTriggerEntry | undefined
for (const e of sorted) { if (e.lv <= lv) active = e }
const pick = active ?? sorted[0]
this.addSkillIcon(Number(key), pick.s_lv ?? 1, !active, false, type)
const keys = Object.keys(grouped).map(Number).filter(k => grouped[k]?.length)
if (keys.length === 0) return
// 图标/等级取组内首个 s_uuid 的生效档
const sorted = [...grouped[keys[0]]].sort((a, b) => a.lv - b.lv)
let active: LvTriggerEntry | undefined
for (const e of sorted) { if (e.lv <= lv) active = e }
// 整组激活判定:组内任一 s_uuid 存在生效档即激活
let anyActive = !!active
for (const key of keys) {
if (anyActive) break
for (const e of grouped[key] ?? []) {
if (e.lv <= lv) { anyActive = true; break }
}
}
const pick = active ?? sorted[0]
this.addSkillIcon(keys[0], pick.s_lv ?? 1, !anyActive, false, type, true)
}
/** 驻场光环:未激活取最低档预览,激活取 ≤lv 最大档的词条列表 */
private addFieldIcons(field: LvFieldEntry[] | undefined, lv: number) {
/**
* 驻场光环槽位整组一个图标:组内多词条合并展示,
* 图标取生效档(未激活预览最低档)的首个词条
*/
private addFieldGroupIcon(field: LvFieldEntry[] | undefined, lv: number) {
if (!field?.length) return
const sorted = [...field].sort((a, b) => a.lv - b.lv)
let active: LvFieldEntry | undefined
for (const e of sorted) { if (e.lv <= lv) active = e }
const pick = active ?? sorted[0]
for (const entry of pick.uuids) {
// 词条档位无 s_lv 概念,技能等级恒为 1(等级标签展示技能等级而非解锁的英雄等级)
this.addSkillIcon(entry.uuid, 1, !active, true, SkillTriggerType.Field)
}
const firstUuid = pick.uuids[0]?.uuid
if (!firstUuid) return
// 词条档位无 s_lv 概念,技能等级恒为 1(等级标签展示技能等级而非解锁的英雄等级)
this.addSkillIcon(firstUuid, 1, !active, true, SkillTriggerType.Field, true)
}
/** 复活技能已纳入 dead 触发组,通过 dead 配置展示(revive_hp / revive_count),无需单独处理 */