LevelConfigManager.ts 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. import { _decorator, Component, JsonAsset, resources } from 'cc';
  2. const { ccclass, property } = _decorator;
  3. /**
  4. * 关卡配置数据结构
  5. */
  6. interface LevelConfig {
  7. levelId: number;
  8. name: string;
  9. scene: string;
  10. description?: string;
  11. backgroundImage?: string; // 背景图片路径
  12. weapons: string[];
  13. waves: WaveConfig[];
  14. levelSettings?: {
  15. initialHealth?: number;
  16. timeLimit?: number;
  17. difficulty?: string;
  18. energyMax?: number;
  19. healthMultiplier?: number;
  20. };
  21. }
  22. /**
  23. * 波次配置数据结构
  24. */
  25. interface WaveConfig {
  26. waveId: number;
  27. enemies: EnemyWaveConfig[];
  28. healthMultiplier?: number;
  29. }
  30. /**
  31. * 敌人波次配置
  32. */
  33. interface EnemyWaveConfig {
  34. enemyType: string;
  35. count: number;
  36. spawnInterval: number;
  37. characteristics: string[];
  38. }
  39. /**
  40. * 关卡JSON文件配置项
  41. */
  42. @ccclass('LevelJsonConfig')
  43. export class LevelJsonConfig {
  44. @property({
  45. tooltip: '关卡ID'
  46. })
  47. public levelId: number = 1;
  48. @property({
  49. tooltip: '关卡名称'
  50. })
  51. public levelName: string = '';
  52. @property({
  53. type: JsonAsset,
  54. tooltip: '关卡配置JSON文件'
  55. })
  56. public configFile: JsonAsset = null;
  57. }
  58. /**
  59. * 关卡配置管理器
  60. * 让策划在编辑器中配置所有关卡的JSON文件
  61. */
  62. @ccclass('LevelConfigManager')
  63. export class LevelConfigManager extends Component {
  64. private static instance: LevelConfigManager = null;
  65. @property({
  66. tooltip: '总关卡数量'
  67. })
  68. public totalLevels: number = 5;
  69. @property({
  70. type: [LevelJsonConfig],
  71. tooltip: '关卡配置列表(拖入对应的JSON文件)'
  72. })
  73. public levelConfigs: LevelJsonConfig[] = [];
  74. // 缓存已加载的关卡配置
  75. private loadedConfigs: Map<number, LevelConfig> = new Map();
  76. onLoad() {
  77. if (LevelConfigManager.instance === null) {
  78. LevelConfigManager.instance = this;
  79. this.validateConfigs();
  80. } else if (LevelConfigManager.instance !== this) {
  81. this.destroy();
  82. return;
  83. }
  84. }
  85. onDestroy() {
  86. if (LevelConfigManager.instance === this) {
  87. LevelConfigManager.instance = null;
  88. }
  89. }
  90. public static getInstance(): LevelConfigManager {
  91. return LevelConfigManager.instance;
  92. }
  93. /**
  94. * 验证关卡配置的完整性
  95. */
  96. private validateConfigs() {
  97. // 检查是否所有关卡都有配置
  98. for (let i = 1; i <= this.totalLevels; i++) {
  99. const config = this.levelConfigs.find(c => c.levelId === i);
  100. if (!config) {
  101. console.warn(`关卡 ${i} 缺少配置`);
  102. } else if (!config.configFile) {
  103. console.warn(`关卡 ${i} 缺少JSON文件`);
  104. }
  105. }
  106. // 检查是否有重复的关卡ID
  107. const levelIds = this.levelConfigs.map(c => c.levelId);
  108. const duplicates = levelIds.filter((id, index) => levelIds.indexOf(id) !== index);
  109. if (duplicates.length > 0) {
  110. console.error(`发现重复的关卡ID: ${duplicates.join(', ')}`);
  111. }
  112. }
  113. /**
  114. * 获取关卡配置
  115. */
  116. public async getLevelConfig(levelId: number): Promise<LevelConfig | null> {
  117. // 先从缓存中查找
  118. if (this.loadedConfigs.has(levelId)) {
  119. return this.loadedConfigs.get(levelId);
  120. }
  121. // 查找对应的配置项
  122. const configItem = this.levelConfigs.find(c => c.levelId === levelId);
  123. if (!configItem || !configItem.configFile) {
  124. console.error(`关卡 ${levelId} 配置不存在或缺少JSON文件`);
  125. return null;
  126. }
  127. try {
  128. // 解析JSON数据
  129. const jsonData = configItem.configFile.json;
  130. if (!jsonData) {
  131. console.error(`关卡 ${levelId} JSON文件解析失败`);
  132. return null;
  133. }
  134. // 验证JSON数据结构
  135. const levelConfig = this.validateLevelConfigData(jsonData, levelId);
  136. if (levelConfig) {
  137. // 缓存配置
  138. this.loadedConfigs.set(levelId, levelConfig);
  139. return levelConfig;
  140. }
  141. } catch (error) {
  142. console.error(`关卡 ${levelId} 配置加载失败:`, error);
  143. }
  144. return null;
  145. }
  146. /**
  147. * 验证关卡配置数据结构
  148. */
  149. private validateLevelConfigData(jsonData: any, levelId: number): LevelConfig | null {
  150. try {
  151. // 基本字段验证
  152. const weapons = jsonData.weapons || jsonData.availableWeapons;
  153. if (!jsonData.name || !jsonData.scene || !weapons) {
  154. console.error(`关卡 ${levelId} JSON数据缺少必要字段`);
  155. if (!jsonData.name) console.error(` 缺少字段: name`);
  156. if (!jsonData.scene) console.error(` 缺少字段: scene`);
  157. if (!weapons) console.error(` 缺少字段: weapons 或 availableWeapons`);
  158. return null;
  159. }
  160. // 检查武器列表是否为空
  161. if (!Array.isArray(weapons) || weapons.length === 0) {
  162. console.error(`关卡 ${levelId} 没有配置武器列表`);
  163. return null;
  164. }
  165. // 构建关卡配置对象
  166. const levelConfig: LevelConfig = {
  167. levelId: levelId,
  168. name: jsonData.name,
  169. scene: jsonData.scene,
  170. description: jsonData.description,
  171. backgroundImage: jsonData.backgroundImage, // 添加背景图片字段
  172. weapons: weapons,
  173. waves: [],
  174. levelSettings: {
  175. timeLimit: jsonData.timeLimit,
  176. difficulty: jsonData.difficulty,
  177. healthMultiplier: jsonData.healthMultiplier,
  178. ...jsonData.levelSettings
  179. }
  180. };
  181. // 验证波次配置
  182. if (Array.isArray(jsonData.waves)) {
  183. for (const waveData of jsonData.waves) {
  184. const wave: WaveConfig = {
  185. waveId: waveData.waveId || 0,
  186. enemies: [],
  187. healthMultiplier: waveData.healthMultiplier
  188. };
  189. if (Array.isArray(waveData.enemies)) {
  190. for (const enemyData of waveData.enemies) {
  191. const enemy: EnemyWaveConfig = {
  192. enemyType: enemyData.enemyType || '',
  193. count: enemyData.count || 1,
  194. spawnInterval: enemyData.spawnInterval || 5,
  195. characteristics: enemyData.characteristics || []
  196. };
  197. wave.enemies.push(enemy);
  198. }
  199. }
  200. levelConfig.waves.push(wave);
  201. }
  202. }
  203. return levelConfig;
  204. } catch (error) {
  205. console.error(`关卡 ${levelId} 数据验证失败:`, error);
  206. return null;
  207. }
  208. }
  209. /**
  210. * 获取所有可用关卡ID列表
  211. */
  212. public getAvailableLevels(): number[] {
  213. return this.levelConfigs
  214. .filter(config => config.configFile !== null)
  215. .map(config => config.levelId)
  216. .sort((a, b) => a - b);
  217. }
  218. /**
  219. * 获取关卡名称
  220. */
  221. public getLevelName(levelId: number): string {
  222. const config = this.levelConfigs.find(c => c.levelId === levelId);
  223. return config ? config.levelName : `关卡 ${levelId}`;
  224. }
  225. /**
  226. * 检查关卡是否已配置
  227. */
  228. public isLevelConfigured(levelId: number): boolean {
  229. const config = this.levelConfigs.find(c => c.levelId === levelId);
  230. return config !== undefined && config.configFile !== null;
  231. }
  232. /**
  233. * 获取总关卡数
  234. */
  235. public getTotalLevels(): number {
  236. return this.totalLevels;
  237. }
  238. /**
  239. * 重新加载所有关卡配置(开发调试用)
  240. */
  241. public reloadAllConfigs() {
  242. this.loadedConfigs.clear();
  243. this.validateConfigs();
  244. }
  245. /**
  246. * 调试:打印所有关卡配置信息
  247. */
  248. public debugPrintAllConfigs() {
  249. console.log(`=== 所有关卡配置信息 ===`);
  250. console.log(`总关卡数: ${this.totalLevels}`);
  251. for (const config of this.levelConfigs) {
  252. console.log(`关卡 ${config.levelId}: ${config.levelName}`);
  253. console.log(` JSON文件: ${config.configFile ? config.configFile.name : '未设置'}`);
  254. }
  255. }
  256. }