Browse Source

隐身修复

181404010226 1 month ago
parent
commit
7b90ed7c0f

File diff suppressed because it is too large
+ 285 - 267
assets/Scenes/GameLevel.scene


+ 2 - 2
assets/data/skill.json

@@ -84,8 +84,8 @@
       "currentLevel": 0,
       "levelEffects": {
         "multiShotChance": [
-          0,
-          0.1,
+          1,
+          1,
           0.2,
           0.3,
           0.4,

+ 9 - 9
assets/data/weapons.json

@@ -25,7 +25,7 @@
           "amount": 1,
           "spreadAngle": 0,
           "burstCount": 1,
-          "burstDelay": 0
+          "burstDelay": 2
         },
         "trajectory": {
           "type": "straight",
@@ -141,7 +141,7 @@
           "amount": 1,
           "spreadAngle": 0,
           "burstCount": 1,
-          "burstDelay": 0
+          "burstDelay": 2
         },
         "trajectory": {
           "type": "straight",
@@ -260,7 +260,7 @@
           "amount": 1,
           "spreadAngle": 0,
           "burstCount": 1,
-          "burstDelay": 0
+          "burstDelay": 2
         },
         "trajectory": {
           "type": "straight",
@@ -384,7 +384,7 @@
           "amount": 1,
           "spreadAngle": 0,
           "burstCount": 1,
-          "burstDelay": 0
+          "burstDelay": 2
         },
         "trajectory": {
           "type": "arc",
@@ -503,7 +503,7 @@
           "amount": 1,
           "spreadAngle": 0,
           "burstCount": 1,
-          "burstDelay": 0
+          "burstDelay": 2
         },
         "trajectory": {
           "type": "arc",
@@ -621,7 +621,7 @@
           "amount": 1,
           "spreadAngle": 0,
           "burstCount": 1,
-          "burstDelay": 0
+          "burstDelay": 2
         },
         "trajectory": {
           "type": "straight",
@@ -746,7 +746,7 @@
           "amount": 5,
           "spreadAngle": 30,
           "burstCount": 1,
-          "burstDelay": 0
+          "burstDelay": 2
         },
         "trajectory": {
           "type": "straight",
@@ -754,7 +754,7 @@
           "gravity": 0,
           "arcHeight": 0,
           "homingStrength": 0,
-          "homingDelay": 0
+          "homingDelay": 2
         },
         "hitEffects": [
           {
@@ -863,7 +863,7 @@
           "amount": 1,
           "spreadAngle": 0,
           "burstCount": 1,
-          "burstDelay": 0
+          "burstDelay": 2
         },
         "trajectory": {
           "type": "arc",

+ 1 - 1
assets/scripts/CombatSystem/BulletEffects/BulletCount.ts

@@ -313,7 +313,7 @@ export class BulletCount extends Component {
             type: 'burst',
             amount: 2, // 每次连射发射1发子弹
             burstCount: multiShotBulletCount, // 连射次数
-            burstDelay: 50 // 连射间隔50毫秒,可以根据需要调整
+            burstDelay: 200 // 连射间隔200毫秒,可以根据需要调整
         };
         console.log(`多重射击触发!技能等级: ${multiShotSkillLevel}, 子弹数量: ${multiShotBulletCount}`);
         return modifiedConfig;

+ 34 - 3
assets/scripts/CombatSystem/BulletEffects/BulletHitEffect.ts

@@ -9,6 +9,7 @@ import { WeaponBullet } from '../WeaponBullet';
 import EventBus, { GameEvents } from '../../Core/EventBus';
 import { Audio } from '../../AudioManager/AudioManager';
 import { BundleLoader } from '../../Core/BundleLoader';
+import { EnemyAttackStateManager } from '../EnemyAttackStateManager';
 const { ccclass, property } = _decorator;
 
 /**
@@ -481,9 +482,12 @@ export class BulletHitEffect extends Component {
         if (!enemyContainer) return 0;
         
         let totalDamage = 0;
-        const enemies = enemyContainer.children.filter(child => 
-            child.active && this.isEnemyNode(child)
-        );
+        const attackStateManager = EnemyAttackStateManager.getInstance();
+        const enemies = enemyContainer.children.filter(child => {
+            if (!child.active || !this.isEnemyNode(child)) return false;
+            // 仅对可攻击敌人造成范围伤害
+            return attackStateManager ? attackStateManager.isEnemyAttackable(child) : true;
+        });
         
         // 对于持续伤害(如地面灼烧),直接使用传入的伤害值
         // 对于爆炸伤害,使用WeaponBullet的最终伤害值
@@ -525,6 +529,12 @@ export class BulletHitEffect extends Component {
             console.log(`[BulletHitEffect] 节点不是敌人,跳过伤害`);
             return;
         }
+        // 检查是否可攻击(例如隐身状态不可攻击)
+        const attackStateManager = EnemyAttackStateManager.getInstance();
+        if (attackStateManager && !attackStateManager.isEnemyAttackable(enemyNode)) {
+            console.log(`[BulletHitEffect] 敌人不可攻击(隐身/状态),跳过伤害: ${enemyNode.name}`);
+            return;
+        }
         
         // 通过EventBus发送伤害事件
         const eventBus = EventBus.getInstance();
@@ -604,6 +614,7 @@ export class BulletHitEffect extends Component {
         }
         
         const currentPos = this.node.worldPosition;
+        const attackStateManager = EnemyAttackStateManager.getInstance();
         let nearestEnemy: Node | null = null;
         let nearestDistance = Infinity;
         
@@ -614,6 +625,13 @@ export class BulletHitEffect extends Component {
                 if (this.currentTargetEnemy && enemy === this.currentTargetEnemy) {
                     continue;
                 }
+                // 排除不可攻击或漂移中的敌人
+                const enemyInstance = enemy.getComponent('EnemyInstance') as any;
+                const isDrifting = enemyInstance && typeof enemyInstance.isDrifting === 'function' ? enemyInstance.isDrifting() : false;
+                const isAttackable = attackStateManager ? attackStateManager.isEnemyAttackable(enemy) : true;
+                if (!isAttackable || isDrifting) {
+                    continue;
+                }
                 
                 // 获取敌人的实时位置
                 const enemyCurrentPos = enemy.worldPosition;
@@ -643,6 +661,7 @@ export class BulletHitEffect extends Component {
         const enemyContainer = find('Canvas/GameLevelUI/enemyContainer');
         if (!enemyContainer) return null;
 
+        const attackStateManager = EnemyAttackStateManager.getInstance();
         const enemies = enemyContainer.children.filter(child => {
             if (!child.active) return false;
             const nameLower = child.name.toLowerCase();
@@ -658,6 +677,13 @@ export class BulletHitEffect extends Component {
         const bulletPos = this.node.worldPosition;
         
         for (const enemy of enemies) {
+            // 排除不可攻击或漂移中的敌人
+            const enemyInstance = enemy.getComponent('EnemyInstance') as any;
+            const isDrifting = enemyInstance && typeof enemyInstance.isDrifting === 'function' ? enemyInstance.isDrifting() : false;
+            const isAttackable = attackStateManager ? attackStateManager.isEnemyAttackable(enemy) : true;
+            if (!isAttackable || isDrifting) {
+                continue;
+            }
             const dist = Vec3.distance(bulletPos, enemy.worldPosition);
             // 只考虑在检测范围内的敌人
             if (dist <= maxRange && dist < nearestDist) {
@@ -825,6 +851,11 @@ export class BulletHitEffect extends Component {
         const otherNode = otherCollider.node;
 
         if (this.isEnemyNode(otherNode)) {
+            // 仅在可攻击状态下追踪敌人
+            const attackStateManager = EnemyAttackStateManager.getInstance();
+            if (attackStateManager && !attackStateManager.isEnemyAttackable(otherNode)) {
+                return;
+            }
             // 获取武器信息,确认是否为锯齿草武器
             const weaponBullet = this.getComponent(WeaponBullet);
             const weaponInfo = weaponBullet ? weaponBullet.getWeaponInfo() : null;

+ 13 - 0
assets/scripts/CombatSystem/BulletEffects/GroundBurnArea.ts

@@ -1,6 +1,7 @@
 import { _decorator, Component, Node, Vec3, find, BoxCollider2D, RigidBody2D, Collider2D, Contact2DType, IPhysics2DContact, ERigidBody2DType } from 'cc';
 import { sp } from 'cc';
 import EventBus, { GameEvents } from '../../Core/EventBus';
+import { EnemyAttackStateManager } from '../EnemyAttackStateManager';
 import { GroundBurnAreaManager } from './GroundBurnAreaManager';
 import { PhysicsManager } from '../../Core/PhysicsManager';
 const { ccclass, property } = _decorator;
@@ -161,6 +162,12 @@ export class GroundBurnArea extends Component {
         
         // 检查是否是敌人
         if (this.isEnemyNode(enemyNode)) {
+            // 仅记录可攻击的敌人进入燃烧区域(隐身等状态不记录)
+            const attackStateManager = EnemyAttackStateManager.getInstance();
+            if (attackStateManager && !attackStateManager.isEnemyAttackable(enemyNode)) {
+                console.log(`[GroundBurnArea] 敌人不可攻击(隐身等),不加入区域: ${enemyNode.name}`);
+                return;
+            }
             this._enemiesInArea.add(enemyNode);
             console.log(`[GroundBurnArea] 敌人进入燃烧区域: ${enemyNode.name}, 区域内敌人数量: ${this._enemiesInArea.size}`);
         } else {
@@ -276,6 +283,7 @@ export class GroundBurnArea extends Component {
         console.log(`[GroundBurnArea] 对区域内 ${this._enemiesInArea.size} 个敌人造成燃烧伤害: ${this._damage}`);
         
         const eventBus = EventBus.getInstance();
+        const attackStateManager = EnemyAttackStateManager.getInstance();
         
         // 对每个区域内的敌人造成伤害
         for (const enemyNode of this._enemiesInArea) {
@@ -286,6 +294,11 @@ export class GroundBurnArea extends Component {
                     console.log(`[GroundBurnArea] 敌人 ${enemyNode.name} 处于漂移状态,跳过燃烧伤害`);
                     continue;
                 }
+                // 检查敌人是否可攻击(隐身等状态不可受伤害)
+                if (attackStateManager && !attackStateManager.isEnemyAttackable(enemyNode)) {
+                    console.log(`[GroundBurnArea] 敌人不可攻击,跳过燃烧伤害: ${enemyNode.name}`);
+                    continue;
+                }
                 
                 const damageData = {
                     enemyNode: enemyNode,

+ 281 - 0
assets/scripts/CombatSystem/EnemyAttackStateManager.ts

@@ -0,0 +1,281 @@
+import { _decorator, Node, Component, find } from 'cc';
+import EventBus, { GameEvents } from '../Core/EventBus';
+
+const { ccclass, property } = _decorator;
+
+/**
+ * 敌人攻击状态管理器
+ * 使用字典结构快速查询敌人是否可以被攻击
+ * 采用标准单例模式,不依赖BaseSingleton
+ */
+@ccclass('EnemyAttackStateManager')
+export class EnemyAttackStateManager extends Component {
+    private static _instance: EnemyAttackStateManager | null = null;
+
+    // 敌人攻击状态字典:key为敌人节点的uuid,value为是否可攻击
+    private enemyAttackableStates: Map<string, boolean> = new Map();
+    
+    // 敌人节点引用字典:key为敌人节点的uuid,value为敌人节点
+    private enemyNodeReferences: Map<string, Node> = new Map();
+
+    // Debug(属性检查器展示)
+    @property({ tooltip: '是否更新调试信息(属性检查器)' })
+    public showDebug: boolean = true;
+
+    @property({ tooltip: '调试信息刷新间隔(秒)' })
+    public debugUpdateInterval: number = 0.5;
+
+    // 以下字段通过装饰器在属性检查器中显示(只读调试)
+    @property({ tooltip: '敌人总数(只读)' })
+    public totalEnemies: number = 0;
+
+    @property({ tooltip: '可攻击敌人数(只读)' })
+    public attackableCount: number = 0;
+
+    @property({ type: [String], tooltip: '示例敌人名称(最多10个)' })
+    public enemyNameSamples: string[] = [];
+
+    @property({ tooltip: '字典状态预览(JSON,只读)' })
+    public statesPreviewJson: string = '';
+
+    // 组件加载时设置单例并注册事件
+    onLoad() {
+        EnemyAttackStateManager._instance = this;
+        this.setupEventListeners();
+        if (this.showDebug) {
+            this.schedule(this.refreshInspectorDebug.bind(this), Math.max(0.1, this.debugUpdateInterval));
+        }
+        this.refreshInspectorDebug();
+    }
+
+    public static getInstance(): EnemyAttackStateManager {
+        // 若已有实例则直接返回
+        if (EnemyAttackStateManager._instance) {
+            return EnemyAttackStateManager._instance;
+        }
+
+        // 尝试从场景中获取或挂载到 Canvas/GameLevelUI/enemyContainer
+        const enemyContainer = find('Canvas/GameLevelUI/enemyContainer');
+        if (enemyContainer) {
+            let comp = enemyContainer.getComponent(EnemyAttackStateManager) as EnemyAttackStateManager;
+            if (!comp) {
+                comp = enemyContainer.addComponent(EnemyAttackStateManager) as EnemyAttackStateManager;
+            }
+            EnemyAttackStateManager._instance = comp;
+            return comp;
+        }
+
+        // 如果未找到节点,返回一个占位的组件实例(不建议,但保证调用不崩溃)
+        // 注意:占位实例无法可视化显示,仅用于避免空指针
+        const placeholder = new EnemyAttackStateManager();
+        EnemyAttackStateManager._instance = placeholder as any;
+        return EnemyAttackStateManager._instance;
+    }
+
+    private setupEventListeners() {
+        const eventBus = EventBus.getInstance();
+        
+        // 监听敌人隐身开始事件
+        eventBus.on(GameEvents.ENEMY_STEALTH_START, this.onEnemyStealthStart, this);
+        
+        // 监听敌人隐身结束事件
+        eventBus.on(GameEvents.ENEMY_STEALTH_END, this.onEnemyStealthEnd, this);
+        
+        // 监听敌人死亡事件
+        eventBus.on('ENEMY_KILLED', this.onEnemyKilled, this);
+        
+        // 监听清除所有敌人事件
+        eventBus.on(GameEvents.CLEAR_ALL_ENEMIES, this.onClearAllEnemies, this);
+    }
+
+    /**
+     * 注册敌人到攻击状态管理器
+     * @param enemyNode 敌人节点
+     * @param isAttackable 初始攻击状态,默认为true
+     */
+    public registerEnemy(enemyNode: Node, isAttackable: boolean = true) {
+        if (!enemyNode || !enemyNode.isValid) return;
+        
+        const enemyId = enemyNode.uuid;
+        this.enemyAttackableStates.set(enemyId, isAttackable);
+        this.enemyNodeReferences.set(enemyId, enemyNode);
+        
+        console.log(`[EnemyAttackStateManager] 注册敌人: ${enemyNode.name} (${enemyId}), 可攻击: ${isAttackable}`);
+        this.refreshInspectorDebug();
+    }
+
+    /**
+     * 从攻击状态管理器中移除敌人
+     * @param enemyNode 敌人节点
+     */
+    public unregisterEnemy(enemyNode: Node) {
+        if (!enemyNode) return;
+        
+        const enemyId = enemyNode.uuid;
+        this.enemyAttackableStates.delete(enemyId);
+        this.enemyNodeReferences.delete(enemyId);
+        
+        console.log(`[EnemyAttackStateManager] 移除敌人: ${enemyNode.name} (${enemyId})`);
+        this.refreshInspectorDebug();
+    }
+
+    /**
+     * 设置敌人的攻击状态
+     * @param enemyNode 敌人节点
+     * @param attackable 是否可攻击
+     */
+    public setEnemyAttackable(enemyNode: Node, attackable: boolean): void {
+        if (!enemyNode || !enemyNode.isValid) return;
+        
+        const enemyId = enemyNode.uuid;
+        const previousState = this.enemyAttackableStates.get(enemyId);
+        
+        this.enemyAttackableStates.set(enemyId, attackable);
+        
+        // 添加状态变化日志
+        if (previousState !== attackable) {
+            const stateText = attackable ? '可攻击' : '不可攻击';
+            const reasonText = attackable ? '(退出隐身)' : '(进入隐身)';
+            console.log(`[EnemyAttackStateManager] 敌人状态变化: ${enemyNode.name} (${enemyId.substring(0, 8)}...) -> ${stateText} ${reasonText}`);
+        }
+        this.refreshInspectorDebug();
+    }
+
+    /**
+     * 检查敌人是否可以被攻击
+     * @param enemyNode 敌人节点
+     * @returns 是否可攻击
+     */
+    public isEnemyAttackable(enemyNode: Node): boolean {
+        if (!enemyNode || !enemyNode.isValid) return false;
+        
+        const enemyId = enemyNode.uuid;
+        return this.enemyAttackableStates.get(enemyId) ?? false;
+    }
+
+    /**
+     * 获取所有可攻击的敌人节点
+     * @returns 可攻击的敌人节点数组
+     */
+    public getAttackableEnemies(): Node[] {
+        const attackableEnemies: Node[] = [];
+        
+        for (const [enemyId, isAttackable] of this.enemyAttackableStates.entries()) {
+            if (isAttackable) {
+                const enemyNode = this.enemyNodeReferences.get(enemyId);
+                if (enemyNode && enemyNode.isValid) {
+                    attackableEnemies.push(enemyNode);
+                } else {
+                    // 清理无效的引用
+                    this.enemyAttackableStates.delete(enemyId);
+                    this.enemyNodeReferences.delete(enemyId);
+                }
+            }
+        }
+        
+        return attackableEnemies;
+    }
+
+    /**
+     * 清理所有敌人状态
+     */
+    public clearAllEnemies() {
+        this.enemyAttackableStates.clear();
+        this.enemyNodeReferences.clear();
+        console.log(`[EnemyAttackStateManager] 清理所有敌人状态`);
+        this.refreshInspectorDebug();
+    }
+
+    /**
+     * 获取当前管理的敌人数量
+     */
+    public getEnemyCount(): number {
+        return this.enemyAttackableStates.size;
+    }
+
+    /**
+     * 获取可攻击敌人数量
+     */
+    public getAttackableEnemyCount(): number {
+        let count = 0;
+        for (const isAttackable of this.enemyAttackableStates.values()) {
+            if (isAttackable) count++;
+        }
+        return count;
+    }
+
+    // 事件处理方法
+    private onEnemyStealthStart(data: { enemy: any, duration?: number }) {
+        if (data.enemy && data.enemy.node) {
+            this.setEnemyAttackable(data.enemy.node, false);
+            console.log(`[EnemyAttackStateManager] 敌人进入隐身状态: ${data.enemy.node.name}`);
+        }
+    }
+
+    private onEnemyStealthEnd(data: { enemy: any }) {
+        if (data.enemy && data.enemy.node) {
+            this.setEnemyAttackable(data.enemy.node, true);
+            console.log(`[EnemyAttackStateManager] 敌人退出隐身状态: ${data.enemy.node.name}`);
+        }
+    }
+
+    private onEnemyKilled() {
+        // 注意:敌人节点的移除已经在EnemyInstance.onDestroy()中通过unregisterEnemy()处理
+        // 这个事件处理器主要用于其他需要响应敌人死亡的逻辑
+        console.log(`[EnemyAttackStateManager] 接收到敌人死亡事件`);
+    }
+
+    private onClearAllEnemies() {
+        this.clearAllEnemies();
+    }
+
+    onDestroy() {
+        const eventBus = EventBus.getInstance();
+        
+        // 移除所有事件监听器
+        eventBus.off(GameEvents.ENEMY_STEALTH_START, this.onEnemyStealthStart, this);
+        eventBus.off(GameEvents.ENEMY_STEALTH_END, this.onEnemyStealthEnd, this);
+        eventBus.off('ENEMY_KILLED', this.onEnemyKilled, this);
+        eventBus.off(GameEvents.CLEAR_ALL_ENEMIES, this.onClearAllEnemies, this);
+        
+        // 清空所有数据
+        this.enemyAttackableStates.clear();
+        this.enemyNodeReferences.clear();
+        
+        // 清空单例实例
+        EnemyAttackStateManager._instance = null;
+    }
+
+    // === Debug(属性检查器) ===
+    private refreshInspectorDebug() {
+        if (!this.showDebug) return;
+
+        // 概览统计
+        this.totalEnemies = this.getEnemyCount();
+        this.attackableCount = this.getAttackableEnemyCount();
+
+        // 示例名称(最多10个)
+        const samples: string[] = [];
+        let i = 0;
+        for (const [, enemyNode] of this.enemyNodeReferences.entries()) {
+            if (enemyNode && enemyNode.isValid) {
+                samples.push(enemyNode.name);
+                i++;
+                if (i >= 10) break;
+            }
+        }
+        this.enemyNameSamples = samples;
+
+        // 预览字典(最多20条,JSON字符串)
+        const previewItems: Array<{ name: string; id: string; attackable: boolean }> = [];
+        i = 0;
+        for (const [enemyId, isAttackable] of this.enemyAttackableStates.entries()) {
+            const enemyNode = this.enemyNodeReferences.get(enemyId);
+            const name = enemyNode && enemyNode.isValid ? enemyNode.name : '(invalid)';
+            previewItems.push({ name, id: enemyId.substring(0, 8), attackable: !!isAttackable });
+            i++;
+            if (i >= 20) break;
+        }
+        this.statesPreviewJson = JSON.stringify(previewItems, null, 2);
+    }
+}

+ 9 - 0
assets/scripts/CombatSystem/EnemyAttackStateManager.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "dc9a8799-3709-487f-888e-c59cf379a9c7",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 29 - 17
assets/scripts/CombatSystem/EnemyComponent.ts

@@ -1,6 +1,7 @@
 import { _decorator, Component, Node, Color, UIOpacity } from 'cc';
 import { sp } from 'cc';
 import { ConfigManager, EnemyConfig } from '../Core/ConfigManager';
+import EventBus, { GameEvents } from '../Core/EventBus';
 const { ccclass, property } = _decorator;
 
 /**
@@ -17,28 +18,22 @@ export class EnemyComponent extends Component {
     private stealthActive: boolean = false;
 
     start() {
-        // 如果具备隐身能力,则初始化隐身循环
-        const ability = this.getSpecialAbility();
-        if (ability === 'stealth') {
-            const cooldown = this.getSpecialAbilityCooldown();
-            // 初始进入隐身
-            this.applyStealthOpacity(100);
-            this.stealthActive = true;
-            // 按冷却时间循环切换显隐
-            this.schedule(() => {
-                if (this.stealthActive) {
-                    this.applyStealthOpacity(255);
-                } else {
-                    this.applyStealthOpacity(100);
-                }
-                this.stealthActive = !this.stealthActive;
-            }, Math.max(0.1, cooldown));
-        }
+        // 统一由 EnemyInstance 发出隐身事件并驱动 AttackStateManager。
+        // 此组件仅响应事件并更新视觉透明度,不再主动调度隐身。
+    }
+
+    onEnable() {
+        const eventBus = EventBus.getInstance();
+        eventBus.on(GameEvents.ENEMY_STEALTH_START, this.onStealthStart, this);
+        eventBus.on(GameEvents.ENEMY_STEALTH_END, this.onStealthEnd, this);
     }
 
     onDisable() {
         // 组件禁用时取消调度,避免泄漏
         this.unscheduleAllCallbacks();
+        const eventBus = EventBus.getInstance();
+        eventBus.off(GameEvents.ENEMY_STEALTH_START, this.onStealthStart, this);
+        eventBus.off(GameEvents.ENEMY_STEALTH_END, this.onStealthEnd, this);
     }
 
     // 获取敌人生命值
@@ -255,6 +250,23 @@ export class EnemyComponent extends Component {
         return null;
     }
 
+    // === 隐身事件响应 ===
+    private onStealthStart(data: { enemy: any, duration?: number }) {
+        if (!data || !data.enemy || !data.enemy.node) return;
+        if (data.enemy.node !== this.node) return;
+        this.stealthActive = true;
+        this.applyStealthOpacity(100);
+        console.log(`[EnemyComponent] 接收隐身开始事件: ${this.node.name}, 持续: ${data.duration ?? '-'} 秒`);
+    }
+
+    private onStealthEnd(data: { enemy: any }) {
+        if (!data || !data.enemy || !data.enemy.node) return;
+        if (data.enemy.node !== this.node) return;
+        this.stealthActive = false;
+        this.applyStealthOpacity(255);
+        console.log(`[EnemyComponent] 接收隐身结束事件: ${this.node.name}`);
+    }
+
     // === 狂暴状态管理 ===
     // 检查是否处于狂暴状态
     public isInRage(): boolean {

+ 14 - 3
assets/scripts/CombatSystem/EnemyController.ts

@@ -7,6 +7,7 @@ import { BaseSingleton } from '../Core/BaseSingleton';
 import { SaveDataManager } from '../LevelSystem/SaveDataManager';
 import { LevelConfigManager } from '../LevelSystem/LevelConfigManager';
 import EventBus, { GameEvents } from '../Core/EventBus';
+import { EnemyAttackStateManager } from './EnemyAttackStateManager';
 import { Wall } from './Wall';
 import { BurnEffect } from './BulletEffects/BurnEffect';
 import { Audio } from '../AudioManager/AudioManager';
@@ -1034,10 +1035,13 @@ export class EnemyController extends BaseSingleton {
         const enemies = this.getActiveEnemies();
         if (enemies.length === 0) return null;
 
-        // 过滤掉漂移状态的敌人
+        // 过滤掉漂移状态和不可攻击(隐身等状态)的敌人
+        const attackStateManager = EnemyAttackStateManager.getInstance();
         const nonDriftingEnemies = enemies.filter(enemy => {
             const enemyInstance = enemy.getComponent('EnemyInstance') as any;
-            return !enemyInstance || !enemyInstance.isDrifting();
+            const notDrifting = !enemyInstance || !enemyInstance.isDrifting();
+            const attackable = attackStateManager ? attackStateManager.isEnemyAttackable(enemy) : true;
+            return notDrifting && attackable;
         });
         
         if (nonDriftingEnemies.length === 0) return null;
@@ -1138,7 +1142,14 @@ export class EnemyController extends BaseSingleton {
         // 获取敌人组件
         const enemyComp = enemy.getComponent(EnemyInstance);
         if (!enemyComp) return;
-        
+
+        // 检查是否可攻击(隐身等状态不可被攻击)
+        const attackStateManager = EnemyAttackStateManager.getInstance();
+        if (attackStateManager && !attackStateManager.isEnemyAttackable(enemy)) {
+            console.log(`[EnemyController] 敌人不可攻击,跳过伤害: ${enemy.name}`);
+            return;
+        }
+
         // 减少敌人血量
         enemyComp.takeDamage(damage, isCritical);
         

+ 224 - 0
assets/scripts/CombatSystem/EnemyInstance.ts

@@ -6,6 +6,7 @@ import { HPBarAnimation } from '../Animations/HPBarAnimation';
 import { EnemyComponent } from './EnemyComponent';
 import { EnemyAudio } from '../AudioManager/EnemyAudios';
 import { ScreenShakeManager } from './EnemyWeapon/ScreenShakeManager';
+import { EnemyAttackStateManager } from './EnemyAttackStateManager';
 
 import EventBus, { GameEvents } from '../Core/EventBus';
 const { ccclass, property } = _decorator;
@@ -30,6 +31,7 @@ enum EnemyState {
     MOVING,   // 移动中
     IDLE,     // 待机中(碰到墙体后停止移动但可以攻击)
     ATTACKING, // 攻击中
+    STEALTH,  // 隐身中(不被方块武器检测到)
     DEAD      // 死亡
 }
 
@@ -112,6 +114,16 @@ export class EnemyInstance extends Component {
     // 攻击状态下的动画管理
     private isPlayingAttackAnimation: boolean = false;
 
+    // 隐身相关属性
+    private stealthDuration: number = 0; // 隐身持续时间
+    private stealthTimer: number = 0; // 隐身计时器
+    private originalOpacity: number = 255; // 原始透明度
+    // 隐身循环相关
+    private hasStealthAbility: boolean = false;
+    private stealthCooldown: number = 5.0;
+    private initialStealthScheduled: boolean = false; // 首次隐身定时是否已安排
+    private stealthActive: boolean = false; // 隐身为叠加标记,不改变行为状态
+
 
     start() {
         // 初始化敌人
@@ -154,6 +166,9 @@ export class EnemyInstance extends Component {
         
         // 应用配置到敌人属性
         this.applyEnemyConfig();
+
+        // 应用配置后,重新初始化特殊能力(确保在运行时添加组件后也能按配置触发)
+        this.initializeSpecialAbilities();
     }
     
     // 应用敌人配置到属性
@@ -252,6 +267,42 @@ export class EnemyInstance extends Component {
         
         // 初始化碰撞检测
         this.setupCollider();
+        
+        // 注册到攻击状态管理器
+        const attackStateManager = EnemyAttackStateManager.getInstance();
+        attackStateManager.registerEnemy(this.node, true); // 初始状态为可攻击
+        
+        // 初始化特殊能力
+        console.log(`[EnemyInstance] 准备初始化特殊能力 - 敌人节点: ${this.node.name}`);
+        this.initializeSpecialAbilities();
+    }
+    
+    // 初始化特殊能力
+    private initializeSpecialAbilities(): void {
+        console.log(`[EnemyInstance] 初始化特殊能力 - 敌人: ${this.getEnemyName()}, 配置存在: ${!!this.enemyConfig}, 特殊能力: ${this.enemyConfig?.special_abilities ? JSON.stringify(this.enemyConfig.special_abilities) : '无'}`);
+        
+        if (!this.enemyConfig || !this.enemyConfig.special_abilities) {
+            console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 没有特殊能力配置`);
+            return;
+        }
+        
+        // 检查是否有隐身能力
+        const stealthAbility = this.enemyConfig.special_abilities.find(ability => ability.type === 'stealth');
+        if (stealthAbility) {
+            console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 具有隐身能力,冷却时间: ${stealthAbility.cooldown}秒`);
+            this.hasStealthAbility = true;
+            this.stealthCooldown = typeof stealthAbility.cooldown === 'number' ? stealthAbility.cooldown : 5.0;
+
+            // 生成后按冷却时间开始循环隐身:首次隐身在 cooldown 秒后触发
+            if (!this.initialStealthScheduled) {
+                this.initialStealthScheduled = true;
+                this.scheduleOnce(() => {
+                    if (!this.node || !this.node.isValid) return;
+                    if (this.state === EnemyState.DEAD) return;
+                    this.enterStealth(this.stealthCooldown);
+                }, this.stealthCooldown);
+            }
+        }
     }
     
     // 设置碰撞器
@@ -650,6 +701,13 @@ export class EnemyInstance extends Component {
 
     onDestroy() {
         console.log(`[EnemyInstance] onDestroy 被调用,准备通知控制器`);
+        // 取消所有调度,避免隐身循环在销毁后继续
+        this.unscheduleAllCallbacks();
+        
+        // 从攻击状态管理器中移除敌人
+        const attackStateManager = EnemyAttackStateManager.getInstance();
+        attackStateManager.unregisterEnemy(this.node);
+        
         // 通知控制器 & GameManager
         if (this.controller && typeof (this.controller as any).notifyEnemyDead === 'function') {
             // 检查控制器是否处于清理状态,避免在清理过程中触发游戏事件
@@ -685,6 +743,11 @@ export class EnemyInstance extends Component {
             this.updateAttack(deltaTime);
         }
 
+        // 隐身为叠加效果:不改变行为状态,但仍需要计时与退出逻辑
+        if (this.stealthActive) {
+            this.updateStealth(deltaTime);
+        }
+
         // 不再每帧播放攻击动画,避免日志刷屏
     }
     
@@ -1530,4 +1593,165 @@ export class EnemyInstance extends Component {
             console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 进入狂暴状态`);
         }
     }
+
+    // ===== 隐身相关方法 =====
+    
+    /**
+     * 进入隐身状态
+     * @param duration 隐身持续时间(秒)
+     */
+    public enterStealth(duration: number = 5.0): void {
+        if (this.state === EnemyState.DEAD) {
+            console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 已死亡,无法进入隐身状态`);
+            return; // 死亡状态下不能隐身
+        }
+        
+        if (this.stealthActive) {
+            console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 已经处于隐身状态,跳过重复进入`);
+            return; // 已经在隐身状态,不重复进入
+        }
+        
+        console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 进入隐身状态,持续时间: ${duration}秒,当前状态: ${this.state}`);
+
+        // 隐身为叠加标记,不改变行为状态
+        this.stealthActive = true;
+        this.stealthDuration = duration;
+        this.stealthTimer = 0;
+        
+        // 应用隐身视觉效果
+        this.applyStealthVisualEffect();
+
+        // 直接更新攻击状态,确保不依赖事件监听顺序
+        const attackStateManager = EnemyAttackStateManager.getInstance();
+        attackStateManager.setEnemyAttackable(this.node, false);
+        console.log(`[EnemyInstance] 已直接将 ${this.getEnemyName()} 标记为不可攻击(隐身中)`);
+        
+        // 触发隐身事件
+        EventBus.getInstance().emit(GameEvents.ENEMY_STEALTH_START, {
+            enemy: this,
+            duration: duration
+        });
+        
+        console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 隐身状态激活,透明度已调整`);
+    }
+    
+    /**
+     * 退出隐身状态
+     */
+    public exitStealth(): void {
+        if (!this.stealthActive) {
+            console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 不在隐身状态(当前状态: ${this.state}),无需退出`);
+            return; // 不在隐身状态
+        }
+        
+        console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 退出隐身状态`);
+
+        // 结束隐身:行为状态不变,仅关闭叠加标记
+        this.stealthActive = false;
+        this.stealthTimer = 0;
+        this.stealthDuration = 0;
+        
+        // 移除隐身视觉效果
+        this.removeStealthVisualEffect();
+        
+        // 触发隐身结束事件
+        EventBus.getInstance().emit(GameEvents.ENEMY_STEALTH_END, {
+            enemy: this
+        });
+
+        // 直接更新攻击状态,确保不依赖事件监听顺序
+        const attackStateManager2 = EnemyAttackStateManager.getInstance();
+        attackStateManager2.setEnemyAttackable(this.node, true);
+        console.log(`[EnemyInstance] 已直接将 ${this.getEnemyName()} 标记为可攻击(退出隐身)`);
+        
+        console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 隐身状态结束,透明度已恢复`);
+
+        // 若具备隐身能力,按照冷却时间再次尝试进入隐身(在回调中再检查是否已死亡/节点失效)
+        if (this.hasStealthAbility) {
+            this.scheduleOnce(() => {
+                if (!this.node || !this.node.isValid) return;
+                if (this.state === EnemyState.DEAD) return;
+                this.enterStealth(this.stealthCooldown);
+            }, this.stealthCooldown);
+        }
+    }
+    
+    /**
+     * 检查是否处于隐身状态
+     * @returns 是否隐身
+     */
+    public isStealth(): boolean {
+        return this.stealthActive;
+    }
+    
+    /**
+     * 应用隐身视觉效果
+     */
+    private applyStealthVisualEffect(): void {
+        const enemySprite = this.node.getChildByName('EnemySprite');
+        if (!enemySprite) {
+            console.warn('[EnemyInstance] 未找到EnemySprite节点,无法应用隐身视觉效果');
+            return;
+        }
+        
+        // 保存原始透明度
+        const uiOpacity = enemySprite.getComponent(UIOpacity);
+        if (uiOpacity) {
+            this.originalOpacity = uiOpacity.opacity;
+            
+            // 使用缓动动画降低透明度到30%
+            tween(uiOpacity)
+                .to(0.5, { opacity: 77 }) // 30% 透明度 (255 * 0.3 ≈ 77)
+                .start();
+        } else {
+            // 如果没有UIOpacity组件,添加一个
+            const newUIOpacity = enemySprite.addComponent(UIOpacity);
+            this.originalOpacity = 255;
+            newUIOpacity.opacity = 255;
+            
+            tween(newUIOpacity)
+                .to(0.5, { opacity: 77 })
+                .start();
+        }
+        
+        console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 应用隐身视觉效果`);
+    }
+    
+    /**
+     * 移除隐身视觉效果
+     */
+    private removeStealthVisualEffect(): void {
+        const enemySprite = this.node.getChildByName('EnemySprite');
+        if (!enemySprite) {
+            console.warn('[EnemyInstance] 未找到EnemySprite节点,无法移除隐身视觉效果');
+            return;
+        }
+        
+        const uiOpacity = enemySprite.getComponent(UIOpacity);
+        if (uiOpacity) {
+            // 使用缓动动画恢复原始透明度
+            tween(uiOpacity)
+                .to(0.5, { opacity: this.originalOpacity })
+                .start();
+        }
+        
+        console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 移除隐身视觉效果`);
+    }
+    
+    /**
+     * 更新隐身状态
+     * @param deltaTime 帧时间间隔
+     */
+    private updateStealth(deltaTime: number): void {
+        if (!this.stealthActive) {
+            return;
+        }
+        
+        this.stealthTimer += deltaTime;
+        
+        // 检查隐身时间是否结束
+        if (this.stealthTimer >= this.stealthDuration) {
+            this.exitStealth();
+        }
+    }
 }

+ 30 - 14
assets/scripts/CombatSystem/WeaponBullet.ts

@@ -6,6 +6,7 @@ import { BulletTrajectory } from './BulletEffects/BulletTrajectory';
 import { BulletHitEffect, HitResult } from './BulletEffects/BulletHitEffect';
 import { BulletLifecycle } from './BulletEffects/BulletLifecycle';
 import { BulletTrailController } from './BulletTrailController';
+import { EnemyAttackStateManager } from './EnemyAttackStateManager';
 import { ConfigManager, WeaponConfig, BulletCountConfig, BulletTrajectoryConfig, HitEffectConfig, BulletLifecycleConfig } from '../Core/ConfigManager';
 import  EventBus,{ GameEvents } from '../Core/EventBus';
 import { PersistentSkillManager } from '../FourUI/SkillSystem/PersistentSkillManager';
@@ -467,6 +468,13 @@ export class WeaponBullet extends Component {
                 return; // 漂移状态下不受到子弹攻击
             }
             
+            // 使用EnemyAttackStateManager检查敌人是否可攻击(包括隐身状态)
+            const attackStateManager = EnemyAttackStateManager.getInstance();
+            if (!attackStateManager.isEnemyAttackable(enemyRootNode)) {
+                console.log(`[WeaponBullet] 敌人 ${enemyRootNode.name} 不可攻击(可能处于隐身状态),跳过子弹攻击`);
+                return; // 不可攻击状态下不受到子弹攻击
+            }
+            
             // 计算是否暴击
             const isCritical = Math.random() < this.getCritChance();
             const finalDamage = isCritical ? this.getFinalCritDamage() : this.getFinalDamage();
@@ -701,30 +709,38 @@ export class WeaponBullet extends Component {
     
     /**
      * 静态:根据给定世界坐标,寻找最近的敌人节点
+     * 使用EnemyAttackStateManager进行高效查找
      */
     private static findNearestEnemyGlobal(worldPos: Vec3): Node | null {
-        const enemyContainer = find('Canvas/GameLevelUI/enemyContainer');
-        if (!enemyContainer) return null;
-
-        const enemies = enemyContainer.children.filter(child => {
-            if (!child.active) return false;
-            const nameLower = child.name.toLowerCase();
-            if (nameLower.includes('enemy') || nameLower.includes('敌人')) return true;
-            if (child.getComponent('EnemyInstance')) return true;
-            return false;
-        });
-
-        if (enemies.length === 0) return null;
-
+        // 使用攻击状态管理器获取所有可攻击的敌人
+        const attackStateManager = EnemyAttackStateManager.getInstance();
+        const attackableEnemies = attackStateManager.getAttackableEnemies();
+        
+        if (attackableEnemies.length === 0) {
+            return null;
+        }
+        
         let nearest: Node = null;
         let nearestDist = Infinity;
-        for (const enemy of enemies) {
+        
+        for (const enemy of attackableEnemies) {
+            if (!enemy || !enemy.isValid || !enemy.activeInHierarchy) {
+                continue;
+            }
+            
             const dist = Vec3.distance(worldPos, enemy.worldPosition);
             if (dist < nearestDist) {
                 nearestDist = dist;
                 nearest = enemy;
             }
         }
+        
+        if (nearest) {
+            console.log(`[WeaponBullet] 找到最近的可攻击敌人: ${nearest.name}, 距离: ${nearestDist.toFixed(2)}`);
+        } else {
+            console.log(`[WeaponBullet] 未找到可攻击的敌人`);
+        }
+        
         return nearest;
     }
     

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

@@ -66,6 +66,8 @@ export enum GameEvents {
     ENEMY_UPDATE_COUNT = 'ENEMY_UPDATE_COUNT',
     ENEMY_SPAWNING_STARTED = 'ENEMY_SPAWNING_STARTED',
     ENEMY_SPAWNING_STOPPED = 'ENEMY_SPAWNING_STOPPED',
+    ENEMY_STEALTH_START = 'ENEMY_STEALTH_START',
+    ENEMY_STEALTH_END = 'ENEMY_STEALTH_END',
     
     // 伤害事件
     APPLY_DAMAGE_TO_ENEMY = 'APPLY_DAMAGE_TO_ENEMY',

+ 22 - 14
assets/scripts/FourUI/UpgradeSystem/UpgradeController.ts

@@ -191,12 +191,25 @@ export class UpgradeController extends Component {
      */
     private initializeWeapons() {
         if (!this.weaponsConfig) return;
-        
+
+        const maxUnlockedLevel = this.saveDataManager.getMaxUnlockedLevel();
+        console.log(`[UpgradeController] 初始化武器数据,当前最大解锁关卡: ${maxUnlockedLevel}`);
+
         // 为每个武器创建初始数据(如果不存在)
         this.weaponsConfig.weapons.forEach(weaponConfig => {
             const existingWeapon = this.saveDataManager.getWeapon(weaponConfig.id);
             if (!existingWeapon) {
-                this.saveDataManager.addWeapon(weaponConfig.id, weaponConfig.rarity);
+                const requiredLevel = this.getWeaponUnlockLevelById(weaponConfig.id);
+                const shouldBeUnlocked = maxUnlockedLevel >= requiredLevel;
+                const initialLevel = shouldBeUnlocked ? 1 : 0;
+                
+                this.saveDataManager.addWeapon(weaponConfig.id, weaponConfig.rarity, initialLevel);
+                
+                if (shouldBeUnlocked) {
+                    console.log(`[UpgradeController] 初始化时自动解锁武器: ${weaponConfig.name} (${weaponConfig.id})`);
+                } else {
+                    console.log(`[UpgradeController] 初始化武器为未解锁状态: ${weaponConfig.name} (${weaponConfig.id}), 需要关卡${requiredLevel}`);
+                }
             }
         });
     }
@@ -918,20 +931,15 @@ export class UpgradeController extends Component {
             if (shouldBeUnlocked && !isCurrentlyUnlocked) {
                 // 确保武器数据存在
                 if (!weaponData) {
-                    this.saveDataManager.addWeapon(weaponId, weaponConfig.rarity);
+                    this.saveDataManager.addWeapon(weaponId, weaponConfig.rarity, 1);
+                } else {
+                    // 武器数据存在但未解锁,直接设置为解锁状态
+                    weaponData.level = 1;
                 }
                 
-                // 自动解锁武器(设置为level 1,免费解锁)
-                const updatedWeaponData = this.saveDataManager.getWeapon(weaponId);
-                if (updatedWeaponData) {
-                    updatedWeaponData.level = 1;
-                    // 由于WeaponData类型中没有unlockTime属性,暂时注释掉这行
-                    // updatedWeaponData.unlockTime = Date.now();
-                    
-                    console.log(`[UpgradeController] 自动解锁武器: ${weaponConfig.name} (${weaponId})`);
-                    newlyUnlockedWeapons.push(weaponConfig.name);
-                    hasNewUnlocks = true;
-                }
+                console.log(`[UpgradeController] 自动解锁武器: ${weaponConfig.name} (${weaponId})`);
+                newlyUnlockedWeapons.push(weaponConfig.name);
+                hasNewUnlocks = true;
             }
         }
         

+ 17 - 6
assets/scripts/LevelSystem/SaveDataManager.ts

@@ -731,21 +731,24 @@ export class SaveDataManager {
     /**
      * 添加武器
      */
-    public addWeapon(weaponId: string, rarity: string = 'common'): boolean {
+    public addWeapon(weaponId: string, rarity: string = 'common', initialLevel: number = 0): boolean {
         if (!this.playerData) return false;
         
         if (!this.playerData.inventory.weapons[weaponId]) {
             this.playerData.inventory.weapons[weaponId] = {
                 weaponId: weaponId,
-                level: 1, // 初始等级为1,符合游戏设计
+                level: initialLevel, // 初始等级可配置,0表示未解锁,1表示已解锁
                 rarity: rarity,
                 obtainTime: Date.now(),
                 upgradeCount: 0,
                 isEquipped: false
             };
             
-            this.playerData.statistics.weaponsUnlocked += 1;
-            this.addReward('weapon', weaponId, 1, 'drop');
+            // 只有解锁的武器才计入统计
+            if (initialLevel > 0) {
+                this.playerData.statistics.weaponsUnlocked += 1;
+                this.addReward('weapon', weaponId, 1, 'drop');
+            }
             
             return true;
         }
@@ -826,15 +829,23 @@ export class SaveDataManager {
     public unlockWeapon(weaponId: string): boolean {
         if (!this.playerData) return false;
         
-        // 如果武器不存在,先添加(添加时已经是等级1
+        // 如果武器不存在,先添加(添加时设置为解锁状态
         if (!this.playerData.inventory.weapons[weaponId]) {
-            this.addWeapon(weaponId);
+            this.addWeapon(weaponId, 'common', 1);
             return true; // 添加武器即表示解锁成功
         }
         
         const weapon = this.playerData.inventory.weapons[weaponId];
         if (!weapon) return false;
         
+        // 如果武器存在但未解锁(level为0),则解锁它
+        if (weapon.level === 0) {
+            weapon.level = 1;
+            this.playerData.statistics.weaponsUnlocked += 1;
+            this.addReward('weapon', weaponId, 1, 'unlock');
+            return true;
+        }
+        
         // 武器已经存在且等级>=1,表示已解锁
         return weapon.level >= 1;
     }

+ 9 - 0
assets/scripts/Utils.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "e977776d-7ad4-4554-b39a-0543a5b32ac9",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

Some files were not shown because too many files changed in this diff