181404010226 5 miesięcy temu
rodzic
commit
07a9120016
44 zmienionych plików z 2493 dodań i 799 usunięć
  1. 20 0
      assets/resources/data/ballController.json
  2. 11 0
      assets/resources/data/ballController.json.meta
  3. 96 509
      assets/resources/data/enemies.json
  4. 19 0
      assets/resources/data/excel/BallController配置模板.csv
  5. 1 1
      assets/resources/data/excel/BallController配置模板.csv.meta
  6. 0 21
      assets/resources/data/excel/BallController配置表.csv
  7. BIN
      assets/resources/data/excel/BallController配置表.xlsx
  8. 12 0
      assets/resources/data/excel/BallController配置表.xlsx.meta
  9. 9 0
      assets/resources/data/excel/__pycache__.meta
  10. BIN
      assets/resources/data/excel/__pycache__/config_manager.cpython-313.pyc
  11. 2 2
      assets/resources/data/excel/__pycache__/config_manager.cpython-313.pyc.meta
  12. 895 0
      assets/resources/data/excel/config_manager.py
  13. 2 2
      assets/resources/data/excel/config_manager.py.meta
  14. 17 0
      assets/resources/data/excel/requirements.txt
  15. 1 1
      assets/resources/data/excel/requirements.txt.meta
  16. 127 0
      assets/resources/data/excel/test_excel_parse.py
  17. 12 0
      assets/resources/data/excel/test_excel_parse.py.meta
  18. 141 0
      assets/resources/data/excel/一键部署工具.bat
  19. 12 0
      assets/resources/data/excel/一键部署工具.bat.meta
  20. BIN
      assets/resources/data/excel/关卡配置.zip
  21. 0 12
      assets/resources/data/excel/关卡配置/EnemyTypes.csv
  22. 0 2
      assets/resources/data/excel/关卡配置/LevelInfo.csv
  23. 0 6
      assets/resources/data/excel/关卡配置/LevelInfo_updated.csv
  24. 25 0
      assets/resources/data/excel/启动配置工具.bat
  25. 12 0
      assets/resources/data/excel/启动配置工具.bat.meta
  26. 162 0
      assets/resources/data/excel/多配置表管理工具使用说明.md
  27. 1 1
      assets/resources/data/excel/多配置表管理工具使用说明.md.meta
  28. 119 0
      assets/resources/data/excel/安装依赖库.bat
  29. 12 0
      assets/resources/data/excel/安装依赖库.bat.meta
  30. BIN
      assets/resources/data/excel/技能配置表.xlsx
  31. BIN
      assets/resources/data/excel/敌人配置表.xlsx
  32. BIN
      assets/resources/data/excel/方块武器配置.zip
  33. BIN
      assets/resources/data/excel/方块武器配置/~$方块武器配置表_更新_v2.xlsx
  34. 12 0
      assets/resources/data/excel/方块武器配置/~$方块武器配置表_更新_v2.xlsx.meta
  35. 137 0
      assets/resources/data/excel/自动部署工具.bat
  36. 12 0
      assets/resources/data/excel/自动部署工具.bat.meta
  37. 163 0
      assets/resources/data/excel/部署使用说明.md
  38. 1 1
      assets/resources/data/excel/部署使用说明.md.meta
  39. 43 35
      assets/resources/data/skill.json
  40. 131 61
      assets/scripts/CombatSystem/BallController.ts
  41. 86 66
      assets/scripts/CombatSystem/BlockManager.ts
  42. 145 79
      assets/scripts/CombatSystem/BlockSelection/GameBlockSelection.ts
  43. 46 0
      assets/scripts/Core/ConfigManager.ts
  44. 9 0
      assets/scripts/Test.meta

+ 20 - 0
assets/resources/data/ballController.json

@@ -0,0 +1,20 @@
+{
+  "baseSpeed": 10.0,
+  "maxReflectionRandomness": 0.2,
+  "antiTrapTimeWindow": 5.0,
+  "antiTrapHitThreshold": 5,
+  "deflectionAttemptThreshold": 3,
+  "antiTrapDeflectionMultiplier": 3.0,
+  "FIRE_COOLDOWN": 0.05,
+  "ballRadius": 25.0,
+  "gravityScale": 0.0,
+  "linearDamping": 0.0,
+  "angularDamping": 0.0,
+  "colliderGroup": 1,
+  "colliderTag": 1,
+  "friction": 0.0,
+  "restitution": 1.0,
+  "safeDistance": 20.0,
+  "edgeOffset": 20.0,
+  "sensor": false
+}

+ 11 - 0
assets/resources/data/ballController.json.meta

@@ -0,0 +1,11 @@
+{
+  "ver": "2.0.1",
+  "importer": "json",
+  "imported": true,
+  "uuid": "6251555c-8fac-4a81-a760-6ec36a78de4a",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 96 - 509
assets/resources/data/enemies.json

@@ -6,45 +6,13 @@
       "type": "basic",
       "rarity": "common",
       "weight": 35,
-      "stats": {
-        "health": 10,
-        "speed": 50,
-        "damage": 20,
-        "attackRange": 30,
-        "attackSpeed": 1.0,
-        "defense": 0,
-        "coinReward": 10
-      },
-      "movement": {
-        "type": "straight",
-        "pattern": "walk_forward",
-        "speedVariation": 0.1
-      },
-      "combat": {
-        "attackType": "melee",
-        "attackDelay": 0.5,
-        "attackCooldown": 1.0,
-        "canBlock": false,
-        "blockChance": 0
-      },
-      "visualConfig": {
-        "spritePrefab": "@EnemyAni/001/001",
-        "animations": {
-          "idle": "idle",
-          "walk": "walk",
-          "attack": "attack",
-          "dead": "dead"
-        },
-        "scale": 1.0,
-        "flipX": false
-      },
-      "audioConfig": {
-        "walkSound": "audio/zombie_walk",
-        "attackSound": "audio/zombie_attack",
-        "deathSound": "audio/zombie_death",
-        "hurtSound": "audio/zombie_hurt"
-      },
-      "specialAbilities": []
+      "health": 10,
+      "speed": 50,
+      "attack": 20,
+      "range": 30,
+      "attackSpeed": 1.0,
+      "defense": 0,
+      "goldReward": 1
     },
     {
       "id": "roadblock_zombie",
@@ -52,46 +20,13 @@
       "type": "armored",
       "rarity": "common",
       "weight": 25,
-      "stats": {
-        "health": 18,
-        "speed": 40,
-        "damage": 25,
-        "attackRange": 30,
-        "attackSpeed": 0.8,
-        "defense": 5,
-        "coinReward": 15
-      },
-      "movement": {
-        "type": "straight",
-        "pattern": "walk_forward",
-        "speedVariation": 0.1
-      },
-      "combat": {
-        "attackType": "melee",
-        "attackDelay": 0.6,
-        "attackCooldown": 1.2,
-        "canBlock": true,
-        "blockChance": 0.2
-      },
-      "visualConfig": {
-        "spritePrefab": "@EnemyAni/002/002",
-        "animations": {
-          "idle": "idle",
-          "walk": "walk",
-          "attack": "attack",
-          "dead": "dead",
-          "block": "roadblock_block"
-        },
-        "scale": 1.1,
-        "flipX": false
-      },
-      "audioConfig": {
-        "walkSound": "audio/roadblock_walk",
-        "attackSound": "audio/roadblock_attack",
-        "deathSound": "audio/roadblock_death",
-        "blockSound": "audio/roadblock_block"
-      },
-      "specialAbilities": ["armor_break"]
+      "health": 18,
+      "speed": 40,
+      "attack": 25,
+      "range": 30,
+      "attackSpeed": 0.8,
+      "defense": 5,
+      "goldReward": 15
     },
     {
       "id": "wandering_zombie",
@@ -99,48 +34,13 @@
       "type": "wanderer",
       "rarity": "uncommon",
       "weight": 20,
-      "stats": {
-        "health": 12,
-        "speed": 45,
-        "damage": 30,
-        "attackRange": 40,
-        "attackSpeed": 1.2,
-        "defense": 2,
-        "coinReward": 18
-      },
-      "movement": {
-        "type": "sway",
-        "pattern": "zigzag_forward",
-        "speedVariation": 0.2,
-        "swayAmplitude": 20,
-        "swayFrequency": 2.0
-      },
-      "combat": {
-        "attackType": "melee_weapon",
-        "attackDelay": 0.4,
-        "attackCooldown": 0.8,
-        "weaponType": "baseball_bat",
-        "canBlock": false,
-        "blockChance": 0
-      },
-      "visualConfig": {
-        "spritePrefab": "@EnemyAni/003/003",
-        "animations": {
-          "idle": "idle",
-          "walk": "walk",
-          "attack": "attack",
-          "dead": "dead"
-        },
-        "scale": 1.0,
-        "flipX": false,
-        "weapon": "props/baseball_bat"
-      },
-      "audioConfig": {
-        "walkSound": "audio/wandering_walk",
-        "attackSound": "audio/bat_swing",
-        "deathSound": "audio/wandering_death"
-      },
-      "specialAbilities": ["unpredictable_movement"]
+      "health": 12,
+      "speed": 45,
+      "attack": 30,
+      "range": 40,
+      "attackSpeed": 1.2,
+      "defense": 2,
+      "goldReward": 18
     },
     {
       "id": "mage_zombie",
@@ -148,52 +48,13 @@
       "type": "ranged_caster",
       "rarity": "uncommon",
       "weight": 18,
-      "stats": {
-        "health": 8,
-        "speed": 35,
-        "damage": 35,
-        "attackRange": 200,
-        "attackSpeed": 0.6,
-        "defense": 0,
-        "coinReward": 25
-      },
-      "movement": {
-        "type": "straight",
-        "pattern": "walk_forward",
-        "speedVariation": 0.1
-      },
-      "combat": {
-        "attackType": "magic_projectile",
-        "attackDelay": 0.8,
-        "attackCooldown": 2.0,
-        "projectileType": "magic_bolt",
-        "projectileSpeed": 150,
-        "canBlock": false,
-        "blockChance": 0
-      },
-      "visualConfig": {
-        "spritePrefab": "@EnemyAni/004/004",
-        "animations": {
-          "idle": "idle",
-          "walk": "walk",
-          "attack": "attack",
-          "dead": "dead"
-        },
-        "scale": 1.0,
-        "flipX": false,
-        "weapon": "props/magic_staff"
-      },
-      "audioConfig": {
-        "walkSound": "audio/mage_walk",
-        "attackSound": "audio/magic_cast",
-        "deathSound": "audio/mage_death"
-      },
-      "projectileConfig": {
-        "bulletPrefab": "bullets/MagicBolt",
-        "hitEffect": "effects/MagicHit",
-        "trailEffect": "effects/MagicTrail"
-      },
-      "specialAbilities": ["ranged_attack", "magic_resistance"]
+      "health": 8,
+      "speed": 35,
+      "attack": 35,
+      "range": 200,
+      "attackSpeed": 0.6,
+      "defense": 0,
+      "goldReward": 25
     },
     {
       "id": "archer_zombie",
@@ -201,52 +62,13 @@
       "type": "ranged_archer",
       "rarity": "uncommon",
       "weight": 16,
-      "stats": {
-        "health": 9,
-        "speed": 40,
-        "damage": 40,
-        "attackRange": 250,
-        "attackSpeed": 0.7,
-        "defense": 1,
-        "coinReward": 22
-      },
-      "movement": {
-        "type": "straight",
-        "pattern": "walk_forward",
-        "speedVariation": 0.1
-      },
-      "combat": {
-        "attackType": "arrow_projectile",
-        "attackDelay": 0.6,
-        "attackCooldown": 1.8,
-        "projectileType": "arrow",
-        "projectileSpeed": 200,
-        "canBlock": false,
-        "blockChance": 0
-      },
-      "visualConfig": {
-        "spritePrefab": "@EnemyAni/005/005",
-        "animations": {
-          "idle": "idle",
-          "walk": "walk",
-          "attack": "attack",
-          "dead": "dead"
-        },
-        "scale": 1.0,
-        "flipX": false,
-        "weapon": "props/bow"
-      },
-      "audioConfig": {
-        "walkSound": "audio/archer_walk",
-        "attackSound": "audio/bow_shoot",
-        "deathSound": "audio/archer_death"
-      },
-      "projectileConfig": {
-        "bulletPrefab": "bullets/Arrow",
-        "hitEffect": "effects/ArrowHit",
-        "trailEffect": null
-      },
-      "specialAbilities": ["ranged_attack", "piercing_shot"]
+      "health": 9,
+      "speed": 40,
+      "attack": 40,
+      "range": 250,
+      "attackSpeed": 0.7,
+      "defense": 1,
+      "goldReward": 22
     },
     {
       "id": "stealth_zombie",
@@ -254,52 +76,13 @@
       "type": "stealth",
       "rarity": "rare",
       "weight": 12,
-      "stats": {
-        "health": 7,
-        "speed": 60,
-        "damage": 45,
-        "attackRange": 25,
-        "attackSpeed": 1.5,
-        "defense": 0,
-        "coinReward": 35
-      },
-      "movement": {
-        "type": "straight",
-        "pattern": "walk_forward",
-        "speedVariation": 0.15
-      },
-      "combat": {
-        "attackType": "stealth_strike",
-        "attackDelay": 0.3,
-        "attackCooldown": 0.6,
-        "canBlock": false,
-        "blockChance": 0
-      },
-      "visualConfig": {
-        "spritePrefab": "@EnemyAni/006/006",
-        "animations": {
-          "idle": "idle",
-          "walk": "walk",
-          "attack": "attack",
-          "dead": "dead",
-          "invisible": "stealth_invisible"
-        },
-        "scale": 1.0,
-        "flipX": false
-      },
-      "audioConfig": {
-        "walkSound": "audio/stealth_walk",
-        "attackSound": "audio/stealth_attack",
-        "deathSound": "audio/stealth_death",
-        "stealthSound": "audio/stealth_activate"
-      },
-      "specialAbilities": ["invisibility", "stealth_attack", "speed_boost"],
-      "stealthConfig": {
-        "stealthDuration": 3.0,
-        "stealthCooldown": 8.0,
-        "revealOnAttack": true,
-        "visibilityAlpha": 0.3
-      }
+      "health": 7,
+      "speed": 60,
+      "attack": 45,
+      "range": 25,
+      "attackSpeed": 1.5,
+      "defense": 0,
+      "goldReward": 35
     },
     {
       "id": "bucket_zombie",
@@ -307,52 +90,13 @@
       "type": "heavy_armor",
       "rarity": "uncommon",
       "weight": 15,
-      "stats": {
-        "health": 30,
-        "speed": 30,
-        "damage": 35,
-        "attackRange": 30,
-        "attackSpeed": 0.6,
-        "defense": 10,
-        "coinReward": 30
-      },
-      "movement": {
-        "type": "straight",
-        "pattern": "walk_forward",
-        "speedVariation": 0.05
-      },
-      "combat": {
-        "attackType": "heavy_melee",
-        "attackDelay": 0.8,
-        "attackCooldown": 1.5,
-        "canBlock": true,
-        "blockChance": 0.4
-      },
-      "visualConfig": {
-        "spritePrefab": "@EnemyAni/007/007",
-        "animations": {
-          "idle": "idle",
-          "walk": "walk",
-          "attack": "attack",
-          "dead": "dead",
-          "armor_break": "bucket_armor_break"
-        },
-        "scale": 1.2,
-        "flipX": false,
-        "armor": "props/iron_bucket"
-      },
-      "audioConfig": {
-        "walkSound": "audio/bucket_walk",
-        "attackSound": "audio/bucket_attack",
-        "deathSound": "audio/bucket_death",
-        "armorBreakSound": "audio/armor_break"
-      },
-      "specialAbilities": ["heavy_armor", "armor_break_threshold"],
-      "armorConfig": {
-        "armorHealth": 150,
-        "armorReduction": 0.5,
-        "breakThreshold": 50
-      }
+      "health": 30,
+      "speed": 30,
+      "attack": 35,
+      "range": 30,
+      "attackSpeed": 0.6,
+      "defense": 10,
+      "goldReward": 30
     },
     {
       "id": "barrel_zombie",
@@ -360,55 +104,13 @@
       "type": "explosive",
       "rarity": "rare",
       "weight": 10,
-      "stats": {
-        "health": 6,
-        "speed": 45,
-        "damage": 25,
-        "attackRange": 30,
-        "attackSpeed": 1.0,
-        "defense": 0,
-        "coinReward": 40,
-        "explosionDamage": 120,
-        "explosionRadius": 100
-      },
-      "movement": {
-        "type": "straight",
-        "pattern": "walk_forward",
-        "speedVariation": 0.1
-      },
-      "combat": {
-        "attackType": "melee",
-        "attackDelay": 0.5,
-        "attackCooldown": 1.0,
-        "canBlock": false,
-        "blockChance": 0
-      },
-      "visualConfig": {
-        "spritePrefab": "@EnemyAni/008/008",
-        "animations": {
-          "idle": "idle",
-          "walk": "walk",
-          "attack": "attack",
-          "dead": "dead",
-          "explode": "barrel_explode"
-        },
-        "scale": 1.0,
-        "flipX": false,
-        "prop": "props/gunpowder_barrel"
-      },
-      "audioConfig": {
-        "walkSound": "audio/barrel_walk",
-        "attackSound": "audio/barrel_attack",
-        "deathSound": "audio/barrel_explode",
-        "fuseSound": "audio/barrel_fuse"
-      },
-      "specialAbilities": ["death_explosion", "area_damage"],
-      "explosionConfig": {
-        "explosionDelay": 0.5,
-        "explosionEffect": "effects/BarrelExplosion",
-        "damageRadius": 100,
-        "knockbackForce": 200
-      }
+      "health": 6,
+      "speed": 45,
+      "attack": 25,
+      "range": 30,
+      "attackSpeed": 1.0,
+      "defense": 0,
+      "goldReward": 40
     },
     {
       "id": "boss1_gatekeeper",
@@ -416,56 +118,13 @@
       "type": "boss",
       "rarity": "boss",
       "weight": 1,
-      "stats": {
-        "health": 80,
-        "speed": 25,
-        "damage": 80,
-        "attackRange": 50,
-        "attackSpeed": 0.4,
-        "defense": 15,
-        "coinReward": 200
-      },
-      "movement": {
-        "type": "straight",
-        "pattern": "walk_forward",
-        "speedVariation": 0.05
-      },
-      "combat": {
-        "attackType": "gate_slam",
-        "attackDelay": 1.0,
-        "attackCooldown": 2.5,
-        "canBlock": true,
-        "blockChance": 0.6
-      },
-      "visualConfig": {
-        "spritePrefab": "@EnemyAni/009/009",
-        "animations": {
-          "idle": "idle",
-          "walk": "walk",
-          "attack": "attack",
-          "dead": "dead",
-          "block": "boss1_block"
-        },
-        "scale": 2.0,
-        "flipX": false,
-        "weapon": "props/iron_gate"
-      },
-      "audioConfig": {
-        "walkSound": "audio/boss1_walk",
-        "attackSound": "audio/gate_slam",
-        "deathSound": "audio/boss1_death",
-        "blockSound": "audio/gate_block"
-      },
-      "specialAbilities": ["boss_immunity", "gate_shield", "area_slam"],
-      "bossConfig": {
-        "phases": 2,
-        "phaseHealthThreshold": 0.5,
-        "enrageBonus": {
-          "speed": 1.5,
-          "damage": 1.3,
-          "attackSpeed": 1.2
-        }
-      }
+      "health": 80,
+      "speed": 25,
+      "attack": 80,
+      "range": 50,
+      "attackSpeed": 0.4,
+      "defense": 15,
+      "goldReward": 200
     },
     {
       "id": "boss2_gravedigger",
@@ -473,56 +132,13 @@
       "type": "boss",
       "rarity": "boss",
       "weight": 1,
-      "stats": {
-        "health": 100,
-        "speed": 20,
-        "damage": 100,
-        "attackRange": 60,
-        "attackSpeed": 0.3,
-        "defense": 20,
-        "coinReward": 250
-      },
-      "movement": {
-        "type": "straight",
-        "pattern": "walk_forward",
-        "speedVariation": 0.05
-      },
-      "combat": {
-        "attackType": "tombstone_smash",
-        "attackDelay": 1.2,
-        "attackCooldown": 3.0,
-        "canBlock": true,
-        "blockChance": 0.7
-      },
-      "visualConfig": {
-        "spritePrefab": "@EnemyAni/010/010",
-        "animations": {
-          "idle": "idle",
-          "walk": "walk",
-          "attack": "attack",
-          "dead": "dead",
-          "summon": "boss2_summon"
-        },
-        "scale": 2.2,
-        "flipX": false,
-        "weapon": "props/tombstone"
-      },
-      "audioConfig": {
-        "walkSound": "audio/boss2_walk",
-        "attackSound": "audio/tombstone_smash",
-        "deathSound": "audio/boss2_death",
-        "summonSound": "audio/boss2_summon"
-      },
-      "specialAbilities": ["boss_immunity", "minion_summon", "ground_slam"],
-      "bossConfig": {
-        "phases": 3,
-        "phaseHealthThreshold": 0.66,
-        "summonAbility": {
-          "minionType": "normal_zombie",
-          "summonCount": 2,
-          "summonCooldown": 10.0
-        }
-      }
+      "health": 100,
+      "speed": 20,
+      "attack": 100,
+      "range": 60,
+      "attackSpeed": 0.3,
+      "defense": 20,
+      "goldReward": 250
     },
     {
       "id": "boss3_cyborg",
@@ -530,57 +146,13 @@
       "type": "boss",
       "rarity": "boss",
       "weight": 1,
-      "stats": {
-        "health": 120,
-        "speed": 35,
-        "damage": 120,
-        "attackRange": 80,
-        "attackSpeed": 0.5,
-        "defense": 25,
-        "coinReward": 300
-      },
-      "movement": {
-        "type": "straight",
-        "pattern": "walk_forward",
-        "speedVariation": 0.1
-      },
-      "combat": {
-        "attackType": "cyber_arm_combo",
-        "attackDelay": 0.8,
-        "attackCooldown": 2.0,
-        "canBlock": true,
-        "blockChance": 0.5
-      },
-      "visualConfig": {
-        "spritePrefab": "@EnemyAni/011/011",
-        "animations": {
-          "idle": "idle",
-          "walk": "walk",
-          "attack": "attack",
-          "dead": "dead",
-          "laser": "boss3_laser"
-        },
-        "scale": 2.5,
-        "flipX": false,
-        "weapon": "props/cyber_arm"
-      },
-      "audioConfig": {
-        "walkSound": "audio/boss3_walk",
-        "attackSound": "audio/cyber_attack",
-        "deathSound": "audio/boss3_death",
-        "laserSound": "audio/laser_beam"
-      },
-      "specialAbilities": ["boss_immunity", "laser_beam", "cyber_enhancement"],
-      "bossConfig": {
-        "phases": 3,
-        "phaseHealthThreshold": 0.5,
-        "laserAbility": {
-          "damage": 150,
-          "range": 400,
-          "chargeTime": 2.0,
-          "cooldown": 8.0
-        }
-      }
+      "health": 120,
+      "speed": 35,
+      "attack": 120,
+      "range": 80,
+      "attackSpeed": 0.5,
+      "defense": 25,
+      "goldReward": 300
     }
   ],
   "spawnWeights": {
@@ -590,10 +162,25 @@
     "boss": 2
   },
   "waveProgression": {
-    "earlyWaves": ["normal_zombie", "roadblock_zombie"],
-    "midWaves": ["wandering_zombie", "mage_zombie", "archer_zombie"],
-    "lateWaves": ["stealth_zombie", "bucket_zombie", "barrel_zombie"],
-    "bossWaves": ["boss1_gatekeeper", "boss2_gravedigger", "boss3_cyborg"]
+    "earlyWaves": [
+      "normal_zombie",
+      "roadblock_zombie"
+    ],
+    "midWaves": [
+      "wandering_zombie",
+      "mage_zombie",
+      "archer_zombie"
+    ],
+    "lateWaves": [
+      "stealth_zombie",
+      "bucket_zombie",
+      "barrel_zombie"
+    ],
+    "bossWaves": [
+      "boss1_gatekeeper",
+      "boss2_gravedigger",
+      "boss3_cyborg"
+    ]
   },
   "nameToIdMapping": {
     "普通僵尸": "normal_zombie",

+ 19 - 0
assets/resources/data/excel/BallController配置模板.csv

@@ -0,0 +1,19 @@
+参数名,参数值,说明
+baseSpeed,60,基础球速
+maxReflectionRandomness,0.2,反弹随机偏移角度
+antiTrapTimeWindow,5.0,防围困检测时间窗口(秒)
+antiTrapHitThreshold,5,防围困撞击次数阈值
+deflectionAttemptThreshold,3,偏移尝试次数阈值
+antiTrapDeflectionMultiplier,3.0,防围困偏移强度倍数
+FIRE_COOLDOWN,0.05,子弹发射冷却时间(秒)
+ballRadius,10,球半径
+gravityScale,0,重力缩放
+linearDamping,0,线性阻尼
+angularDamping,0,角度阻尼
+colliderGroup,2,碰撞组
+colliderTag,1,碰撞标签
+friction,0,摩擦系数
+restitution,1,弹性系数
+safeDistance,50,安全距离
+edgeOffset,20,边缘偏移
+sensor,False,是否为传感器(true/false)

+ 1 - 1
assets/resources/data/excel/关卡配置/EnemyTypes.csv.meta → assets/resources/data/excel/BallController配置模板.csv.meta

@@ -2,7 +2,7 @@
   "ver": "1.0.1",
   "importer": "text",
   "imported": true,
-  "uuid": "bc85e8b8-6b53-4b69-a288-e4a08ba3793c",
+  "uuid": "0c7d5699-8fae-42a2-b6da-e0532694f0be",
   "files": [
     ".json"
   ],

+ 0 - 21
assets/resources/data/excel/BallController配置表.csv

@@ -1,21 +0,0 @@
-球控制器参数,数据类型,默认值,有效范围,功能说明,备注
-baseSpeed,number,60,30-200,球的基础移动速度,影响球的移动快慢
-maxReflectionRandomness,number,0.2,0.0-1.0,反弹随机偏移最大角度,增加球反弹的随机性
-antiTrapTimeWindow,number,5.0,2.0-10.0,防围困检测时间窗口,检测球被围困的时间范围
-antiTrapHitThreshold,number,5,3-15,防围困撞击次数阈值,触发防围困机制的撞击次数
-deflectionAttemptThreshold,number,3,1-10,偏移尝试次数阈值,达到后使用穿透机制
-antiTrapDeflectionMultiplier,number,3.0,1.5-5.0,防围困偏移强度倍数,偏移力度的倍数
-FIRE_COOLDOWN,number,0.05,0.01-0.2,子弹发射冷却时间,控制子弹发射频率
-ballRadius,number,25,15-50,球的半径,影响碰撞检测范围
-restitution,number,1.0,0.8-1.2,弹性系数,控制碰撞后的反弹力度
-linearDamping,number,0,0-0.5,线性阻尼,控制球速度的衰减
-angularDamping,number,0,0-0.5,角阻尼,控制球旋转的衰减
-gravityScale,number,0,0-2.0,重力缩放,控制重力对球的影响
-friction,number,0,0-1.0,摩擦系数,控制球与表面的摩擦
-safeDistance,number,20,10-50,安全距离,球生成时与方块的最小距离
-edgeOffset,number,20,10-100,边缘偏移,球生成时距离边界的距离
-maxAttempts,number,50,20-100,最大尝试次数,寻找有效生成位置的最大尝试
-colliderGroup,number,1,1-10,碰撞组,球的碰撞组设置
-colliderTag,number,1,1-10,碰撞标签,球的碰撞标签设置
-checkInterval,number,0.1,0.05-0.5,检测间隔,防围困机制的检测间隔时间
-ballStarted,boolean,false,true/false,球是否已开始运动,控制球的运动状态

BIN
assets/resources/data/excel/BallController配置表.xlsx


+ 12 - 0
assets/resources/data/excel/BallController配置表.xlsx.meta

@@ -0,0 +1,12 @@
+{
+  "ver": "1.0.0",
+  "importer": "*",
+  "imported": true,
+  "uuid": "48f5796f-1914-4bee-bc73-a14176c08de0",
+  "files": [
+    ".json",
+    ".xlsx"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
assets/resources/data/excel/__pycache__.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "68e7725e-b4d9-4309-aeeb-d7ff6321a0f6",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

BIN
assets/resources/data/excel/__pycache__/config_manager.cpython-313.pyc


+ 2 - 2
assets/resources/data/excel/方块武器配置.zip.meta → assets/resources/data/excel/__pycache__/config_manager.cpython-313.pyc.meta

@@ -2,10 +2,10 @@
   "ver": "1.0.0",
   "importer": "*",
   "imported": true,
-  "uuid": "55328403-7678-4fd0-aacd-3db3521c57ec",
+  "uuid": "0452c79a-83ea-4f82-9acd-95471c209a0d",
   "files": [
     ".json",
-    ".zip"
+    ".pyc"
   ],
   "subMetas": {},
   "userData": {}

+ 895 - 0
assets/resources/data/excel/config_manager.py

@@ -0,0 +1,895 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+BallController配置管理工具 - 简化版
+图形界面版本,不依赖pandas,支持浏览文件并选择Excel/CSV表格进行配置导入
+
+功能:
+1. 图形界面浏览项目文件
+2. 多选Excel/CSV配置表格
+3. 预览配置内容
+4. 一键导入配置到JSON
+5. 配置备份和恢复
+
+作者: AI Assistant
+日期: 2024
+"""
+
+import tkinter as tk
+from tkinter import ttk, filedialog, messagebox, scrolledtext
+import json
+import os
+import csv
+from datetime import datetime
+from pathlib import Path
+import threading
+
+# 尝试导入pandas,如果失败则使用纯CSV模式
+try:
+    import pandas as pd
+    PANDAS_AVAILABLE = True
+except ImportError:
+    PANDAS_AVAILABLE = False
+    print("警告: pandas未安装,将使用纯CSV模式(不支持Excel文件)")
+
+class ConfigManagerGUI:
+    def __init__(self):
+        self.root = tk.Tk()
+        self.root.title("游戏配置管理工具")
+        self.root.geometry("1000x700")
+        self.root.resizable(True, True)
+        
+        # 配置文件路径 - 动态获取项目根目录
+        # 从当前脚本位置向上查找项目根目录
+        current_dir = Path(__file__).parent
+        self.project_root = current_dir.parent.parent.parent.parent  # 从excel目录向上4级到项目根目录
+        self.excel_dir = current_dir
+        
+        # 配置表映射 - 定义每种表格对应的JSON文件和参数类型
+        self.config_mappings = {
+            'BallController配置表.xlsx': {
+                'json_path': self.project_root / "assets/resources/data/ballController.json",
+                'param_types': {
+                    'baseSpeed': float,
+                    'maxReflectionRandomness': float,
+                    'antiTrapTimeWindow': float,
+                    'antiTrapHitThreshold': int,
+                    'deflectionAttemptThreshold': int,
+                    'antiTrapDeflectionMultiplier': float,
+                    'FIRE_COOLDOWN': float,
+                    'ballRadius': float,
+                    'gravityScale': float,
+                    'linearDamping': float,
+                    'angularDamping': float,
+                    'colliderGroup': int,
+                    'colliderTag': int,
+                    'friction': float,
+                    'restitution': float,
+                    'safeDistance': float,
+                    'edgeOffset': float,
+                    'sensor': bool
+                },
+                'format_type': 'vertical'  # 纵向表格:参数名在第一列,值在第二/三列
+            },
+            '敌人配置表.xlsx': {
+                'json_path': self.project_root / "assets/resources/data/enemies.json",
+                'param_types': {
+                    '敌人ID': str,
+                    '敌人名称': str,
+                    '敌人类型': str,
+                    '稀有度': str,
+                    '权重': int,
+                    '生命值': int,
+                    '移动速度': int,
+                    '攻击力': int,
+                    '攻击范围': int,
+                    '攻击速度': float,
+                    '防御力': int,
+                    '金币奖励': int
+                },
+                'format_type': 'horizontal'  # 横向表格:第一行是参数名,下面是数据行
+            },
+            '方块武器配置表_更新_v2.xlsx': {
+                'json_path': self.project_root / "assets/resources/data/weapons.json",
+                'param_types': {
+                    'ID': str,
+                    '名称': str,
+                    '类型': str,
+                    '稀有度': str,
+                    '权重': int,
+                    '伤害': int,
+                    '射速': float,
+                    '射程': int,
+                    '子弹速度': int
+                },
+                'format_type': 'horizontal'
+            },
+            '关卡配置表_完整版_更新_v2.xlsx': {
+                'json_path': self.project_root / "assets/resources/data/levels",  # 目录路径,包含多个关卡JSON文件
+                'param_types': {
+                    '关卡ID': str,
+                    '关卡名称': str,
+                    '场景': str,
+                    '描述': str,
+                    '可用武器': str,
+                    '初始生命': int,
+                    '时间限制': int,
+                    '难度': str,
+                    '生命倍数': float,
+                    '金币奖励': int,
+                    '钻石奖励': int
+                },
+                'format_type': 'horizontal'
+            },
+            '技能配置表.xlsx': {
+                'json_path': self.project_root / "assets/resources/data/skill.json",
+                'param_types': {
+                    '技能ID': str,
+                    '技能名称': str,
+                    '技能描述': str,
+                    '图标路径': str,
+                    '最大等级': int,
+                    '当前等级': int,
+                    '价格减少': float,
+                    '暴击几率增加': float,
+                    '暴击伤害加成': float,
+                    '生命值增加': float,
+                    '多重射击几率': float,
+                    '能量加成': float,
+                    '速度提升': float
+                },
+                'format_type': 'horizontal',
+                'sheet_name': '技能信息表'  # 指定使用的工作表名称
+            }
+        }
+        
+        # 当前选择的配置映射
+        self.current_mapping = None
+        self.json_config_path = None
+        self.param_types = {}
+        
+        # 默认配置值
+        self.default_config = {
+            'baseSpeed': 60,
+            'maxReflectionRandomness': 0.2,
+            'antiTrapTimeWindow': 5.0,
+            'antiTrapHitThreshold': 5,
+            'deflectionAttemptThreshold': 3,
+            'antiTrapDeflectionMultiplier': 3.0,
+            'FIRE_COOLDOWN': 0.05,
+            'ballRadius': 10,
+            'gravityScale': 0,
+            'linearDamping': 0,
+            'angularDamping': 0,
+            'colliderGroup': 2,
+            'colliderTag': 1,
+            'friction': 0,
+            'restitution': 1,
+            'safeDistance': 50,
+            'edgeOffset': 20,
+            'sensor': False
+        }
+        
+        self.selected_files = []
+        self.config_data = {}
+        
+        self.setup_ui()
+        self.load_current_config()
+    
+    def setup_ui(self):
+        """设置用户界面"""
+        # 主框架
+        main_frame = ttk.Frame(self.root, padding="10")
+        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
+        
+        # 配置根窗口的网格权重
+        self.root.columnconfigure(0, weight=1)
+        self.root.rowconfigure(0, weight=1)
+        main_frame.columnconfigure(1, weight=1)
+        main_frame.rowconfigure(2, weight=1)
+        
+        # 标题
+        title_label = ttk.Label(main_frame, text="游戏配置管理工具", 
+                               font=("Arial", 16, "bold"))
+        title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20))
+        
+        # 左侧面板 - 文件选择
+        file_types_text = "Excel/CSV配置文件选择" if PANDAS_AVAILABLE else "CSV配置文件选择"
+        left_frame = ttk.LabelFrame(main_frame, text=file_types_text, padding="10")
+        left_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 10))
+        left_frame.columnconfigure(0, weight=1)
+        left_frame.rowconfigure(1, weight=1)
+        
+        # 文件浏览按钮
+        browse_frame = ttk.Frame(left_frame)
+        browse_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
+        browse_frame.columnconfigure(0, weight=1)
+        
+        browse_text = "浏览Excel/CSV文件" if PANDAS_AVAILABLE else "浏览CSV文件"
+        ttk.Button(browse_frame, text=browse_text, 
+                  command=self.browse_files).grid(row=0, column=0, sticky=(tk.W, tk.E))
+        ttk.Button(browse_frame, text="扫描项目目录", 
+                  command=self.scan_project_files).grid(row=0, column=1, padx=(10, 0))
+        
+        # 文件列表
+        list_frame = ttk.Frame(left_frame)
+        list_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
+        list_frame.columnconfigure(0, weight=1)
+        list_frame.rowconfigure(0, weight=1)
+        
+        self.file_listbox = tk.Listbox(list_frame, selectmode=tk.MULTIPLE, height=15)
+        scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.file_listbox.yview)
+        self.file_listbox.configure(yscrollcommand=scrollbar.set)
+        
+        self.file_listbox.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
+        scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
+        
+        # 文件操作按钮
+        file_btn_frame = ttk.Frame(left_frame)
+        file_btn_frame.grid(row=2, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
+        
+        ttk.Button(file_btn_frame, text="预览选中文件", 
+                  command=self.preview_selected_files).pack(side=tk.LEFT)
+        ttk.Button(file_btn_frame, text="清空选择", 
+                  command=self.clear_selection).pack(side=tk.LEFT, padx=(10, 0))
+        
+        # 右侧面板 - 配置预览和操作
+        right_frame = ttk.LabelFrame(main_frame, text="配置预览与操作", padding="10")
+        right_frame.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S))
+        right_frame.columnconfigure(0, weight=1)
+        right_frame.rowconfigure(0, weight=1)
+        
+        # 配置预览文本框
+        self.preview_text = scrolledtext.ScrolledText(right_frame, height=20, width=50)
+        self.preview_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
+        
+        # 操作按钮
+        btn_frame = ttk.Frame(right_frame)
+        btn_frame.grid(row=1, column=0, sticky=(tk.W, tk.E))
+        
+        ttk.Button(btn_frame, text="导入配置", 
+                  command=self.import_config).pack(side=tk.LEFT)
+        ttk.Button(btn_frame, text="备份当前配置", 
+                  command=self.backup_config).pack(side=tk.LEFT, padx=(10, 0))
+        ttk.Button(btn_frame, text="恢复默认配置", 
+                  command=self.restore_default_config).pack(side=tk.LEFT, padx=(10, 0))
+        
+        # 底部状态栏
+        self.status_var = tk.StringVar()
+        self.status_var.set("就绪")
+        status_bar = ttk.Label(main_frame, textvariable=self.status_var, 
+                              relief=tk.SUNKEN, anchor=tk.W)
+        status_bar.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(10, 0))
+        
+        # 绑定事件
+        self.file_listbox.bind('<<ListboxSelect>>', self.on_file_select)
+    
+    def browse_files(self):
+        """浏览Excel/CSV文件"""
+        if PANDAS_AVAILABLE:
+            title = "选择Excel/CSV配置文件"
+            filetypes = [("Excel文件", "*.xlsx *.xls"), ("CSV文件", "*.csv"), ("文本文件", "*.txt"), ("所有文件", "*.*")]
+        else:
+            title = "选择CSV配置文件"
+            filetypes = [("CSV文件", "*.csv"), ("文本文件", "*.txt"), ("所有文件", "*.*")]
+        
+        files = filedialog.askopenfilenames(
+            title=title,
+            filetypes=filetypes,
+            initialdir=str(self.excel_dir)
+        )
+        
+        if files:
+            self.file_listbox.delete(0, tk.END)
+            for file in files:
+                self.file_listbox.insert(tk.END, file)
+            self.status_var.set(f"已选择 {len(files)} 个文件")
+    
+    def scan_project_files(self):
+        """扫描项目目录中的Excel/CSV文件"""
+        self.status_var.set("正在扫描项目目录...")
+        
+        def scan_thread():
+            config_files = []
+            
+            # 扫描常见的配置目录
+            scan_dirs = [
+                self.project_root / "assets/resources/data",
+                self.project_root / "assets/resources/config",
+                self.project_root / "assets/excel",
+                self.project_root / "config",
+                self.project_root / "data",
+                self.project_root
+            ]
+            
+            # 根据pandas可用性选择扫描的文件类型
+            patterns = ['*.xlsx', '*.xls', '*.csv', '*.txt'] if PANDAS_AVAILABLE else ['*.csv', '*.txt']
+            
+            for scan_dir in scan_dirs:
+                if scan_dir.exists():
+                    for pattern in patterns:
+                        config_files.extend(scan_dir.rglob(pattern))
+            
+            # 更新UI
+            self.root.after(0, self.update_file_list, config_files)
+        
+        threading.Thread(target=scan_thread, daemon=True).start()
+    
+    def update_file_list(self, files):
+        """更新文件列表"""
+        self.file_listbox.delete(0, tk.END)
+        for file in files:
+            self.file_listbox.insert(tk.END, str(file))
+        
+        file_type_text = "Excel/CSV文件" if PANDAS_AVAILABLE else "CSV文件"
+        self.status_var.set(f"找到 {len(files)} 个{file_type_text}")
+    
+    def on_file_select(self, event):
+        """文件选择事件处理"""
+        selection = self.file_listbox.curselection()
+        if selection:
+            self.selected_files = [self.file_listbox.get(i) for i in selection]
+            # 自动识别配置映射
+            self.auto_detect_config_mapping()
+            self.preview_selected_files()
+            self.status_var.set(f"已选择 {len(selection)} 个文件")
+    
+    def auto_detect_config_mapping(self):
+        """自动检测配置映射"""
+        if not self.selected_files:
+            return
+        
+        # 取第一个选中的文件进行映射检测
+        selected_file = self.selected_files[0]
+        filename = Path(selected_file).name
+        
+        # 检查是否有对应的配置映射
+        for config_name, mapping in self.config_mappings.items():
+            if config_name in filename or filename in config_name:
+                self.current_mapping = mapping
+                self.json_config_path = mapping['json_path']
+                self.param_types = mapping['param_types']
+                print(f"检测到配置映射: {config_name} -> {self.json_config_path}")
+                return
+        
+        # 如果没有找到映射,使用默认的BallController映射
+        self.current_mapping = self.config_mappings['BallController配置表.xlsx']
+        self.json_config_path = self.current_mapping['json_path']
+        self.param_types = self.current_mapping['param_types']
+        print(f"使用默认配置映射: BallController -> {self.json_config_path}")
+    
+    def preview_selected_files(self):
+        """预览选中的文件"""
+        selection = self.file_listbox.curselection()
+        if not selection:
+            messagebox.showwarning("警告", "请先选择要预览的文件")
+            return
+        
+        self.preview_text.delete(1.0, tk.END)
+        self.config_data = {}
+        
+        for i in selection:
+            file_path = Path(self.file_listbox.get(i))
+            self.preview_text.insert(tk.END, f"=== {file_path.name} ===\n")
+            
+            try:
+                # 根据文件扩展名和pandas可用性选择读取方法
+                if file_path.suffix.lower() in ['.xlsx', '.xls']:
+                    if PANDAS_AVAILABLE:
+                        file_config = self.read_excel_config(file_path)
+                    else:
+                        self.preview_text.insert(tk.END, "Excel文件需要pandas库支持,请安装: pip install pandas openpyxl\n\n")
+                        continue
+                elif file_path.suffix.lower() in ['.csv', '.txt']:
+                    file_config = self.read_csv_config(file_path)
+                else:
+                    self.preview_text.insert(tk.END, "不支持的文件格式\n\n")
+                    continue
+                
+                if file_config:
+                    self.config_data.update(file_config)
+                    
+                    # 显示预览
+                    self.preview_text.insert(tk.END, f"找到 {len(file_config)} 个配置参数:\n")
+                    for key, value in file_config.items():
+                        self.preview_text.insert(tk.END, f"  {key}: {value}\n")
+                else:
+                    self.preview_text.insert(tk.END, "未找到有效的配置数据\n")
+                
+            except Exception as e:
+                self.preview_text.insert(tk.END, f"读取失败: {str(e)}\n")
+            
+            self.preview_text.insert(tk.END, "\n")
+        
+        # 显示合并后的配置
+        if self.config_data:
+            self.preview_text.insert(tk.END, "=== 合并后的配置 ===\n")
+            self.preview_text.insert(tk.END, json.dumps(self.config_data, indent=2, ensure_ascii=False))
+        
+        self.status_var.set(f"预览完成,共 {len(self.config_data)} 个配置参数")
+    
+    def read_excel_config(self, file_path):
+        """读取Excel配置文件"""
+        config = {}
+        
+        if not PANDAS_AVAILABLE:
+            print("错误: pandas未安装,无法读取Excel文件")
+            return config
+        
+        try:
+            # 检查是否有指定的工作表名称
+            sheet_name = None
+            if self.current_mapping and 'sheet_name' in self.current_mapping:
+                sheet_name = self.current_mapping['sheet_name']
+            
+            # 读取Excel文件
+            if sheet_name:
+                df = pd.read_excel(file_path, sheet_name=sheet_name)
+            else:
+                df = pd.read_excel(file_path)
+            config = self.parse_config_data(df, file_path.name)
+            
+        except Exception as e:
+            print(f"读取Excel文件 {file_path.name} 时出错: {e}")
+        
+        return config
+    
+    def read_csv_config(self, file_path):
+        """读取CSV配置文件"""
+        config = {}
+        
+        try:
+            # 如果pandas可用,优先使用pandas读取CSV(支持更多格式)
+            if PANDAS_AVAILABLE:
+                try:
+                    df = pd.read_csv(file_path)
+                    config = self.parse_config_data(df, file_path.name)
+                except:
+                    # 如果pandas读取失败,回退到原始CSV读取方法
+                    config = self.read_csv_fallback(file_path)
+            else:
+                # 如果pandas不可用,直接使用原始CSV读取方法
+                config = self.read_csv_fallback(file_path)
+            
+        except Exception as e:
+            print(f"读取文件 {file_path.name} 时出错: {e}")
+        
+        return config
+    
+    def read_csv_fallback(self, file_path):
+        """原始CSV读取方法(不依赖pandas)"""
+        config = {}
+        
+        try:
+            with open(file_path, 'r', encoding='utf-8') as f:
+                reader = csv.reader(f)
+                for row_num, row in enumerate(reader, 1):
+                    if len(row) < 2:
+                        continue
+                    
+                    param_name = row[0].strip()
+                    param_value = row[1].strip()
+                    
+                    # 跳过标题行
+                    if param_name in ['参数名', 'parameter', 'name']:
+                        continue
+                    
+                    # 检查参数是否有效
+                    if param_name in self.param_types:
+                        try:
+                            param_type = self.param_types[param_name]
+                            if param_type == bool:
+                                config[param_name] = param_value.lower() in ['true', '1', 'yes', 'on']
+                            else:
+                                config[param_name] = param_type(param_value)
+                        except (ValueError, TypeError):
+                            continue
+        except Exception as e:
+            print(f"读取CSV文件 {file_path.name} 时出错: {e}")
+        
+        return config
+    
+    def parse_config_data(self, df, filename):
+        """解析配置数据(支持多种格式,需要pandas)"""
+        config = {}
+        
+        if not PANDAS_AVAILABLE or not self.current_mapping:
+            return config
+        
+        format_type = self.current_mapping['format_type']
+        
+        try:
+            if format_type == 'vertical':
+                # 纵向表格:参数名在第一列,值在第二/三列
+                for _, row in df.iterrows():
+                    param_name = str(row.iloc[0]).strip()
+                    
+                    # 跳过标题行和无效行
+                    if param_name in ['参数名', 'parameter', 'name', 'nan', '球控制器参数'] or param_name == 'nan':
+                        continue
+                    
+                    # 检查参数是否有效
+                    if param_name in self.param_types:
+                        try:
+                            # 优先使用第3列(默认值),如果不存在则使用第2列
+                            param_value = row.iloc[2] if len(row) > 2 and not pd.isna(row.iloc[2]) else row.iloc[1]
+                            
+                            if pd.isna(param_value):
+                                continue
+                            
+                            param_type = self.param_types[param_name]
+                            if param_type == bool:
+                                config[param_name] = str(param_value).lower() in ['true', '1', 'yes', 'on']
+                            else:
+                                config[param_name] = param_type(param_value)
+                        except (ValueError, TypeError, IndexError):
+                            continue
+            
+            elif format_type == 'horizontal':
+                # 横向表格:第一行是参数名,下面是数据行
+                # 检查第1行是否为描述行
+                # 如果第1行的第一个单元格包含描述性文字,则跳过它
+                data_start_row = 1
+                if len(df) > 1:
+                    first_cell = str(df.iloc[1, 0]).strip()
+                    # 如果第1行第一个单元格是描述性文字,则从第2行开始
+                    if first_cell in ['唯一标识符', '描述', 'description', 'desc']:
+                        data_start_row = 2
+                    else:
+                        data_start_row = 1
+                print(f"横向表格解析: 数据起始行={data_start_row}, 总行数={len(df)}")
+                print(f"表格列名: {list(df.columns)}")
+                
+                # 打印前几行数据用于调试
+                for i in range(min(5, len(df))):
+                    print(f"第{i}行数据: {df.iloc[i].to_dict()}")
+                
+                # 解析多行数据(如敌人配置、武器配置等)
+                config_list = []
+                for i in range(data_start_row, len(df)):
+                    row_config = {}
+                    for col_idx, col_name in enumerate(df.columns):
+                        param_name = str(col_name).strip()
+                        if param_name in self.param_types:
+                            try:
+                                param_value = df.iloc[i, col_idx]
+                                if pd.isna(param_value):
+                                    continue
+                                
+                                param_type = self.param_types[param_name]
+                                if param_type == bool:
+                                    row_config[param_name] = str(param_value).lower() in ['true', '1', 'yes', 'on']
+                                else:
+                                    row_config[param_name] = param_type(param_value)
+                            except (ValueError, TypeError, IndexError):
+                                continue
+                    
+                    if row_config:  # 只添加非空配置
+                        config_list.append(row_config)
+                
+                # 对于横向表格,返回配置列表
+                config = {'items': config_list}
+            
+        except Exception as e:
+            print(f"解析文件 {filename} 时出错: {e}")
+        
+        return config
+    
+    def clear_selection(self):
+        """清空选择"""
+        self.file_listbox.selection_clear(0, tk.END)
+        self.preview_text.delete(1.0, tk.END)
+        self.config_data = {}
+        self.status_var.set("已清空选择")
+        self.load_current_config()
+    
+    def load_current_config(self):
+        """加载当前配置"""
+        try:
+            if not self.json_config_path:
+                self.preview_text.insert(tk.END, "请先选择配置文件\n")
+                return
+            
+            if self.json_config_path.exists():
+                with open(self.json_config_path, 'r', encoding='utf-8') as f:
+                    current_config = json.load(f)
+                
+                self.preview_text.insert(tk.END, f"=== 当前配置 ({self.json_config_path.name}) ===\n")
+                
+                # 根据配置类型显示不同的预览格式
+                if self.current_mapping and self.current_mapping['format_type'] == 'horizontal':
+                    # 横向表格配置(如敌人、武器)
+                    if 'enemies' in current_config:
+                        self.preview_text.insert(tk.END, f"敌人配置 ({len(current_config['enemies'])} 个):\n")
+                        for i, enemy in enumerate(current_config['enemies'][:5]):  # 只显示前5个
+                            self.preview_text.insert(tk.END, f"  {i+1}. {enemy.get('name', enemy.get('id', 'Unknown'))}\n")
+                        if len(current_config['enemies']) > 5:
+                            self.preview_text.insert(tk.END, f"  ... 还有 {len(current_config['enemies']) - 5} 个\n")
+                    
+                    if 'weapons' in current_config:
+                        self.preview_text.insert(tk.END, f"\n武器配置 ({len(current_config['weapons'])} 个):\n")
+                        for i, weapon in enumerate(current_config['weapons'][:5]):  # 只显示前5个
+                            self.preview_text.insert(tk.END, f"  {i+1}. {weapon.get('name', weapon.get('id', 'Unknown'))}\n")
+                        if len(current_config['weapons']) > 5:
+                            self.preview_text.insert(tk.END, f"  ... 还有 {len(current_config['weapons']) - 5} 个\n")
+                else:
+                    # 纵向表格配置(如BallController)
+                    self.preview_text.insert(tk.END, json.dumps(current_config, indent=2, ensure_ascii=False))
+                
+                file_hint = "Excel/CSV文件" if PANDAS_AVAILABLE else "CSV文件"
+                self.preview_text.insert(tk.END, f"\n\n请选择{file_hint}进行配置导入...\n")
+            else:
+                self.preview_text.insert(tk.END, "配置文件不存在\n")
+        except Exception as e:
+            self.preview_text.insert(tk.END, f"加载当前配置失败: {e}\n")
+    
+    def backup_config(self):
+        """备份当前配置"""
+        try:
+            if not self.json_config_path.exists():
+                messagebox.showwarning("警告", "配置文件不存在")
+                return
+            
+            backup_path = self.json_config_path.parent / f"ballController_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
+            
+            with open(self.json_config_path, 'r', encoding='utf-8') as f:
+                content = f.read()
+            
+            with open(backup_path, 'w', encoding='utf-8') as f:
+                f.write(content)
+            
+            messagebox.showinfo("成功", f"配置已备份到:\n{backup_path}")
+            self.status_var.set(f"配置已备份")
+            
+        except Exception as e:
+            messagebox.showerror("错误", f"备份失败: {e}")
+    
+    def import_config(self):
+        """导入配置到JSON文件"""
+        if not self.config_data:
+            messagebox.showwarning("警告", "没有配置数据可导入")
+            return
+        
+        if not self.current_mapping:
+            messagebox.showwarning("警告", "未检测到有效的配置映射")
+            return
+        
+        try:
+            format_type = self.current_mapping['format_type']
+            
+            if format_type == 'vertical':
+                # 处理纵向表格(如BallController)
+                self._import_vertical_config()
+            elif format_type == 'horizontal':
+                # 处理横向表格(如敌人配置、武器配置)
+                self._import_horizontal_config()
+            
+        except Exception as e:
+            messagebox.showerror("错误", f"导入配置失败: {str(e)}")
+    
+    def _import_vertical_config(self):
+        """导入纵向表格配置"""
+        # 读取现有JSON配置
+        if self.json_config_path.exists():
+            with open(self.json_config_path, 'r', encoding='utf-8') as f:
+                current_config = json.load(f)
+        else:
+            current_config = self.default_config.copy()
+        
+        # 合并配置
+        updated_count = 0
+        for key, value in self.config_data.items():
+            if key in current_config and current_config[key] != value:
+                current_config[key] = value
+                updated_count += 1
+            elif key not in current_config:
+                current_config[key] = value
+                updated_count += 1
+        
+        # 写入更新后的配置
+        with open(self.json_config_path, 'w', encoding='utf-8') as f:
+            json.dump(current_config, f, indent=2, ensure_ascii=False)
+        
+        messagebox.showinfo("成功", f"配置导入成功!\n更新了 {updated_count} 个参数")
+        self.status_var.set("配置导入成功")
+        
+        # 刷新预览
+        self.clear_selection()
+    
+    def _import_horizontal_config(self):
+        """导入横向表格配置"""
+        if 'items' not in self.config_data:
+            messagebox.showwarning("警告", "横向表格数据格式错误")
+            return
+        
+        items = self.config_data['items']
+        if not items:
+            messagebox.showwarning("警告", "没有有效的配置项")
+            return
+        
+        # 读取现有JSON配置
+        if self.json_config_path.exists():
+            with open(self.json_config_path, 'r', encoding='utf-8') as f:
+                current_config = json.load(f)
+        else:
+            current_config = {}
+        
+        # 根据不同的配置类型处理
+        filename = Path(self.selected_files[0]).name
+        
+        if '敌人配置' in filename:
+            # 敌人配置:更新enemies数组
+            if 'enemies' not in current_config:
+                current_config['enemies'] = []
+            
+            # 将Excel数据转换为JSON格式
+            updated_enemies = []
+            for item in items:
+                enemy_data = self._convert_enemy_data(item)
+                if enemy_data:
+                    updated_enemies.append(enemy_data)
+            
+            current_config['enemies'] = updated_enemies
+            
+        elif '武器配置' in filename:
+            # 武器配置:更新weapons数组
+            if 'weapons' not in current_config:
+                current_config['weapons'] = []
+            
+            # 将Excel数据转换为JSON格式
+            updated_weapons = []
+            for item in items:
+                weapon_data = self._convert_weapon_data(item)
+                if weapon_data:
+                    updated_weapons.append(weapon_data)
+            
+            current_config['weapons'] = updated_weapons
+            
+        elif '技能配置' in filename:
+            # 技能配置:更新skills数组
+            if 'skills' not in current_config:
+                current_config['skills'] = []
+            
+            # 将Excel数据转换为JSON格式
+            updated_skills = []
+            for item in items:
+                skill_data = self._convert_skill_data(item)
+                if skill_data:
+                    updated_skills.append(skill_data)
+            
+            current_config['skills'] = updated_skills
+        
+        # 写入更新后的配置
+        with open(self.json_config_path, 'w', encoding='utf-8') as f:
+            json.dump(current_config, f, indent=2, ensure_ascii=False)
+        
+        messagebox.showinfo("成功", f"配置导入成功!\n更新了 {len(items)} 个配置项")
+        self.status_var.set("配置导入成功")
+        
+        # 刷新预览
+        self.clear_selection()
+    
+    def _convert_enemy_data(self, item):
+        """转换敌人数据格式"""
+        try:
+            enemy_id = item.get('敌人ID', '')
+            print(f"正在转换敌人数据: {enemy_id} - {item.get('敌人名称', '')}")
+            
+            # 检查必要字段
+            if not enemy_id:
+                print(f"跳过无效敌人数据: 缺少敌人ID - {item}")
+                return None
+                
+            result = {
+                'id': enemy_id,
+                'name': item.get('敌人名称', ''),
+                'type': item.get('敌人类型', ''),
+                'rarity': item.get('稀有度', ''),
+                'weight': item.get('权重', 1),
+                'health': item.get('生命值', 100),
+                'speed': item.get('移动速度', 50),
+                'attack': item.get('攻击力', 10),
+                'range': item.get('攻击范围', 100),
+                'attackSpeed': item.get('攻击速度', 1.0),
+                'defense': item.get('防御力', 0),
+                'goldReward': item.get('金币奖励', 10)
+            }
+            print(f"成功转换敌人数据: {result}")
+            return result
+        except Exception as e:
+            print(f"转换敌人数据失败: {e} - 数据: {item}")
+            return None
+    
+    def _convert_weapon_data(self, item):
+        """转换武器数据格式"""
+        try:
+            return {
+                'id': item.get('ID', ''),
+                'name': item.get('名称', ''),
+                'type': item.get('类型', ''),
+                'rarity': item.get('稀有度', ''),
+                'weight': item.get('权重', 1),
+                'damage': item.get('伤害', 10),
+                'fireRate': item.get('射速', 1.0),
+                'range': item.get('射程', 100),
+                'bulletSpeed': item.get('子弹速度', 100)
+            }
+        except Exception as e:
+            print(f"转换武器数据失败: {e}")
+            return None
+    
+    def _convert_skill_data(self, item):
+        """转换技能数据格式"""
+        try:
+            skill_id = item.get('技能ID', '')
+            print(f"正在转换技能数据: {skill_id} - {item.get('技能名称', '')}")
+            
+            # 检查必要字段
+            if not skill_id:
+                print(f"跳过无效技能数据: 缺少技能ID - {item}")
+                return None
+                
+            result = {
+                 'id': skill_id,
+                 'name': item.get('技能名称', ''),
+                 'description': item.get('技能描述', ''),
+                 'iconPath': item.get('图标路径', ''),
+                 'maxLevel': item.get('最大等级', 1),
+                 'currentLevel': item.get('当前等级', 0),
+                 'priceReduction': item.get('价格减少', 0.0),
+                 'critChanceIncrease': item.get('暴击几率增加', 0.0),
+                 'critDamageBonus': item.get('暴击伤害加成', 0.0),
+                 'healthIncrease': item.get('生命值增加', 0.0),
+                 'multiShotChance': item.get('多重射击几率', 0.0),
+                 'energyGainIncrease': item.get('能量加成', 0.0),
+                 'ballSpeedIncrease': item.get('速度提升', 0.0)
+             }
+            print(f"成功转换技能数据: {result}")
+            return result
+        except Exception as e:
+            print(f"转换技能数据失败: {e} - 数据: {item}")
+            return None
+    
+    def restore_default_config(self):
+        """恢复默认配置"""
+        result = messagebox.askyesno("确认", "确定要恢复默认配置吗?\n当前配置将被覆盖!")
+        if not result:
+            return
+        
+        try:
+            # 备份当前配置
+            if self.json_config_path.exists():
+                backup_path = self.json_config_path.parent / f"ballController_backup_before_default_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
+                with open(self.json_config_path, 'r', encoding='utf-8') as f:
+                    content = f.read()
+                with open(backup_path, 'w', encoding='utf-8') as f:
+                    f.write(content)
+            
+            # 写入默认配置
+            with open(self.json_config_path, 'w', encoding='utf-8') as f:
+                json.dump(self.default_config, f, indent=2, ensure_ascii=False)
+            
+            messagebox.showinfo("成功", "已恢复默认配置")
+            self.status_var.set("已恢复默认配置")
+            
+            # 刷新预览
+            self.clear_selection()
+            
+        except Exception as e:
+            messagebox.showerror("错误", f"恢复默认配置失败: {e}")
+    
+    def run(self):
+        """运行应用"""
+        self.root.mainloop()
+
+def main():
+    """主函数"""
+    try:
+        app = ConfigManagerGUI()
+        app.run()
+    except Exception as e:
+        print(f"启动应用失败: {e}")
+        input("按回车键退出...")
+
+if __name__ == "__main__":
+    main()

+ 2 - 2
assets/resources/data/excel/关卡配置.zip.meta → assets/resources/data/excel/config_manager.py.meta

@@ -2,10 +2,10 @@
   "ver": "1.0.0",
   "importer": "*",
   "imported": true,
-  "uuid": "e04f84d3-a16d-41e9-8b1e-b02b13b1c9d7",
+  "uuid": "739190a1-56a6-4773-b86e-adeb0999b109",
   "files": [
     ".json",
-    ".zip"
+    ".py"
   ],
   "subMetas": {},
   "userData": {}

+ 17 - 0
assets/resources/data/excel/requirements.txt

@@ -0,0 +1,17 @@
+# 游戏配置管理工具依赖库
+# Game Configuration Management Tool Dependencies
+
+# Excel文件读写支持
+pandas>=1.3.0
+openpyxl>=3.0.0
+
+# GUI界面支持 (Python内置,无需安装)
+# tkinter - Built-in with Python
+
+# 其他标准库 (Python内置,无需安装)
+# json - Built-in
+# os - Built-in
+# csv - Built-in
+# datetime - Built-in
+# pathlib - Built-in
+# threading - Built-in

+ 1 - 1
assets/resources/data/excel/BallController配置表.csv.meta → assets/resources/data/excel/requirements.txt.meta

@@ -2,7 +2,7 @@
   "ver": "1.0.1",
   "importer": "text",
   "imported": true,
-  "uuid": "59701a24-120d-41da-9122-7b7b28564efd",
+  "uuid": "f05a2974-e276-4c9c-8faf-291238ae94e3",
   "files": [
     ".json"
   ],

+ 127 - 0
assets/resources/data/excel/test_excel_parse.py

@@ -0,0 +1,127 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import pandas as pd
+import json
+from pathlib import Path
+
+def test_excel_parsing():
+    """测试Excel文件解析"""
+    excel_path = "敌人配置表.xlsx"
+    sheet_name = "敌人基础配置"
+    
+    print(f"正在读取Excel文件: {excel_path}")
+    
+    try:
+        # 读取Excel文件
+        df = pd.read_excel(excel_path, sheet_name=sheet_name)
+        print(f"成功读取Excel文件,共{len(df)}行,{len(df.columns)}列")
+        
+        # 打印列名
+        print(f"列名: {list(df.columns)}")
+        
+        # 打印前几行数据
+        print("\n前5行数据:")
+        for i in range(min(5, len(df))):
+            print(f"第{i}行: {df.iloc[i].to_dict()}")
+        
+        # 模拟配置解析逻辑
+        param_types = {
+            '敌人ID': str,
+            '敌人名称': str,
+            '敌人类型': str,
+            '稀有度': str,
+            '权重': int,
+            '生命值': int,
+            '移动速度': int,
+            '攻击力': int,
+            '攻击范围': int,
+            '攻击速度': float,
+            '防御力': int,
+            '金币奖励': int
+        }
+        
+        # 检查第1行是否为描述行
+        data_start_row = 1
+        if len(df) > 1:
+            first_cell = str(df.iloc[1, 0]).strip()
+            print(f"第1行第一个单元格: '{first_cell}'")
+            # 如果第1行第一个单元格是描述性文字,则从第2行开始
+            if first_cell in ['唯一标识符', '描述', 'description', 'desc']:
+                data_start_row = 2
+                print("检测到描述行,跳过第1行")
+            else:
+                data_start_row = 1
+                print("第1行是数据行,从第1行开始")
+        print(f"\n数据起始行: {data_start_row}")
+        
+        # 解析多行数据
+        config_list = []
+        for i in range(data_start_row, len(df)):
+            row_config = {}
+            print(f"\n处理第{i}行数据:")
+            
+            for col_idx, col_name in enumerate(df.columns):
+                param_name = str(col_name).strip()
+                print(f"  列{col_idx}: {param_name}")
+                
+                if param_name in param_types:
+                    try:
+                        param_value = df.iloc[i, col_idx]
+                        print(f"    原始值: {param_value} (类型: {type(param_value)})")
+                        
+                        if pd.isna(param_value):
+                            print(f"    跳过空值")
+                            continue
+                        
+                        param_type = param_types[param_name]
+                        if param_type == bool:
+                            row_config[param_name] = str(param_value).lower() in ['true', '1', 'yes', 'on']
+                        else:
+                            row_config[param_name] = param_type(param_value)
+                        
+                        print(f"    转换后: {row_config[param_name]}")
+                    except (ValueError, TypeError, IndexError) as e:
+                        print(f"    转换失败: {e}")
+                        continue
+                else:
+                    print(f"    参数名不在类型映射中")
+            
+            if row_config:  # 只添加非空配置
+                config_list.append(row_config)
+                print(f"  添加配置: {row_config}")
+            else:
+                print(f"  跳过空配置")
+        
+        print(f"\n最终解析结果: 共{len(config_list)}个配置项")
+        for i, config in enumerate(config_list):
+            print(f"配置{i}: {config}")
+        
+        # 转换为敌人数据格式
+        print("\n转换为敌人数据格式:")
+        enemies = []
+        for item in config_list:
+            enemy_data = {
+                'id': item.get('敌人ID', ''),
+                'name': item.get('敌人名称', ''),
+                'type': item.get('敌人类型', ''),
+                'rarity': item.get('稀有度', ''),
+                'weight': item.get('权重', 1),
+                'health': item.get('生命值', 100),
+                'speed': item.get('移动速度', 50),
+                'attack': item.get('攻击力', 10),
+                'range': item.get('攻击范围', 100),
+                'attackSpeed': item.get('攻击速度', 1.0),
+                'defense': item.get('防御力', 0),
+                'goldReward': item.get('金币奖励', 10)
+            }
+            enemies.append(enemy_data)
+            print(f"敌人数据: {enemy_data}")
+        
+    except Exception as e:
+        print(f"解析失败: {e}")
+        import traceback
+        traceback.print_exc()
+
+if __name__ == "__main__":
+    test_excel_parsing()

+ 12 - 0
assets/resources/data/excel/test_excel_parse.py.meta

@@ -0,0 +1,12 @@
+{
+  "ver": "1.0.0",
+  "importer": "*",
+  "imported": true,
+  "uuid": "53d93cfd-5877-46e6-a680-cad132a1c127",
+  "files": [
+    ".json",
+    ".py"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 141 - 0
assets/resources/data/excel/一键部署工具.bat

@@ -0,0 +1,141 @@
+@echo off
+cd /d "%~dp0"
+
+echo ========================================
+echo Game Configuration Tool - One-Click Deploy
+echo ========================================
+echo.
+echo This script will automatically:
+echo 1. Check Python environment
+echo 2. Install required dependencies
+echo 3. Launch configuration tool
+echo.
+echo Press any key to start deployment...
+pause >nul
+echo.
+
+:: Check Python environment
+echo [Step 1/3] Checking Python environment...
+echo.
+
+python --version >nul 2>&1
+if errorlevel 1 (
+    py --version >nul 2>&1
+    if errorlevel 1 (
+        python3 --version >nul 2>&1
+        if errorlevel 1 (
+            echo [ERROR] Python not found!
+            echo.
+            echo Please install Python 3.7 or higher:
+            echo https://www.python.org/downloads/
+            echo.
+            echo Please run this script again after installation
+            pause
+            exit /b 1
+        ) else (
+            set PYTHON_CMD=python3
+        )
+    ) else (
+        set PYTHON_CMD=py
+    )
+) else (
+    set PYTHON_CMD=python
+)
+
+echo [SUCCESS] Python environment check passed
+%PYTHON_CMD% --version
+echo.
+
+:: Check dependencies
+echo [Step 2/3] Checking and installing dependencies...
+echo.
+
+:: Check pandas
+echo Checking pandas...
+%PYTHON_CMD% -c "import pandas" >nul 2>&1
+if errorlevel 1 (
+    echo pandas not found, installing...
+    %PYTHON_CMD% -m pip install pandas>=1.3.0
+    if errorlevel 1 (
+        echo [ERROR] pandas installation failed! Trying Chinese mirror...
+        %PYTHON_CMD% -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pandas>=1.3.0
+        if errorlevel 1 (
+            echo [ERROR] Failed to install pandas!
+            goto :error
+        )
+    )
+    echo [SUCCESS] pandas installed
+) else (
+    echo [SUCCESS] pandas already installed
+)
+
+:: Check openpyxl
+echo Checking openpyxl...
+%PYTHON_CMD% -c "import openpyxl" >nul 2>&1
+if errorlevel 1 (
+    echo openpyxl not found, installing...
+    %PYTHON_CMD% -m pip install openpyxl>=3.0.0
+    if errorlevel 1 (
+        echo [ERROR] openpyxl installation failed! Trying Chinese mirror...
+        %PYTHON_CMD% -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple openpyxl>=3.0.0
+        if errorlevel 1 (
+            echo [ERROR] Failed to install openpyxl!
+            goto :error
+        )
+    )
+    echo [SUCCESS] openpyxl installed
+) else (
+    echo [SUCCESS] openpyxl already installed
+)
+
+echo.
+echo [SUCCESS] All dependencies check completed!
+echo.
+
+:: Launch configuration tool
+echo [Step 3/3] Launching configuration tool...
+echo.
+
+if not exist "config_manager.py" (
+    echo [ERROR] config_manager.py file not found!
+    echo Please make sure to run this script in the correct directory
+    pause
+    exit /b 1
+)
+
+echo Starting configuration management tool...
+echo.
+echo ========================================
+echo Tool started successfully! Please operate in the popup window
+echo ========================================
+echo.
+echo Instructions:
+echo 1. Select Excel configuration files on the left
+echo 2. Click "Preview Selected Files" to view content
+echo 3. Click "Import Configuration" to complete data import
+echo.
+echo Closing this window will also close the configuration tool
+echo.
+
+%PYTHON_CMD% config_manager.py
+echo.
+echo Configuration tool closed
+pause
+exit /b 0
+
+:error
+echo.
+echo ========================================
+echo [ERROR] Deployment failed!
+echo ========================================
+echo.
+echo Possible solutions:
+echo 1. Check network connection
+echo 2. Manual installation:
+echo    %PYTHON_CMD% -m pip install pandas openpyxl
+echo 3. Use Chinese mirror:
+echo    %PYTHON_CMD% -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pandas openpyxl
+echo 4. Contact technical support
+echo.
+pause
+exit /b 1

+ 12 - 0
assets/resources/data/excel/一键部署工具.bat.meta

@@ -0,0 +1,12 @@
+{
+  "ver": "1.0.0",
+  "importer": "*",
+  "imported": true,
+  "uuid": "8bf4dd47-3f46-4c2b-8221-4527ecb43270",
+  "files": [
+    ".bat",
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

BIN
assets/resources/data/excel/关卡配置.zip


+ 0 - 12
assets/resources/data/excel/关卡配置/EnemyTypes.csv

@@ -1,12 +0,0 @@
-enemyType,baseHealth,moveSpeed,damage,specialAbility,description
-普通僵尸,100,1,10,无,基础敌人单位
-路障僵尸,200,0.5,15,高防御,高血量慢速敌人
-漫步僵尸,120,0.8,12,左右摇摆,移动轨迹不规则
-铁桶僵尸,500,0.3,20,超高防御,极高血量单位
-火药桶僵尸,80,1.2,50,死亡爆炸,死亡时造成范围伤害
-弓箭僵尸,90,1,25,远程攻击,可攻击防御塔
-法师僵尸,100,0.9,30,魔法攻击,远程魔法子弹
-隐身僵尸,110,1.1,15,周期隐身,定期进入隐身状态
-BOSS1,1000,0.4,100,近战冲锋,持铁栅门的BOSS
-BOSS2,1200,0.3,120,范围攻击,持墓碑的BOSS
-BOSS3,1500,0.5,150,多技能,赛博机械臂终极BOSS 

+ 0 - 2
assets/resources/data/excel/关卡配置/LevelInfo.csv

@@ -1,2 +0,0 @@
-name,scene,description,weapons,initialHealth,timeLimit,difficulty,energyMax
-新手引导(草地平原),grassland,新手引导关卡,学习基础塔防玩法,毛豆射手;尖胡萝卜,100,300,easy,3

+ 0 - 6
assets/resources/data/excel/关卡配置/LevelInfo_updated.csv

@@ -1,6 +0,0 @@
-name,scene,description,weapons,initialHealth,timeLimit,difficulty,energyMax
-新手引导(草地平原),grassland,新手引导关卡,学习基础塔防玩法,毛豆射手;尖胡萝卜,100,300,easy,3
-丛林探险(森林场景),forest,森林场景的塔防挑战,引入新的敌人类型和武器组合,锯齿草;西瓜炸弹;毛豆射手,100,360,normal,4
-魔法废墟(魔幻场景),magic_ruins,魔幻场景的塔防挑战,引入远程攻击敌人和隐身机制,回旋镖盆栽;炙热辣椒;尖胡萝卜,100,420,normal,5
-钢铁堡垒(工业场景),industrial,工业场景的高难度挑战,引入BOSS战和超高防御敌人,仙人散弹;秋葵导弹;西瓜炸弹,100,480,hard,6
-终极挑战(赛博都市),cyberpunk,终极挑战关卡,包含多个BOSS和复杂的敌人组合,毛豆射手;尖胡萝卜;锯齿草;西瓜炸弹;回旋镖盆栽;炙热辣椒;仙人散弹;秋葵导弹,100,600,extreme,8

+ 25 - 0
assets/resources/data/excel/启动配置工具.bat

@@ -0,0 +1,25 @@
+@echo off
+cd /d "%~dp0"
+
+echo 游戏配置管理工具 - 多配置表支持
+echo Starting GUI tool...
+echo.
+
+python config_manager.py
+if errorlevel 1 (
+    py config_manager.py
+    if errorlevel 1 (
+        python3 config_manager.py
+        if errorlevel 1 (
+            echo.
+            echo Cannot start Python environment
+            echo Please ensure Python is installed and added to PATH
+            echo Or run manually: py config_manager.py
+            echo.
+            echo If pandas library is not installed, run:
+            echo pip install pandas openpyxl
+            echo.
+            pause
+        )
+    )
+)

+ 12 - 0
assets/resources/data/excel/启动配置工具.bat.meta

@@ -0,0 +1,12 @@
+{
+  "ver": "1.0.0",
+  "importer": "*",
+  "imported": true,
+  "uuid": "eeb08af8-da13-4a67-977d-f3daa112a0f7",
+  "files": [
+    ".bat",
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 162 - 0
assets/resources/data/excel/多配置表管理工具使用说明.md

@@ -0,0 +1,162 @@
+# 游戏配置管理工具使用说明
+
+## 概述
+
+游戏配置管理工具是一个图形界面工具,支持读取和管理多种类型的Excel配置表,并将配置数据导入到对应的JSON文件中。
+
+## 支持的配置表类型
+
+### 1. BallController配置表 (纵向表格)
+- **文件**: `BallController配置表.xlsx`
+- **格式**: 纵向表格,参数名在第一列,参数值在第二/三列
+- **对应JSON**: `ballController.json`
+- **参数类型**: 包含球控制器的各种物理参数(速度、阻尼、碰撞等)
+
+### 2. 敌人配置表 (横向表格)
+- **文件**: `敌人配置表.xlsx`
+- **格式**: 横向表格,第一行是字段名,下面是数据行
+- **对应JSON**: `enemies.json`
+- **参数类型**: 敌人ID、名称、类型、生命值、攻击力等
+
+### 3. 武器配置表 (横向表格)
+- **文件**: `方块武器配置表_更新_v2.xlsx`
+- **格式**: 横向表格,第一行是字段名,下面是数据行
+- **对应JSON**: `weapons.json`
+- **参数类型**: 武器ID、名称、类型、伤害、射速等
+
+### 4. 关卡配置表 (横向表格)
+- **文件**: `关卡配置表_完整版_更新_v2.xlsx`
+- **格式**: 横向表格,第一行是字段名,下面是数据行
+- **对应JSON**: `levels/` 目录下的各个关卡JSON文件
+- **参数类型**: 关卡ID、名称、场景、难度、奖励等
+
+## 使用步骤
+
+### 1. 启动工具
+```bash
+cd d:\CocosGame\Pong\assets\resources\data\excel
+python config_manager.py
+```
+
+### 2. 选择配置文件
+1. 在左侧文件列表中浏览项目文件
+2. 选择要导入的Excel配置表文件
+3. 工具会自动识别文件类型并匹配对应的JSON文件
+
+### 3. 预览配置
+- 选择文件后,右侧预览区域会显示:
+  - Excel表格的配置数据
+  - 当前JSON文件的配置内容
+  - 检测到的配置映射信息
+
+### 4. 导入配置
+1. 确认预览内容正确后,点击"导入配置"按钮
+2. 工具会自动备份原有配置文件
+3. 将Excel数据转换并合并到JSON文件中
+4. 显示导入结果和更新的参数数量
+
+## 配置映射规则
+
+工具会根据文件名自动检测配置映射:
+
+| Excel文件名包含 | 对应JSON文件 | 表格类型 |
+|----------------|-------------|----------|
+| `BallController配置表` | `ballController.json` | 纵向 |
+| `敌人配置` | `enemies.json` | 横向 |
+| `武器配置` | `weapons.json` | 横向 |
+| `关卡配置` | `levels/` 目录 | 横向 |
+
+## 数据转换规则
+
+### 纵向表格 (如BallController)
+- 第一列:参数名
+- 第二列:参数值
+- 第三列:默认值(如果存在,优先使用)
+- 自动进行类型转换(int、float、bool、string)
+
+### 横向表格 (如敌人、武器)
+- 第一行:字段名
+- 第二行:字段说明(跳过)
+- 第三行及以后:数据记录
+- 每行数据转换为一个JSON对象
+
+## 备份机制
+
+- 每次导入前自动备份原有JSON文件
+- 备份文件命名格式:`原文件名_backup_before_import_时间戳.json`
+- 备份文件保存在同一目录下
+
+## 错误处理
+
+- 文件不存在:显示错误提示
+- 格式错误:跳过无效数据行,继续处理其他数据
+- 类型转换失败:使用默认值或跳过该参数
+- 权限问题:显示具体错误信息
+
+## 注意事项
+
+1. **依赖库要求**:需要安装 `pandas` 和 `openpyxl`
+   ```bash
+   pip install pandas openpyxl
+   ```
+
+2. **文件路径**:确保Excel文件路径正确,支持中文路径
+
+3. **数据格式**:
+   - 纵向表格:参数名不能重复
+   - 横向表格:第一行必须是字段名
+   - 空值会被自动跳过
+
+4. **JSON结构**:
+   - 纵向表格:直接更新JSON对象的属性
+   - 横向表格:更新JSON数组(如enemies、weapons)
+
+5. **多工作表支持**:对于包含多个工作表的Excel文件,工具会自动选择合适的工作表
+
+## 扩展配置
+
+如需添加新的配置表类型,可以在 `config_manager.py` 中的 `config_mappings` 字典中添加新的映射:
+
+```python
+'新配置表.xlsx': {
+    'json_path': self.project_root / "assets/resources/data/新配置.json",
+    'param_types': {
+        '参数1': str,
+        '参数2': int,
+        '参数3': float,
+        # ...
+    },
+    'format_type': 'horizontal'  # 或 'vertical'
+}
+```
+
+## 故障排除
+
+### 常见问题
+
+1. **pandas未安装**
+   - 错误:`ModuleNotFoundError: No module named 'pandas'`
+   - 解决:`pip install pandas openpyxl`
+
+2. **文件读取失败**
+   - 检查文件路径是否正确
+   - 确认文件没有被其他程序占用
+   - 检查文件权限
+
+3. **配置映射未找到**
+   - 工具会使用默认的BallController映射
+   - 可以手动添加新的配置映射
+
+4. **数据类型转换错误**
+   - 检查Excel中的数据格式
+   - 确认数值类型参数不包含文本
+
+### 日志信息
+
+工具运行时会在控制台输出详细的日志信息,包括:
+- 配置映射检测结果
+- 文件读取状态
+- 数据转换过程
+- 错误和警告信息
+
+通过查看这些日志可以快速定位问题所在。

+ 1 - 1
assets/resources/data/excel/关卡配置/LevelInfo.csv.meta → assets/resources/data/excel/多配置表管理工具使用说明.md.meta

@@ -2,7 +2,7 @@
   "ver": "1.0.1",
   "importer": "text",
   "imported": true,
-  "uuid": "b62a9981-d4d2-484c-b306-de7ead4528a6",
+  "uuid": "cf81a553-0989-467e-a3a6-94df79010ff7",
   "files": [
     ".json"
   ],

+ 119 - 0
assets/resources/data/excel/安装依赖库.bat

@@ -0,0 +1,119 @@
+@echo off
+chcp 65001 >nul
+cd /d "%~dp0"
+
+echo ========================================
+echo 游戏配置管理工具 - 依赖库自动安装
+echo Game Configuration Tool - Auto Install
+echo ========================================
+echo.
+
+echo 正在检查Python环境...
+echo Checking Python environment...
+echo.
+
+:: 检查Python是否安装
+python --version >nul 2>&1
+if errorlevel 1 (
+    py --version >nul 2>&1
+    if errorlevel 1 (
+        python3 --version >nul 2>&1
+        if errorlevel 1 (
+            echo [错误] 未找到Python环境!
+            echo [ERROR] Python not found!
+            echo.
+            echo 请先安装Python 3.7或更高版本:
+            echo Please install Python 3.7 or higher:
+            echo https://www.python.org/downloads/
+            echo.
+            pause
+            exit /b 1
+        ) else (
+            set PYTHON_CMD=python3
+        )
+    ) else (
+        set PYTHON_CMD=py
+    )
+) else (
+    set PYTHON_CMD=python
+)
+
+echo [成功] 找到Python环境
+echo [SUCCESS] Python environment found
+%PYTHON_CMD% --version
+echo.
+
+echo 正在安装依赖库...
+echo Installing dependencies...
+echo.
+
+:: 升级pip
+echo [1/3] 升级pip...
+echo [1/3] Upgrading pip...
+%PYTHON_CMD% -m pip install --upgrade pip
+if errorlevel 1 (
+    echo [警告] pip升级失败,继续安装依赖库
+    echo [WARNING] pip upgrade failed, continuing with dependencies
+)
+echo.
+
+:: 安装pandas
+echo [2/3] 安装pandas(Excel文件支持)...
+echo [2/3] Installing pandas (Excel file support)...
+%PYTHON_CMD% -m pip install pandas>=1.3.0
+if errorlevel 1 (
+    echo [错误] pandas安装失败!
+    echo [ERROR] Failed to install pandas!
+    goto :error
+)
+echo.
+
+:: 安装openpyxl
+echo [3/3] 安装openpyxl(Excel文件读写)...
+echo [3/3] Installing openpyxl (Excel file read/write)...
+%PYTHON_CMD% -m pip install openpyxl>=3.0.0
+if errorlevel 1 (
+    echo [错误] openpyxl安装失败!
+    echo [ERROR] Failed to install openpyxl!
+    goto :error
+)
+echo.
+
+echo ========================================
+echo [成功] 所有依赖库安装完成!
+echo [SUCCESS] All dependencies installed!
+echo ========================================
+echo.
+echo 现在可以运行配置工具了:
+echo You can now run the configuration tool:
+echo 1. 双击 "启动配置工具.bat"
+echo 2. Double-click "启动配置工具.bat"
+echo.
+echo 或者直接运行:
+echo Or run directly:
+echo %PYTHON_CMD% config_manager.py
+echo.
+pause
+exit /b 0
+
+:error
+echo.
+echo ========================================
+echo [错误] 依赖库安装失败!
+echo [ERROR] Dependencies installation failed!
+echo ========================================
+echo.
+echo 可能的解决方案:
+echo Possible solutions:
+echo 1. 检查网络连接
+echo    Check network connection
+echo 2. 使用国内镜像源:
+echo    Use Chinese mirror:
+echo    %PYTHON_CMD% -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pandas openpyxl
+echo 3. 手动安装:
+echo    Manual installation:
+echo    %PYTHON_CMD% -m pip install pandas
+echo    %PYTHON_CMD% -m pip install openpyxl
+echo.
+pause
+exit /b 1

+ 12 - 0
assets/resources/data/excel/安装依赖库.bat.meta

@@ -0,0 +1,12 @@
+{
+  "ver": "1.0.0",
+  "importer": "*",
+  "imported": true,
+  "uuid": "6d27532f-2157-4f33-bdfe-762849a1d03c",
+  "files": [
+    ".bat",
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

BIN
assets/resources/data/excel/技能配置表.xlsx


BIN
assets/resources/data/excel/敌人配置表.xlsx


BIN
assets/resources/data/excel/方块武器配置.zip


BIN
assets/resources/data/excel/方块武器配置/~$方块武器配置表_更新_v2.xlsx


+ 12 - 0
assets/resources/data/excel/方块武器配置/~$方块武器配置表_更新_v2.xlsx.meta

@@ -0,0 +1,12 @@
+{
+  "ver": "1.0.0",
+  "importer": "*",
+  "imported": true,
+  "uuid": "1bba1a93-ff1c-401d-84c1-1452109f1f46",
+  "files": [
+    ".json",
+    ".xlsx"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 137 - 0
assets/resources/data/excel/自动部署工具.bat

@@ -0,0 +1,137 @@
+@echo off
+setlocal enabledelayedexpansion
+cd /d "%~dp0"
+
+echo ========================================
+echo Game Configuration Tool - Auto Deploy
+echo ========================================
+echo.
+echo This script will automatically:
+echo 1. Check Python environment
+echo 2. Install required dependencies
+echo 3. Launch configuration tool
+echo.
+echo Starting deployment...
+echo.
+
+:: Check Python environment
+echo [Step 1/3] Checking Python environment...
+echo.
+
+set PYTHON_CMD=
+for %%i in (python py python3) do (
+    %%i --version >nul 2>&1
+    if !errorlevel! equ 0 (
+        set PYTHON_CMD=%%i
+        goto :python_found
+    )
+)
+
+echo [ERROR] Python not found!
+echo Please visit https://www.python.org/downloads/ to install Python
+echo Make sure to check "Add Python to PATH" during installation
+echo.
+goto :error_exit
+
+:python_found
+echo [SUCCESS] Python environment check passed
+%PYTHON_CMD% --version
+echo.
+
+:: Check dependencies
+echo [Step 2/3] Checking and installing dependencies...
+echo.
+
+:: Check pandas
+echo Checking pandas...
+%PYTHON_CMD% -c "import pandas" >nul 2>&1
+if errorlevel 1 (
+    echo pandas not found, installing...
+    %PYTHON_CMD% -m pip install pandas>=1.3.0
+    if errorlevel 1 (
+        echo [ERROR] pandas installation failed! Trying Chinese mirror...
+        %PYTHON_CMD% -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pandas>=1.3.0
+        if errorlevel 1 (
+            echo [ERROR] Failed to install pandas!
+            goto :error
+        )
+    )
+    echo [SUCCESS] pandas installed
+) else (
+    echo [SUCCESS] pandas already installed
+)
+
+:: Check openpyxl
+echo Checking openpyxl...
+%PYTHON_CMD% -c "import openpyxl" >nul 2>&1
+if errorlevel 1 (
+    echo openpyxl not found, installing...
+    %PYTHON_CMD% -m pip install openpyxl>=3.0.0
+    if errorlevel 1 (
+        echo [ERROR] openpyxl installation failed! Trying Chinese mirror...
+        %PYTHON_CMD% -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple openpyxl>=3.0.0
+        if errorlevel 1 (
+            echo [ERROR] Failed to install openpyxl!
+            goto :error
+        )
+    )
+    echo [SUCCESS] openpyxl installed
+) else (
+    echo [SUCCESS] openpyxl already installed
+)
+
+echo.
+echo [SUCCESS] All dependencies check completed!
+echo.
+
+:: Launch configuration tool
+echo [Step 3/3] Launching configuration tool...
+echo.
+
+if not exist "config_manager.py" (
+    echo [ERROR] config_manager.py file not found!
+    echo Please make sure to run this script in the correct directory
+    pause
+    exit /b 1
+)
+
+echo Starting configuration management tool...
+echo.
+echo ========================================
+echo Tool started successfully! Please operate in the popup window
+echo ========================================
+echo.
+echo Instructions:
+echo 1. Select Excel configuration files on the left
+echo 2. Click "Preview Selected Files" to view content
+echo 3. Click "Import Configuration" to complete data import
+echo.
+echo Closing this window will also close the configuration tool
+echo.
+
+%PYTHON_CMD% config_manager.py
+echo.
+echo Configuration tool closed
+pause
+exit /b 0
+
+:error
+echo.
+echo ========================================
+echo [ERROR] Deployment failed!
+echo ========================================
+echo.
+echo Possible solutions:
+echo 1. Check network connection
+echo 2. Manual installation:
+echo    %PYTHON_CMD% -m pip install pandas openpyxl
+echo 3. Use Chinese mirror:
+echo    %PYTHON_CMD% -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pandas openpyxl
+echo 4. Contact technical support
+echo.
+pause
+exit /b 1
+
+:error_exit
+pause
+exit /b 1

+ 12 - 0
assets/resources/data/excel/自动部署工具.bat.meta

@@ -0,0 +1,12 @@
+{
+  "ver": "1.0.0",
+  "importer": "*",
+  "imported": true,
+  "uuid": "f909647c-56cc-40c4-81e9-5e15c721ec4f",
+  "files": [
+    ".bat",
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 163 - 0
assets/resources/data/excel/部署使用说明.md

@@ -0,0 +1,163 @@
+# 游戏配置管理工具 - 部署使用说明
+
+## 📋 概述
+
+本工具用于管理游戏配置数据,支持从Excel表格导入配置到JSON文件。适用于策划人员在不同电脑上快速部署和使用。
+
+## 🚀 快速开始
+
+### 方法一:一键部署(推荐)
+
+1. **双击运行** `一键部署工具.bat`
+2. **按任意键** 开始自动部署
+3. **等待完成** 工具会自动检查环境、安装依赖、启动程序
+
+### 方法二:分步部署
+
+1. **安装依赖库**:双击 `安装依赖库.bat`
+2. **启动工具**:双击 `启动配置工具.bat`
+
+## 📦 系统要求
+
+### 必需环境
+- **Python 3.7+**(如未安装,请访问 https://www.python.org/downloads/)
+- **Windows 操作系统**
+
+### 依赖库
+- `pandas>=1.3.0` - Excel文件读取支持
+- `openpyxl>=3.0.0` - Excel文件写入支持
+- `tkinter` - GUI界面(Python内置)
+
+## 📁 文件说明
+
+```
+excel/
+├── config_manager.py          # 主程序文件
+├── requirements.txt           # 依赖库列表
+├── 启动配置工具.bat           # 启动脚本
+├── 安装依赖库.bat             # 依赖安装脚本
+├── 一键部署工具.bat           # 一键部署脚本
+├── 部署使用说明.md            # 本说明文档
+├── 敌人配置表.xlsx            # 示例配置表
+├── 技能配置表.xlsx            # 示例配置表
+└── 其他配置表...              # 其他Excel配置文件
+```
+
+## 🛠️ 使用方法
+
+### 1. 启动工具
+- 运行 `一键部署工具.bat` 或 `启动配置工具.bat`
+- 等待GUI界面弹出
+
+### 2. 选择配置表
+- 在左侧文件列表中**勾选**要导入的Excel配置表
+- 支持多选,可同时处理多个配置表
+
+### 3. 预览配置
+- 点击 **"预览选中文件"** 按钮
+- 在右侧文本区域查看解析结果
+- 确认数据格式正确
+
+### 4. 导入配置
+- 点击 **"导入配置"** 按钮
+- 系统自动将Excel数据转换为JSON格式
+- 保存到对应的配置文件中
+
+### 5. 其他功能
+- **"清除选择"**:取消所有文件选择
+- **"备份配置"**:备份当前JSON配置文件
+- **"恢复默认"**:恢复默认配置值
+
+## 📊 支持的配置表
+
+| 配置表名称 | 输出文件 | 说明 |
+|-----------|----------|------|
+| 敌人配置表.xlsx | enemies.json | 敌人属性配置 |
+| 技能配置表.xlsx | skill.json | 技能效果配置 |
+| 方块武器配置表_更新_v2.xlsx | weapons.json | 武器属性配置 |
+| BallController配置表.xlsx | ballController.json | 球体控制参数 |
+| 关卡配置表_完整版_更新_v2.xlsx | levels/ | 关卡配置文件 |
+
+## ❗ 常见问题
+
+### Q1: Python环境问题
+**问题**:提示"未找到Python环境"
+**解决**:
+1. 下载安装Python:https://www.python.org/downloads/
+2. 安装时勾选"Add Python to PATH"
+3. 重启电脑后重新运行
+
+### Q2: 依赖库安装失败
+**问题**:pandas或openpyxl安装失败
+**解决**:
+1. 检查网络连接
+2. 使用国内镜像源:
+   ```bash
+   python -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pandas openpyxl
+   ```
+3. 手动安装:
+   ```bash
+   python -m pip install pandas
+   python -m pip install openpyxl
+   ```
+
+### Q3: Excel文件无法识别
+**问题**:配置表无法预览或导入
+**解决**:
+1. 确认Excel文件格式正确
+2. 检查文件名是否匹配支持列表
+3. 确保Excel文件未被其他程序占用
+
+### Q4: 权限问题
+**问题**:无法写入JSON文件
+**解决**:
+1. 以管理员身份运行批处理文件
+2. 检查目标目录的写入权限
+3. 关闭可能占用文件的其他程序
+
+## 🔧 高级配置
+
+### 添加新的配置表支持
+
+如需支持新的Excel配置表,请在 `config_manager.py` 中的 `config_mappings` 字典添加配置:
+
+```python
+'新配置表.xlsx': {
+    'json_path': self.project_root / "assets/resources/data/new_config.json",
+    'param_types': {
+        '参数名1': str,
+        '参数名2': int,
+        '参数名3': float
+    },
+    'format_type': 'horizontal'  # 或 'vertical'
+}
+```
+
+### 自定义安装源
+
+如果默认的pip源速度较慢,可以修改 `安装依赖库.bat` 中的安装命令,使用其他镜像源:
+
+```batch
+# 清华源
+python -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pandas openpyxl
+
+# 阿里源
+python -m pip install -i https://mirrors.aliyun.com/pypi/simple/ pandas openpyxl
+
+# 豆瓣源
+python -m pip install -i https://pypi.douban.com/simple/ pandas openpyxl
+```
+
+## 📞 技术支持
+
+如遇到其他问题,请联系技术支持并提供以下信息:
+1. 操作系统版本
+2. Python版本(运行 `python --version` 查看)
+3. 错误信息截图
+4. 具体操作步骤
+
+---
+
+**版本**:v1.0  
+**更新日期**:2024年  
+**维护者**:AI Assistant

+ 1 - 1
assets/resources/data/excel/关卡配置/LevelInfo_updated.csv.meta → assets/resources/data/excel/部署使用说明.md.meta

@@ -2,7 +2,7 @@
   "ver": "1.0.1",
   "importer": "text",
   "imported": true,
-  "uuid": "21a89ae6-fb79-4989-bcab-e637bdbf198b",
+  "uuid": "d6d373f6-2690-442c-972f-5b76b158099b",
   "files": [
     ".json"
   ],

+ 43 - 35
assets/resources/data/skill.json

@@ -1,71 +1,79 @@
 {
   "skills": [
-    {
-      "id": "cheaper_units",
-      "name": "便宜单位",
-      "description": "方块价格降低 10%。能让玩家在购买单位时花费更少资源(如金币等)。",
-      "icon": "images/SkillImages/cheaper_units",
-      "maxLevel": 5,
-      "currentLevel": 0,
-      "effects": {
-        "priceReduction": 0.1
-      }
-    },
     {
       "id": "crit_chance",
       "name": "暴击几率",
-      "description": "将造成暴击伤害的几率提高 10%。触发暴击可使伤害大幅提升,有概率触发暴击伤害,计算方式:原来伤害+10%,最终伤害考虑暴击率,暴击率从玩家数据获得(初始暴击率10%)。",
-      "icon": "images/SkillImages/crit_chance",
+      "description": "将造成暴击伤害的几率提高 10%。触发暴击可使伤害大幅提升,有概率触发暴击伤害。",
+      "iconPath": "images/SkillImages/crit_chance",
       "maxLevel": 5,
       "currentLevel": 0,
-      "effects": {
-        "critChanceIncrease": 0.1,
-        "critDamageBonus": 0.1
-      }
+      "priceReduction": 0.0,
+      "critChanceIncrease": 0.1,
+      "critDamageBonus": 0.1,
+      "healthIncrease": 0.0,
+      "multiShotChance": 0.0,
+      "energyGainIncrease": 0.0,
+      "ballSpeedIncrease": 0.0
     },
     {
       "id": "heal",
       "name": "治疗",
       "description": "使墙的生命值增加 10%。",
-      "icon": "images/SkillImages/heal",
+      "iconPath": "images/SkillImages/heal",
       "maxLevel": 5,
       "currentLevel": 0,
-      "effects": {
-        "healthIncrease": 0.1
-      }
+      "priceReduction": 0.0,
+      "critChanceIncrease": 0.0,
+      "critDamageBonus": 0.0,
+      "healthIncrease": 0.1,
+      "multiShotChance": 0.0,
+      "energyGainIncrease": 0.0,
+      "ballSpeedIncrease": 0.0
     },
     {
       "id": "multi_shots",
       "name": "多重射击",
       "description": "将多发子弹同时射出(多射)的几率提高 10%。在游戏战斗场景中,触发多射可在一次攻击时发射多颗子弹(原来的单发子弹有概率多发子弹)。",
-      "icon": "images/SkillImages/multi_shots",
+      "iconPath": "images/SkillImages/multi_shots",
       "maxLevel": 5,
       "currentLevel": 0,
-      "effects": {
-        "multiShotChance": 0.1
-      }
+      "priceReduction": 0.0,
+      "critChanceIncrease": 0.0,
+      "critDamageBonus": 0.0,
+      "healthIncrease": 0.0,
+      "multiShotChance": 0.1,
+      "energyGainIncrease": 0.0,
+      "ballSpeedIncrease": 0.0
     },
-       {
+    {
       "id": "energy_hunter",
       "name": "能量猎手",
       "description": "获取的能量值增加 10%。在游戏进程中,更多能量值能让玩家更快触发技能选择,解锁新技能、道具或提升角色属性,从长远角度助力角色成长和游戏进度推进。",
-      "icon": "images/SkillImages/xp_hunter",
+      "iconPath": "images/SkillImages/xp_hunter",
       "maxLevel": 5,
       "currentLevel": 0,
-      "effects": {
-        "energyBonus": 0.1
-      }
+      "priceReduction": 0.0,
+      "critChanceIncrease": 0.0,
+      "critDamageBonus": 0.0,
+      "healthIncrease": 0.0,
+      "multiShotChance": 0.0,
+      "energyGainIncrease": 0.1,
+      "ballSpeedIncrease": 0.0
     },
     {
       "id": "ball_speed",
       "name": "球速提升",
       "description": "在游戏中,球的速度加快10%。",
-      "icon": "images/SkillImages/ball_speed",
+      "iconPath": "images/SkillImages/ball_speed",
       "maxLevel": 5,
       "currentLevel": 0,
-      "effects": {
-        "speedIncrease": 0.1
-      }
+      "priceReduction": 0.0,
+      "critChanceIncrease": 0.0,
+      "critDamageBonus": 0.0,
+      "healthIncrease": 0.0,
+      "multiShotChance": 0.0,
+      "energyGainIncrease": 0.0,
+      "ballSpeedIncrease": 0.1
     }
   ]
-}
+}

+ 131 - 61
assets/scripts/CombatSystem/BallController.ts

@@ -4,6 +4,7 @@ import { WeaponBullet, BulletInitData, WeaponConfig } from './WeaponBullet';
 import EventBus, { GameEvents } from '../Core/EventBus';
 import { PersistentSkillManager } from '../FourUI/SkillSystem/PersistentSkillManager';
 import { BallAni } from '../Animations/BallAni';
+import { ConfigManager, BallControllerConfig } from '../Core/ConfigManager';
 const { ccclass, property } = _decorator;
 
 @ccclass('BallController')
@@ -22,13 +23,11 @@ export class BallController extends Component {
     })
     public placedBlocksContainer: Node = null;
 
-    // 球的移动速度
-    @property
+    // 球的移动速度(从配置文件加载)
     public baseSpeed: number = 60; 
     public currentSpeed: number = 60;
     
-    // 反弹随机偏移最大角度(弧度)
-    @property
+    // 反弹随机偏移最大角度(弧度)(从配置文件加载)
     public maxReflectionRandomness: number = 0.2;
     
     // 当前活动的球
@@ -48,6 +47,9 @@ export class BallController extends Component {
     // 球的半径
     private radius: number = 0;
 
+    // 配置数据
+    private config: BallControllerConfig = null;
+
     // 是否已初始化
     private initialized: boolean = false;
     
@@ -71,25 +73,10 @@ export class BallController extends Component {
     private blockFireCooldown: Map<string, number> = new Map();
     private FIRE_COOLDOWN = 0.05;
 
-    // 防围困机制配置
-    @property({
-        tooltip: '防围困检测时间窗口(秒)'
-    })
+    // 防围困机制配置(从配置文件加载)
     public antiTrapTimeWindow: number = 5.0;
-
-    @property({
-        tooltip: '防围困撞击次数阈值'
-    })
     public antiTrapHitThreshold: number = 5;
-
-    @property({
-        tooltip: '偏移尝试次数阈值(达到后才使用穿透)'
-    })
     public deflectionAttemptThreshold: number = 3;
-
-    @property({
-        tooltip: '防围困偏移强度倍数'
-    })
     public antiTrapDeflectionMultiplier: number = 3.0;
 
     // 防围困机制状态
@@ -113,6 +100,9 @@ export class BallController extends Component {
             }
         }
         
+        // 加载配置
+        this.loadConfig();
+        
         // 只进行初始设置,不创建小球
         this.calculateGameBounds();
 
@@ -126,6 +116,87 @@ export class BallController extends Component {
     /**
      * 设置事件监听器
      */
+
+    // 加载配置
+    private loadConfig() {
+        const configManager = ConfigManager.getInstance();
+        if (configManager.isConfigLoaded()) {
+            this.config = configManager.getBallControllerConfig();
+            this.applyConfig();
+        } else {
+            // 如果配置还没加载完成,等待一段时间后重试
+            this.scheduleOnce(() => {
+                this.loadConfig();
+            }, 0.1);
+        }
+    }
+
+    // 应用配置
+    private applyConfig() {
+        if (!this.config) return;
+        
+        this.baseSpeed = this.config.baseSpeed;
+        this.currentSpeed = this.config.baseSpeed;
+        this.maxReflectionRandomness = this.config.maxReflectionRandomness;
+        this.antiTrapTimeWindow = this.config.antiTrapTimeWindow;
+        this.antiTrapHitThreshold = this.config.antiTrapHitThreshold;
+        this.deflectionAttemptThreshold = this.config.deflectionAttemptThreshold;
+        this.antiTrapDeflectionMultiplier = this.config.antiTrapDeflectionMultiplier;
+        this.FIRE_COOLDOWN = this.config.FIRE_COOLDOWN;
+        
+        console.log('BallController配置已应用:', this.config);
+    }
+
+    // 从配置中获取参数值的辅助方法
+    private getConfigValue<T>(key: keyof BallControllerConfig, defaultValue: T): T {
+        return this.config && this.config[key] !== undefined ? this.config[key] as T : defaultValue;
+    }
+
+    // 配置参数的getter方法
+    private get ballRadius(): number {
+        return this.getConfigValue('ballRadius', 10);
+    }
+
+    private get gravityScale(): number {
+        return this.getConfigValue('gravityScale', 0);
+    }
+
+    private get linearDamping(): number {
+        return this.getConfigValue('linearDamping', 0);
+    }
+
+    private get angularDamping(): number {
+        return this.getConfigValue('angularDamping', 0);
+    }
+
+    private get colliderGroup(): number {
+        return this.getConfigValue('colliderGroup', 2);
+    }
+
+    private get colliderTag(): number {
+        return this.getConfigValue('colliderTag', 1);
+    }
+
+    private get friction(): number {
+        return this.getConfigValue('friction', 0);
+    }
+
+    private get restitution(): number {
+        return this.getConfigValue('restitution', 1);
+    }
+
+    private get safeDistance(): number {
+        return this.getConfigValue('safeDistance', 50);
+    }
+
+    private get edgeOffset(): number {
+        return this.getConfigValue('edgeOffset', 20);
+    }
+
+    private get sensor(): boolean {
+        return this.getConfigValue('sensor', false);
+    }
+
     private setupEventListeners() {
         const eventBus = EventBus.getInstance();
         
@@ -308,13 +379,13 @@ export class BallController extends Component {
         if (!ball) return;
 
         const transform = ball.getComponent(UITransform);
-        const ballRadius = transform ? transform.width / 2 : 25;
+        const ballRadius = transform ? transform.width / 2 : this.ballRadius;
         
         // 计算可生成的范围(考虑小球半径,避免生成在边缘)
-        const minX = this.gameBounds.left + ballRadius + 20;
-        const maxX = this.gameBounds.right - ballRadius - 20;
-        const minY = this.gameBounds.bottom + ballRadius + 20;
-        const maxY = this.gameBounds.top - ballRadius - 20;
+        const minX = this.gameBounds.left + ballRadius + this.edgeOffset;
+        const maxX = this.gameBounds.right - ballRadius - this.edgeOffset;
+        const minY = this.gameBounds.bottom + ballRadius + this.edgeOffset;
+        const maxY = this.gameBounds.top - ballRadius - this.edgeOffset;
 
         // 获取GameArea节点
         const gameArea = find('Canvas/GameLevelUI/GameArea');
@@ -339,13 +410,13 @@ export class BallController extends Component {
             collider = ball.addComponent(CircleCollider2D);
         }
         
-        // 设置碰撞属性
-        collider.radius = ball.getComponent(UITransform)?.width / 2 || 25;
-        collider.group = 1; // 设置为球的碰撞组
-        collider.tag = 1; // 小球标签
-        collider.sensor = false;
-        collider.friction = 0; // 无摩擦
-        collider.restitution = 1; // 完全弹性碰撞
+        // 设置碰撞属性(使用配置值)
+        collider.radius = this.ballRadius;
+        collider.group = this.colliderGroup;
+        collider.tag = this.colliderTag;
+        collider.sensor = this.sensor;
+        collider.friction = this.friction;
+        collider.restitution = this.restitution;
         
         // 添加刚体组件
         let rigidBody = ball.getComponent(RigidBody2D);
@@ -353,12 +424,12 @@ export class BallController extends Component {
             rigidBody = ball.addComponent(RigidBody2D);
         }
         
-        // 设置刚体属性
+        // 设置刚体属性(使用配置值)
         rigidBody.type = 2; // 2 = 动态刚体
         rigidBody.allowSleep = false;
-        rigidBody.gravityScale = 0;
-        rigidBody.linearDamping = 0; // 无线性阻尼,保持速度不衰减
-        rigidBody.angularDamping = 0; // 无角阻尼
+        rigidBody.gravityScale = this.gravityScale;
+        rigidBody.linearDamping = this.linearDamping;
+        rigidBody.angularDamping = this.angularDamping;
         rigidBody.fixedRotation = true;
         rigidBody.enabledContactListener = true; // 启用碰撞监听
         
@@ -454,13 +525,13 @@ export class BallController extends Component {
         if (!this.activeBall) return;
 
         const transform = this.activeBall.getComponent(UITransform);
-        const ballRadius = transform ? transform.width / 2 : 25;
+        const ballRadius = transform ? transform.width / 2 : this.ballRadius;
         
         // 计算可生成的范围(考虑小球半径,避免生成在边缘)
-        const minX = this.gameBounds.left + ballRadius + 20; // 额外偏移,避免生成在边缘
-        const maxX = this.gameBounds.right - ballRadius - 20;
-        const minY = this.gameBounds.bottom + ballRadius + 20;
-        const maxY = this.gameBounds.top - ballRadius - 20;
+        const minX = this.gameBounds.left + ballRadius + this.edgeOffset; // 额外偏移,避免生成在边缘
+        const maxX = this.gameBounds.right - ballRadius - this.edgeOffset;
+        const minY = this.gameBounds.bottom + ballRadius + this.edgeOffset;
+        const maxY = this.gameBounds.top - ballRadius - this.edgeOffset;
 
         // 获取GameArea节点
         const gameArea = find('Canvas/GameLevelUI/GameArea');
@@ -521,11 +592,10 @@ export class BallController extends Component {
                 // 获取方块的尺寸
                 const blockTransform = block.getComponent(UITransform);
                 const blockSize = blockTransform ? 
-                    Math.max(blockTransform.width, blockTransform.height) / 2 : 50;
+                    Math.max(blockTransform.width, blockTransform.height) / 2 : this.safeDistance;
                 
                 // 如果距离小于小球半径+方块尺寸的一半+安全距离,认为重叠
-                const safeDistance = 20; // 额外安全距离
-                if (distance < ballRadius + blockSize + safeDistance) {
+                if (distance < ballRadius + blockSize + this.safeDistance) {
                     overlapping = true;
                     break;
                 }
@@ -542,7 +612,7 @@ export class BallController extends Component {
         // 如果找不到有效位置,使用默认位置(游戏区域底部中心)
         if (!validPosition) {
             randomX = (this.gameBounds.left + this.gameBounds.right) / 2;
-            randomY = this.gameBounds.bottom + ballRadius + 50; // 底部上方50单位
+            randomY = this.gameBounds.bottom + ballRadius + this.safeDistance; // 底部上方安全距离单位
         }
         
         // 将世界坐标转换为相对于GameArea的本地坐标
@@ -576,18 +646,18 @@ export class BallController extends Component {
         if (!rigidBody) {
             rigidBody = this.activeBall.addComponent(RigidBody2D);
             rigidBody.type = 2; // Dynamic
-            rigidBody.gravityScale = 0; // 不受重力影响
+            rigidBody.gravityScale = this.gravityScale; // 从配置获取重力缩放
             rigidBody.enabledContactListener = true; // 启用碰撞监听
             rigidBody.fixedRotation = true; // 固定旋转
             rigidBody.allowSleep = false; // 不允许休眠
-            rigidBody.linearDamping = 0; // 无线性阻尼,保持速度不衰减
-            rigidBody.angularDamping = 0; // 无角阻尼
+            rigidBody.linearDamping = this.linearDamping; // 从配置获取线性阻尼
+            rigidBody.angularDamping = this.angularDamping; // 从配置获取角阻尼
         } else {
             // 确保已有的刚体组件设置正确
             rigidBody.enabledContactListener = true;
-            rigidBody.gravityScale = 0;
-            rigidBody.linearDamping = 0; // 确保无线性阻尼,保持速度不衰减
-            rigidBody.angularDamping = 0; // 确保无角阻尼
+            rigidBody.gravityScale = this.gravityScale;
+            rigidBody.linearDamping = this.linearDamping; // 从配置获取线性阻尼
+            rigidBody.angularDamping = this.angularDamping; // 从配置获取角阻尼
             rigidBody.allowSleep = false; // 不允许休眠
         }
 
@@ -595,18 +665,18 @@ export class BallController extends Component {
         let collider = this.activeBall.getComponent(CircleCollider2D);
         if (!collider) {
             collider = this.activeBall.addComponent(CircleCollider2D);
-            collider.radius = this.radius || 25; // 使用已计算的半径或默认
-            collider.tag = 1; // 小球标签
-            collider.group = 1; // 碰撞组1 - Ball
-            collider.sensor = false; // 非传感器(实际碰撞)
-            collider.friction = 0; // 无摩擦
-            collider.restitution = 1; // 完全弹性碰撞
+            collider.radius = this.radius || this.ballRadius; // 使用已计算的半径或配置
+            collider.tag = this.colliderTag; // 从配置获取小球标签
+            collider.group = this.colliderGroup; // 从配置获取碰撞
+            collider.sensor = this.sensor; // 从配置获取传感器设置
+            collider.friction = this.friction; // 从配置获取摩擦系数
+            collider.restitution = this.restitution; // 从配置获取恢复系数
         } else {
             // 确保已有的碰撞组件设置正确
-            collider.sensor = false;
-            collider.restitution = 1;
-            collider.group = 1; // 确保是Ball
-            collider.tag = 1; // 确保标签正确
+            collider.sensor = this.sensor;
+            collider.restitution = this.restitution;
+            collider.group = this.colliderGroup; // 从配置获取碰撞
+            collider.tag = this.colliderTag; // 从配置获取标签
         }
 
         // === 使用全局回调监听器 ===

+ 86 - 66
assets/scripts/CombatSystem/BlockManager.ts

@@ -1737,89 +1737,109 @@ export class BlockManager extends Component {
 
     /** 检查当前放置的方块能否与相同稀有度的方块合成 */
     public async tryMergeBlock(block: Node) {
-        const rarity = this.getBlockRarity(block);
-        if (!rarity) return;
+        try {
+            const rarity = this.getBlockRarity(block);
+            if (!rarity) return;
 
-        // 在 placedBlocksContainer 中寻找与之重叠且可以合成的其他方块
-        if (!this.placedBlocksContainer) return;
+            // 在 placedBlocksContainer 中寻找与之重叠且可以合成的其他方块
+            if (!this.placedBlocksContainer) return;
 
-        const blockBB = this.getWorldAABB(block);
-        for (const other of this.placedBlocksContainer.children) {
-            if (other === block) continue;
-            
-            // 使用新的合成检查方法
-            if (!this.canMergeBlocks(block, other)) continue;
+            const blockBB = this.getWorldAABB(block);
+            for (const other of this.placedBlocksContainer.children) {
+                if (other === block) continue;
+                
+                // 使用新的合成检查方法
+                if (!this.canMergeBlocks(block, other)) continue;
 
-            const otherBB = this.getWorldAABB(other);
-            if (this.rectIntersects(blockBB, otherBB)) {
-                // 找到合成目标
-                await this.performMerge(block, other, rarity);
-                break;
+                const otherBB = this.getWorldAABB(other);
+                if (this.rectIntersects(blockBB, otherBB)) {
+                    // 找到合成目标
+                    await this.performMerge(block, other, rarity);
+                    break;
+                }
             }
+        } catch (error) {
+            console.error('[BlockManager] tryMergeBlock 发生错误:', error);
+            // 合成失败不影响游戏继续,只记录错误
         }
     }
 
     private async performMerge(target: Node, source: Node, rarity: string) {
-        console.log(`[BlockManager] 开始合成方块: 目标=${target.name}, 源=${source.name}, 稀有度=${rarity}`);
-        
-        // 隐藏价格标签并处理 db 关联
-        this.hidePriceLabel(source);
-        const srcDb = source['dbNode'];
-        if (srcDb) srcDb.active = false;
+        try {
+            console.log(`[BlockManager] 开始合成方块: 目标=${target.name}, 源=${source.name}, 稀有度=${rarity}`);
+            
+            // 隐藏价格标签并处理 db 关联
+            this.hidePriceLabel(source);
+            const srcDb = source['dbNode'];
+            if (srcDb) srcDb.active = false;
 
-        // 销毁被合并方块
-        source.destroy();
+            // 销毁被合并方块
+            source.destroy();
 
-        // 升级稀有度
-        const nextRarity = this.getNextRarity(rarity);
-        if (nextRarity) {
-            console.log(`[BlockManager] 合成成功,稀有度升级: ${rarity} -> ${nextRarity}`);
-            
-            // 获取当前武器配置
-            const currentConfig = this.blockWeaponConfigs.get(target);
-            if (currentConfig) {
-                // 保存原始武器名称用于日志输出
-                const originalWeaponName = currentConfig.name;
+            // 升级稀有度
+            const nextRarity = this.getNextRarity(rarity);
+            if (nextRarity) {
+                console.log(`[BlockManager] 合成成功,稀有度升级: ${rarity} -> ${nextRarity}`);
                 
-                // 获取当前关卡允许的武器列表
-                const levelWeapons = await this.getCurrentLevelWeapons();
-                
-                // 检查是否应该限制在关卡配置的武器范围内
-                if (levelWeapons.length > 0) {
-                    // 如果当前武器不在关卡配置中,说明是通过合成获得的,应该保持原武器不变,只升级稀有度
-                    if (!levelWeapons.some(weapon => weapon === originalWeaponName)) {
-                        console.log(`[BlockManager] 武器 ${originalWeaponName} 不在关卡配置中,保持原武器,只升级稀有度`);
-                    }
+                // 获取当前武器配置
+                const currentConfig = this.blockWeaponConfigs.get(target);
+                if (currentConfig) {
+                    // 保存原始武器名称用于日志输出
+                    const originalWeaponName = currentConfig.name;
                     
-                    // 无论如何,都只升级稀有度,不改变武器类型
-                    const upgradedConfig = { ...currentConfig };
-                    upgradedConfig.rarity = nextRarity;
-                    this.blockWeaponConfigs.set(target, upgradedConfig);
-                    console.log(`[BlockManager] 武器配置升级: ${originalWeaponName} 稀有度 ${rarity} -> ${nextRarity}`);
-                } else {
-                    // 如果没有关卡配置限制,正常升级稀有度
-                    const upgradedConfig = { ...currentConfig };
-                    upgradedConfig.rarity = nextRarity;
-                    this.blockWeaponConfigs.set(target, upgradedConfig);
-                    console.log(`[BlockManager] 武器配置升级: ${originalWeaponName} 稀有度 ${rarity} -> ${nextRarity}`);
+                    try {
+                        // 获取当前关卡允许的武器列表
+                        const levelWeapons = await this.getCurrentLevelWeapons();
+                        
+                        // 检查是否应该限制在关卡配置的武器范围内
+                        if (levelWeapons.length > 0) {
+                            // 如果当前武器不在关卡配置中,说明是通过合成获得的,应该保持原武器不变,只升级稀有度
+                            if (!levelWeapons.some(weapon => weapon === originalWeaponName)) {
+                                console.log(`[BlockManager] 武器 ${originalWeaponName} 不在关卡配置中,保持原武器,只升级稀有度`);
+                            }
+                            
+                            // 无论如何,都只升级稀有度,不改变武器类型
+                            const upgradedConfig = { ...currentConfig };
+                            upgradedConfig.rarity = nextRarity;
+                            this.blockWeaponConfigs.set(target, upgradedConfig);
+                            console.log(`[BlockManager] 武器配置升级: ${originalWeaponName} 稀有度 ${rarity} -> ${nextRarity}`);
+                        } else {
+                            // 如果没有关卡配置限制,正常升级稀有度
+                            const upgradedConfig = { ...currentConfig };
+                            upgradedConfig.rarity = nextRarity;
+                            this.blockWeaponConfigs.set(target, upgradedConfig);
+                            console.log(`[BlockManager] 武器配置升级: ${originalWeaponName} 稀有度 ${rarity} -> ${nextRarity}`);
+                        }
+                    } catch (weaponError) {
+                        console.error('[BlockManager] 获取关卡武器配置失败:', weaponError);
+                        // 武器配置获取失败时,仍然升级稀有度
+                        const upgradedConfig = { ...currentConfig };
+                        upgradedConfig.rarity = nextRarity;
+                        this.blockWeaponConfigs.set(target, upgradedConfig);
+                        console.log(`[BlockManager] 武器配置升级(降级处理): ${originalWeaponName} 稀有度 ${rarity} -> ${nextRarity}`);
+                    }
                 }
+                
+                // 更新方块颜色
+                this.setBlockRarityColor(target, nextRarity);
             }
-            
-            // 更新方块颜色
-            this.setBlockRarityColor(target, nextRarity);
-        }
 
-        // 播放烟雾动画
-        const worldPos = new Vec3();
-        target.getWorldPosition(worldPos);
-        this.spawnMergeSmoke(worldPos);
+            // 播放烟雾动画
+            const worldPos = new Vec3();
+            target.getWorldPosition(worldPos);
+            this.spawnMergeSmoke(worldPos);
 
-        // 递归检查是否还能继续合成
-        if (nextRarity) {
-            await this.tryMergeBlock(target);
+            // 递归检查是否还能继续合成
+            if (nextRarity) {
+                await this.tryMergeBlock(target);
+            }
+            
+            console.log(`[BlockManager] 合成完成`);
+        } catch (error) {
+            console.error('[BlockManager] performMerge 发生错误:', error);
+            // 合成失败时重新抛出错误,让上层处理
+            throw error;
         }
-        
-        console.log(`[BlockManager] 合成完成`);
     }
 
     private getBlockRarity(block: Node): string | null {

+ 145 - 79
assets/scripts/CombatSystem/BlockSelection/GameBlockSelection.ts

@@ -216,9 +216,27 @@ export class GameBlockSelection extends Component {
     // 处理方块拖拽事件设置请求
     private onSetupBlockDragEventsEvent(blocks: Node[]) {
         console.log('[GameBlockSelection] 接收到方块拖拽事件设置请求,方块数量:', blocks.length);
+        
+        // 确保组件仍然有效
+        if (!this.node || !this.node.isValid) {
+            console.warn('[GameBlockSelection] 组件节点无效,跳过拖拽事件设置');
+            return;
+        }
+        
         for (const block of blocks) {
-            this.setupBlockDragEvents(block);
-            console.log(`[GameBlockSelection] 为方块 ${block.name} 设置拖拽事件`);
+            if (block && block.isValid) {
+                // 先移除可能存在的旧事件监听器
+                block.off(Node.EventType.TOUCH_START);
+                block.off(Node.EventType.TOUCH_MOVE);
+                block.off(Node.EventType.TOUCH_END);
+                block.off(Node.EventType.TOUCH_CANCEL);
+                
+                // 重新设置拖拽事件
+                this.setupBlockDragEvents(block);
+                console.log(`[GameBlockSelection] 为方块 ${block.name} 重新设置拖拽事件`);
+            } else {
+                console.warn('[GameBlockSelection] 跳过无效方块的拖拽事件设置');
+            }
         }
     }
     
@@ -302,33 +320,50 @@ export class GameBlockSelection extends Component {
 
     // 刷新方块按钮点击
     private onRefreshClicked() {
+        console.log('[GameBlockSelection] 刷新方块按钮被点击');
+        
         // 应用便宜技能效果计算实际费用
         const actualCost = this.getActualCost(this.REFRESH_COST);
+        console.log(`[GameBlockSelection] 计算实际费用: ${actualCost}`);
         
         if (!this.canSpendCoins(actualCost)) {
+            console.log('[GameBlockSelection] 金币不足,无法刷新方块');
             this.showInsufficientCoinsUI();
             return;
         }
 
         // 扣除金币
         if (this.session.spendCoins(actualCost)) {
+            console.log(`[GameBlockSelection] 成功扣除 ${actualCost} 金币`);
             this.updateCoinDisplay();
             
             // 刷新方块
             if (this.blockManager) {
+                console.log('[GameBlockSelection] 开始刷新方块流程');
+                
                 // 找到PlacedBlocks容器
                 const placedBlocksContainer = find('Canvas/GameLevelUI/PlacedBlocks');
                 if (placedBlocksContainer) {
+                    console.log('[GameBlockSelection] 移除已放置方块的标签');
                     // 移除已放置方块的标签
                     BlockTag.removeTagsInContainer(placedBlocksContainer);
                 }
                 
                 // 刷新方块
+                console.log('[GameBlockSelection] 调用 blockManager.refreshBlocks()');
                 this.blockManager.refreshBlocks();
-                console.log(`刷新方块成功,扣除${actualCost}金币`);
+                
+                // 等待一帧确保方块生成完成
+                this.scheduleOnce(() => {
+                    console.log('[GameBlockSelection] 刷新方块延迟检查完成');
+                }, 0.1);
+                
+                console.log(`[GameBlockSelection] 刷新方块成功,扣除${actualCost}金币`);
             } else {
-                console.error('找不到BlockManager,无法刷新方块');
+                console.error('[GameBlockSelection] 找不到BlockManager,无法刷新方块');
             }
+        } else {
+            console.error('[GameBlockSelection] 扣除金币失败');
         }
     }
 
@@ -595,7 +630,13 @@ export class GameBlockSelection extends Component {
             }
             
             if (this.currentDragBlock) {
-                await this.handleBlockDrop(event);
+                try {
+                    await this.handleBlockDrop(event);
+                } catch (error) {
+                    console.error('[GameBlockSelection] 处理方块拖拽放置时发生错误:', error);
+                    // 发生错误时,将方块返回原位置
+                    this.returnBlockToOriginalPosition();
+                }
                 
                 this.currentDragBlock = null;
                 // 拖拽结束时恢复碰撞体
@@ -628,57 +669,64 @@ export class GameBlockSelection extends Component {
     
     // 处理方块放下
     private async handleBlockDrop(event: EventTouch) {
-        const touchPos = event.getLocation();
-        const startLocation = this.currentDragBlock['startLocation'];
-        
-        if (this.isInKuangArea(touchPos)) {
-            // 检查是否有标签,只有有标签的方块才能放回kuang
-            if (BlockTag.hasTag(this.currentDragBlock)) {
-                this.returnBlockToKuang(startLocation);
-                // 返回kuang后输出格子占用情况
-                this.blockManager.printGridOccupationMatrix();
-                // 刷新方块占用情况
-                this.refreshGridOccupation();
-            } else {
-                // 没有标签的方块不能放回kuang,返回原位置
-                console.log(`[GameBlockSelection] 方块 ${this.currentDragBlock.name} 没有标签,不能放回kuang区域`);
-                this.returnBlockToOriginalPosition();
-                // 返回原位置后输出格子占用情况
-                this.blockManager.printGridOccupationMatrix();
-                // 刷新方块占用情况
-                this.refreshGridOccupation();
-            }
-        } else if (this.blockManager.tryPlaceBlockToGrid(this.currentDragBlock)) {
-            await this.handleSuccessfulPlacement(startLocation);
-            // 放置成功后输出格子占用情况
-            this.blockManager.printGridOccupationMatrix();
-            // 刷新方块占用情况
-            this.refreshGridOccupation();
-        } else {
-            console.log(`[GameBlockSelection] 方块 ${this.currentDragBlock.name} 放置到网格失败`);
-            console.log(`[GameBlockSelection] 方块标签状态:`, BlockTag.hasTag(this.currentDragBlock));
-            console.log(`[GameBlockSelection] 方块UUID:`, this.currentDragBlock.uuid);
+        try {
+            const touchPos = event.getLocation();
+            const startLocation = this.currentDragBlock['startLocation'];
             
-            // 放置失败,尝试直接与重叠方块合成
-            if (this.blockManager.tryMergeOnOverlap(this.currentDragBlock)) {
-                // 合成成功时,若来自 kuang 则扣费
-                if (startLocation !== 'grid') {
-                    const price = this.blockManager.getBlockPrice(this.currentDragBlock);
-                    this.blockManager.deductPlayerCoins(price);
+            if (this.isInKuangArea(touchPos)) {
+                // 检查是否有标签,只有有标签的方块才能放回kuang
+                if (BlockTag.hasTag(this.currentDragBlock)) {
+                    this.returnBlockToKuang(startLocation);
+                    // 返回kuang后输出格子占用情况
+                    this.blockManager.printGridOccupationMatrix();
+                    // 刷新方块占用情况
+                    this.refreshGridOccupation();
+                } else {
+                    // 没有标签的方块不能放回kuang,返回原位置
+                    console.log(`[GameBlockSelection] 方块 ${this.currentDragBlock.name} 没有标签,不能放回kuang区域`);
+                    this.returnBlockToOriginalPosition();
+                    // 返回原位置后输出格子占用情况
+                    this.blockManager.printGridOccupationMatrix();
+                    // 刷新方块占用情况
+                    this.refreshGridOccupation();
                 }
-                // 当前拖拽块已被销毁(合并时),无需复位
-                // 合成成功后输出格子占用情况
+            } else if (this.blockManager.tryPlaceBlockToGrid(this.currentDragBlock)) {
+                await this.handleSuccessfulPlacement(startLocation);
+                // 放置成功后输出格子占用情况
                 this.blockManager.printGridOccupationMatrix();
                 // 刷新方块占用情况
                 this.refreshGridOccupation();
             } else {
-                console.log(`[GameBlockSelection] 方块 ${this.currentDragBlock.name} 合成也失败,返回原位置`);
-                this.returnBlockToOriginalPosition();
-                // 放置失败后输出格子占用情况
-                this.blockManager.printGridOccupationMatrix();
-                // 刷新方块占用情况
-                this.refreshGridOccupation();
+                console.log(`[GameBlockSelection] 方块 ${this.currentDragBlock.name} 放置到网格失败`);
+                console.log(`[GameBlockSelection] 方块标签状态:`, BlockTag.hasTag(this.currentDragBlock));
+                console.log(`[GameBlockSelection] 方块UUID:`, this.currentDragBlock.uuid);
+                
+                // 放置失败,尝试直接与重叠方块合成
+                if (this.blockManager.tryMergeOnOverlap(this.currentDragBlock)) {
+                    // 合成成功时,若来自 kuang 则扣费
+                    if (startLocation !== 'grid') {
+                        const price = this.blockManager.getBlockPrice(this.currentDragBlock);
+                        this.blockManager.deductPlayerCoins(price);
+                    }
+                    // 当前拖拽块已被销毁(合并时),无需复位
+                    // 合成成功后输出格子占用情况
+                    this.blockManager.printGridOccupationMatrix();
+                    // 刷新方块占用情况
+                    this.refreshGridOccupation();
+                } else {
+                    console.log(`[GameBlockSelection] 方块 ${this.currentDragBlock.name} 合成也失败,返回原位置`);
+                    this.returnBlockToOriginalPosition();
+                    // 放置失败后输出格子占用情况
+                    this.blockManager.printGridOccupationMatrix();
+                    // 刷新方块占用情况
+                    this.refreshGridOccupation();
+                }
             }
+        } catch (error) {
+            console.error('[GameBlockSelection] handleBlockDrop 发生错误:', error);
+            // 发生错误时,将方块返回原位置
+            this.returnBlockToOriginalPosition();
+            throw error; // 重新抛出错误,让上层处理
         }
     }
     
@@ -731,33 +779,10 @@ export class GameBlockSelection extends Component {
     
     // 处理成功放置
     private async handleSuccessfulPlacement(startLocation: string) {
-        const price = this.blockManager.getBlockPrice(this.currentDragBlock);
-        
-        if (startLocation === 'grid') {
-            this.blockManager.clearTempStoredOccupiedGrids(this.currentDragBlock);
-            this.blockManager.blockLocations.set(this.currentDragBlock, 'grid');
-            this.blockManager.hidePriceLabel(this.currentDragBlock);
-            
-            const dbNode = this.currentDragBlock['dbNode'];
-            if (dbNode) {
-                dbNode.active = false;
-            }
-            
-            // 立即将方块移动到PlacedBlocks节点下,不等游戏开始
-            this.blockManager.moveBlockToPlacedBlocks(this.currentDragBlock);
+        try {
+            const price = this.blockManager.getBlockPrice(this.currentDragBlock);
             
-            // 如果游戏已开始,添加锁定视觉提示
-            if (this.blockManager.gameStarted) {
-                this.blockManager.addLockedVisualHint(this.currentDragBlock);
-                
-                // 游戏开始后放置的方块移除标签,不能再放回kuang
-                BlockTag.removeTag(this.currentDragBlock);
-            }
-
-            // 检查并执行合成
-            await this.blockManager.tryMergeBlock(this.currentDragBlock);
-        } else {
-            if (this.blockManager.deductPlayerCoins(price)) {
+            if (startLocation === 'grid') {
                 this.blockManager.clearTempStoredOccupiedGrids(this.currentDragBlock);
                 this.blockManager.blockLocations.set(this.currentDragBlock, 'grid');
                 this.blockManager.hidePriceLabel(this.currentDragBlock);
@@ -767,8 +792,6 @@ export class GameBlockSelection extends Component {
                     dbNode.active = false;
                 }
                 
-                this.currentDragBlock['placedBefore'] = true;
-                
                 // 立即将方块移动到PlacedBlocks节点下,不等游戏开始
                 this.blockManager.moveBlockToPlacedBlocks(this.currentDragBlock);
                 
@@ -781,10 +804,52 @@ export class GameBlockSelection extends Component {
                 }
 
                 // 检查并执行合成
-                await this.blockManager.tryMergeBlock(this.currentDragBlock);
+                try {
+                    await this.blockManager.tryMergeBlock(this.currentDragBlock);
+                } catch (mergeError) {
+                    console.error('[GameBlockSelection] 方块合成时发生错误:', mergeError);
+                    // 合成失败不影响方块放置,只记录错误
+                }
             } else {
-                this.returnBlockToOriginalPosition();
+                if (this.blockManager.deductPlayerCoins(price)) {
+                    this.blockManager.clearTempStoredOccupiedGrids(this.currentDragBlock);
+                    this.blockManager.blockLocations.set(this.currentDragBlock, 'grid');
+                    this.blockManager.hidePriceLabel(this.currentDragBlock);
+                    
+                    const dbNode = this.currentDragBlock['dbNode'];
+                    if (dbNode) {
+                        dbNode.active = false;
+                    }
+                    
+                    this.currentDragBlock['placedBefore'] = true;
+                    
+                    // 立即将方块移动到PlacedBlocks节点下,不等游戏开始
+                    this.blockManager.moveBlockToPlacedBlocks(this.currentDragBlock);
+                    
+                    // 如果游戏已开始,添加锁定视觉提示
+                    if (this.blockManager.gameStarted) {
+                        this.blockManager.addLockedVisualHint(this.currentDragBlock);
+                        
+                        // 游戏开始后放置的方块移除标签,不能再放回kuang
+                        BlockTag.removeTag(this.currentDragBlock);
+                    }
+
+                    // 检查并执行合成
+                    try {
+                        await this.blockManager.tryMergeBlock(this.currentDragBlock);
+                    } catch (mergeError) {
+                        console.error('[GameBlockSelection] 方块合成时发生错误:', mergeError);
+                        // 合成失败不影响方块放置,只记录错误
+                    }
+                } else {
+                    this.returnBlockToOriginalPosition();
+                }
             }
+        } catch (error) {
+            console.error('[GameBlockSelection] handleSuccessfulPlacement 发生错误:', error);
+            // 发生错误时,将方块返回原位置
+            this.returnBlockToOriginalPosition();
+            throw error; // 重新抛出错误,让上层处理
         }
     }
     
@@ -944,5 +1009,6 @@ export class GameBlockSelection extends Component {
         const eventBus = EventBus.getInstance();
         eventBus.off(GameEvents.RESET_BLOCK_SELECTION, this.onResetBlockSelectionEvent, this);
         eventBus.off(GameEvents.GAME_START, this.onGameStartEvent, this);
+        eventBus.off(GameEvents.SETUP_BLOCK_DRAG_EVENTS, this.onSetupBlockDragEventsEvent, this);
     }
 }

+ 46 - 0
assets/scripts/Core/ConfigManager.ts

@@ -52,6 +52,28 @@ export interface WeaponConfig {
     };
 }
 
+// 球控制器配置接口
+export interface BallControllerConfig {
+    baseSpeed: number;
+    maxReflectionRandomness: number;
+    antiTrapTimeWindow: number;
+    antiTrapHitThreshold: number;
+    deflectionAttemptThreshold: number;
+    antiTrapDeflectionMultiplier: number;
+    FIRE_COOLDOWN: number;
+    ballRadius: number;
+    gravityScale: number;
+    linearDamping: number;
+    angularDamping: number;
+    colliderGroup: number;
+    colliderTag: number;
+    friction: number;
+    restitution: number;
+    safeDistance: number;
+    edgeOffset: number;
+    sensor: boolean;
+}
+
 // 敌人配置接口
 export interface EnemyConfig {
     id: string;
@@ -153,6 +175,7 @@ export class ConfigManager extends BaseSingleton {
     
     private weaponsConfig: any = null;
     private enemiesConfig: any = null;
+    private ballControllerConfig: BallControllerConfig = null;
     private configLoaded: boolean = false;
     
     // 武器权重缓存
@@ -176,6 +199,8 @@ export class ConfigManager extends BaseSingleton {
             await this.loadWeaponsConfig();
             // 加载敌人配置
             await this.loadEnemiesConfig();
+            // 加载球控制器配置
+            await this.loadBallControllerConfig();
             
             this.configLoaded = true;
         } catch (error) {
@@ -233,6 +258,22 @@ export class ConfigManager extends BaseSingleton {
         });
     }
 
+    // 加载球控制器配置
+    private loadBallControllerConfig(): Promise<void> {
+        return new Promise((resolve, reject) => {
+            resources.load('data/ballController', JsonAsset, (err, jsonAsset) => {
+                if (err) {
+                    console.error('加载球控制器配置失败:', err);
+                    reject(err);
+                } else {
+                    this.ballControllerConfig = jsonAsset.json as BallControllerConfig;
+                    console.log('球控制器配置加载成功:', this.ballControllerConfig);
+                    resolve();
+                }
+            });
+        });
+    }
+
     // 构建武器权重列表
     private buildWeaponWeightedList() {
         if (!this.weaponsConfig) return;
@@ -441,4 +482,9 @@ export class ConfigManager extends BaseSingleton {
     public isConfigLoaded(): boolean {
         return this.configLoaded;
     }
+
+    // 获取球控制器配置
+    public getBallControllerConfig(): BallControllerConfig | null {
+        return this.ballControllerConfig;
+    }
 }

+ 9 - 0
assets/scripts/Test.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "55964fe8-28a8-454c-b9d4-20c976806a53",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}