level_config_manager.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. # -*- coding: utf-8 -*-
  2. """
  3. 关卡配置管理器
  4. 负责从Excel读取关卡配置数据,并智能合并到现有JSON文件中
  5. 只修改或新增策划表中配置的字段,不会完全覆盖JSON文件
  6. """
  7. import pandas as pd
  8. import json
  9. import os
  10. from datetime import datetime
  11. from pathlib import Path
  12. import re
  13. class LevelConfigManager:
  14. def __init__(self, excel_path=None, levels_dir=None):
  15. """
  16. 初始化关卡配置管理器
  17. Args:
  18. excel_path: Excel文件路径
  19. levels_dir: 关卡JSON文件目录
  20. """
  21. self.excel_path = excel_path
  22. self.levels_dir = levels_dir or "d:/CocosGame/Pong/assets/resources/data/levels"
  23. self.backup_dir = os.path.join(os.path.dirname(self.levels_dir), "backups", "levels")
  24. # 确保备份目录存在
  25. os.makedirs(self.backup_dir, exist_ok=True)
  26. # 配置数据存储
  27. self.basic_config = None
  28. self.weapon_config = None
  29. self.wave_config = None
  30. self.enemy_config = None
  31. self.energy_exp_config = None
  32. self.energy_upgrade_config = None
  33. def read_excel_data(self, excel_path=None):
  34. """
  35. 读取Excel文件中的所有工作表数据
  36. Args:
  37. excel_path: Excel文件路径,如果不提供则使用初始化时的路径
  38. Returns:
  39. bool: 是否成功读取数据
  40. """
  41. if excel_path:
  42. self.excel_path = excel_path
  43. if not self.excel_path or not os.path.exists(self.excel_path):
  44. print(f"错误: Excel文件不存在: {self.excel_path}")
  45. return False
  46. try:
  47. # 读取所有工作表
  48. all_sheets = pd.read_excel(self.excel_path, sheet_name=None)
  49. # 解析各个工作表
  50. self._parse_basic_config(all_sheets)
  51. self._parse_weapon_config(all_sheets)
  52. self._parse_wave_config(all_sheets)
  53. self._parse_enemy_config(all_sheets)
  54. self._parse_energy_exp_config(all_sheets)
  55. self._parse_energy_upgrade_config(all_sheets)
  56. print(f"成功读取Excel数据: {self.excel_path}")
  57. return True
  58. except Exception as e:
  59. print(f"读取Excel文件失败: {e}")
  60. return False
  61. def _parse_basic_config(self, all_sheets):
  62. """
  63. 解析关卡基础配置工作表
  64. """
  65. sheet_names = ['关卡基础配置', 'Level Config', 'levels', '关卡配置']
  66. for sheet_name in sheet_names:
  67. if sheet_name in all_sheets:
  68. self.basic_config = all_sheets[sheet_name]
  69. print(f"找到关卡基础配置工作表: {sheet_name}")
  70. return
  71. print("警告: 未找到关卡基础配置工作表")
  72. def _parse_weapon_config(self, all_sheets):
  73. """
  74. 解析关卡武器配置工作表
  75. """
  76. sheet_names = ['关卡武器配置', 'Level Weapon Config', 'level_weapons', '武器配置']
  77. for sheet_name in sheet_names:
  78. if sheet_name in all_sheets:
  79. self.weapon_config = all_sheets[sheet_name]
  80. print(f"找到关卡武器配置工作表: {sheet_name}")
  81. return
  82. print("警告: 未找到关卡武器配置工作表")
  83. def _parse_wave_config(self, all_sheets):
  84. """
  85. 解析关卡波次配置工作表
  86. """
  87. sheet_names = ['关卡波次配置', 'Wave Config', 'waves', '波次配置']
  88. for sheet_name in sheet_names:
  89. if sheet_name in all_sheets:
  90. self.wave_config = all_sheets[sheet_name]
  91. print(f"找到关卡波次配置工作表: {sheet_name}")
  92. return
  93. print("警告: 未找到关卡波次配置工作表")
  94. def _parse_enemy_config(self, all_sheets):
  95. """
  96. 解析敌人详细配置工作表
  97. """
  98. sheet_names = ['敌人详细配置', 'Enemy Detail Config', 'enemy_details', '敌人配置']
  99. for sheet_name in sheet_names:
  100. if sheet_name in all_sheets:
  101. self.enemy_config = all_sheets[sheet_name]
  102. print(f"找到敌人详细配置工作表: {sheet_name}")
  103. return
  104. print("警告: 未找到敌人详细配置工作表")
  105. def _parse_energy_exp_config(self, all_sheets):
  106. """
  107. 解析能量条经验值配置工作表
  108. """
  109. sheet_names = ['能量经验配置', 'Energy Exp Config', 'energy_exp', '能量经验']
  110. for sheet_name in sheet_names:
  111. if sheet_name in all_sheets:
  112. self.energy_exp_config = all_sheets[sheet_name]
  113. print(f"找到能量条经验值配置工作表: {sheet_name}")
  114. return
  115. print("警告: 未找到能量条经验值配置工作表")
  116. def _parse_energy_upgrade_config(self, all_sheets):
  117. """
  118. 解析能量条升级配置工作表
  119. """
  120. sheet_names = ['能量最大值升级', 'Energy Upgrade Config', 'energy_upgrade', '能量升级']
  121. for sheet_name in sheet_names:
  122. if sheet_name in all_sheets:
  123. self.energy_upgrade_config = all_sheets[sheet_name]
  124. print(f"找到能量条升级配置工作表: {sheet_name}")
  125. return
  126. print("警告: 未找到能量条升级配置工作表")
  127. def get_all_level_ids(self):
  128. """
  129. 获取所有关卡ID
  130. Returns:
  131. set: 所有关卡ID的集合
  132. """
  133. level_ids = set()
  134. # 从基础配置中获取关卡ID
  135. if self.basic_config is not None:
  136. for _, row in self.basic_config.iterrows():
  137. if pd.notna(row['关卡ID']):
  138. level_ids.add(str(row['关卡ID']))
  139. # 从武器配置中获取关卡ID
  140. if self.weapon_config is not None:
  141. for _, row in self.weapon_config.iterrows():
  142. if pd.notna(row['关卡ID']):
  143. level_ids.add(str(row['关卡ID']))
  144. # 从波次配置中获取关卡ID
  145. if self.wave_config is not None:
  146. for _, row in self.wave_config.iterrows():
  147. if pd.notna(row['关卡ID']):
  148. level_ids.add(str(row['关卡ID']))
  149. return level_ids
  150. def merge_configurations(self):
  151. """
  152. 合并Excel配置到现有JSON文件
  153. Returns:
  154. dict: 合并结果统计
  155. """
  156. if not any([self.basic_config is not None, self.weapon_config is not None,
  157. self.wave_config is not None, self.enemy_config is not None]):
  158. print("错误: 没有可用的配置数据")
  159. return {"success": False, "message": "没有可用的配置数据"}
  160. level_ids = self.get_all_level_ids()
  161. if not level_ids:
  162. print("错误: 没有找到任何关卡ID")
  163. return {"success": False, "message": "没有找到任何关卡ID"}
  164. results = {
  165. "success": True,
  166. "processed_levels": [],
  167. "created_levels": [],
  168. "updated_levels": [],
  169. "errors": []
  170. }
  171. for level_id in level_ids:
  172. try:
  173. result = self._update_level_config(level_id)
  174. results["processed_levels"].append(level_id)
  175. if result["created"]:
  176. results["created_levels"].append(level_id)
  177. else:
  178. results["updated_levels"].append(level_id)
  179. except Exception as e:
  180. error_msg = f"处理关卡 {level_id} 时出错: {str(e)}"
  181. print(error_msg)
  182. results["errors"].append(error_msg)
  183. return results
  184. def _update_level_config(self, level_id):
  185. """
  186. 更新单个关卡的配置
  187. Args:
  188. level_id: 关卡ID
  189. Returns:
  190. dict: 更新结果
  191. """
  192. json_path = os.path.join(self.levels_dir, f"{level_id}.json")
  193. # 读取现有配置或创建新配置
  194. if os.path.exists(json_path):
  195. with open(json_path, 'r', encoding='utf-8') as f:
  196. existing_config = json.load(f)
  197. created = False
  198. else:
  199. existing_config = {
  200. "levelId": level_id,
  201. "name": "",
  202. "scene": "grassland",
  203. "description": "",
  204. "backgroundImage": "images/LevelBackground/BG1",
  205. "availableWeapons": [],
  206. "coinReward": 100,
  207. "diamondReward": 0,
  208. "initialCoins": 50,
  209. "timeLimit": 300,
  210. "difficulty": "normal",
  211. "healthMultiplier": 1.0,
  212. "levelSettings": {
  213. "energyMax": 5 # 默认能量最大值
  214. },
  215. "waves": []
  216. }
  217. created = True
  218. # 备份现有文件
  219. if not created:
  220. self._backup_level_file(json_path, level_id)
  221. # 更新基础配置
  222. if self.basic_config is not None:
  223. basic_row = self.basic_config[self.basic_config['关卡ID'] == level_id]
  224. if not basic_row.empty:
  225. row = basic_row.iloc[0]
  226. if pd.notna(row['关卡名称']):
  227. existing_config['name'] = str(row['关卡名称'])
  228. if pd.notna(row['场景']):
  229. existing_config['scene'] = str(row['场景'])
  230. if pd.notna(row['描述']):
  231. existing_config['description'] = str(row['描述'])
  232. if pd.notna(row['关卡背景图路径']):
  233. existing_config['backgroundImage'] = str(row['关卡背景图路径'])
  234. if pd.notna(row['钞票奖励']):
  235. existing_config['coinReward'] = int(row['钞票奖励'])
  236. if pd.notna(row['钻石奖励']):
  237. existing_config['diamondReward'] = int(row['钻石奖励'])
  238. if pd.notna(row['生命倍数']):
  239. existing_config['healthMultiplier'] = float(row['生命倍数'])
  240. if pd.notna(row['难度']):
  241. existing_config['difficulty'] = str(row['难度'])
  242. if pd.notna(row['初始金币']):
  243. existing_config['initialCoins'] = int(row['初始金币'])
  244. # 确保levelSettings存在(用于后续的energyMaxUpgrades配置)
  245. if 'levelSettings' not in existing_config:
  246. existing_config['levelSettings'] = {}
  247. # 注释:energyMax配置已移除,现在只使用energyMaxUpgrades数组管理能量最大值
  248. # 注释:energyExpValues配置已移除,现在只使用energyMaxUpgrades配置
  249. # 能量条经验值现在直接在游戏代码中硬编码为递增模式
  250. # 更新能量条升级配置
  251. if self.energy_upgrade_config is not None:
  252. energy_upgrade_row = self.energy_upgrade_config[self.energy_upgrade_config['关卡ID'] == level_id]
  253. if not energy_upgrade_row.empty:
  254. row = energy_upgrade_row.iloc[0]
  255. if 'levelSettings' not in existing_config:
  256. existing_config['levelSettings'] = {}
  257. # 读取20次升级后的最大值配置
  258. energy_max_values = []
  259. for i in range(1, 21): # 1到20次升级
  260. col_name = f'第{i}次升级后最大值'
  261. if col_name in row and pd.notna(row[col_name]):
  262. energy_max_values.append(int(row[col_name]))
  263. else:
  264. # 如果没有配置,使用默认值(基于固定初始值6递增)
  265. base_max = 6 # 固定初始值,与energyMaxUpgrades数组第一个值保持一致
  266. energy_max_values.append(base_max + (i - 1)) # 修正递增逻辑
  267. existing_config['levelSettings']['energyMaxUpgrades'] = energy_max_values
  268. # 更新武器配置
  269. if self.weapon_config is not None:
  270. weapon_rows = self.weapon_config[self.weapon_config['关卡ID'] == level_id]
  271. available_weapons = []
  272. for _, weapon_row in weapon_rows.iterrows():
  273. if pd.notna(weapon_row['可用武器']):
  274. weapons_str = str(weapon_row['可用武器'])
  275. # 支持多种分隔符
  276. weapons = re.split(r'[、,,;;]', weapons_str)
  277. available_weapons.extend([w.strip() for w in weapons if w.strip()])
  278. if available_weapons:
  279. existing_config['availableWeapons'] = available_weapons
  280. # 更新波次配置
  281. if self.wave_config is not None and self.enemy_config is not None:
  282. level_waves = self.wave_config[self.wave_config['关卡ID'] == level_id]
  283. waves_data = []
  284. for _, wave_row in level_waves.iterrows():
  285. wave_id = int(wave_row['波次ID']) if pd.notna(wave_row['波次ID']) else 1
  286. # 获取该波次的敌人配置
  287. wave_enemies = []
  288. wave_enemy_data = self.enemy_config[
  289. (self.enemy_config['关卡ID'] == level_id) &
  290. (self.enemy_config['波次ID'] == wave_id)
  291. ]
  292. for _, enemy_row in wave_enemy_data.iterrows():
  293. enemy_data = {
  294. 'enemyType': str(enemy_row['敌人类型']) if pd.notna(enemy_row['敌人类型']) else 'normal_zombie',
  295. 'count': int(enemy_row['数量']) if pd.notna(enemy_row['数量']) else 1,
  296. 'spawnInterval': float(enemy_row['生成间隔']) if pd.notna(enemy_row['生成间隔']) else 2.0,
  297. 'spawnDelay': float(enemy_row['生成延迟']) if pd.notna(enemy_row['生成延迟']) else 0.0,
  298. 'characteristics': str(enemy_row['特征描述']) if pd.notna(enemy_row['特征描述']) else ''
  299. }
  300. wave_enemies.append(enemy_data)
  301. wave_data = {
  302. 'waveId': wave_id,
  303. 'enemies': wave_enemies
  304. }
  305. waves_data.append(wave_data)
  306. if waves_data:
  307. # 按波次ID排序
  308. waves_data.sort(key=lambda x: x['waveId'])
  309. existing_config['waves'] = waves_data
  310. # 保存更新后的配置
  311. with open(json_path, 'w', encoding='utf-8') as f:
  312. json.dump(existing_config, f, ensure_ascii=False, indent=2)
  313. print(f"{'创建' if created else '更新'}关卡配置: {level_id}")
  314. return {"created": created, "updated": not created}
  315. def _backup_level_file(self, json_path, level_id):
  316. """
  317. 备份关卡JSON文件
  318. Args:
  319. json_path: JSON文件路径
  320. level_id: 关卡ID
  321. """
  322. try:
  323. timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
  324. backup_filename = f"{level_id}_{timestamp}.json"
  325. backup_path = os.path.join(self.backup_dir, backup_filename)
  326. with open(json_path, 'r', encoding='utf-8') as src:
  327. with open(backup_path, 'w', encoding='utf-8') as dst:
  328. dst.write(src.read())
  329. print(f"备份关卡文件: {backup_path}")
  330. except Exception as e:
  331. print(f"备份关卡文件失败: {e}")
  332. def import_from_excel(self, excel_path=None, levels_dir=None):
  333. """
  334. 从Excel导入关卡配置的完整流程
  335. Args:
  336. excel_path: Excel文件路径
  337. levels_dir: 关卡JSON文件目录
  338. Returns:
  339. dict: 导入结果
  340. """
  341. if excel_path:
  342. self.excel_path = excel_path
  343. if levels_dir:
  344. self.levels_dir = levels_dir
  345. print("开始导入关卡配置...")
  346. # 读取Excel数据
  347. if not self.read_excel_data():
  348. return {"success": False, "message": "读取Excel数据失败"}
  349. # 合并配置
  350. results = self.merge_configurations()
  351. if results["success"]:
  352. print(f"关卡配置导入完成:")
  353. print(f" 处理关卡数: {len(results['processed_levels'])}")
  354. print(f" 新建关卡数: {len(results['created_levels'])}")
  355. print(f" 更新关卡数: {len(results['updated_levels'])}")
  356. if results["errors"]:
  357. print(f" 错误数: {len(results['errors'])}")
  358. for error in results["errors"]:
  359. print(f" {error}")
  360. return results
  361. # 使用示例
  362. if __name__ == "__main__":
  363. # 创建关卡配置管理器
  364. manager = LevelConfigManager(
  365. excel_path="d:/CocosGame/Pong/assets/resources/data/excel/关卡配置/关卡配置表.xlsx",
  366. levels_dir="d:/CocosGame/Pong/assets/resources/data/levels"
  367. )
  368. # 导入配置
  369. result = manager.import_from_excel()
  370. if result["success"]:
  371. print("关卡配置导入成功!")
  372. else:
  373. print(f"关卡配置导入失败: {result['message']}")