| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459 |
- # -*- coding: utf-8 -*-
- """
- 关卡配置管理器
- 负责从Excel读取关卡配置数据,并智能合并到现有JSON文件中
- 只修改或新增策划表中配置的字段,不会完全覆盖JSON文件
- """
- import pandas as pd
- import json
- import os
- from datetime import datetime
- from pathlib import Path
- import re
- class LevelConfigManager:
- def __init__(self, excel_path=None, levels_dir=None):
- """
- 初始化关卡配置管理器
-
- Args:
- excel_path: Excel文件路径
- levels_dir: 关卡JSON文件目录
- """
- self.excel_path = excel_path
- self.levels_dir = levels_dir or "d:/CocosGame/Pong/assets/resources/data/levels"
- self.backup_dir = os.path.join(os.path.dirname(self.levels_dir), "backups", "levels")
-
- # 确保备份目录存在
- os.makedirs(self.backup_dir, exist_ok=True)
-
- # 配置数据存储
- self.basic_config = None
- self.weapon_config = None
- self.wave_config = None
- self.enemy_config = None
- self.energy_exp_config = None
- self.energy_upgrade_config = None
-
- def read_excel_data(self, excel_path=None):
- """
- 读取Excel文件中的所有工作表数据
-
- Args:
- excel_path: Excel文件路径,如果不提供则使用初始化时的路径
-
- Returns:
- bool: 是否成功读取数据
- """
- if excel_path:
- self.excel_path = excel_path
-
- if not self.excel_path or not os.path.exists(self.excel_path):
- print(f"错误: Excel文件不存在: {self.excel_path}")
- return False
-
- try:
- # 读取所有工作表
- all_sheets = pd.read_excel(self.excel_path, sheet_name=None)
-
- # 解析各个工作表
- self._parse_basic_config(all_sheets)
- self._parse_weapon_config(all_sheets)
- self._parse_wave_config(all_sheets)
- self._parse_enemy_config(all_sheets)
- self._parse_energy_exp_config(all_sheets)
- self._parse_energy_upgrade_config(all_sheets)
-
- print(f"成功读取Excel数据: {self.excel_path}")
- return True
-
- except Exception as e:
- print(f"读取Excel文件失败: {e}")
- return False
-
- def _parse_basic_config(self, all_sheets):
- """
- 解析关卡基础配置工作表
- """
- sheet_names = ['关卡基础配置', 'Level Config', 'levels', '关卡配置']
-
- for sheet_name in sheet_names:
- if sheet_name in all_sheets:
- self.basic_config = all_sheets[sheet_name]
- print(f"找到关卡基础配置工作表: {sheet_name}")
- return
-
- print("警告: 未找到关卡基础配置工作表")
-
- def _parse_weapon_config(self, all_sheets):
- """
- 解析关卡武器配置工作表
- """
- sheet_names = ['关卡武器配置', 'Level Weapon Config', 'level_weapons', '武器配置']
-
- for sheet_name in sheet_names:
- if sheet_name in all_sheets:
- self.weapon_config = all_sheets[sheet_name]
- print(f"找到关卡武器配置工作表: {sheet_name}")
- return
-
- print("警告: 未找到关卡武器配置工作表")
-
- def _parse_wave_config(self, all_sheets):
- """
- 解析关卡波次配置工作表
- """
- sheet_names = ['关卡波次配置', 'Wave Config', 'waves', '波次配置']
-
- for sheet_name in sheet_names:
- if sheet_name in all_sheets:
- self.wave_config = all_sheets[sheet_name]
- print(f"找到关卡波次配置工作表: {sheet_name}")
- return
-
- print("警告: 未找到关卡波次配置工作表")
-
- def _parse_enemy_config(self, all_sheets):
- """
- 解析敌人详细配置工作表
- """
- sheet_names = ['敌人详细配置', 'Enemy Detail Config', 'enemy_details', '敌人配置']
-
- for sheet_name in sheet_names:
- if sheet_name in all_sheets:
- self.enemy_config = all_sheets[sheet_name]
- print(f"找到敌人详细配置工作表: {sheet_name}")
- return
-
- print("警告: 未找到敌人详细配置工作表")
-
- def _parse_energy_exp_config(self, all_sheets):
- """
- 解析能量条经验值配置工作表
- """
- sheet_names = ['能量经验配置', 'Energy Exp Config', 'energy_exp', '能量经验']
-
- for sheet_name in sheet_names:
- if sheet_name in all_sheets:
- self.energy_exp_config = all_sheets[sheet_name]
- print(f"找到能量条经验值配置工作表: {sheet_name}")
- return
-
- print("警告: 未找到能量条经验值配置工作表")
-
- def _parse_energy_upgrade_config(self, all_sheets):
- """
- 解析能量条升级配置工作表
- """
- sheet_names = ['能量最大值升级', 'Energy Upgrade Config', 'energy_upgrade', '能量升级']
-
- for sheet_name in sheet_names:
- if sheet_name in all_sheets:
- self.energy_upgrade_config = all_sheets[sheet_name]
- print(f"找到能量条升级配置工作表: {sheet_name}")
- return
-
- print("警告: 未找到能量条升级配置工作表")
-
- def get_all_level_ids(self):
- """
- 获取所有关卡ID
-
- Returns:
- set: 所有关卡ID的集合
- """
- level_ids = set()
-
- # 从基础配置中获取关卡ID
- if self.basic_config is not None:
- for _, row in self.basic_config.iterrows():
- if pd.notna(row['关卡ID']):
- level_ids.add(str(row['关卡ID']))
-
- # 从武器配置中获取关卡ID
- if self.weapon_config is not None:
- for _, row in self.weapon_config.iterrows():
- if pd.notna(row['关卡ID']):
- level_ids.add(str(row['关卡ID']))
-
- # 从波次配置中获取关卡ID
- if self.wave_config is not None:
- for _, row in self.wave_config.iterrows():
- if pd.notna(row['关卡ID']):
- level_ids.add(str(row['关卡ID']))
-
- return level_ids
-
- def merge_configurations(self):
- """
- 合并Excel配置到现有JSON文件
-
- Returns:
- dict: 合并结果统计
- """
- if not any([self.basic_config is not None, self.weapon_config is not None,
- self.wave_config is not None, self.enemy_config is not None]):
- print("错误: 没有可用的配置数据")
- return {"success": False, "message": "没有可用的配置数据"}
-
- level_ids = self.get_all_level_ids()
- if not level_ids:
- print("错误: 没有找到任何关卡ID")
- return {"success": False, "message": "没有找到任何关卡ID"}
-
- results = {
- "success": True,
- "processed_levels": [],
- "created_levels": [],
- "updated_levels": [],
- "errors": []
- }
-
- for level_id in level_ids:
- try:
- result = self._update_level_config(level_id)
- results["processed_levels"].append(level_id)
-
- if result["created"]:
- results["created_levels"].append(level_id)
- else:
- results["updated_levels"].append(level_id)
-
- except Exception as e:
- error_msg = f"处理关卡 {level_id} 时出错: {str(e)}"
- print(error_msg)
- results["errors"].append(error_msg)
-
- return results
-
- def _update_level_config(self, level_id):
- """
- 更新单个关卡的配置
-
- Args:
- level_id: 关卡ID
-
- Returns:
- dict: 更新结果
- """
- json_path = os.path.join(self.levels_dir, f"{level_id}.json")
-
- # 读取现有配置或创建新配置
- if os.path.exists(json_path):
- with open(json_path, 'r', encoding='utf-8') as f:
- existing_config = json.load(f)
- created = False
- else:
- existing_config = {
- "levelId": level_id,
- "name": "",
- "scene": "grassland",
- "description": "",
- "backgroundImage": "images/LevelBackground/BG1",
- "availableWeapons": [],
- "coinReward": 100,
- "diamondReward": 0,
- "initialCoins": 50,
- "timeLimit": 300,
- "difficulty": "normal",
- "healthMultiplier": 1.0,
- "levelSettings": {
- "energyMax": 5 # 默认能量最大值
- },
- "waves": []
- }
- created = True
-
- # 备份现有文件
- if not created:
- self._backup_level_file(json_path, level_id)
-
- # 更新基础配置
- if self.basic_config is not None:
- basic_row = self.basic_config[self.basic_config['关卡ID'] == level_id]
- if not basic_row.empty:
- row = basic_row.iloc[0]
-
- if pd.notna(row['关卡名称']):
- existing_config['name'] = str(row['关卡名称'])
- if pd.notna(row['场景']):
- existing_config['scene'] = str(row['场景'])
- if pd.notna(row['描述']):
- existing_config['description'] = str(row['描述'])
- if pd.notna(row['关卡背景图路径']):
- existing_config['backgroundImage'] = str(row['关卡背景图路径'])
- if pd.notna(row['钞票奖励']):
- existing_config['coinReward'] = int(row['钞票奖励'])
- if pd.notna(row['钻石奖励']):
- existing_config['diamondReward'] = int(row['钻石奖励'])
- if pd.notna(row['生命倍数']):
- existing_config['healthMultiplier'] = float(row['生命倍数'])
- if pd.notna(row['难度']):
- existing_config['difficulty'] = str(row['难度'])
- if pd.notna(row['初始金币']):
- existing_config['initialCoins'] = int(row['初始金币'])
-
- # 确保levelSettings存在(用于后续的energyMaxUpgrades配置)
- if 'levelSettings' not in existing_config:
- existing_config['levelSettings'] = {}
-
- # 注释:energyMax配置已移除,现在只使用energyMaxUpgrades数组管理能量最大值
-
- # 注释:energyExpValues配置已移除,现在只使用energyMaxUpgrades配置
- # 能量条经验值现在直接在游戏代码中硬编码为递增模式
-
- # 更新能量条升级配置
- if self.energy_upgrade_config is not None:
- energy_upgrade_row = self.energy_upgrade_config[self.energy_upgrade_config['关卡ID'] == level_id]
- if not energy_upgrade_row.empty:
- row = energy_upgrade_row.iloc[0]
-
- if 'levelSettings' not in existing_config:
- existing_config['levelSettings'] = {}
-
- # 读取20次升级后的最大值配置
- energy_max_values = []
- for i in range(1, 21): # 1到20次升级
- col_name = f'第{i}次升级后最大值'
- if col_name in row and pd.notna(row[col_name]):
- energy_max_values.append(int(row[col_name]))
- else:
- # 如果没有配置,使用默认值(基于固定初始值6递增)
- base_max = 6 # 固定初始值,与energyMaxUpgrades数组第一个值保持一致
- energy_max_values.append(base_max + (i - 1)) # 修正递增逻辑
-
- existing_config['levelSettings']['energyMaxUpgrades'] = energy_max_values
-
- # 更新武器配置
- if self.weapon_config is not None:
- weapon_rows = self.weapon_config[self.weapon_config['关卡ID'] == level_id]
- available_weapons = []
-
- for _, weapon_row in weapon_rows.iterrows():
- if pd.notna(weapon_row['可用武器']):
- weapons_str = str(weapon_row['可用武器'])
- # 支持多种分隔符
- weapons = re.split(r'[、,,;;]', weapons_str)
- available_weapons.extend([w.strip() for w in weapons if w.strip()])
-
- if available_weapons:
- existing_config['availableWeapons'] = available_weapons
-
- # 更新波次配置
- if self.wave_config is not None and self.enemy_config is not None:
- level_waves = self.wave_config[self.wave_config['关卡ID'] == level_id]
- waves_data = []
-
- for _, wave_row in level_waves.iterrows():
- wave_id = int(wave_row['波次ID']) if pd.notna(wave_row['波次ID']) else 1
-
- # 获取该波次的敌人配置
- wave_enemies = []
- wave_enemy_data = self.enemy_config[
- (self.enemy_config['关卡ID'] == level_id) &
- (self.enemy_config['波次ID'] == wave_id)
- ]
-
- for _, enemy_row in wave_enemy_data.iterrows():
- enemy_data = {
- 'enemyType': str(enemy_row['敌人类型']) if pd.notna(enemy_row['敌人类型']) else 'normal_zombie',
- 'count': int(enemy_row['数量']) if pd.notna(enemy_row['数量']) else 1,
- 'spawnInterval': float(enemy_row['生成间隔']) if pd.notna(enemy_row['生成间隔']) else 2.0,
- 'spawnDelay': float(enemy_row['生成延迟']) if pd.notna(enemy_row['生成延迟']) else 0.0,
- 'characteristics': str(enemy_row['特征描述']) if pd.notna(enemy_row['特征描述']) else ''
- }
- wave_enemies.append(enemy_data)
-
- wave_data = {
- 'waveId': wave_id,
- 'enemies': wave_enemies
- }
- waves_data.append(wave_data)
-
- if waves_data:
- # 按波次ID排序
- waves_data.sort(key=lambda x: x['waveId'])
- existing_config['waves'] = waves_data
-
- # 保存更新后的配置
- with open(json_path, 'w', encoding='utf-8') as f:
- json.dump(existing_config, f, ensure_ascii=False, indent=2)
-
- print(f"{'创建' if created else '更新'}关卡配置: {level_id}")
- return {"created": created, "updated": not created}
-
- def _backup_level_file(self, json_path, level_id):
- """
- 备份关卡JSON文件
-
- Args:
- json_path: JSON文件路径
- level_id: 关卡ID
- """
- try:
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
- backup_filename = f"{level_id}_{timestamp}.json"
- backup_path = os.path.join(self.backup_dir, backup_filename)
-
- with open(json_path, 'r', encoding='utf-8') as src:
- with open(backup_path, 'w', encoding='utf-8') as dst:
- dst.write(src.read())
-
- print(f"备份关卡文件: {backup_path}")
-
- except Exception as e:
- print(f"备份关卡文件失败: {e}")
-
- def import_from_excel(self, excel_path=None, levels_dir=None):
- """
- 从Excel导入关卡配置的完整流程
-
- Args:
- excel_path: Excel文件路径
- levels_dir: 关卡JSON文件目录
-
- Returns:
- dict: 导入结果
- """
- if excel_path:
- self.excel_path = excel_path
- if levels_dir:
- self.levels_dir = levels_dir
-
- print("开始导入关卡配置...")
-
- # 读取Excel数据
- if not self.read_excel_data():
- return {"success": False, "message": "读取Excel数据失败"}
-
- # 合并配置
- results = self.merge_configurations()
-
- if results["success"]:
- print(f"关卡配置导入完成:")
- print(f" 处理关卡数: {len(results['processed_levels'])}")
- print(f" 新建关卡数: {len(results['created_levels'])}")
- print(f" 更新关卡数: {len(results['updated_levels'])}")
- if results["errors"]:
- print(f" 错误数: {len(results['errors'])}")
- for error in results["errors"]:
- print(f" {error}")
-
- return results
- # 使用示例
- if __name__ == "__main__":
- # 创建关卡配置管理器
- manager = LevelConfigManager(
- excel_path="d:/CocosGame/Pong/assets/resources/data/excel/关卡配置/关卡配置表.xlsx",
- levels_dir="d:/CocosGame/Pong/assets/resources/data/levels"
- )
-
- # 导入配置
- result = manager.import_from_excel()
-
- if result["success"]:
- print("关卡配置导入成功!")
- else:
- print(f"关卡配置导入失败: {result['message']}")
|