From dbc013a20f90397cb175c5d69699c397ba818858 Mon Sep 17 00:00:00 2001 From: panFD Date: Mon, 7 Sep 2026 23:58:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(hero-config):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E8=8B=B1=E9=9B=84=E9=A2=9D=E5=A4=96=E5=B1=9E=E6=80=A7=E5=8A=A0?= =?UTF-8?q?=E6=88=90=E6=A1=A3=E4=BD=8D=E7=9A=84=E5=9B=BE=E9=89=B4=E5=B1=95?= =?UTF-8?q?=E7=A4=BA=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 本次变更完成了英雄bonus属性加成系统的图鉴可视化改造: 1. 扩展LvBonusAttrsEntry类型,新增name/icon/info配置字段 2. 服务端渲染兼容新增字段,仅在配置存在时输出 3. 更新英雄配置文档,补充bonus字段的新参数说明 4. 新增属性加成编辑器表单,支持配置名称、图标和自定义描述 5. 实现英雄图鉴的bonus档位图标渲染逻辑,支持锁定状态和点击弹窗 6. 新增bonus效果文案生成工具,支持自定义模板和属性自动生成 7. 为技能弹窗新增bonus专属渲染逻辑,适配属性加成档位的展示 8. 为部分测试英雄配置了bonus档位的名称属性 --- assets/script/game/common/HeroSkillText.ts | 33 ++++++++++++++ assets/script/game/common/config/heroSet.ts | 6 +++ assets/script/game/map/HerosListComp.ts | 22 ++++++++- assets/script/game/map/SSkillBoxComp.ts | 31 +++++++++++++ assets/script/game/map/SinfoComp.ts | 49 ++++++++++++++++++++- put/server/data/heroes.json | 42 ++++++++++++------ put/server/data/heroes.json编辑指引.md | 2 +- put/server/index.js | 6 ++- put/web/src/components/BonusEditor.vue | 11 +++++ 9 files changed, 183 insertions(+), 19 deletions(-) diff --git a/assets/script/game/common/HeroSkillText.ts b/assets/script/game/common/HeroSkillText.ts index 475cf3bb..7e51d3d8 100644 --- a/assets/script/game/common/HeroSkillText.ts +++ b/assets/script/game/common/HeroSkillText.ts @@ -32,6 +32,7 @@ import { HeroInfo, heroInfo, SkillTriggerType, SkillTriggerName, HSkillInfo, LvTriggerEntry, FieldEntry, TriggerGrouped, resolveSkillOverrides, resolveTriggerByLv, resolveFieldByLv, getGroupedTriggerName, getFieldSkillName, getTriggerGroupName, getFieldGroupName, + LvBonusAttrsEntry, BonusAttrKey, } from "./config/heroSet"; import { SkillSet, mergeSkillParams, SkillConfig, SkillKind, TGroup, FieldSkillSet } from "./config/SkillSet"; import { BuffList } from "./config/BuffSet"; @@ -714,3 +715,35 @@ function resolveTriggerSkillName(hero: heroInfo, skillUuid: number): string { } return SkillSet[skillUuid]?.name ?? ""; } + +/* ==================== bonus(额外属性加成)展示文案 ==================== */ + +/** bonus 属性键 → 中文展示名与单位(hp/ap/def 为百分比,概率类为固定百分点) */ +const BONUS_ATTR_LABEL: Record = { + hp: { name: "生命", suffix: "%" }, + ap: { name: "攻击", suffix: "%" }, + def: { name: "防御", suffix: "%" }, + crt: { name: "暴击率", suffix: "" }, + frz: { name: "冰冻", suffix: "" }, + stun: { name: "击晕", suffix: "" }, + para: { name: "麻痹", suffix: "" }, +}; + +/** + * 生成单个 bonus 档位的效果描述(富文本)。 + * entry.info 显式配置时优先渲染(占位符 {lv} 替换为解锁等级);否则按 attrs 键自动生成。 + * @param entry bonus 档位配置 + * @returns 富文本效果串(不含名称与解锁等级,由调用方拼接) + */ +export function buildBonusEffectDesc(entry: LvBonusAttrsEntry): string { + if (entry.info) return entry.info.replace(/\{lv\}/g, `${entry.lv}`); + const parts: string[] = []; + for (const key of Object.keys(entry.attrs) as BonusAttrKey[]) { + const v = entry.attrs[key]; + if (v === undefined || v === 0) continue; + const meta = BONUS_ATTR_LABEL[key]; + const sign = v > 0 ? "+" : ""; + parts.push(`${meta.name} ${valueRich(`${sign}${v}${meta.suffix}`)}`); + } + return parts.join(",") || "无效果"; +} diff --git a/assets/script/game/common/config/heroSet.ts b/assets/script/game/common/config/heroSet.ts index 781e30d0..c9d63cfd 100644 --- a/assets/script/game/common/config/heroSet.ts +++ b/assets/script/game/common/config/heroSet.ts @@ -519,6 +519,12 @@ export type BonusAttrKey = 'hp' | 'ap' | 'def' | 'crt' | 'frz' | 'stun' | 'para' export interface LvBonusAttrsEntry { lv: number; attrs: Partial>; + /** 展示名(图鉴技能图标/弹窗用,如「钢铁壁垒」);缺省不出图标 */ + name?: string; + /** 图标帧名(gui/ui7 图集);缺省该档不在图鉴出图标 */ + icon?: string; + /** 自定义描述模板(可选);缺省由 attrs 自动生成效果文案 */ + info?: string; } /* *=============英雄配置列表================ diff --git a/assets/script/game/map/HerosListComp.ts b/assets/script/game/map/HerosListComp.ts index f44022b6..bc2ba816 100644 --- a/assets/script/game/map/HerosListComp.ts +++ b/assets/script/game/map/HerosListComp.ts @@ -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 { oops } from "db://oops-framework/core/Oops"; import { mLogger } from "../common/Logger"; -import { HeroInfo, HeroList, SkillTriggerType, LvFieldEntry, LvTriggerEntry, TriggerGrouped, HSkillInfo, getHeroDisplayStats } from "../common/config/heroSet"; +import { HeroInfo, HeroList, SkillTriggerType, LvFieldEntry, LvTriggerEntry, TriggerGrouped, HSkillInfo, getHeroDisplayStats, LvBonusAttrsEntry } from "../common/config/heroSet"; import { buildHeroSkillDesc } from "../common/HeroSkillText"; import { GameEvent } from "../common/config/GameEvent"; import { HERO_UPGRADE_NEED_FRAGMENTS, RARITY_BG_COLORS } from "../common/config/GameSet"; @@ -537,6 +537,26 @@ export class HerosListComp extends CCComp { this.addTriggerGroupIcon(hero[type] as TriggerGrouped | undefined, lv, type) } } + + // ---- bonus 档位:配置了 icon 的档位在技能列表最末各出一个图标 ---- + this.addBonusIcons(hero.bonus, lv) + } + + /** + * bonus 档位图标:配置了 icon 的档位各出一个图标(追加在技能列表最末)。 + * 激活判定:英雄局外等级 ≥ 档位 lv;未激活显示锁定遮罩。 + * 点击弹窗走 SinfoComp bonus 专属渲染(名称/图标取档位配置,主说明为 attrs 效果)。 + */ + private addBonusIcons(bonus: LvBonusAttrsEntry[] | undefined, lv: number) { + if (!bonus?.length) return + for (const entry of bonus) { + if (!entry.icon) continue + const node = instantiate(this.skill_icon_prefab) + node.name = `bonus_lv${entry.lv}` + const comp = node.getComponent(SSkillBoxComp) || node.addComponent(SSkillBoxComp) + comp.initBonus(this.huuid, entry, lv < entry.lv) + this.skills_node.addChild(node) + } } /** diff --git a/assets/script/game/map/SSkillBoxComp.ts b/assets/script/game/map/SSkillBoxComp.ts index b8e0beb7..2fc2ae4c 100644 --- a/assets/script/game/map/SSkillBoxComp.ts +++ b/assets/script/game/map/SSkillBoxComp.ts @@ -7,6 +7,7 @@ import { SkillSet, FieldSkillSet } from "../common/config/SkillSet"; import { FieldEntry, HeroInfo, TriggerGrouped, SkillTriggerType, getGroupedTriggerName, getFieldSkillName, getTriggerGroupName, getFieldGroupName, getHeroSkillIcon, + LvBonusAttrsEntry, } from "../common/config/heroSet"; import { UIID } from "../common/config/GameUIConfig"; import { smc } from "../common/SingletonModuleComp"; @@ -63,6 +64,8 @@ export class SSkillBoxComp extends CCComp { public field: FieldEntry[] = []; /** 英雄图鉴模式下的图标来源:skill=SkillSet,field=FieldSkillSet;undefined 表示装备卡模式 */ private hero_icon_source?: "skill" | "field"; + /** bonus 档位模式:该图标代表英雄额外属性加成的某一档(图鉴展示,无技能底座 uuid) */ + private bonus_entry?: LvBonusAttrsEntry; /** 点击事件是否已注册(防止重复绑定) */ private _clickRegistered: boolean = false; @@ -79,6 +82,16 @@ export class SSkillBoxComp extends CCComp { /** 点击:弹出技能信息框 */ private onClick() { oops.audio.playEffect("music/button"); + // bonus 档位模式:专属弹窗参数(无技能底座 uuid,直接透传档位数据) + if (this.bonus_entry) { + oops.gui.open(UIID.SInfo, { + uuid: 0, + skill_lv: 1, + hero_uuid: this.hero_uuid || undefined, + bonus_entry: this.bonus_entry, + }); + return; + } oops.gui.open(UIID.SInfo, { uuid: this.s_uuid, skill_lv: this.rarity, @@ -108,6 +121,24 @@ export class SSkillBoxComp extends CCComp { } } + /** + * 初始化 bonus 档位图标(英雄图鉴面板专用): + * 图标/名称直接取档位配置的 icon/name,无技能底座 uuid;点击弹窗走 bonus 专属渲染。 + * @param heroUuid 所属英雄 UUID(激活判定用局外等级) + * @param entry bonus 档位配置(含 lv/attrs/name/icon/info) + * @param locked 当前英雄等级未达档位解锁等级时显示锁定遮罩 + */ + initBonus(heroUuid: number, entry: LvBonusAttrsEntry, locked: boolean) { + this.hero_uuid = heroUuid; + this.bonus_entry = entry; + // 等级标签展示解锁所需英雄等级(Lv.n),便于玩家直观看到解锁条件 + this.rarity = entry.lv; + this.card_icon = entry.icon; + this.skill_name = entry.name ?? ""; + this.updateUI(); + this.setLocked(locked); + } + /** * 初始化英雄技能图标(英雄图鉴面板专用): * 图标按 s_uuid 直接取 SkillSet / FieldSkillSet 配置,不走 EquipPoolList。 diff --git a/assets/script/game/map/SinfoComp.ts b/assets/script/game/map/SinfoComp.ts index bd495eac..01688b8e 100644 --- a/assets/script/game/map/SinfoComp.ts +++ b/assets/script/game/map/SinfoComp.ts @@ -35,9 +35,9 @@ import { oops } from "db://oops-framework/core/Oops"; import { CardTriggerType, CardConfig } from "../common/config/CardSet"; import { SkillSet, FieldSkillSet } from "../common/config/SkillSet"; import { SkillCardList } from "../common/config/SCardSet"; -import { FieldEntry, SkillTriggerType, HeroInfo, getHeroSkillIcon } from "../common/config/heroSet"; +import { FieldEntry, SkillTriggerType, HeroInfo, getHeroSkillIcon, LvBonusAttrsEntry } from "../common/config/heroSet"; import { buildCardDescRich } from "../common/config/CardSkillText"; -import { buildHeroSkillTierLines, buildHeroGroupTierLines, HeroSkillTierLine } from "../common/HeroSkillText"; +import { buildHeroSkillTierLines, buildHeroGroupTierLines, HeroSkillTierLine, buildBonusEffectDesc } from "../common/HeroSkillText"; import { smc } from "../common/SingletonModuleComp"; const { ccclass, property } = _decorator; @@ -67,6 +67,9 @@ export interface SInfoParams { /** 普攻 lv2+ 强化档专属模式(英雄图鉴):名称/图标取 lv1,只展示 lv2+ 档位说明 * (裁剪 lv1 档,隐藏 2/3 级面板);等级显示偏移一档(配置 Lv2 → 显示 Lv1,Lv3 → Lv2) */ hero_normal_lv2?: boolean; + /** bonus 档位模式(英雄图鉴):直接透传额外属性加成档位,弹窗走专属渲染 + * (名称/图标取档位配置,主说明为 attrs 自动生成或 info 模板,无分档面板) */ + bonus_entry?: LvBonusAttrsEntry; } /** 等级提升信息(用于 2 级 / 3 级面板) */ @@ -311,6 +314,42 @@ export class SinfoComp extends CCComp { } } + /** + * bonus 档位模式渲染:展示额外属性加成某一档位的名称/图标/效果/解锁状态。 + * 名称取 entry.name,图标取 entry.icon,主说明为 attrs 自动生成或 info 模板; + * 隐藏 2/3 级面板(bonus 档位各自独立出图标,无需分档对比)。 + */ + private renderBonus(p: SInfoParams) { + const entry = p.bonus_entry!; + // 图标:直接用档位配置的 icon(不经底座解析) + if (this.icon_node && this.icon_node.isValid) { + let sprite = this.icon_node.getComponent(Sprite) || this.icon_node.addComponent(Sprite); + if (smc.skillIconsAtlas && entry.icon) { + sprite.spriteFrame = smc.skillIconsAtlas.getSpriteFrame(entry.icon) || null; + } else { + sprite.spriteFrame = null; + } + } + this.setLabel(this.name_label, entry.name ?? "属性强化"); + // 等级标签展示解锁所需英雄等级 + this.setLabel(this.lv_label, `Lv.${entry.lv}`); + // 主说明:attrs 自动生成效果文案(或 info 模板) + if (this.main_info && this.main_info.isValid) { + this.main_info.string = buildBonusEffectDesc(entry); + } + // 激活状态:英雄局外等级对比档位解锁等级 + const heroLv = p.hero_uuid ? Math.max(1, smc.getHeroOuterLv(p.hero_uuid)) : 1; + const unlocked = heroLv >= entry.lv; + if (this.main_status && this.main_status.isValid) { + this.main_status.string = unlocked ? "已激活" : `${entry.lv}级解锁`; + this.main_status.color = unlocked ? new Color(0, 255, 10, 255) : new Color(150, 150, 150, 255); + } + this.updateInactiveBg(unlocked); + // 隐藏 2/3 级面板 + this.setPanelActive(this.lv2_node, false); + this.setPanelActive(this.lv3_node, false); + } + /** 按档位渲染单等级面板:激活状态按英雄等级对比档位解锁等级 */ private renderLvPanelFromTier(tier: HeroSkillTierLine | undefined, heroLv: number, statusLabel: Label | null, descRich: RichText | null) { if (statusLabel && statusLabel.isValid) { @@ -331,6 +370,12 @@ export class SinfoComp extends CCComp { if (!this.params) return; const p = this.params; + // ---- bonus 档位模式:专属渲染(名称/图标取档位配置,无分档面板) ---- + if (p.bonus_entry) { + this.renderBonus(p); + return; + } + // ---- 英雄技能模式:按英雄语境渲染分档描述(文本二) ---- if (p.hero_uuid && this.renderHeroSkill(p)) return; diff --git a/put/server/data/heroes.json b/put/server/data/heroes.json index 27ddd092..6c0942b6 100644 --- a/put/server/data/heroes.json +++ b/put/server/data/heroes.json @@ -100,7 +100,8 @@ "lv": 12, "attrs": { "def": 50 - } + }, + "name": "钢铁壁垒" } ] }, @@ -200,7 +201,8 @@ "lv": 12, "attrs": { "hp": 50 - } + }, + "name": "不屈战意" } ] }, @@ -297,7 +299,8 @@ "lv": 12, "attrs": { "hp": 50 - } + }, + "name": "不屈战意" } ] }, @@ -396,7 +399,8 @@ "lv": 12, "attrs": { "ap": 50 - } + }, + "name": "致命本能" } ] }, @@ -496,7 +500,8 @@ "lv": 12, "attrs": { "ap": 50 - } + }, + "name": "致命本能" } ] }, @@ -594,7 +599,8 @@ "lv": 12, "attrs": { "ap": 50 - } + }, + "name": "鹰眼锁定" } ] }, @@ -692,7 +698,8 @@ "lv": 12, "attrs": { "ap": 50 - } + }, + "name": "奥术共鸣" } ] }, @@ -787,7 +794,8 @@ "lv": 12, "attrs": { "hp": 50 - } + }, + "name": "守护之心" } ] }, @@ -889,7 +897,8 @@ "lv": 12, "attrs": { "def": 50 - } + }, + "name": "钢铁壁垒" } ] }, @@ -989,7 +998,8 @@ "lv": 12, "attrs": { "ap": 50 - } + }, + "name": "鹰眼锁定" } ] }, @@ -1092,7 +1102,8 @@ "lv": 12, "attrs": { "def": 50 - } + }, + "name": "钢铁壁垒" } ] }, @@ -1214,7 +1225,8 @@ "lv": 12, "attrs": { "ap": 50 - } + }, + "name": "奥术共鸣" } ] }, @@ -1321,7 +1333,8 @@ "lv": 12, "attrs": { "hp": 50 - } + }, + "name": "守护之心" } ] }, @@ -1426,7 +1439,8 @@ "lv": 12, "attrs": { "hp": 50 - } + }, + "name": "不屈战意" } ] } diff --git a/put/server/data/heroes.json编辑指引.md b/put/server/data/heroes.json编辑指引.md index 445602b2..38e816c2 100644 --- a/put/server/data/heroes.json编辑指引.md +++ b/put/server/data/heroes.json编辑指引.md @@ -44,7 +44,7 @@ | `skills` | array | **恰好 2 个**:`[普攻, 大招]`,结构见 §3 | | `atking`/`atked`/`fstart`/`dead` | object | 触发组,结构见 §4 | | `field` | array | 驻场光环档位,结构见 §5 | -| `bonus` | array | 额外属性加成 `[{lv,attrs:{...}}]`,运行时累加所有 lv ≤ 英雄等级 的档位;`hp`/`ap`/`def` 为百分比(基数取"基础×品质+成长"快照,多档加法叠加),`crt`/`frz`/`stun`/`para` 为固定百分点;常规英雄留空 | +| `bonus` | array | 额外属性加成 `[{lv,attrs:{...},name?,icon?,info?}]`,运行时累加所有 lv ≤ 英雄等级 的档位;`hp`/`ap`/`def` 为百分比(基数取"基础×品质+成长"快照,多档加法叠加),`crt`/`frz`/`stun`/`para` 为固定百分点;配了 `icon` 的档位在图鉴技能列表最末出图标(点击弹窗展示 name/效果,描述缺省按 attrs 自动生成,`info` 可自定义模板支持 `{lv}` 占位);常规英雄留空 | | `info` | string | 描述文案,可空 | | `revive` | — | **已废弃**:复活改在 `dead` 组 overrides 里配 `revive_hp`/`revive_count`,不要写独立字段 | diff --git a/put/server/index.js b/put/server/index.js index c47e1768..347994b0 100644 --- a/put/server/index.js +++ b/put/server/index.js @@ -206,7 +206,11 @@ function renderSingleHero(h) { if (bonusList.length) { push(` bonus: [`) for (const b of bonusList) { - push(` { lv: ${b.lv}, attrs: ${JSON.stringify(b.attrs)} },`) + // 展示字段(name/icon/info)非空才输出,保持底座纯净 + const nameSeg = b.name ? ` name: ${JSON.stringify(b.name)},` : '' + const iconSeg = b.icon ? ` icon: ${JSON.stringify(b.icon)},` : '' + const infoSeg = b.info ? ` info: ${JSON.stringify(b.info)},` : '' + push(` { lv: ${b.lv}, attrs: ${JSON.stringify(b.attrs)},${nameSeg}${iconSeg}${infoSeg} },`) } push(` ],`) } diff --git a/put/web/src/components/BonusEditor.vue b/put/web/src/components/BonusEditor.vue index 253bf12c..6225a98f 100644 --- a/put/web/src/components/BonusEditor.vue +++ b/put/web/src/components/BonusEditor.vue @@ -9,7 +9,18 @@ + + + + + + + + + + +