feat(关卡系统): 实现章节-关-节三级关卡推进机制

本次提交重构了关卡系统,实现了完整的小节级关卡推进流程:
1. 新增关卡配置表StageSet,定义章节-关-节三级结构与怪物数值公式
2. 重构刷怪逻辑,按小节配置生成对应怪物波次
3. 实现关卡进度存档与恢复,支持断点续玩
4. 新增结算弹窗分支逻辑,区分小节胜利/关卡通关/失败状态
5. 优化胜利按钮文案与显示逻辑,将"再来一局"改为"下一关"
6. 新增一套完整的关卡事件系统,驱动各模块协同工作
This commit is contained in:
pan
2026-09-10 17:30:37 +08:00
parent 270f734a6a
commit 7b9545e2a8
9 changed files with 804 additions and 308 deletions

View File

@@ -100,5 +100,15 @@ export enum GameEvent {
HERO_GROWTH_UPDATE = "HERO_GROWTH_UPDATE",
/** 天赋数据变更(等级/点数更新,payload 为空,UI 重新读取 smc.data.talents / talent_points) */
TALENT_UPDATE = "TALENT_UPDATE",
/** 小节开始(payload: { chapter, stage, section },MissionMonComp 据此生成怪物) */
SectionStart = "SectionStart",
/** 小节结束(payload: { chapter, stage, section },玩家点击"下一节"后推进) */
SectionEnd = "SectionEnd",
/** 大关结束(payload: { chapter, stage },10 节打完) */
StageEnd = "StageEnd",
/** 章节结束(payload: { chapter },10 关打完) */
ChapterEnd = "ChapterEnd",
/** 结算弹窗点击"下一节"(payload 为空,MissionComp 据此推进到下一小节) */
NextSection = "NextSection",
}

View File

@@ -0,0 +1,255 @@
/**
* @file StageSet.ts
* @description 关卡系统配置(章节-关-节三级结构)
*
* 设计要点:
* - 1 章 = 10 关,每关 = 10 小节,共 100 个小节
* - 第 5 关为精英关,第 10 关为 Boss 关,其余为普通关
* - 怪物等级 = 关卡号(1-1 的怪为 Lv1,1-5 的怪为 Lv5)
* - 怪物属性 = 基础 × 开局系数 × (1 + 成长斜率 × (lv-1)) × 类型倍率
*
* 数值基准:
* - 开局系数 0.8(1 级小怪略弱于英雄)
* - 成长斜率 +5%/级(10 级 ≈ 1.45 倍)
* - 精英倍率 × 1.5,Boss 倍率 × 3.0(精英 × 2)
*/
import { MonType } from "./heroSet";
// ======================== 常量 ========================
/** 怪物开局系数:1 级小怪略弱于英雄(0.8 倍) */
export const MON_START_COEF = 0.8;
/** 怪物成长斜率:每级 +5% 基础属性 */
export const MON_GROW_SLOPE = 0.05;
/** 精英属性倍率:同级普通怪的 1.5 倍 */
export const ELITE_MUL = 1.5;
/** Boss 属性倍率:同级普通怪的 3.0 倍(精英 × 2) */
export const BOSS_MUL = 3.0;
/** 普通关小怪数量 */
export const NORMAL_MON_COUNT = 9;
/** 普通关开局立即刷出数量 */
export const NORMAL_FIRST_SPAWN = 3;
/** 普通关后续刷怪间隔(秒) */
export const NORMAL_SPAWN_INTERVAL = 5;
/** 精英关精英数量 */
export const ELITE_COUNT = 3;
/** Boss 关精英数量 */
export const BOSS_ELITE_COUNT = 2;
/** Boss 关 Boss 延迟刷出时间(秒) */
export const BOSS_DELAY = 1;
// ======================== 类型定义 ========================
/** 小节类型 */
export enum SectionType {
Normal = 0, // 普通关:9 小怪
Elite = 1, // 精英关:3 精英
Boss = 2, // Boss 关:1 Boss + 2 精英
}
/** 小节配置 */
export interface SectionConfig {
/** 小节类型 */
type: SectionType;
/** 怪物等级(驱动线性成长) */
monLv: number;
/** 精英数量(精英关/Boss 关用) */
eliteCount?: number;
/** Boss UUID(Boss 关用,可选,缺省随机) */
bossUuid?: number;
/** 怪物类型池(普通关用,缺省随机) */
monTypes?: MonType[];
}
/** 大关配置(1-1 ~ 1-10) */
export interface StageConfig {
/** 章节号 */
chapter: number;
/** 关卡号 1~10 */
stage: number;
/** 10 个小节 */
sections: SectionConfig[];
}
/** 章节配置 */
export interface ChapterConfig {
/** 章节号 */
chapter: number;
/** 章节名称 */
name: string;
/** 10 个大关 */
stages: StageConfig[];
}
// ======================== 工具函数 ========================
/**
* 计算怪物最终属性
* @param baseHp 基础 HP
* @param baseAp 基础 AP
* @param lv 怪物等级
* @param isElite 是否精英
* @param isBoss 是否 Boss
* @returns 最终 HP/AP
*/
export function calcMonsterAttrs(
baseHp: number,
baseAp: number,
lv: number,
isElite: boolean = false,
isBoss: boolean = false
): { hp: number; ap: number } {
const growCoef = 1 + MON_GROW_SLOPE * (lv - 1);
let typeMul = 1;
if (isBoss) {
typeMul = BOSS_MUL;
} else if (isElite) {
typeMul = ELITE_MUL;
}
const hp = Math.max(1, Math.round(baseHp * MON_START_COEF * growCoef * typeMul));
const ap = Math.max(1, Math.round(baseAp * MON_START_COEF * growCoef * typeMul));
return { hp, ap };
}
/**
* 获取指定关卡的怪物等级
* @param stage 关卡号(1~10)
* @returns 怪物等级
*/
export function getMonsterLv(stage: number): number {
return Math.max(1, Math.min(10, stage));
}
/**
* 判断是否为精英关
* @param stage 关卡号(1~10)
*/
export function isEliteStage(stage: number): boolean {
return stage === 5;
}
/**
* 判断是否为 Boss 关
* @param stage 关卡号(1~10)
*/
export function isBossStage(stage: number): boolean {
return stage === 10;
}
/**
* 获取小节类型
* @param stage 关卡号(1~10)
*/
export function getSectionType(stage: number): SectionType {
if (isBossStage(stage)) return SectionType.Boss;
if (isEliteStage(stage)) return SectionType.Elite;
return SectionType.Normal;
}
// ======================== 第 1 章配置 ========================
/** 生成单个大关的 10 个小节配置 */
function makeStageSections(stage: number): SectionConfig[] {
const type = getSectionType(stage);
const monLv = getMonsterLv(stage);
const sections: SectionConfig[] = [];
for (let i = 1; i <= 10; i++) {
const section: SectionConfig = { type, monLv };
if (type === SectionType.Elite) {
section.eliteCount = ELITE_COUNT;
} else if (type === SectionType.Boss) {
section.eliteCount = BOSS_ELITE_COUNT;
// bossUuid 缺省随机,由生成引擎从 Boss 池抽取
} else {
// 普通关:近战为主,穿插远程/重型
section.monTypes = [MonType.Melee, MonType.Melee, MonType.Melee, MonType.Heavy, MonType.Long];
}
sections.push(section);
}
return sections;
}
/** 第 1 章配置 */
export const Chapter1: ChapterConfig = {
chapter: 1,
name: "兽人入侵",
stages: [],
};
// 初始化第 1 章 10 个大关
for (let stage = 1; stage <= 10; stage++) {
Chapter1.stages.push({
chapter: 1,
stage,
sections: makeStageSections(stage),
});
}
/** 所有章节配置表(当前仅第 1 章,后续扩展) */
export const ChapterSet: Record<number, ChapterConfig> = {
1: Chapter1,
};
// ======================== 查询接口 ========================
/**
* 获取小节配置
* @param chapter 章节号
* @param stage 关卡号
* @param section 小节号(1~10)
* @returns 小节配置,不存在时返回 null
*/
export function getSectionConfig(chapter: number, stage: number, section: number): SectionConfig | null {
const ch = ChapterSet[chapter];
if (!ch) return null;
const st = ch.stages.find(s => s.stage === stage);
if (!st) return null;
if (section < 1 || section > st.sections.length) return null;
return st.sections[section - 1];
}
/**
* 获取大关配置
* @param chapter 章节号
* @param stage 关卡号
* @returns 大关配置,不存在时返回 null
*/
export function getStageConfig(chapter: number, stage: number): StageConfig | null {
const ch = ChapterSet[chapter];
if (!ch) return null;
return ch.stages.find(s => s.stage === stage) ?? null;
}
/**
* 获取章节配置
* @param chapter 章节号
* @returns 章节配置,不存在时返回 null
*/
export function getChapterConfig(chapter: number): ChapterConfig | null {
return ChapterSet[chapter] ?? null;
}
/**
* 获取当前进度显示文本
* @param chapter 章节号
* @param stage 关卡号
* @param section 小节号
* @returns 格式化文本,如 "1-5-3"
*/
export function getProgressText(chapter: number, stage: number, section: number): string {
return `${chapter}-${stage}-${section}`;
}
/**
* 获取大关显示文本
* @param chapter 章节号
* @param stage 关卡号
* @returns 格式化文本,如 "1-5"
*/
export function getStageText(chapter: number, stage: number): string {
return `${chapter}-${stage}`;
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "f2599ec4-e0c3-4d35-8ca3-5054e674d927",
"files": [],
"subMetas": {},
"userData": {}
}