From 2eb4257daeb474e39d7ef4e669dc24dad219238f Mon Sep 17 00:00:00 2001 From: pan Date: Mon, 17 Aug 2026 10:01:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E8=8B=B1=E9=9B=84?= =?UTF-8?q?=E5=B1=80=E5=A4=96=E6=88=90=E9=95=BF=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 新增GameEvent事件枚举HERO_GROWTH_UPDATE 2. 新增英雄局外升级所需碎片配置表 3. 新增英雄局外成长数据持久化与相关工具方法 4. 重构英雄卡片等级逻辑,统一使用局外成长等级 5. 更新英雄设计文档与相关注释 --- .../script/game/common/SingletonModuleComp.ts | 64 +++++++++++++++++++ assets/script/game/common/config/GameEvent.ts | 2 + assets/script/game/common/config/GameSet.ts | 13 ++++ .../common/config/几何王国技能复刻方案.md | 16 ++--- assets/script/game/map/CHeroComp.ts | 7 +- assets/script/game/map/CardComp.ts | 6 +- assets/script/game/map/CardLiteComp.ts | 4 +- assets/script/game/map/MissionHeroComp.ts | 5 +- 8 files changed, 98 insertions(+), 19 deletions(-) diff --git a/assets/script/game/common/SingletonModuleComp.ts b/assets/script/game/common/SingletonModuleComp.ts index 0be20c3d..e559b8dc 100644 --- a/assets/script/game/common/SingletonModuleComp.ts +++ b/assets/script/game/common/SingletonModuleComp.ts @@ -33,12 +33,24 @@ export interface GameDate { switch_effect?: boolean, // 音效开关 switch_music?: boolean, // 音乐开关 timestamp?: number, // 用于比对本地与云端数据的最新状态 + /** 英雄局外成长数据:key 为英雄 uuid,value 为成长记录 */ + hero_growth?: Record, } export interface CloudData { openid: string; data: GameDate; } +/** 英雄局外成长记录 */ +export interface HeroGrowthRecord { + /** 英雄模板 uuid(对应 heroSet.HeroInfo 的 key) */ + uuid: number; + /** 当前局外等级(从 1 开始) */ + lv: number; + /** 当前拥有的英雄碎片数量 */ + fragments: number; +} + /** * 死亡英雄快照:英雄真实死亡时记录,供后续复活机制(重新召唤)消费。 * 仅记录静态/成长数据,不含战斗内临时状态(buff、CD 等)。 @@ -97,6 +109,8 @@ export class SingletonModuleComp extends ecs.Comp { skill: false, // 购买技能卷轴面板按钮 shop: false, // 购买药品面板按钮 }, + /** 英雄局外成长数据:记录每个英雄的局外等级与碎片数量 */ + hero_growth: {} as Record, } vmdata: any = { @@ -302,6 +316,56 @@ export class SingletonModuleComp extends ecs.Comp { } } + // ==================== 英雄局外成长 ==================== + + /** + * 获取英雄局外成长记录;若不存在则初始化并返回 + * @param uuid 英雄模板 uuid + */ + getHeroGrowth(uuid: number): HeroGrowthRecord { + if (!this.data.hero_growth) this.data.hero_growth = {}; + if (!this.data.hero_growth[uuid]) { + this.data.hero_growth[uuid] = { uuid, lv: 1, fragments: 0 }; + } + return this.data.hero_growth[uuid]; + } + + /** + * 获取英雄局外等级(未初始化时返回 1) + * @param uuid 英雄模板 uuid + */ + getHeroOuterLv(uuid: number): number { + return this.getHeroGrowth(uuid).lv; + } + + /** + * 增加英雄碎片 + * @param uuid 英雄模板 uuid + * @param count 增加的碎片数量(正数) + */ + addHeroFragments(uuid: number, count: number): void { + const growth = this.getHeroGrowth(uuid); + growth.fragments = Math.max(0, growth.fragments + count); + gameDataSync.markDataDirty(); + oops.message.dispatchEvent(GameEvent.HERO_GROWTH_UPDATE); + } + + /** + * 消耗碎片提升英雄局外等级 + * @param uuid 英雄模板 uuid + * @param needFragments 本次升级所需碎片数量(由外部配置表计算) + * @returns 是否升级成功 + */ + upgradeHero(uuid: number, needFragments: number): boolean { + const growth = this.getHeroGrowth(uuid); + if (growth.fragments < needFragments) return false; + growth.fragments -= needFragments; + growth.lv += 1; + gameDataSync.markDataDirty(); + oops.message.dispatchEvent(GameEvent.HERO_GROWTH_UPDATE); + return true; + } + /** * 在游戏载入早期调用,预加载常用图集 */ diff --git a/assets/script/game/common/config/GameEvent.ts b/assets/script/game/common/config/GameEvent.ts index 75b7eea8..f820d93e 100644 --- a/assets/script/game/common/config/GameEvent.ts +++ b/assets/script/game/common/config/GameEvent.ts @@ -96,5 +96,7 @@ export enum GameEvent { CoinFly = "CoinFly", /** 连杀达阈值(payload: { count, tier },tier 0/1/2 对应 5/10/20 连杀) */ ComboReach = "ComboReach", + /** 英雄局外成长数据变更(碎片/等级更新,payload 为空,UI 重新读取 smc.data.hero_growth) */ + HERO_GROWTH_UPDATE = "HERO_GROWTH_UPDATE", } diff --git a/assets/script/game/common/config/GameSet.ts b/assets/script/game/common/config/GameSet.ts index aac5191f..73e3882c 100644 --- a/assets/script/game/common/config/GameSet.ts +++ b/assets/script/game/common/config/GameSet.ts @@ -62,6 +62,19 @@ export enum FightSet { REFRESH_STONE_COST = 1, } +/** + * 英雄局外等级升级所需碎片数量(key 为目标等级)。 + * 查询方式:HERO_UPGRADE_NEED_FRAGMENTS[growth.lv + 1],即升到下一级所需的碎片数。 + * 局外等级独立于局内战斗等级,持久化保存于 smc.data.hero_growth。 + */ +export const HERO_UPGRADE_NEED_FRAGMENTS: Record = { + 2: 10, + 3: 20, + 4: 40, + 5: 80, + 6: 160, +}; + /** * 技能卡牌出现的回合配置(单一数据源)。 * 数组索引 i 对应卡牌档位 card_lv = i + 1: diff --git a/assets/script/game/common/config/几何王国技能复刻方案.md b/assets/script/game/common/config/几何王国技能复刻方案.md index 8f183334..21ee6823 100644 --- a/assets/script/game/common/config/几何王国技能复刻方案.md +++ b/assets/script/game/common/config/几何王国技能复刻方案.md @@ -1,13 +1,9 @@ -# 几何王国技能复刻设计文档 - -> **目的**:基于《几何王国》热门英雄技能,分析哪些可以**零代码改动**或**最小改动**复刻到本系统(1 回合定生死机制)。 -> +# 英雄设计文档 > **前置约束**(已确认的设计原则): -> - 局内固定 lv=1,等级成长移交局外 -> - 单场战斗 30 秒 -> - card_lv 纯技能数量差异(1~5 个技能槽) -> - 技能触发极简(`t_num=1`),高频高数值 -> - 无相同能力英雄,无阵容限制 +> - 局内固定(战斗开始召唤的时候同)等级成长移交局外 +> - 单场战斗 30 左右秒 +> - card_lv 角色品质(1~5):初始基础数值带有品质增量,每升 1级都是按 20% 增量。 + --- @@ -16,8 +12,6 @@ | 触发类型 | 枚举 | 语义 | 几何王国对应场景 | |---|---|---|---| | 战斗开始 | `SkillTriggerType.FStart` | 入场时触发 1 次 | 战吼、入场护盾 | -| 战斗结束 | `SkillTriggerType.FEnd` | 战斗胜利后触发 | (30s 短局意义弱) | -| 召唤入场 | `SkillTriggerType.Call` | 落地触发 1 次 | 登场技 | | 死亡触发 | `SkillTriggerType.Dead` | 死亡时触发 1 次 | 亡语、遗志 | | 攻击触发 | `SkillTriggerType.Atking` | 普攻 N 次触发 | 概率普攻特效(t_num 模拟概率) | | 受击触发 | `SkillTriggerType.Atked` | 被击 N 次触发 | 受击反击、叠甲 | diff --git a/assets/script/game/map/CHeroComp.ts b/assets/script/game/map/CHeroComp.ts index a370e320..278713fd 100644 --- a/assets/script/game/map/CHeroComp.ts +++ b/assets/script/game/map/CHeroComp.ts @@ -271,11 +271,14 @@ export class CHeroComp extends CCComp { }); } - // 等级标签(★ 表示卡牌等级;升级卡显示英雄目标等级) + // 等级标签(★ 表示卡牌等级;升级卡显示英雄目标等级;普通英雄卡显示局外等级) if (this.level_label) { if (isUpgradeCard) { const heroLv = Math.max(1, this.cardData.hero_lv ?? 1); this.level_label.string = heroLv > 1 ? `Lv.${heroLv - 1} -> Lv.${heroLv}` : `Lv.${heroLv}`; + } else if (this.cardData.type === CardType.Hero) { + const heroLv = smc.getHeroOuterLv(this.cardData.uuid); + this.level_label.string = heroLv > 1 ? `Lv.${heroLv}` : ""; } else { const lv = this.cardData.card_lv ?? 1; this.level_label.string = lv >= 2 ? "★".repeat(lv - 1) : ""; @@ -581,7 +584,7 @@ export class CHeroComp extends CCComp { cancel: false, reason: "", uuid: this.cardData.uuid, - hero_lv: this.cardData.hero_lv ?? 1, + hero_lv: smc.getHeroOuterLv(this.cardData.uuid), card_lv: this.cardData.base_card_lv ?? this.cardData.card_lv ?? 1 }; oops.message.dispatchEvent(GameEvent.UseHeroCard, guard); diff --git a/assets/script/game/map/CardComp.ts b/assets/script/game/map/CardComp.ts index 6f4b9f22..595bf42d 100644 --- a/assets/script/game/map/CardComp.ts +++ b/assets/script/game/map/CardComp.ts @@ -289,7 +289,7 @@ export class CardComp extends CCComp { cancel: false, reason: "", uuid: this.cardData.uuid, - hero_lv: this.cardData.hero_lv ?? 1, + hero_lv: smc.getHeroOuterLv(this.cardData.uuid), card_lv: this.cardData.base_card_lv ?? this.cardData.card_lv ?? 1 }; oops.message.dispatchEvent(GameEvent.UseHeroCard, guard); @@ -540,7 +540,7 @@ export class CardComp extends CCComp { const heroUuid = isUpgradeCard ? (this.queryHeroUuidByEid(this.cardData!.target_hero_eid as number) ?? this.card_uuid) : this.card_uuid; - const heroLv = Math.max(1, this.cardData?.hero_lv ?? 1); + const heroLv = Math.max(1, smc.getHeroOuterLv(heroUuid)); const poolLv = Math.max(1, this.cardData?.base_card_lv ?? this.cardData?.card_lv ?? 1); oops.gui.remove(UIID.HInfo); oops.gui.open(UIID.HInfo, { heroUuid, heroLv, poolLv }); @@ -756,7 +756,7 @@ export class CardComp extends CCComp { if (renderType === CardType.Hero) { const hero = HeroInfo[renderUuid]; - const heroLv = Math.max(1, this.cardData.hero_lv ?? hero?.lv ?? 1); + const heroLv = Math.max(1, smc.getHeroOuterLv(renderUuid) || hero?.lv || 1); this.setLabel(this.name_node, `${hero?.name || ""}`); if (this.lvl_node) { // 升级卡:显示「当前→升级后」;普通英雄卡:显示当前等级。统一使用默认颜色 diff --git a/assets/script/game/map/CardLiteComp.ts b/assets/script/game/map/CardLiteComp.ts index 760bad95..78c30ea6 100644 --- a/assets/script/game/map/CardLiteComp.ts +++ b/assets/script/game/map/CardLiteComp.ts @@ -132,7 +132,7 @@ export class CardLiteComp extends CCComp { weight: 25, card_lv: cardLv, kind: CKind.Hero, - hero_lv: hero.lv || 1 + hero_lv: smc.getHeroOuterLv(hero.uuid) }); } } @@ -231,7 +231,7 @@ export class CardLiteComp extends CCComp { if (this.card_type === CardType.Hero) { const hero = HeroInfo[this.card_uuid]; - const heroLv = Math.max(1, this.cardData.hero_lv ?? hero?.lv ?? 1); + const heroLv = Math.max(1, smc.getHeroOuterLv(this.card_uuid) || hero?.lv || 1); this.setLabel(this.name_node, `${hero?.name || ""}`); if (this.lvl_node) { this.lvl_node.string = `Lv.${heroLv}`; diff --git a/assets/script/game/map/MissionHeroComp.ts b/assets/script/game/map/MissionHeroComp.ts index 46d160b8..23a08f09 100644 --- a/assets/script/game/map/MissionHeroComp.ts +++ b/assets/script/game/map/MissionHeroComp.ts @@ -153,7 +153,10 @@ export class MissionHeroComp extends CCComp { private async call_hero(event: string, args: any) { const payload = args ?? event; const uuid = Number(payload?.uuid ?? 1001); - const hero_lv = Math.max(1, Number(payload?.hero_lv ?? 1)); + // 局内登场等级统一以局外成长等级为准(升级后登场即为升级后的等级) + // payload.hero_lv 保留作为下限兜底,防止外部显式指定更高等级 + const outer_lv = smc.getHeroOuterLv(uuid); + const hero_lv = Math.max(outer_lv, Math.max(1, Number(payload?.hero_lv ?? 1))); const card_lv = Math.max(1, Number(payload?.card_lv ?? 1)); // 二合一前置校验:同 uuid 已存在且合成后等级会超上限时,直接拒绝