feat: 实现英雄局外成长系统
1. 新增GameEvent事件枚举HERO_GROWTH_UPDATE 2. 新增英雄局外升级所需碎片配置表 3. 新增英雄局外成长数据持久化与相关工具方法 4. 重构英雄卡片等级逻辑,统一使用局外成长等级 5. 更新英雄设计文档与相关注释
This commit is contained in:
@@ -33,12 +33,24 @@ export interface GameDate {
|
|||||||
switch_effect?: boolean, // 音效开关
|
switch_effect?: boolean, // 音效开关
|
||||||
switch_music?: boolean, // 音乐开关
|
switch_music?: boolean, // 音乐开关
|
||||||
timestamp?: number, // 用于比对本地与云端数据的最新状态
|
timestamp?: number, // 用于比对本地与云端数据的最新状态
|
||||||
|
/** 英雄局外成长数据:key 为英雄 uuid,value 为成长记录 */
|
||||||
|
hero_growth?: Record<number, HeroGrowthRecord>,
|
||||||
}
|
}
|
||||||
export interface CloudData {
|
export interface CloudData {
|
||||||
openid: string;
|
openid: string;
|
||||||
data: GameDate;
|
data: GameDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 英雄局外成长记录 */
|
||||||
|
export interface HeroGrowthRecord {
|
||||||
|
/** 英雄模板 uuid(对应 heroSet.HeroInfo 的 key) */
|
||||||
|
uuid: number;
|
||||||
|
/** 当前局外等级(从 1 开始) */
|
||||||
|
lv: number;
|
||||||
|
/** 当前拥有的英雄碎片数量 */
|
||||||
|
fragments: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 死亡英雄快照:英雄真实死亡时记录,供后续复活机制(重新召唤)消费。
|
* 死亡英雄快照:英雄真实死亡时记录,供后续复活机制(重新召唤)消费。
|
||||||
* 仅记录静态/成长数据,不含战斗内临时状态(buff、CD 等)。
|
* 仅记录静态/成长数据,不含战斗内临时状态(buff、CD 等)。
|
||||||
@@ -97,6 +109,8 @@ export class SingletonModuleComp extends ecs.Comp {
|
|||||||
skill: false, // 购买技能卷轴面板按钮
|
skill: false, // 购买技能卷轴面板按钮
|
||||||
shop: false, // 购买药品面板按钮
|
shop: false, // 购买药品面板按钮
|
||||||
},
|
},
|
||||||
|
/** 英雄局外成长数据:记录每个英雄的局外等级与碎片数量 */
|
||||||
|
hero_growth: {} as Record<number, HeroGrowthRecord>,
|
||||||
}
|
}
|
||||||
|
|
||||||
vmdata: any = {
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在游戏载入早期调用,预加载常用图集
|
* 在游戏载入早期调用,预加载常用图集
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -96,5 +96,7 @@ export enum GameEvent {
|
|||||||
CoinFly = "CoinFly",
|
CoinFly = "CoinFly",
|
||||||
/** 连杀达阈值(payload: { count, tier },tier 0/1/2 对应 5/10/20 连杀) */
|
/** 连杀达阈值(payload: { count, tier },tier 0/1/2 对应 5/10/20 连杀) */
|
||||||
ComboReach = "ComboReach",
|
ComboReach = "ComboReach",
|
||||||
|
/** 英雄局外成长数据变更(碎片/等级更新,payload 为空,UI 重新读取 smc.data.hero_growth) */
|
||||||
|
HERO_GROWTH_UPDATE = "HERO_GROWTH_UPDATE",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,19 @@ export enum FightSet {
|
|||||||
REFRESH_STONE_COST = 1,
|
REFRESH_STONE_COST = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 英雄局外等级升级所需碎片数量(key 为目标等级)。
|
||||||
|
* 查询方式:HERO_UPGRADE_NEED_FRAGMENTS[growth.lv + 1],即升到下一级所需的碎片数。
|
||||||
|
* 局外等级独立于局内战斗等级,持久化保存于 smc.data.hero_growth。
|
||||||
|
*/
|
||||||
|
export const HERO_UPGRADE_NEED_FRAGMENTS: Record<number, number> = {
|
||||||
|
2: 10,
|
||||||
|
3: 20,
|
||||||
|
4: 40,
|
||||||
|
5: 80,
|
||||||
|
6: 160,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 技能卡牌出现的回合配置(单一数据源)。
|
* 技能卡牌出现的回合配置(单一数据源)。
|
||||||
* 数组索引 i 对应卡牌档位 card_lv = i + 1:
|
* 数组索引 i 对应卡牌档位 card_lv = i + 1:
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
# 几何王国技能复刻设计文档
|
# 英雄设计文档
|
||||||
|
|
||||||
> **目的**:基于《几何王国》热门英雄技能,分析哪些可以**零代码改动**或**最小改动**复刻到本系统(1 回合定生死机制)。
|
|
||||||
>
|
|
||||||
> **前置约束**(已确认的设计原则):
|
> **前置约束**(已确认的设计原则):
|
||||||
> - 局内固定 lv=1,等级成长移交局外
|
> - 局内固定(战斗开始召唤的时候同)等级成长移交局外
|
||||||
> - 单场战斗 30 秒
|
> - 单场战斗 30 左右秒
|
||||||
> - card_lv 纯技能数量差异(1~5 个技能槽)
|
> - card_lv 角色品质(1~5):初始基础数值带有品质增量,每升 1级都是按 20% 增量。
|
||||||
> - 技能触发极简(`t_num=1`),高频高数值
|
|
||||||
> - 无相同能力英雄,无阵容限制
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -16,8 +12,6 @@
|
|||||||
| 触发类型 | 枚举 | 语义 | 几何王国对应场景 |
|
| 触发类型 | 枚举 | 语义 | 几何王国对应场景 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| 战斗开始 | `SkillTriggerType.FStart` | 入场时触发 1 次 | 战吼、入场护盾 |
|
| 战斗开始 | `SkillTriggerType.FStart` | 入场时触发 1 次 | 战吼、入场护盾 |
|
||||||
| 战斗结束 | `SkillTriggerType.FEnd` | 战斗胜利后触发 | (30s 短局意义弱) |
|
|
||||||
| 召唤入场 | `SkillTriggerType.Call` | 落地触发 1 次 | 登场技 |
|
|
||||||
| 死亡触发 | `SkillTriggerType.Dead` | 死亡时触发 1 次 | 亡语、遗志 |
|
| 死亡触发 | `SkillTriggerType.Dead` | 死亡时触发 1 次 | 亡语、遗志 |
|
||||||
| 攻击触发 | `SkillTriggerType.Atking` | 普攻 N 次触发 | 概率普攻特效(t_num 模拟概率) |
|
| 攻击触发 | `SkillTriggerType.Atking` | 普攻 N 次触发 | 概率普攻特效(t_num 模拟概率) |
|
||||||
| 受击触发 | `SkillTriggerType.Atked` | 被击 N 次触发 | 受击反击、叠甲 |
|
| 受击触发 | `SkillTriggerType.Atked` | 被击 N 次触发 | 受击反击、叠甲 |
|
||||||
|
|||||||
@@ -271,11 +271,14 @@ export class CHeroComp extends CCComp {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 等级标签(★ 表示卡牌等级;升级卡显示英雄目标等级)
|
// 等级标签(★ 表示卡牌等级;升级卡显示英雄目标等级;普通英雄卡显示局外等级)
|
||||||
if (this.level_label) {
|
if (this.level_label) {
|
||||||
if (isUpgradeCard) {
|
if (isUpgradeCard) {
|
||||||
const heroLv = Math.max(1, this.cardData.hero_lv ?? 1);
|
const heroLv = Math.max(1, this.cardData.hero_lv ?? 1);
|
||||||
this.level_label.string = heroLv > 1 ? `Lv.${heroLv - 1} -> Lv.${heroLv}` : `Lv.${heroLv}`;
|
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 {
|
} else {
|
||||||
const lv = this.cardData.card_lv ?? 1;
|
const lv = this.cardData.card_lv ?? 1;
|
||||||
this.level_label.string = lv >= 2 ? "★".repeat(lv - 1) : "";
|
this.level_label.string = lv >= 2 ? "★".repeat(lv - 1) : "";
|
||||||
@@ -581,7 +584,7 @@ export class CHeroComp extends CCComp {
|
|||||||
cancel: false,
|
cancel: false,
|
||||||
reason: "",
|
reason: "",
|
||||||
uuid: this.cardData.uuid,
|
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
|
card_lv: this.cardData.base_card_lv ?? this.cardData.card_lv ?? 1
|
||||||
};
|
};
|
||||||
oops.message.dispatchEvent(GameEvent.UseHeroCard, guard);
|
oops.message.dispatchEvent(GameEvent.UseHeroCard, guard);
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ export class CardComp extends CCComp {
|
|||||||
cancel: false,
|
cancel: false,
|
||||||
reason: "",
|
reason: "",
|
||||||
uuid: this.cardData.uuid,
|
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
|
card_lv: this.cardData.base_card_lv ?? this.cardData.card_lv ?? 1
|
||||||
};
|
};
|
||||||
oops.message.dispatchEvent(GameEvent.UseHeroCard, guard);
|
oops.message.dispatchEvent(GameEvent.UseHeroCard, guard);
|
||||||
@@ -540,7 +540,7 @@ export class CardComp extends CCComp {
|
|||||||
const heroUuid = isUpgradeCard
|
const heroUuid = isUpgradeCard
|
||||||
? (this.queryHeroUuidByEid(this.cardData!.target_hero_eid as number) ?? this.card_uuid)
|
? (this.queryHeroUuidByEid(this.cardData!.target_hero_eid as number) ?? this.card_uuid)
|
||||||
: 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);
|
const poolLv = Math.max(1, this.cardData?.base_card_lv ?? this.cardData?.card_lv ?? 1);
|
||||||
oops.gui.remove(UIID.HInfo);
|
oops.gui.remove(UIID.HInfo);
|
||||||
oops.gui.open(UIID.HInfo, { heroUuid, heroLv, poolLv });
|
oops.gui.open(UIID.HInfo, { heroUuid, heroLv, poolLv });
|
||||||
@@ -756,7 +756,7 @@ export class CardComp extends CCComp {
|
|||||||
|
|
||||||
if (renderType === CardType.Hero) {
|
if (renderType === CardType.Hero) {
|
||||||
const hero = HeroInfo[renderUuid];
|
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 || ""}`);
|
this.setLabel(this.name_node, `${hero?.name || ""}`);
|
||||||
if (this.lvl_node) {
|
if (this.lvl_node) {
|
||||||
// 升级卡:显示「当前→升级后」;普通英雄卡:显示当前等级。统一使用默认颜色
|
// 升级卡:显示「当前→升级后」;普通英雄卡:显示当前等级。统一使用默认颜色
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ export class CardLiteComp extends CCComp {
|
|||||||
weight: 25,
|
weight: 25,
|
||||||
card_lv: cardLv,
|
card_lv: cardLv,
|
||||||
kind: CKind.Hero,
|
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) {
|
if (this.card_type === CardType.Hero) {
|
||||||
const hero = HeroInfo[this.card_uuid];
|
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 || ""}`);
|
this.setLabel(this.name_node, `${hero?.name || ""}`);
|
||||||
if (this.lvl_node) {
|
if (this.lvl_node) {
|
||||||
this.lvl_node.string = `Lv.${heroLv}`;
|
this.lvl_node.string = `Lv.${heroLv}`;
|
||||||
|
|||||||
@@ -153,7 +153,10 @@ export class MissionHeroComp extends CCComp {
|
|||||||
private async call_hero(event: string, args: any) {
|
private async call_hero(event: string, args: any) {
|
||||||
const payload = args ?? event;
|
const payload = args ?? event;
|
||||||
const uuid = Number(payload?.uuid ?? 1001);
|
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));
|
const card_lv = Math.max(1, Number(payload?.card_lv ?? 1));
|
||||||
|
|
||||||
// 二合一前置校验:同 uuid 已存在且合成后等级会超上限时,直接拒绝
|
// 二合一前置校验:同 uuid 已存在且合成后等级会超上限时,直接拒绝
|
||||||
|
|||||||
Reference in New Issue
Block a user