Procházet zdrojové kódy

敌人抛掷战斗震动等

181404010226 před 2 měsíci
rodič
revize
053e8185d5
27 změnil soubory, kde provedl 1906 přidání a 247 odebrání
  1. 171 152
      assets/Scenes/GameLevel.scene
  2. 238 0
      assets/assets/Prefabs/EnemyProjectile.prefab
  3. 13 0
      assets/assets/Prefabs/EnemyProjectile.prefab.meta
  4. 52 41
      assets/data/enemies.json
  5. binární
      assets/data/excel/__pycache__/enemy_config_manager.cpython-313.pyc
  6. 7 4
      assets/data/excel/enemy_config_manager.py
  7. binární
      assets/data/excel/~$敌人配置表.xlsx
  8. 0 12
      assets/data/excel/~$敌人配置表.xlsx.meta
  9. binární
      assets/data/excel/敌人配置表.xlsx
  10. 99 0
      assets/scripts/CombatSystem/BlockManager.ts
  11. 5 0
      assets/scripts/CombatSystem/EnemyComponent.ts
  12. 5 1
      assets/scripts/CombatSystem/EnemyController.ts
  13. 199 35
      assets/scripts/CombatSystem/EnemyInstance.ts
  14. 9 0
      assets/scripts/CombatSystem/EnemyWeapon.meta
  15. 88 0
      assets/scripts/CombatSystem/EnemyWeapon/EnemyProjectile.ts
  16. 9 0
      assets/scripts/CombatSystem/EnemyWeapon/EnemyProjectile.ts.meta
  17. 479 0
      assets/scripts/CombatSystem/EnemyWeapon/EnemyProjectileInstance.ts
  18. 9 0
      assets/scripts/CombatSystem/EnemyWeapon/EnemyProjectileInstance.ts.meta
  19. 224 0
      assets/scripts/CombatSystem/EnemyWeapon/ScreenShakeManager.ts
  20. 9 0
      assets/scripts/CombatSystem/EnemyWeapon/ScreenShakeManager.ts.meta
  21. 243 0
      assets/scripts/CombatSystem/EnemyWeapon/WeaponEffectManager.ts
  22. 9 0
      assets/scripts/CombatSystem/EnemyWeapon/WeaponEffectManager.ts.meta
  23. 14 0
      assets/scripts/CombatSystem/Wall.ts
  24. 1 0
      assets/scripts/Core/ConfigManager.ts
  25. 2 0
      assets/scripts/Core/EventBus.ts
  26. 14 0
      assets/scripts/LevelSystem/IN_game.ts
  27. 7 2
      settings/v2/packages/project.json

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 171 - 152
assets/Scenes/GameLevel.scene


+ 238 - 0
assets/assets/Prefabs/EnemyProjectile.prefab

@@ -0,0 +1,238 @@
+[
+  {
+    "__type__": "cc.Prefab",
+    "_name": "EnemyProjectile",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_native": "",
+    "data": {
+      "__id__": 1
+    },
+    "optimizationPolicy": 0,
+    "persistent": false
+  },
+  {
+    "__type__": "cc.Node",
+    "_name": "EnemyProjectile",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": null,
+    "_children": [],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 2
+      },
+      {
+        "__id__": 4
+      },
+      {
+        "__id__": 6
+      },
+      {
+        "__id__": 8
+      },
+      {
+        "__id__": 10
+      }
+    ],
+    "_prefab": {
+      "__id__": 12
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 0.3,
+      "y": 0.3,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 1073741824,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 1
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 3
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 142,
+      "height": 142
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "38OH/Vv/9BMp00Z3SfNrDh"
+  },
+  {
+    "__type__": "cc.Sprite",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 1
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 5
+    },
+    "_customMaterial": null,
+    "_srcBlendFactor": 2,
+    "_dstBlendFactor": 4,
+    "_color": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 255
+    },
+    "_spriteFrame": {
+      "__uuid__": "80f212d1-c325-4e21-b2f5-19f7fa77c9b1@f9941",
+      "__expectedType__": "cc.SpriteFrame"
+    },
+    "_type": 0,
+    "_fillType": 0,
+    "_sizeMode": 1,
+    "_fillCenter": {
+      "__type__": "cc.Vec2",
+      "x": 0,
+      "y": 0
+    },
+    "_fillStart": 0,
+    "_fillRange": 0,
+    "_isTrimmedMode": true,
+    "_useGrayscale": false,
+    "_atlas": null,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "adz2ftoNNNPK/g/rKe2JTW"
+  },
+  {
+    "__type__": "cc.RigidBody2D",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 1
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 7
+    },
+    "enabledContactListener": true,
+    "bullet": true,
+    "awakeOnLoad": true,
+    "_group": 128,
+    "_type": 1,
+    "_allowSleep": true,
+    "_gravityScale": 0,
+    "_linearDamping": 0,
+    "_angularDamping": 0,
+    "_linearVelocity": {
+      "__type__": "cc.Vec2",
+      "x": 0,
+      "y": 0
+    },
+    "_angularVelocity": 0,
+    "_fixedRotation": true,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "178GmZaPlHibpDQAik2N2H"
+  },
+  {
+    "__type__": "cc.CircleCollider2D",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 1
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 9
+    },
+    "tag": 7,
+    "_group": 128,
+    "_density": 1,
+    "_sensor": false,
+    "_friction": 0.2,
+    "_restitution": 0,
+    "_offset": {
+      "__type__": "cc.Vec2",
+      "x": 0,
+      "y": 0
+    },
+    "_radius": 66,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "c3zvMu9rFLjLnFtmXONd1E"
+  },
+  {
+    "__type__": "5b808AziiJHu72FiuokUp6U",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 1
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 11
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "66nN16AptJSq4tkTU77b9G"
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "c46/YsCPVOJYA4mWEpNYRx",
+    "instance": null,
+    "targetOverrides": null
+  }
+]

+ 13 - 0
assets/assets/Prefabs/EnemyProjectile.prefab.meta

@@ -0,0 +1,13 @@
+{
+  "ver": "1.1.50",
+  "importer": "prefab",
+  "imported": true,
+  "uuid": "68af16de-c8d1-4b21-94ff-0bf044cb7356",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {
+    "syncNodeName": "EnemyProjectile"
+  }
+}

+ 52 - 41
assets/data/enemies.json

@@ -23,7 +23,7 @@
     },
     "combat": {
       "attackDamage": 1,
-      "attackRange": 30.0,
+      "attackRange": 0.0,
       "attackSpeed": 2.0,
       "canBlock": false,
       "blockChance": 0.0,
@@ -33,7 +33,8 @@
       "attackDelay": 1.0,
       "weaponType": "none",
       "projectileType": "none",
-      "projectileSpeed": 50.0
+      "projectileSpeed": 50.0,
+      "causesWallShake": false
     },
     "visualConfig": {
       "spritePath": "Animation/EnemyAni/001",
@@ -116,8 +117,8 @@
     },
     "combat": {
       "attackDamage": 1,
-      "attackRange": 30.0,
-      "attackSpeed": 3.0,
+      "attackRange": 0.0,
+      "attackSpeed": 2.0,
       "canBlock": false,
       "blockChance": 0.0,
       "blockDamageReduction": 0.5,
@@ -126,7 +127,8 @@
       "attackDelay": 1.0,
       "weaponType": "none",
       "projectileType": "none",
-      "projectileSpeed": 100.0
+      "projectileSpeed": 100.0,
+      "causesWallShake": true
     },
     "visualConfig": {
       "spritePath": "Animation/EnemyAni/002",
@@ -209,8 +211,8 @@
     },
     "combat": {
       "attackDamage": 1,
-      "attackRange": 30.0,
-      "attackSpeed": 3.0,
+      "attackRange": 0.0,
+      "attackSpeed": 2.0,
       "canBlock": false,
       "blockChance": 0.0,
       "blockDamageReduction": 0.5,
@@ -219,7 +221,8 @@
       "attackDelay": 1.0,
       "weaponType": "baseball_bat",
       "projectileType": "none",
-      "projectileSpeed": 100.0
+      "projectileSpeed": 100.0,
+      "causesWallShake": true
     },
     "visualConfig": {
       "spritePath": "Animation/EnemyAni/004",
@@ -302,8 +305,8 @@
     },
     "combat": {
       "attackDamage": 1,
-      "attackRange": 250.0,
-      "attackSpeed": 4.0,
+      "attackRange": 150.0,
+      "attackSpeed": 0.5,
       "canBlock": false,
       "blockChance": 0.0,
       "blockDamageReduction": 0.5,
@@ -312,7 +315,8 @@
       "attackDelay": 1.0,
       "weaponType": "none",
       "projectileType": "magic_bolt",
-      "projectileSpeed": 50.0
+      "projectileSpeed": 50.0,
+      "causesWallShake": true
     },
     "visualConfig": {
       "spritePath": "Animation/EnemyAni/003",
@@ -395,8 +399,8 @@
     },
     "combat": {
       "attackDamage": 1,
-      "attackRange": 400.0,
-      "attackSpeed": 2.0,
+      "attackRange": 210.0,
+      "attackSpeed": 0.5,
       "canBlock": false,
       "blockChance": 0.0,
       "blockDamageReduction": 0.5,
@@ -405,7 +409,8 @@
       "attackDelay": 1.0,
       "weaponType": "none",
       "projectileType": "arrow",
-      "projectileSpeed": 100.0
+      "projectileSpeed": 300.0,
+      "causesWallShake": true
     },
     "visualConfig": {
       "spritePath": "Animation/EnemyAni/005",
@@ -488,7 +493,7 @@
     },
     "combat": {
       "attackDamage": 1,
-      "attackRange": 30.0,
+      "attackRange": 0.0,
       "attackSpeed": 3.0,
       "canBlock": false,
       "blockChance": 0.0,
@@ -498,7 +503,8 @@
       "attackDelay": 1.0,
       "weaponType": "none",
       "projectileType": "none",
-      "projectileSpeed": 100.0
+      "projectileSpeed": 100.0,
+      "causesWallShake": true
     },
     "visualConfig": {
       "spritePath": "Animation/EnemyAni/008",
@@ -588,17 +594,18 @@
     },
     "combat": {
       "attackDamage": 1,
-      "attackRange": 30.0,
-      "attackSpeed": 3.0,
+      "attackRange": 0.0,
+      "attackSpeed": 1.0,
       "canBlock": true,
       "blockChance": 30.0,
       "blockDamageReduction": 1.0,
-      "attackCooldown": 4.0,
+      "attackCooldown": 3.0,
       "attackType": "heavy_melee",
       "attackDelay": 1.0,
       "weaponType": "none",
       "projectileType": "none",
-      "projectileSpeed": 100.0
+      "projectileSpeed": 100.0,
+      "causesWallShake": true
     },
     "visualConfig": {
       "spritePath": "Animation/EnemyAni/006",
@@ -681,8 +688,8 @@
     },
     "combat": {
       "attackDamage": 1,
-      "attackRange": 30.0,
-      "attackSpeed": 3.0,
+      "attackRange": 0.0,
+      "attackSpeed": 0.5,
       "canBlock": false,
       "blockChance": 0.0,
       "blockDamageReduction": 0.5,
@@ -691,7 +698,8 @@
       "attackDelay": 1.0,
       "weaponType": "none",
       "projectileType": "none",
-      "projectileSpeed": 100.0
+      "projectileSpeed": 100.0,
+      "causesWallShake": true
     },
     "visualConfig": {
       "spritePath": "Animation/EnemyAni/007",
@@ -773,9 +781,9 @@
       "speedVariation": 0.1
     },
     "combat": {
-      "attackDamage": 1,
-      "attackRange": 30.0,
-      "attackSpeed": 4.0,
+      "attackDamage": 2,
+      "attackRange": 0.0,
+      "attackSpeed": 1.0,
       "canBlock": false,
       "blockChance": 0.0,
       "blockDamageReduction": 0.5,
@@ -784,7 +792,8 @@
       "attackDelay": 1.0,
       "weaponType": "none",
       "projectileType": "none",
-      "projectileSpeed": 100.0
+      "projectileSpeed": 100.0,
+      "causesWallShake": true
     },
     "visualConfig": {
       "spritePath": "Animation/EnemyAni/009",
@@ -798,7 +807,7 @@
         "attack": "attack",
         "death": "dead"
       },
-      "weaponProp": "props/cyber_arm"
+      "weaponProp": "props/iron_gate"
     },
     "audioConfig": {
       "attackSound": "enemy_attack",
@@ -843,7 +852,7 @@
         "attack": "attack",
         "death": "dead"
       },
-      "weapon_prop": "props/cyber_arm"
+      "weapon_prop": "props/iron_gate"
     },
     "audio": {
       "attack_sound": "data/弹球音效/hammer1.mp3",
@@ -886,9 +895,9 @@
       "speedVariation": 0.1
     },
     "combat": {
-      "attackDamage": 1,
-      "attackRange": 30.0,
-      "attackSpeed": 4.0,
+      "attackDamage": 2,
+      "attackRange": 0.0,
+      "attackSpeed": 1.0,
       "canBlock": false,
       "blockChance": 0.0,
       "blockDamageReduction": 0.5,
@@ -897,7 +906,8 @@
       "attackDelay": 1.0,
       "weaponType": "none",
       "projectileType": "none",
-      "projectileSpeed": 100.0
+      "projectileSpeed": 100.0,
+      "causesWallShake": true
     },
     "visualConfig": {
       "spritePath": "Animation/EnemyAni/010",
@@ -911,7 +921,7 @@
         "attack": "attack",
         "death": "dead"
       },
-      "weaponProp": "nan"
+      "weaponProp": "props/tombstone"
     },
     "audioConfig": {
       "attackSound": "enemy_attack",
@@ -956,7 +966,7 @@
         "attack": "attack",
         "death": "dead"
       },
-      "weapon_prop": "nan"
+      "weapon_prop": "props/tombstone"
     },
     "audio": {
       "attack_sound": "data/弹球音效/hammer1.mp3",
@@ -999,9 +1009,9 @@
       "speedVariation": 0.1
     },
     "combat": {
-      "attackDamage": 1,
-      "attackRange": 80.0,
-      "attackSpeed": 4.0,
+      "attackDamage": 2,
+      "attackRange": 0.0,
+      "attackSpeed": 1.0,
       "canBlock": false,
       "blockChance": 0.0,
       "blockDamageReduction": 0.5,
@@ -1010,7 +1020,8 @@
       "attackDelay": 1.0,
       "weaponType": "none",
       "projectileType": "none",
-      "projectileSpeed": 100.0
+      "projectileSpeed": 100.0,
+      "causesWallShake": true
     },
     "visualConfig": {
       "spritePath": "Animation/EnemyAni/011",
@@ -1024,7 +1035,7 @@
         "attack": "attack",
         "death": "dead"
       },
-      "weaponProp": "nan"
+      "weaponProp": "props/cyber_arm"
     },
     "audioConfig": {
       "attackSound": "enemy_attack",
@@ -1069,7 +1080,7 @@
         "attack": "attack",
         "death": "dead"
       },
-      "weapon_prop": "nan"
+      "weapon_prop": "props/cyber_arm"
     },
     "audio": {
       "attack_sound": "data/弹球音效/hammer1.mp3",

binární
assets/data/excel/__pycache__/enemy_config_manager.cpython-313.pyc


+ 7 - 4
assets/data/excel/enemy_config_manager.py

@@ -140,10 +140,12 @@ class EnemyConfigManager:
                 'blockDamageReduction': float(row.iloc[6]) if not pd.isna(row.iloc[6]) else 0.5,
                 'attackCooldown': float(row.iloc[7]) if not pd.isna(row.iloc[7]) else 1.0,
                 'attackType': str(row.iloc[8]) if not pd.isna(row.iloc[8]) else 'melee',
-                'attackDelay': float(row.iloc[9]) if not pd.isna(row.iloc[9]) else 1.0,
-                'weaponType': str(row.iloc[10]) if not pd.isna(row.iloc[10]) else 'none',
-                'projectileType': str(row.iloc[11]) if not pd.isna(row.iloc[11]) else 'none',
-                'projectileSpeed': float(row.iloc[12]) if not pd.isna(row.iloc[12]) else 100.0
+                # 第9列是"备注"字段,跳过不导入
+                'causesWallShake': bool(row.iloc[10]) if not pd.isna(row.iloc[10]) else False,
+                'attackDelay': float(row.iloc[11]) if not pd.isna(row.iloc[11]) else 1.0,
+                'weaponType': str(row.iloc[12]) if not pd.isna(row.iloc[12]) else 'none',
+                'projectileType': str(row.iloc[13]) if not pd.isna(row.iloc[13]) else 'none',
+                'projectileSpeed': float(row.iloc[14]) if not pd.isna(row.iloc[14]) else 100.0
             }
         
         print(f"解析战斗配置: {len(config)} 个敌人")
@@ -390,6 +392,7 @@ class EnemyConfigManager:
                 'blockDamageReduction': 'blockDamageReduction',
                 'attackCooldown': 'attackCooldown',
                 'attackType': 'attackType',
+                'causesWallShake': 'causesWallShake',
                 'attackDelay': 'attackDelay',
                 'weaponType': 'weaponType',
                 'projectileType': 'projectileType',

binární
assets/data/excel/~$敌人配置表.xlsx


+ 0 - 12
assets/data/excel/~$敌人配置表.xlsx.meta

@@ -1,12 +0,0 @@
-{
-  "ver": "1.0.0",
-  "importer": "*",
-  "imported": true,
-  "uuid": "25f3ab0d-335d-4caf-b89b-57b751336a0a",
-  "files": [
-    ".json",
-    ".xlsx"
-  ],
-  "subMetas": {},
-  "userData": {}
-}

binární
assets/data/excel/敌人配置表.xlsx


+ 99 - 0
assets/scripts/CombatSystem/BlockManager.ts

@@ -257,6 +257,9 @@ export class BlockManager extends Component {
         
         // 监听波次完成事件(每波结束后生成新方块)
         eventBus.on(GameEvents.WAVE_COMPLETED, this.onWaveCompletedEvent, this);
+        
+        // 移除敌人对玩家方块造成伤害事件监听,因为抛掷物不攻击玩家方块
+        // eventBus.on(GameEvents.ENEMY_DAMAGE_PLAYER_BLOCK, this.onEnemyDamagePlayerBlockEvent, this);
     }
     
     // 处理重置方块管理器事件
@@ -283,6 +286,102 @@ export class BlockManager extends Component {
         this.generateRandomBlocksInKuang();
     }
     
+    /**
+     * 玩家方块受到伤害(已移除,因为抛掷物不攻击玩家方块)
+     */
+    /*
+    private damagePlayerBlock(blockNode: Node, damage: number) {
+        console.log(`[BlockManager] 玩家方块 ${blockNode.name} 受到 ${damage} 点伤害`);
+        
+        // 获取方块信息组件
+        const blockInfo = blockNode.getComponent(BlockInfo);
+        if (!blockInfo) {
+            console.warn(`[BlockManager] 方块 ${blockNode.name} 没有BlockInfo组件,无法处理伤害`);
+            return;
+        }
+        
+        // 减少方块血量
+        const currentHealth = blockInfo.getCurrentHealth();
+        const newHealth = Math.max(0, currentHealth - damage);
+        blockInfo.setCurrentHealth(newHealth);
+        
+        console.log(`[BlockManager] 方块 ${blockNode.name} 血量: ${currentHealth} -> ${newHealth}`);
+        
+        // 检查方块是否被摧毁
+        if (newHealth <= 0) {
+            console.log(`[BlockManager] 方块 ${blockNode.name} 被摧毁`);
+            this.destroyPlayerBlock(blockNode);
+        } else {
+            // 播放受伤效果
+            this.playBlockDamageEffect(blockNode);
+        }
+    }
+    */
+    
+    /**
+     * 摧毁玩家方块
+     */
+    private destroyPlayerBlock(blockNode: Node) {
+        console.log(`[BlockManager] 摧毁玩家方块: ${blockNode.name}`);
+        
+        // 清理方块占用的网格位置
+        this.clearOccupiedPositions(blockNode);
+        
+        // 播放摧毁效果
+        this.playBlockDestroyEffect(blockNode);
+        
+        // 延迟销毁节点,让效果播放完成
+        this.scheduleOnce(() => {
+            if (blockNode && blockNode.isValid) {
+                blockNode.destroy();
+            }
+        }, 0.5);
+    }
+    
+    /**
+     * 播放方块受伤效果
+     */
+    private playBlockDamageEffect(blockNode: Node) {
+        // 简单的闪烁效果
+        const originalOpacity = blockNode.getComponent(Sprite)?.color.a || 255;
+        
+        tween(blockNode)
+            .to(0.1, { scale: new Vec3(0.9, 0.9, 1) })
+            .to(0.1, { scale: new Vec3(1, 1, 1) })
+            .start();
+            
+        // 颜色闪烁
+        const sprite = blockNode.getComponent(Sprite);
+        if (sprite) {
+            const originalColor = new Color(sprite.color);
+            sprite.color = new Color(255, 100, 100, originalOpacity); // 红色闪烁
+            
+            this.scheduleOnce(() => {
+                if (sprite && sprite.node && sprite.node.isValid) {
+                    sprite.color = originalColor;
+                }
+            }, 0.2);
+        }
+    }
+    
+    /**
+     * 播放方块摧毁效果
+     */
+    private playBlockDestroyEffect(blockNode: Node) {
+        // 缩放消失效果
+        tween(blockNode)
+            .to(0.3, { scale: new Vec3(0, 0, 1) })
+            .start();
+            
+        // 透明度消失效果
+        const sprite = blockNode.getComponent(Sprite);
+        if (sprite) {
+            tween(sprite)
+                .to(0.3, { color: new Color(sprite.color.r, sprite.color.g, sprite.color.b, 0) })
+                .start();
+        }
+    }
+    
     start() {
         console.log('[BlockManager] 开始初始化BlockManager');
         

+ 5 - 0
assets/scripts/CombatSystem/EnemyComponent.ts

@@ -138,6 +138,11 @@ export class EnemyComponent extends Component {
         return this.enemyConfig?.combat?.projectileSpeed || 100.0;
     }
 
+    // 是否造成墙体震动
+    public getCausesWallShake(): boolean {
+        return this.enemyConfig?.combat?.causesWallShake || false;
+    }
+
     // === 格挡系统 ===
     // 是否可以格挡
     public canBlock(): boolean {

+ 5 - 1
assets/scripts/CombatSystem/EnemyController.ts

@@ -839,7 +839,11 @@ export class EnemyController extends BaseSingleton {
         const driftWorldPos = new Vec3(driftWorldX, driftWorldY, 0);
         
         // === 根据配置设置敌人 ===
-        const enemyComp = enemy.addComponent(EnemyInstance);
+        // 检查是否已经存在EnemyInstance组件,避免重复添加
+        let enemyComp = enemy.getComponent(EnemyInstance);
+        if (!enemyComp) {
+            enemyComp = enemy.addComponent(EnemyInstance);
+        }
 
         // 确保敌人数据库已加载
         await EnemyInstance.loadEnemyDatabase();

+ 199 - 35
assets/scripts/CombatSystem/EnemyInstance.ts

@@ -5,6 +5,8 @@ import { DamageNumberAni } from '../Animations/DamageNumberAni';
 import { HPBarAnimation } from '../Animations/HPBarAnimation';
 import { EnemyComponent } from './EnemyComponent';
 import { EnemyAudio } from '../AudioManager/EnemyAudios';
+import { ScreenShakeManager } from './EnemyWeapon/ScreenShakeManager';
+import EventBus, { GameEvents } from '../Core/EventBus';
 const { ccclass, property } = _decorator;
 
 // 前向声明EnemyController接口,避免循环引用
@@ -431,8 +433,8 @@ export class EnemyInstance extends Component {
         
         console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 碰撞检测 - 碰撞对象: ${nodeName}, 当前状态: ${EnemyState[this.state]}`);
         
-        // 只有在移动状态下才能切换到攻击状态,避免重复触发
-        if (this.state !== EnemyState.MOVING) {
+        // 只有在移动状态或攻击状态下才能响应墙体碰撞,避免重复触发
+        if (this.state !== EnemyState.MOVING && this.state !== EnemyState.ATTACKING) {
             return;
         }
         
@@ -492,6 +494,12 @@ export class EnemyInstance extends Component {
      * 初始化血条动画组件
      */
     private initializeHPBarAnimation() {
+        // 检查是否已经存在血条动画组件,避免重复添加
+        if (this.hpBarAnimation) {
+            console.log(`[EnemyInstance] 血条动画组件已存在,跳过初始化`);
+            return;
+        }
+        
         const hpBar = this.node.getChildByName('HPBar');
         if (hpBar) {
             // 查找红色和黄色血条节点
@@ -499,8 +507,17 @@ export class EnemyInstance extends Component {
             const yellowBarNode = hpBar.getChildByName('YellowBar');
             
             if (redBarNode && yellowBarNode) {
-                // 添加血条动画组件
-                this.hpBarAnimation = this.node.addComponent(HPBarAnimation);
+                // 检查节点是否已经有HPBarAnimation组件
+                let existingHPBarAnimation = this.node.getComponent(HPBarAnimation);
+                if (existingHPBarAnimation) {
+                    this.hpBarAnimation = existingHPBarAnimation;
+                    console.log(`[EnemyInstance] 使用已存在的血条动画组件`);
+                } else {
+                    // 添加血条动画组件
+                    this.hpBarAnimation = this.node.addComponent(HPBarAnimation);
+                    console.log(`[EnemyInstance] 新建血条动画组件`);
+                }
+                
                 if (this.hpBarAnimation) {
                     // 正确设置红色和黄色血条节点引用
                     this.hpBarAnimation.redBarNode = redBarNode;
@@ -840,14 +857,24 @@ export class EnemyInstance extends Component {
 
     // 更新攻击逻辑
     private updateAttack(deltaTime: number) {
-        // 在攻击状态下停止移动
-        const enemySprite = this.node.getChildByName('EnemySprite');
-        if (enemySprite) {
-            const rigidBody = enemySprite.getComponent(RigidBody2D);
-            if (rigidBody) {
-                // 停止物理移动
-                rigidBody.linearVelocity = new Vec2(0, 0);
+        // 获取敌人的攻击范围配置
+        const enemyComponent = this.getComponent(EnemyComponent);
+        const attackRange = enemyComponent ? enemyComponent.getAttackRange() : 30;
+        
+        if (attackRange <= 0) {
+            // 近战攻击:在攻击状态下停止移动
+            const enemySprite = this.node.getChildByName('EnemySprite');
+            if (enemySprite) {
+                const rigidBody = enemySprite.getComponent(RigidBody2D);
+                if (rigidBody) {
+                    // 停止物理移动
+                    rigidBody.linearVelocity = new Vec2(0, 0);
+                }
             }
+        } else {
+            // 远程攻击:继续移动,但不需要在这里播放行走动画
+            // 攻击动画会在performRangedAttack中播放,完成后会根据状态切换回行走动画
+            this.updateMovement(deltaTime);
         }
         
         this.attackTimer -= deltaTime;
@@ -897,10 +924,12 @@ export class EnemyInstance extends Component {
         EnemyAudio.playAttackSound(this.enemyConfig);
         
         // 根据攻击类型执行不同的攻击逻辑
-        if (attackType === 'ranged' || attackType === 'projectile') {
+        if (attackType === 'ranged' || attackType === 'projectile' || attackType.includes('projectile')) {
+            console.log(`[EnemyInstance] 执行远程攻击,攻击类型: ${attackType}`);
             this.performRangedAttack();
         } else {
             // 近战攻击:播放攻击动画,动画结束后造成伤害
+            console.log(`[EnemyInstance] 执行近战攻击,攻击类型: ${attackType}`);
             this.playAttackAnimationWithDamage();
         }
     }
@@ -910,19 +939,110 @@ export class EnemyInstance extends Component {
         const enemyComponent = this.getComponent(EnemyComponent);
         if (!enemyComponent) return;
         
+        // 远程攻击也需要播放攻击动画,在动画即将结束时发射抛掷物
+        this.playRangedAttackAnimationWithProjectile();
+    }
+    
+    // 播放远程攻击动画并在动画结束时发射抛掷物
+    private playRangedAttackAnimationWithProjectile() {
+        if (!this.skeleton) {
+            // 如果没有骨骼动画,直接发射抛掷物
+            this.fireProjectile();
+            return;
+        }
+        
+        const enemyComp = this.getComponent('EnemyComponent') as any;
+        const anims = enemyComp?.getAnimations ? enemyComp.getAnimations() : {};
+        const attackName = anims.attack ?? 'attack';
+
+        const animation = this.skeleton.findAnimation(attackName);
+        if (animation) {
+            // 播放攻击动画(不循环)
+            this.skeleton.setAnimation(0, attackName, false);
+            
+            // 动画切换后延迟更新血条位置,确保动画尺寸已生效
+            this.scheduleOnce(() => {
+                this.updateHPBarPosition();
+            }, 0.1);
+            
+            // 获取动画时长并在动画即将结束时发射抛掷物
+            const animationDuration = animation.duration;
+            console.log(`[EnemyInstance] 远程攻击动画时长: ${animationDuration}秒`);
+            
+            // 在动画播放到90%时发射抛掷物,让抛掷物发射与动画同步
+            const projectileFireTime = animationDuration * 0.9;
+            this.scheduleOnce(() => {
+                this.fireProjectile();
+            }, projectileFireTime);
+            
+            // 动画完成后根据当前状态切换动画
+            this.scheduleOnce(() => {
+                if (this.state === EnemyState.IDLE) {
+                    // 如果是待机状态,切换回待机动画
+                    this.playIdleAnimation();
+                } else if (this.state === EnemyState.ATTACKING) {
+                    // 如果还在攻击状态(移动中攻击),切换回行走动画
+                    this.playWalkAnimation();
+                }
+            }, animationDuration);
+        } else {
+            // 如果找不到攻击动画,直接发射抛掷物
+            this.fireProjectile();
+        }
+    }
+    
+    // 发射抛掷物
+    private fireProjectile() {
+        const enemyComponent = this.getComponent(EnemyComponent);
+        if (!enemyComponent) return;
+        
         const projectileType = enemyComponent.getProjectileType();
         const projectileSpeed = enemyComponent.getProjectileSpeed();
+        const attackDamage = enemyComponent.getDamage();
         
-        console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 发射投掷物: ${projectileType}, 速度: ${projectileSpeed}`);
+        console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 发射投掷物: ${projectileType}, 速度: ${projectileSpeed}, 伤害: ${attackDamage}`);
         
-        // TODO: 实际创建和发射投掷物的逻辑
-        // 这里需要根据项目的投掷物系统来实现
-        // 例如:创建投掷物预制体,设置速度和方向,添加碰撞检测等
-        
-        // 暂时直接造成伤害作为占位符
-        this.scheduleOnce(() => {
-            this.dealDamageToWall();
-        }, 1.0); // 1秒后造成伤害,模拟投掷物飞行时间
+        // 获取EnemyProjectile组件来创建抛掷物
+        if (this.controller) {
+            console.log('[EnemyInstance] 找到controller,开始获取EnemyProjectile组件');
+            const enemyProjectile = this.controller.getComponent('EnemyProjectile') as any;
+            if (enemyProjectile) {
+                console.log('[EnemyInstance] 找到EnemyProjectile组件,准备创建抛掷物');
+                // 计算抛掷物发射方向(朝向目标墙体)
+                let direction = new Vec3(0, -1, 0); // 默认向下
+                if (this.collidedWall) {
+                    const currentPos = this.node.worldPosition;
+                    const wallPos = this.collidedWall.worldPosition;
+                    direction = wallPos.clone().subtract(currentPos).normalize();
+                    console.log('[EnemyInstance] 计算抛掷物方向,目标墙体:', this.collidedWall.name, '方向:', direction);
+                } else {
+                    console.log('[EnemyInstance] 未找到目标墙体,使用默认方向');
+                }
+                
+                // 创建抛掷物配置
+                const projectileConfig = {
+                    damage: attackDamage,
+                    speed: projectileSpeed,
+                    direction: direction,
+                    projectileType: projectileType,
+                    startPosition: this.node.worldPosition.clone()
+                };
+                
+                console.log('[EnemyInstance] 抛掷物配置:', projectileConfig);
+                
+                // 创建抛掷物
+                const projectileNode = enemyProjectile.createProjectile(projectileConfig);
+                if (projectileNode) {
+                    console.log(`[EnemyInstance] 成功创建抛掷物,目标方向: ${direction.toString()}`);
+                } else {
+                    console.error(`[EnemyInstance] 创建抛掷物失败 - 远程敌人攻击无效`);
+                }
+            } else {
+                console.error(`[EnemyInstance] 未找到EnemyProjectile组件 - 远程敌人攻击无效`);
+            }
+        } else {
+            console.error(`[EnemyInstance] 没有controller引用 - 远程敌人攻击无效`);
+        }
     }
 
     // 播放行走动画
@@ -1033,7 +1153,20 @@ export class EnemyInstance extends Component {
             const enemyComponent = this.getComponent(EnemyComponent);
             const currentAttackPower = enemyComponent ? enemyComponent.getCurrentAttackPower() : this.attackPower;
             
-            this.controller.damageWall(currentAttackPower);
+            // 检查是否需要触发画面震动
+            if (enemyComponent && enemyComponent.getCausesWallShake()) {
+                const attackType = enemyComponent.getAttackType();
+                console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 攻击墙体,触发画面震动,攻击类型: ${attackType}`);
+                
+                // 根据攻击类型触发对应的震动效果
+                ScreenShakeManager.getInstance().playShakeByAttackType(attackType);
+            }
+            
+            // 使用统一的事件机制触发墙体伤害
+            EventBus.getInstance().emit(GameEvents.ENEMY_DAMAGE_WALL, {
+                damage: currentAttackPower,
+                source: this.node
+            });
         }
     }
 
@@ -1182,7 +1315,7 @@ export class EnemyInstance extends Component {
     }
     
     /**
-     * 基于距离检查墙体碰撞
+     * 基于距离检查墙体碰撞和攻击范围
      */
     private checkWallCollisionByDistance(): void {
         if (this.state === EnemyState.ATTACKING || this.state === EnemyState.IDLE) {
@@ -1193,7 +1326,24 @@ export class EnemyInstance extends Component {
         if (!enemySprite) return;
 
         const currentPos = enemySprite.worldPosition;
-        const collisionDistance = 30; // 碰撞检测距离
+        
+        // 获取敌人的攻击范围配置
+        const enemyComponent = this.getComponent(EnemyComponent);
+        const attackRange = enemyComponent ? enemyComponent.getAttackRange() : 30;
+        
+        // 根据攻击范围确定检测距离和攻击类型
+        let detectionDistance: number;
+        let isRangedAttack: boolean;
+        
+        if (attackRange <= 0) {
+            // 近战攻击:需要接近墙体
+            detectionDistance = 30;
+            isRangedAttack = false;
+        } else {
+            // 远程攻击:在攻击范围内就开始攻击
+            detectionDistance = attackRange;
+            isRangedAttack = true;
+        }
 
         // 获取所有墙体节点进行距离检测
         const wallNodes = this.getAllWallNodes();
@@ -1202,18 +1352,32 @@ export class EnemyInstance extends Component {
             const wallPos = wallNode.worldPosition;
             const distance = Vec3.distance(currentPos, wallPos);
             
-            if (distance <= collisionDistance) {
-                console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 距离检测到墙体 ${wallNode.name},距离: ${distance.toFixed(2)},切换到待机状态`);
-                
-                this.state = EnemyState.IDLE;
-                this.attackTimer = 0; // 立即开始攻击
-                this.collidedWall = wallNode; // 记录碰撞的墙体
-                
-                // 停止移动
-                this.stopRigidBodyMovement();
+            if (distance <= detectionDistance) {
+                const attackTypeStr = isRangedAttack ? '远程攻击' : '近战攻击';
+                console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 进入${attackTypeStr}范围,距离墙体 ${wallNode.name}: ${distance.toFixed(2)},攻击范围: ${attackRange}`);
                 
-                // 切换到待机动画
-                this.playIdleAnimation();
+                if (isRangedAttack) {
+                    // 远程攻击:切换到攻击状态,继续移动并发射抛掷物
+                    this.state = EnemyState.ATTACKING;
+                    this.attackTimer = 0; // 立即开始攻击
+                    this.collidedWall = wallNode; // 记录目标墙体
+                    
+                    // 远程攻击时继续播放walk动画,不停止移动
+                    this.playWalkAnimation();
+                    
+                    console.log(`[EnemyInstance] 远程敌人 ${this.getEnemyName()} 进入攻击状态,继续移动并播放walk动画`);
+                } else {
+                    // 近战攻击:切换到待机状态,停止移动
+                    this.state = EnemyState.IDLE;
+                    this.attackTimer = 0; // 立即开始攻击
+                    this.collidedWall = wallNode; // 记录碰撞的墙体
+                    
+                    // 停止移动
+                    this.stopRigidBodyMovement();
+                    
+                    // 切换到待机动画
+                    this.playIdleAnimation();
+                }
                 
                 return; // 找到一个墙体就停止检查
             }

+ 9 - 0
assets/scripts/CombatSystem/EnemyWeapon.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "32d7d509-393e-47dd-ae24-ca9f4808b1b1",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 88 - 0
assets/scripts/CombatSystem/EnemyWeapon/EnemyProjectile.ts

@@ -0,0 +1,88 @@
+import { _decorator, Component, Node, Vec3, RigidBody2D, Collider2D, Contact2DType, IPhysics2DContact, Sprite, find, Prefab, instantiate } from 'cc';
+import { BundleLoader } from '../../Core/BundleLoader';
+import { Audio } from '../../AudioManager/AudioManager';
+import EventBus, { GameEvents } from '../../Core/EventBus';
+import { EnemyProjectileInstance } from './EnemyProjectileInstance';
+
+const { ccclass, property } = _decorator;
+
+/**
+ * 敌人抛掷物管理器
+ * 负责管理敌人发射的抛掷物的创建和配置
+ * 挂载到Canvas/GameLevelUI/EnemyController上使用
+ */
+@ccclass('EnemyProjectile')
+export class EnemyProjectile extends Component {
+    
+    @property(Prefab)
+    public projectilePrefab: Prefab = null;
+    
+    onLoad() {
+        console.log('[EnemyProjectile] 敌人抛掷物管理器已加载');
+    }
+    
+    /**
+     * 创建抛掷物
+     * @param config 抛掷物配置
+     */
+    public createProjectile(config: {
+        damage: number;
+        speed: number;
+        direction: Vec3;
+        projectileType: string;
+        startPosition: Vec3;
+    }): Node | null {
+        console.log('[EnemyProjectile] 开始创建抛掷物,配置:', config);
+        
+        if (!this.projectilePrefab) {
+            console.error('[EnemyProjectile] 抛掷物预制体未设置');
+            return null;
+        }
+        
+        console.log('[EnemyProjectile] 预制体已设置,开始实例化');
+        
+        // 实例化预制体
+        const projectileNode = instantiate(this.projectilePrefab);
+        if (!projectileNode) {
+            console.error('[EnemyProjectile] 创建抛掷物节点失败');
+            return null;
+        }
+        
+        console.log('[EnemyProjectile] 抛掷物节点创建成功,节点名称:', projectileNode.name);
+        
+        // 获取抛掷物组件
+        const projectileComponent = projectileNode.getComponent(EnemyProjectileInstance);
+        if (!projectileComponent) {
+            console.error('[EnemyProjectile] 预制体中未找到EnemyProjectileInstance组件');
+            projectileNode.destroy();
+            return null;
+        }
+        
+        console.log('[EnemyProjectile] 找到EnemyProjectileInstance组件,开始初始化');
+        
+        // 初始化抛掷物
+        projectileComponent.init(config);
+        
+        console.log('[EnemyProjectile] 抛掷物初始化完成,准备添加到Canvas');
+        
+        // 添加到Canvas
+        const canvas = find('Canvas');
+        if (canvas) {
+            // 先添加到Canvas,然后设置世界坐标
+            canvas.addChild(projectileNode);
+            
+            // 确保在Canvas坐标系下正确设置位置
+            projectileNode.setWorldPosition(config.startPosition);
+            
+            console.log(`[EnemyProjectile] 创建抛掷物成功: 类型=${config.projectileType}, 伤害=${config.damage}, 速度=${config.speed}`);
+            console.log('[EnemyProjectile] 抛掷物世界坐标:', projectileNode.worldPosition);
+            console.log('[EnemyProjectile] 抛掷物本地坐标:', projectileNode.position);
+        } else {
+            console.error('[EnemyProjectile] 未找到Canvas节点');
+            projectileNode.destroy();
+            return null;
+        }
+        
+        return projectileNode;
+    }
+}

+ 9 - 0
assets/scripts/CombatSystem/EnemyWeapon/EnemyProjectile.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "940fec67-569b-4dc2-9ce0-670a7887f159",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 479 - 0
assets/scripts/CombatSystem/EnemyWeapon/EnemyProjectileInstance.ts

@@ -0,0 +1,479 @@
+import { _decorator, Component, Node, Vec3, RigidBody2D, Collider2D, Contact2DType, IPhysics2DContact, Sprite, find } from 'cc';
+import { BundleLoader } from '../../Core/BundleLoader';
+import { Audio } from '../../AudioManager/AudioManager';
+import EventBus, { GameEvents } from '../../Core/EventBus';
+
+const { ccclass, property } = _decorator;
+
+/**
+ * 敌人抛掷物实例组件
+ * 负责处理单个抛掷物的移动、碰撞和伤害
+ * 此组件应该挂载到抛掷物预制体上
+ */
+@ccclass('EnemyProjectileInstance')
+export class EnemyProjectileInstance extends Component {
+    
+    // 抛掷物属性
+    private damage: number = 10;
+    private speed: number = 100;
+    private direction: Vec3 = new Vec3(0, -1, 0); // 默认向下
+    private projectileType: string = 'arrow';
+    
+    // 组件引用
+    private rigidBody: RigidBody2D = null;
+    private collider: Collider2D = null;
+    private sprite: Sprite = null;
+    
+    // 生命周期
+    private maxLifetime: number = 5.0; // 最大存活时间
+    private currentLifetime: number = 0;
+    
+    // 弧线弹道相关属性
+    private useArcTrajectory: boolean = true; // 是否使用弧线弹道
+    private arcDir: Vec3 = null; // 当前方向
+    private arcTargetDir: Vec3 = null; // 目标方向
+    private targetWallPosition: Vec3 = null; // 目标墙体位置
+    private rotateSpeed: number = 2.0; // 转向速度
+    
+    onLoad() {
+        console.log('[EnemyProjectileInstance] 抛掷物实例组件已加载');
+        this.setupComponents();
+        this.setupProjectileSprite();
+    }
+    
+    start() {
+        this.setupCollisionListener();
+    }
+    
+    update(deltaTime: number) {
+        // 如果已被标记为销毁,停止更新
+        if (this.isDestroyed) {
+            return;
+        }
+        
+        // 更新生命周期
+        this.currentLifetime += deltaTime;
+        if (this.currentLifetime >= this.maxLifetime) {
+            console.log('[EnemyProjectileInstance] 抛掷物超时,自动销毁');
+            this.isDestroyed = true;
+            this.destroyProjectile();
+            return;
+        }
+        
+        // 根据弹道类型移动抛掷物
+        if (this.useArcTrajectory) {
+            this.updateArcTrajectory(deltaTime);
+        } else {
+            this.moveProjectile(deltaTime);
+        }
+    }
+    
+    /**
+     * 初始化抛掷物
+     * @param config 抛掷物配置
+     */
+    public init(config: {
+        damage: number;
+        speed: number;
+        direction: Vec3;
+        projectileType: string;
+        startPosition: Vec3;
+    }) {
+        console.log('[EnemyProjectileInstance] 初始化抛掷物配置:', config);
+        
+        this.damage = config.damage;
+        this.speed = config.speed;
+        this.direction = config.direction.clone().normalize();
+        this.projectileType = config.projectileType;
+        
+        // 注意:不在这里设置位置,因为节点还没有添加到Canvas
+        // 位置设置由EnemyProjectile.ts在添加到Canvas后处理
+        
+        // 初始化弧线弹道
+        this.initArcTrajectory();
+        
+        console.log(`[EnemyProjectileInstance] 初始化抛掷物完成: 类型=${this.projectileType}, 伤害=${this.damage}, 速度=${this.speed}`);
+        console.log('[EnemyProjectileInstance] 起始位置:', config.startPosition);
+    }
+    
+    /**
+     * 获取预制体中已配置的组件
+     */
+    private setupComponents() {
+        // 获取预制体中已配置的刚体组件
+        this.rigidBody = this.getComponent(RigidBody2D);
+        if (!this.rigidBody) {
+            console.error('[EnemyProjectileInstance] 预制体中未找到RigidBody2D组件');
+        } else {
+            console.log('[EnemyProjectileInstance] 找到RigidBody2D组件');
+        }
+        
+        // 获取预制体中已配置的碰撞器组件
+        this.collider = this.getComponent(Collider2D);
+        if (!this.collider) {
+            console.error('[EnemyProjectileInstance] 预制体中未找到Collider2D组件');
+        } else {
+            console.log('[EnemyProjectileInstance] 找到Collider2D组件');
+        }
+        
+        // 获取预制体中已配置的精灵组件
+        this.sprite = this.getComponent(Sprite);
+        if (!this.sprite) {
+            console.error('[EnemyProjectileInstance] 预制体中未找到Sprite组件');
+        } else {
+            console.log('[EnemyProjectileInstance] 找到Sprite组件');
+        }
+    }
+    
+    /**
+     * 设置抛掷物外观,使用3.png
+     */
+    private async setupProjectileSprite() {
+        if (!this.sprite) return;
+    }
+    
+    /**
+     * 设置碰撞监听
+     */
+    private setupCollisionListener() {
+        if (this.collider) {
+            this.collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
+            console.log('[EnemyProjectileInstance] 碰撞监听设置完成');
+        }
+    }
+    
+    /**
+     * 移动抛掷物
+     */
+    private moveProjectile(deltaTime: number) {
+        if (!this.rigidBody) return;
+        
+        // 计算移动向量
+        const moveVector = this.direction.clone().multiplyScalar(this.speed * deltaTime);
+        
+        // 获取当前位置并添加移动向量
+        const currentPos = this.node.getWorldPosition();
+        const newPos = currentPos.add(moveVector);
+        
+        // 设置新位置
+        this.node.setWorldPosition(newPos);
+    }
+    
+    /**
+     * 初始化弧线弹道
+     */
+    private initArcTrajectory() {
+        if (!this.useArcTrajectory) return;
+        
+        // 寻找最近的墙体作为目标
+        this.findNearestWall();
+        
+        // 计算带45°随机偏移的初始方向
+        const baseDir = this.direction.clone().normalize();
+        const sign = Math.random() < 0.5 ? 1 : -1;
+        const rad = 45 * Math.PI / 180 * sign;
+        const cos = Math.cos(rad);
+        const sin = Math.sin(rad);
+        const offsetDir = new Vec3(
+            baseDir.x * cos - baseDir.y * sin,
+            baseDir.x * sin + baseDir.y * cos,
+            0
+        ).normalize();
+        
+        this.arcDir = offsetDir;
+        
+        // 如果找到了目标墙体,设置目标方向
+        if (this.targetWallPosition) {
+            const currentPos = this.node.getWorldPosition();
+            const dirToWall = this.targetWallPosition.clone().subtract(currentPos).normalize();
+            this.arcTargetDir = dirToWall;
+        } else {
+            // 如果没有找到墙体,使用基础方向
+            this.arcTargetDir = baseDir;
+        }
+        
+        console.log(`[EnemyProjectileInstance] 初始化弧线弹道: 目标墙体位置=${this.targetWallPosition}`);
+    }
+    
+    /**
+     * 寻找最近的墙体
+     */
+    private findNearestWall() {
+        const currentPos = this.node.getWorldPosition();
+        let nearestWall: Node = null;
+        let nearestDistance = Infinity;
+        
+        // 方法1:通过EnemyController获取墙体节点
+        const enemyController = find('Canvas/GameLevelUI/EnemyController')?.getComponent('EnemyController') as any;
+        if (enemyController) {
+            const wallNodes = [];
+            if (enemyController.topFenceNode) wallNodes.push(enemyController.topFenceNode);
+            if (enemyController.bottomFenceNode) wallNodes.push(enemyController.bottomFenceNode);
+            
+            for (const wall of wallNodes) {
+                if (wall && wall.active) {
+                    const distance = Vec3.distance(currentPos, wall.getWorldPosition());
+                    if (distance < nearestDistance) {
+                        nearestDistance = distance;
+                        nearestWall = wall;
+                    }
+                }
+            }
+        }
+        
+        // 方法2:在GameArea中搜索墙体节点
+        if (!nearestWall) {
+            const gameArea = find('Canvas/GameLevelUI/GameArea');
+            if (gameArea) {
+                for (let i = 0; i < gameArea.children.length; i++) {
+                    const child = gameArea.children[i];
+                    if (this.isWallNode(child) && child.active) {
+                        const distance = Vec3.distance(currentPos, child.getWorldPosition());
+                        if (distance < nearestDistance) {
+                            nearestDistance = distance;
+                            nearestWall = child;
+                        }
+                    }
+                }
+            }
+        }
+        
+        // 方法3:直接搜索常见墙体路径
+        if (!nearestWall) {
+            const wallPaths = [
+                'Canvas/GameLevelUI/GameArea/TopFence',
+                'Canvas/GameLevelUI/GameArea/BottomFence',
+                'Canvas/GameLevelUI/Wall',
+                'Canvas/Wall',
+                'Canvas/TopWall',
+                'Canvas/BottomWall'
+            ];
+            
+            for (const path of wallPaths) {
+                const wall = find(path);
+                if (wall && wall.active) {
+                    const distance = Vec3.distance(currentPos, wall.getWorldPosition());
+                    if (distance < nearestDistance) {
+                        nearestDistance = distance;
+                        nearestWall = wall;
+                    }
+                }
+            }
+        }
+        
+        if (nearestWall) {
+            this.targetWallPosition = nearestWall.getWorldPosition();
+            console.log(`[EnemyProjectileInstance] 找到目标墙体: ${nearestWall.name}, 距离: ${nearestDistance.toFixed(2)}`);
+        } else {
+            console.warn('[EnemyProjectileInstance] 未找到可用的墙体目标');
+            // 设置一个默认的目标位置(屏幕中心下方)
+            this.targetWallPosition = new Vec3(0, -200, 0);
+        }
+    }
+    
+    /**
+     * 更新弧线弹道
+     */
+    private updateArcTrajectory(deltaTime: number) {
+        if (!this.arcDir || !this.arcTargetDir) return;
+        
+        // 重新寻找墙体目标(动态追踪)
+        if (!this.targetWallPosition) {
+            this.findNearestWall();
+        }
+        
+        // 更新目标方向
+        if (this.targetWallPosition) {
+            const currentPos = this.node.getWorldPosition();
+            const dirToWall = this.targetWallPosition.clone().subtract(currentPos).normalize();
+            this.arcTargetDir.set(dirToWall);
+        }
+        
+        // 计算转向
+        const rotateFactor = this.rotateSpeed * deltaTime;
+        this.arcDir = this.arcDir.lerp(this.arcTargetDir, rotateFactor).normalize();
+        
+        // 计算移动向量
+        const moveVector = this.arcDir.clone().multiplyScalar(this.speed * deltaTime);
+        
+        // 获取当前位置并添加移动向量
+        const currentPos = this.node.getWorldPosition();
+        const newPos = currentPos.add(moveVector);
+        
+        // 设置新位置
+        this.node.setWorldPosition(newPos);
+        
+        // 更新节点朝向(可选)
+        const angle = Math.atan2(this.arcDir.y, this.arcDir.x) * 180 / Math.PI;
+        this.node.angle = angle;
+    }
+    
+    // 防止重复销毁的标志
+    private isDestroyed: boolean = false;
+
+    /**
+     * 碰撞开始事件
+     */
+    private onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
+        // 如果已经被销毁,不再处理碰撞
+        if (this.isDestroyed) {
+            console.log(`[EnemyProjectileInstance] 抛掷物已被销毁,忽略碰撞事件`);
+            return;
+        }
+
+        const otherNode = otherCollider.node;
+        const nodeName = otherNode.name;
+        
+        console.log(`[EnemyProjectileInstance] 抛掷物碰撞检测 - 碰撞对象: ${nodeName}, 节点路径: ${this.getNodePath(otherNode)}`);
+        
+        // 抛掷物的目标是墙体,检查是否碰到墙体
+        if (this.isWallNode(otherNode)) {
+            console.log(`[EnemyProjectileInstance] 抛掷物击中目标墙体: ${nodeName}, 准备销毁`);
+            this.hitWall();
+            return;
+        }
+        
+        console.log(`[EnemyProjectileInstance] 抛掷物碰撞到非目标对象: ${nodeName}, 继续飞行`);
+    }
+
+    /**
+     * 获取节点的完整路径
+     */
+    private getNodePath(node: Node): string {
+        const path = [];
+        let current = node;
+        while (current) {
+            path.unshift(current.name);
+            current = current.parent;
+        }
+        return path.join('/');
+    }
+    
+    /**
+     * 检查是否为墙体节点
+     */
+    private isWallNode(node: Node): boolean {
+        const nodeName = node.name.toLowerCase();
+        
+        // 检查节点名称是否包含墙体关键词
+        const wallKeywords = ['wall', 'fence', 'jiguang', '墙', '围栏'];
+        const isWallByName = wallKeywords.some(keyword => nodeName.includes(keyword));
+        
+        // 检查节点是否有Wall组件
+        const hasWallComponent = node.getComponent('Wall') !== null;
+        
+        return isWallByName || hasWallComponent;
+    }
+    
+    /**
+     * 检查是否为玩家方块
+     */
+    private isPlayerBlock(node: Node): boolean {
+        // 检查节点名称或标签
+        return node.name.includes('Block') || node.name.includes('WeaponBlock');
+    }
+    
+    /**
+     * 击中墙体
+     */
+    private hitWall() {
+        // 防止重复销毁
+        if (this.isDestroyed) {
+            console.log(`[EnemyProjectileInstance] 抛掷物已被销毁,忽略hitWall调用`);
+            return;
+        }
+        
+        this.isDestroyed = true;
+        console.log(`[EnemyProjectileInstance] 抛掷物击中墙体,造成 ${this.damage} 点伤害,开始销毁流程`);
+        
+        // 对墙体造成伤害
+        EventBus.getInstance().emit(GameEvents.ENEMY_DAMAGE_WALL, {
+            damage: this.damage,
+            source: this.node
+        });
+        
+        // 播放击中音效
+        this.playHitSound();
+        
+        // 销毁抛掷物
+        this.destroyProjectile();
+    }
+    
+    /**
+     * 击中玩家方块(已移除,抛掷物不攻击玩家方块)
+     */
+    /*
+    private hitPlayerBlock(blockNode: Node) {
+        // 防止重复销毁
+        if (this.isDestroyed) {
+            console.log(`[EnemyProjectileInstance] 抛掷物已被销毁,忽略hitPlayerBlock调用`);
+            return;
+        }
+        
+        this.isDestroyed = true;
+        console.log(`[EnemyProjectileInstance] 抛掷物击中玩家方块: ${blockNode.name},开始销毁流程`);
+        
+        // 对玩家方块造成伤害 - 使用事件系统
+        EventBus.getInstance().emit(GameEvents.ENEMY_DAMAGE_PLAYER_BLOCK, {
+            damage: this.damage,
+            blockNode: blockNode,
+            source: this.node
+        });
+        
+        // 播放击中音效
+        this.playHitSound();
+        
+        // 销毁抛掷物
+        this.destroyProjectile();
+    }
+    */
+    
+    /**
+     * 播放击中音效
+     */
+    private playHitSound() {
+        // 根据抛掷物类型播放不同音效
+        let soundPath = '';
+        switch (this.projectileType) {
+            case 'arrow':
+                soundPath = 'data/弹球音效/fire';
+                break;
+            case 'magic_bolt':
+                soundPath = 'data/弹球音效/fire';
+                break;
+            default:
+                soundPath = 'data/弹球音效/fire';
+                break;
+        }
+        
+        if (soundPath) {
+            Audio.playWeaponSound(soundPath);
+        }
+    }
+    
+    /**
+     * 销毁抛掷物
+     */
+    private destroyProjectile() {
+        // 防止重复销毁
+        if (this.isDestroyed && (!this.node || !this.node.isValid)) {
+            console.log('[EnemyProjectileInstance] 抛掷物已被销毁,跳过重复销毁');
+            return;
+        }
+        
+        if (this.node && this.node.isValid) {
+            console.log('[EnemyProjectileInstance] 销毁抛掷物节点');
+            this.node.destroy();
+        } else {
+            console.log('[EnemyProjectileInstance] 抛掷物节点已无效或不存在');
+        }
+    }
+    
+    onDestroy() {
+        // 清理碰撞监听
+        if (this.collider) {
+            this.collider.off(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
+        }
+        console.log('[EnemyProjectileInstance] 抛掷物实例组件已销毁');
+    }
+}

+ 9 - 0
assets/scripts/CombatSystem/EnemyWeapon/EnemyProjectileInstance.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "5b808033-8a22-47bb-bd85-8aea24529e94",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 224 - 0
assets/scripts/CombatSystem/EnemyWeapon/ScreenShakeManager.ts

@@ -0,0 +1,224 @@
+import { _decorator, Component, Node, Vec3, tween, find, Camera } from 'cc';
+
+const { ccclass, property } = _decorator;
+
+/**
+ * 画面震动管理器
+ * 负责管理整个游戏画面的震动效果
+ */
+export class ScreenShakeManager {
+    private static instance: ScreenShakeManager = null;
+    
+    // 相机节点
+    private cameraNode: Node = null;
+    
+    // 震动相关属性
+    private originalCameraPosition: Vec3 = new Vec3();
+    private isShaking: boolean = false;
+    private currentShakeTween: any = null;
+    
+    // 震动设置
+    private vibrationEnabled: boolean = true;
+    
+    private constructor() {
+        // 私有构造函数,确保单例模式
+    }
+    
+    /**
+     * 获取单例实例
+     */
+    public static getInstance(): ScreenShakeManager {
+        if (!ScreenShakeManager.instance) {
+            ScreenShakeManager.instance = new ScreenShakeManager();
+        }
+        return ScreenShakeManager.instance;
+    }
+    
+    /**
+     * 初始化相机节点
+     * @param cameraNode 相机节点引用
+     */
+    public initialize(cameraNode: Node): void {
+        this.cameraNode = cameraNode;
+        if (this.cameraNode) {
+            this.originalCameraPosition = this.cameraNode.position.clone();
+            console.log('[ScreenShakeManager] 相机节点初始化完成');
+        } else {
+            console.warn('[ScreenShakeManager] 相机节点为空,画面震动功能将不可用');
+        }
+    }
+    
+    /**
+     * 播放画面震动效果
+     * @param intensity 震动强度 (像素)
+     * @param duration 震动持续时间 (秒)
+     * @param frequency 震动频率 (次数)
+     */
+    public playScreenShake(intensity: number = 10, duration: number = 0.5, frequency: number = 8): void {
+        // 检查震动是否启用
+        if (!this.vibrationEnabled) {
+            console.log('[ScreenShakeManager] 震动已禁用,跳过画面震动');
+            return;
+        }
+        
+        // 检查相机节点是否存在
+        if (!this.cameraNode) {
+            console.warn('[ScreenShakeManager] 相机节点不存在,无法播放画面震动');
+            return;
+        }
+        
+        // 如果正在震动,先停止当前震动
+        this.stopScreenShake();
+        
+        console.log(`[ScreenShakeManager] 开始画面震动 - 强度: ${intensity}, 持续时间: ${duration}s, 频率: ${frequency}`);
+        
+        // 标记为正在震动
+        this.isShaking = true;
+        
+        // 保存当前位置作为原始位置
+        this.originalCameraPosition = this.cameraNode.position.clone();
+        
+        // 创建震动序列
+        let shakeTween = tween(this.cameraNode);
+        const singleShakeTime = duration / frequency;
+        
+        for (let i = 0; i < frequency; i++) {
+            // 计算震动强度衰减
+            const currentIntensity = intensity * (1 - i / frequency);
+            
+            // 随机方向震动
+            const randomAngle = Math.random() * Math.PI * 2;
+            const offsetX = Math.cos(randomAngle) * currentIntensity;
+            const offsetY = Math.sin(randomAngle) * currentIntensity;
+            
+            const targetPosition = this.originalCameraPosition.clone();
+            targetPosition.x += offsetX;
+            targetPosition.y += offsetY;
+            
+            shakeTween = shakeTween.to(singleShakeTime, {
+                position: targetPosition
+            }, {
+                easing: 'sineInOut'
+            });
+        }
+        
+        // 最后回到原始位置
+        shakeTween = shakeTween.to(singleShakeTime * 0.5, {
+            position: this.originalCameraPosition
+        }, {
+            easing: 'sineOut'
+        }).call(() => {
+            this.isShaking = false;
+            this.currentShakeTween = null;
+            console.log('[ScreenShakeManager] 画面震动结束');
+        });
+        
+        this.currentShakeTween = shakeTween;
+        this.currentShakeTween.start();
+    }
+    
+    /**
+     * 停止画面震动
+     */
+    public stopScreenShake(): void {
+        if (this.currentShakeTween) {
+            this.currentShakeTween.stop();
+            this.currentShakeTween = null;
+        }
+        
+        if (this.isShaking && this.cameraNode) {
+            // 立即回到原始位置
+            this.cameraNode.position = this.originalCameraPosition.clone();
+            this.isShaking = false;
+            console.log('[ScreenShakeManager] 画面震动已停止');
+        }
+    }
+    
+    /**
+     * 检查是否正在震动
+     */
+    public isScreenShaking(): boolean {
+        return this.isShaking;
+    }
+    
+    /**
+     * 设置震动启用状态
+     */
+    public setVibrationEnabled(enabled: boolean): void {
+        this.vibrationEnabled = enabled;
+        console.log(`[ScreenShakeManager] 震动设置: ${enabled ? '启用' : '禁用'}`);
+        
+        // 如果禁用震动且正在震动,立即停止
+        if (!enabled && this.isShaking) {
+            this.stopScreenShake();
+        }
+    }
+    
+    /**
+     * 获取震动启用状态
+     */
+    public getVibrationEnabled(): boolean {
+        return this.vibrationEnabled;
+    }
+    
+    /**
+     * 播放轻微震动(用于普通攻击)
+     */
+    public playLightShake(): void {
+        this.playScreenShake(6, 0.3, 6);
+    }
+    
+    /**
+     * 播放中等震动(用于重击)
+     */
+    public playMediumShake(): void {
+        this.playScreenShake(10, 0.5, 8);
+    }
+    
+    /**
+     * 播放强烈震动(用于爆炸或特殊攻击)
+     */
+    public playHeavyShake(): void {
+        this.playScreenShake(15, 0.8, 12);
+    }
+    
+    /**
+     * 根据攻击类型播放对应的震动效果
+     */
+    public playShakeByAttackType(attackType: string, intensity?: number): void {
+        switch (attackType.toLowerCase()) {
+            case 'light':
+            case 'normal':
+            case 'melee':
+                this.playLightShake();
+                break;
+            case 'heavy':
+            case 'strong':
+                this.playMediumShake();
+                break;
+            case 'explosive':
+            case 'critical':
+            case 'special':
+                this.playHeavyShake();
+                break;
+            default:
+                // 如果指定了强度,使用自定义震动
+                if (intensity !== undefined) {
+                    this.playScreenShake(intensity, 0.5, 8);
+                } else {
+                    this.playLightShake();
+                }
+                break;
+        }
+    }
+    
+    onDestroy() {
+        // 清理震动效果
+        this.stopScreenShake();
+        
+        // 清除单例引用
+        if (ScreenShakeManager.instance === this) {
+            ScreenShakeManager.instance = null;
+        }
+    }
+}

+ 9 - 0
assets/scripts/CombatSystem/EnemyWeapon/ScreenShakeManager.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "80e315f3-7ac2-4d35-b2de-a7e1f74be94e",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 243 - 0
assets/scripts/CombatSystem/EnemyWeapon/WeaponEffectManager.ts

@@ -0,0 +1,243 @@
+import { _decorator, Component, Node, Vec3, find, sp, Color } from 'cc';
+import { BundleLoader } from '../../Core/BundleLoader';
+import { Audio } from '../../AudioManager/AudioManager';
+const { ccclass, property } = _decorator;
+
+/**
+ * 武器特效管理器
+ * 负责处理敌人武器的击中特效、轨迹特效、音效等
+ * 挂载到Canvas/GameLevelUI/EnemyController上使用
+ */
+@ccclass('WeaponEffectManager')
+export class WeaponEffectManager extends Component {
+    
+    // 当前播放中的特效节点列表
+    private activeEffects: Node[] = [];
+    
+    // 特效缓存
+    private effectCache: Map<string, sp.SkeletonData> = new Map();
+    
+    onLoad() {
+        console.log('[WeaponEffectManager] 武器特效管理器已加载');
+    }
+    
+    onDestroy() {
+        this.clearAllEffects();
+    }
+    
+    /**
+     * 播放武器特效
+     * @param weaponEffects 武器特效配置
+     * @param position 特效播放位置
+     * @param attackType 攻击类型
+     */
+    public async playWeaponEffects(weaponEffects: any, position: Vec3, attackType: string) {
+        if (!weaponEffects) {
+            return;
+        }
+        
+        // 播放击中特效
+        if (weaponEffects.hitEffect) {
+            await this.playHitEffect(weaponEffects.hitEffect, position, weaponEffects.visualScale || 1.0);
+        }
+        
+        // 播放轨迹特效(主要用于远程攻击)
+        if (weaponEffects.trailEffect && (attackType === 'magic_projectile' || attackType === 'arrow_projectile')) {
+            await this.playTrailEffect(weaponEffects.trailEffect, position, weaponEffects.visualScale || 1.0);
+        }
+        
+        // 播放音效
+        if (weaponEffects.impactSound) {
+            this.playImpactSound(weaponEffects.impactSound);
+        }
+    }
+    
+    /**
+     * 播放击中特效
+     * @param effectName 特效名称
+     * @param position 播放位置
+     * @param scale 缩放比例
+     */
+    private async playHitEffect(effectName: string, position: Vec3, scale: number = 1.0) {
+        const effectPath = this.getEffectPath(effectName);
+        if (!effectPath) {
+            console.warn(`[WeaponEffectManager] 未知的击中特效: ${effectName}`);
+            return;
+        }
+        
+        await this.createAndPlayEffect(effectPath, position, scale, false);
+    }
+    
+    /**
+     * 播放轨迹特效
+     * @param effectName 特效名称
+     * @param position 播放位置
+     * @param scale 缩放比例
+     */
+    private async playTrailEffect(effectName: string, position: Vec3, scale: number = 1.0) {
+        const effectPath = this.getEffectPath(effectName);
+        if (!effectPath) {
+            console.warn(`[WeaponEffectManager] 未知的轨迹特效: ${effectName}`);
+            return;
+        }
+        
+        await this.createAndPlayEffect(effectPath, position, scale, false);
+    }
+    
+    /**
+     * 播放撞击音效
+     * @param soundName 音效名称
+     */
+    private playImpactSound(soundName: string) {
+        const soundPath = this.getSoundPath(soundName);
+        if (soundPath) {
+            Audio.playWeaponSound(soundPath);
+        } else {
+            console.warn(`[WeaponEffectManager] 未知的撞击音效: ${soundName}`);
+        }
+    }
+    
+    /**
+     * 获取特效路径
+     * @param effectName 特效名称
+     */
+    private getEffectPath(effectName: string): string | null {
+        const effectPaths: { [key: string]: string } = {
+            'blood_splash': 'WeaponTx/tx0001/tx0001',
+            'magic_burst': 'WeaponTx/tx0002/tx0002',
+            'arrow_impact': 'WeaponTx/tx0003/tx0003',
+            'weapon_clash': 'WeaponTx/tx0004/tx0004',
+            'fire_explosion': 'WeaponTx/tx0005/tx0005',
+            'ice_shatter': 'WeaponTx/tx0006/tx0006',
+            'lightning_strike': 'WeaponTx/tx0007/tx0007',
+            'dark_energy': 'WeaponTx/tx0008/tx0008'
+        };
+        
+        return effectPaths[effectName] || null;
+    }
+    
+    /**
+     * 获取音效路径
+     * @param soundName 音效名称
+     */
+    private getSoundPath(soundName: string): string | null {
+        const soundPaths: { [key: string]: string } = {
+            'melee_hit': 'data/弹球音效/melee_hit',
+            'magic_impact': 'data/弹球音效/magic_impact',
+            'arrow_hit': 'data/弹球音效/arrow_hit',
+            'weapon_clash': 'data/弹球音效/weapon_clash',
+            'explosion': 'data/弹球音效/explosion',
+            'ice_break': 'data/弹球音效/ice_break',
+            'lightning': 'data/弹球音效/lightning',
+            'dark_magic': 'data/弹球音效/dark_magic'
+        };
+        
+        return soundPaths[soundName] || null;
+    }
+    
+    /**
+     * 创建并播放特效
+     * @param effectPath 特效路径
+     * @param position 播放位置
+     * @param scale 缩放比例
+     * @param loop 是否循环播放
+     */
+    private async createAndPlayEffect(effectPath: string, position: Vec3, scale: number = 1.0, loop: boolean = false) {
+        try {
+            // 尝试从缓存获取特效数据
+            let skeletonData = this.effectCache.get(effectPath);
+            
+            if (!skeletonData) {
+                // 从Bundle加载特效数据
+                skeletonData = await BundleLoader.loadSkeletonData(effectPath);
+                if (!skeletonData) {
+                    console.warn(`[WeaponEffectManager] 加载特效失败: ${effectPath}`);
+                    return;
+                }
+                
+                // 缓存特效数据
+                this.effectCache.set(effectPath, skeletonData);
+            }
+            
+            // 创建特效节点
+            const effectNode = new Node('WeaponEffect');
+            const skeleton = effectNode.addComponent(sp.Skeleton);
+            skeleton.skeletonData = skeletonData;
+            skeleton.premultipliedAlpha = false;
+            
+            // 设置动画
+            skeleton.setAnimation(0, 'animation', loop);
+            
+            // 设置完成监听器(非循环动画)
+            if (!loop) {
+                skeleton.setCompleteListener(() => {
+                    this.removeEffect(effectNode);
+                });
+            }
+            
+            // 添加到Canvas并设置位置和缩放
+            const canvas = find('Canvas');
+            if (canvas) {
+                canvas.addChild(effectNode);
+                effectNode.setWorldPosition(position);
+                effectNode.setScale(scale, scale, 1);
+                
+                // 设置特效颜色(增加亮度)
+                const color = new Color(255, 255, 255, 255);
+                color.r = Math.min(255, color.r * 1.2);
+                color.g = Math.min(255, color.g * 1.2);
+                color.b = Math.min(255, color.b * 1.2);
+                skeleton.color = color;
+                
+                // 添加到活动特效列表
+                this.activeEffects.push(effectNode);
+                
+                console.log(`[WeaponEffectManager] 播放武器特效: ${effectPath}`);
+            } else {
+                effectNode.destroy();
+                console.warn('[WeaponEffectManager] 未找到Canvas节点,无法播放特效');
+            }
+            
+        } catch (error) {
+            console.error(`[WeaponEffectManager] 播放特效失败: ${effectPath}`, error);
+        }
+    }
+    
+    /**
+     * 移除特效节点
+     * @param effectNode 要移除的特效节点
+     */
+    private removeEffect(effectNode: Node) {
+        // 从活动特效列表中移除
+        const index = this.activeEffects.indexOf(effectNode);
+        if (index !== -1) {
+            this.activeEffects.splice(index, 1);
+        }
+        
+        // 销毁节点
+        if (effectNode && effectNode.isValid) {
+            effectNode.destroy();
+        }
+    }
+    
+    /**
+     * 清理所有活动的特效
+     */
+    public clearAllEffects() {
+        for (const effect of this.activeEffects) {
+            if (effect && effect.isValid) {
+                effect.destroy();
+            }
+        }
+        this.activeEffects = [];
+        console.log('[WeaponEffectManager] 清理所有武器特效');
+    }
+    
+    /**
+     * 清理特效缓存
+     */
+    public clearEffectCache() {
+        this.effectCache.clear();
+        console.log('[WeaponEffectManager] 清理特效缓存');
+    }
+}

+ 9 - 0
assets/scripts/CombatSystem/EnemyWeapon/WeaponEffectManager.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "1add0d5a-1d8b-4d1c-a5c7-d718cb9a56da",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 14 - 0
assets/scripts/CombatSystem/Wall.ts

@@ -439,6 +439,9 @@ export class Wall extends Component {
         
         // 监听墙体血量变化事件(用于升级后更新显示)
         eventBus.on(GameEvents.WALL_HEALTH_CHANGED, this.onWallHealthChangedEvent, this);
+        
+        // 监听敌人攻击墙体事件(统一事件处理)
+        eventBus.on(GameEvents.ENEMY_DAMAGE_WALL, this.onEnemyDamageWallEvent, this);
     }
     
     /**
@@ -463,6 +466,16 @@ export class Wall extends Component {
         // 其他情况(如受伤、治疗)不需要重新加载存档,血量已经在相应方法中更新
     }
     
+    /**
+     * 处理敌人攻击墙体事件(统一事件处理)
+     */
+    private onEnemyDamageWallEvent(eventData: { damage: number, source: Node }) {
+        if (eventData && eventData.damage > 0) {
+            console.log(`[Wall] 接收到敌人攻击墙体事件,伤害: ${eventData.damage}, 来源: ${eventData.source?.name || '未知'}`);
+            this.takeDamage(eventData.damage);
+        }
+    }
+    
     /**
      * 设置技能监听器
      */
@@ -502,6 +515,7 @@ export class Wall extends Component {
         const eventBus = EventBus.getInstance();
         eventBus.off(GameEvents.RESET_WALL_HEALTH, this.onResetWallHealthEvent, this);
         eventBus.off(GameEvents.WALL_HEALTH_CHANGED, this.onWallHealthChangedEvent, this);
+        eventBus.off(GameEvents.ENEMY_DAMAGE_WALL, this.onEnemyDamageWallEvent, this);
         
         this.cleanupSkillListeners();
         

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

@@ -164,6 +164,7 @@ export interface EnemyConfig {
         blockDamageReduction: number;
         attackCooldown: number;
         attackType: string;
+        causesWallShake: boolean;
         attackDelay: number;
         weaponType: string;
         projectileType: string;

+ 2 - 0
assets/scripts/Core/EventBus.ts

@@ -70,6 +70,8 @@ export enum GameEvents {
     // 伤害事件
     APPLY_DAMAGE_TO_ENEMY = 'APPLY_DAMAGE_TO_ENEMY',
     BURN_EFFECT_ENDED = 'BURN_EFFECT_ENDED',
+    ENEMY_DAMAGE_WALL = 'ENEMY_DAMAGE_WALL',
+    // ENEMY_DAMAGE_PLAYER_BLOCK = 'ENEMY_DAMAGE_PLAYER_BLOCK', // 已移除,抛掷物不攻击玩家方块
 
 
     ENEMY_START_WAVE = 'ENEMY_START_WAVE',

+ 14 - 0
assets/scripts/LevelSystem/IN_game.ts

@@ -9,6 +9,7 @@ import { SkillManager } from '../CombatSystem/SkillSelection/SkillManager';
 import { StartGame } from './StartGame';
 import { BackgroundManager } from './BackgroundManager';
 import { GroundBurnAreaManager } from '../CombatSystem/BulletEffects/GroundBurnAreaManager';
+import { ScreenShakeManager } from '../CombatSystem/EnemyWeapon/ScreenShakeManager';
 
 const { ccclass, property } = _decorator;
 
@@ -181,6 +182,7 @@ export class InGameManager extends Component {
         this.initGameStartMove();
         this.initWallComponent();
         this.initUINodes();
+        this.initScreenShakeManager();
     }
 
     update(deltaTime: number) {
@@ -290,6 +292,18 @@ export class InGameManager extends Component {
         }
     }
 
+    /**
+     * 初始化ScreenShakeManager
+     */
+    private initScreenShakeManager() {
+        if (this.cameraNode) {
+            ScreenShakeManager.getInstance().initialize(this.cameraNode);
+            console.log('[InGameManager] ScreenShakeManager初始化成功');
+        } else {
+            console.warn('[InGameManager] 相机节点未设置,ScreenShakeManager初始化失败');
+        }
+    }
+
     /**
      * 获取指定波次的敌人配置
      */

+ 7 - 2
settings/v2/packages/project.json

@@ -33,6 +33,10 @@
       {
         "index": 6,
         "name": "Saw"
+      },
+      {
+        "index": 7,
+        "name": "EnemyProjectile"
       }
     ],
     "collisionMatrix": {
@@ -41,8 +45,9 @@
       "2": 22,
       "3": 16,
       "4": 124,
-      "5": 18,
-      "6": 80
+      "5": 146,
+      "6": 80,
+      "7": 32
     }
   },
   "custom_joint_texture_layouts": []

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů