|
|
@@ -6,6 +6,8 @@ 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;
|
|
|
|
|
|
@@ -29,6 +31,7 @@ enum EnemyState {
|
|
|
MOVING, // 移动中
|
|
|
IDLE, // 待机中(碰到墙体后停止移动但可以攻击)
|
|
|
ATTACKING, // 攻击中
|
|
|
+ STEALTH, // 隐身中(不被方块武器检测到)
|
|
|
DEAD // 死亡
|
|
|
}
|
|
|
|
|
|
@@ -81,6 +84,7 @@ export class EnemyInstance extends Component {
|
|
|
|
|
|
// 攻击属性
|
|
|
public attackInterval: number = 0; // 攻击间隔(秒),从配置文件读取
|
|
|
+ public attackRange: number = 0; // 攻击范围,从配置文件读取
|
|
|
private attackTimer: number = 0;
|
|
|
|
|
|
// 对控制器的引用
|
|
|
@@ -110,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() {
|
|
|
// 初始化敌人
|
|
|
@@ -152,6 +166,9 @@ export class EnemyInstance extends Component {
|
|
|
|
|
|
// 应用配置到敌人属性
|
|
|
this.applyEnemyConfig();
|
|
|
+
|
|
|
+ // 应用配置后,重新初始化特殊能力(确保在运行时添加组件后也能按配置触发)
|
|
|
+ this.initializeSpecialAbilities();
|
|
|
}
|
|
|
|
|
|
// 应用敌人配置到属性
|
|
|
@@ -250,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);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// 设置碰撞器
|
|
|
@@ -648,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') {
|
|
|
// 检查控制器是否处于清理状态,避免在清理过程中触发游戏事件
|
|
|
@@ -683,6 +743,11 @@ export class EnemyInstance extends Component {
|
|
|
this.updateAttack(deltaTime);
|
|
|
}
|
|
|
|
|
|
+ // 隐身为叠加效果:不改变行为状态,但仍需要计时与退出逻辑
|
|
|
+ if (this.stealthActive) {
|
|
|
+ this.updateStealth(deltaTime);
|
|
|
+ }
|
|
|
+
|
|
|
// 不再每帧播放攻击动画,避免日志刷屏
|
|
|
}
|
|
|
|
|
|
@@ -913,6 +978,11 @@ export class EnemyInstance extends Component {
|
|
|
|
|
|
// 重置攻击计时器
|
|
|
this.attackTimer = this.attackInterval;
|
|
|
+ } else {
|
|
|
+ // 攻击冷却期间播放待机动画(只有在不播放攻击动画时)
|
|
|
+ if (!this.isPlayingAttackAnimation) {
|
|
|
+ this.playIdleAnimationSafe();
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -979,7 +1049,10 @@ export class EnemyInstance extends Component {
|
|
|
|
|
|
// 在动画播放到90%时发射抛掷物,让抛掷物发射与动画同步
|
|
|
const projectileFireTime = animationDuration * 0.9;
|
|
|
+
|
|
|
+ // 在动画播放到90%时发射抛掷物
|
|
|
this.scheduleOnce(() => {
|
|
|
+ // 发射抛掷物
|
|
|
this.fireProjectile();
|
|
|
}, projectileFireTime);
|
|
|
|
|
|
@@ -1401,15 +1474,18 @@ export class EnemyInstance extends Component {
|
|
|
console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 进入${attackTypeStr}范围,距离墙体 ${wallNode.name}: ${distance.toFixed(2)},攻击范围: ${attackRange}`);
|
|
|
|
|
|
if (isRangedAttack) {
|
|
|
- // 远程攻击:切换到攻击状态,继续移动并发射抛掷物
|
|
|
- this.state = EnemyState.ATTACKING;
|
|
|
+ // 远程攻击:切换到待机状态,停止移动并轮流播放攻击和待机动画
|
|
|
+ this.state = EnemyState.IDLE;
|
|
|
this.attackTimer = 0; // 立即开始攻击
|
|
|
this.collidedWall = wallNode; // 记录目标墙体
|
|
|
|
|
|
- // 远程攻击时继续播放walk动画,不停止移动
|
|
|
- this.playWalkAnimation();
|
|
|
+ // 停止移动
|
|
|
+ this.stopRigidBodyMovement();
|
|
|
+
|
|
|
+ // 切换到待机动画
|
|
|
+ this.playIdleAnimation();
|
|
|
|
|
|
- console.log(`[EnemyInstance] 远程敌人 ${this.getEnemyName()} 进入攻击状态,继续移动并播放walk动画`);
|
|
|
+ console.log(`[EnemyInstance] 远程敌人 ${this.getEnemyName()} 进入待机状态,停止移动并准备攻击`);
|
|
|
} else {
|
|
|
// 近战攻击:切换到待机状态,停止移动
|
|
|
this.state = EnemyState.IDLE;
|
|
|
@@ -1517,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();
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|