| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- 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);
- }
- }
|