| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722 |
- import { _decorator, Component, Node, ProgressBar, Label, Vec3, find, UITransform, Collider2D, Contact2DType, IPhysics2DContact, instantiate, resources, Prefab, JsonAsset, RigidBody2D, ERigidBody2DType, BoxCollider2D, CircleCollider2D, Vec2, Color, UIOpacity, tween } from 'cc';
- import { JsonConfigLoader } from '../Core/JsonConfigLoader';
- import { sp } from 'cc';
- 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 { EnemyAttackStateManager } from './EnemyAttackStateManager';
- import EventBus, { GameEvents } from '../Core/EventBus';
- const { ccclass, property } = _decorator;
- // 前向声明EnemyController接口,避免循环引用
- interface EnemyControllerType {
- gameBounds: {
- left: number;
- right: number;
- top: number;
- bottom: number;
- };
- topFenceNode: Node;
- bottomFenceNode: Node;
- damageWall: (damage: number) => void;
- getComponent: (componentType: any) => any;
- }
- // 敌人状态枚举
- enum EnemyState {
- DRIFTING, // 漂移中(从生成位置移动到线上)
- MOVING, // 移动中
- IDLE, // 待机中(碰到墙体后停止移动但可以攻击)
- ATTACKING, // 攻击中
- STEALTH, // 隐身中(不被方块武器检测到)
- DEAD // 死亡
- }
- // 单个敌人实例的组件
- @ccclass('EnemyInstance')
- export class EnemyInstance extends Component {
- // 敌人属性(从配置文件读取)
- public health: number = 0;
- public maxHealth: number = 0;
- public speed: number = 0;
- public attackPower: number = 0;
-
- // 敌人配置ID
- public enemyId: string = '';
-
- // 敌人配置数据
- private enemyConfig: any = null;
-
- // 敌人配置数据库
- private static enemyDatabase: any = null;
-
- // === 新增属性 ===
- /** 是否从上方生成 */
- public spawnFromTop: boolean = true;
- /** 目标 Fence 节点(TopFence / BottomFence) */
- public targetFence: Node | null = null;
- /** 目标位置(墙体上的随机点) */
- public targetPosition: Vec3 | null = null;
- /** 漂移目标位置(线上的位置) */
- public driftTargetPosition: Vec3 | null = null;
-
- // 移动相关属性
- public movingDirection: number = 1; // 1: 向右, -1: 向左
- public targetY: number = 0; // 目标Y位置
- public changeDirectionTime: number = 0; // 下次改变方向的时间
-
- // 移动配置属性(从配置文件读取)
- public movementPattern: string = 'direct'; // 移动模式:direct, patrol等
- public moveType: string = 'straight'; // 移动类型:straight, sway等
- public patrolRange: number = 100; // 巡逻范围
- public rotationSpeed: number = 90; // 旋转速度
- public speedVariation: number = 0; // 速度变化
- public swingAmplitude: number = 0; // 摆动幅度
- public swingFrequency: number = 1; // 摆动频率
-
- // 摆动移动相关属性
- private swayTime: number = 0; // 摆动时间累计
- private basePosition: Vec3 = new Vec3(); // 基础位置(摆动的中心点)
- private swayOffset: Vec3 = new Vec3(); // 当前摆动偏移
-
- // 攻击属性
- public attackInterval: number = 0; // 攻击间隔(秒),从配置文件读取
- public attackRange: number = 0; // 攻击范围,从配置文件读取
- private attackTimer: number = 0;
-
- // 对控制器的引用
- public controller: EnemyControllerType = null;
-
- // 敌人当前状态
- private state: EnemyState = EnemyState.DRIFTING;
-
- // 游戏区域中心
- private gameAreaCenter: Vec3 = new Vec3();
-
- // 碰撞的墙体
- private collidedWall: Node = null;
- // 骨骼动画组件
- private skeleton: sp.Skeleton | null = null;
-
- // 血条动画组件
- private hpBarAnimation: HPBarAnimation | null = null;
-
- // 暂停状态标记
- private isPaused: boolean = false;
- // 当前播放的动画名称,用于避免重复播放
- private currentAnimationName: string = '';
- // 攻击状态下的动画管理
- 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() {
- // 初始化敌人
- this.initializeEnemy();
- }
-
- // 静态方法:加载敌人配置数据库
- public static async loadEnemyDatabase(): Promise<void> {
- if (EnemyInstance.enemyDatabase) return;
-
- try {
- EnemyInstance.enemyDatabase = await JsonConfigLoader.getInstance().loadConfig('enemies');
- if (!EnemyInstance.enemyDatabase) {
- throw new Error('敌人配置文件内容为空');
- }
- } catch (error) {
- console.error('[EnemyInstance] 加载敌人配置失败:', error);
- throw error;
- }
- }
-
- // 设置敌人配置
- public setEnemyConfig(enemyId: string): void {
- this.enemyId = enemyId;
-
- if (!EnemyInstance.enemyDatabase) {
- console.error('[EnemyInstance] 敌人配置数据库未加载');
- return;
- }
-
- // 从数据库中查找敌人配置
- // 修复:enemies.json是直接的数组结构,不需要.enemies包装
- const enemies = EnemyInstance.enemyDatabase;
- this.enemyConfig = enemies.find((enemy: any) => enemy.id === enemyId);
-
- if (!this.enemyConfig) {
- console.error(`[EnemyInstance] 未找到敌人配置: ${enemyId}`);
- return;
- }
-
- // 应用配置到敌人属性
- this.applyEnemyConfig();
- // 应用配置后,重新初始化特殊能力(确保在运行时添加组件后也能按配置触发)
- this.initializeSpecialAbilities();
- }
-
- // 应用敌人配置到属性
- private applyEnemyConfig(): void {
- if (!this.enemyConfig) return;
-
- // 从stats节点读取基础属性
- const stats = this.enemyConfig.stats || {};
- this.health = stats.health || 30;
- this.maxHealth = stats.maxHealth || this.health;
-
- // 从movement节点读取移动配置
- const movement = this.enemyConfig.movement || {};
- this.speed = movement.speed || 50;
-
- // 读取移动模式配置
- this.movementPattern = movement.pattern || 'direct';
- this.moveType = movement.moveType || 'straight';
- this.patrolRange = movement.patrolRange || 100;
- this.rotationSpeed = movement.rotationSpeed || 90;
- this.speedVariation = movement.speedVariation || 0;
-
- // 读取摆动配置
- this.swingAmplitude = movement.swingAmplitude || 0;
- this.swingFrequency = movement.swingFrequency || 1;
-
- // 从combat节点读取攻击力
- const combat = this.enemyConfig.combat || {};
- this.attackPower = combat.attackDamage || 10;
-
- // 设置攻击间隔
- this.attackInterval = combat.attackCooldown || 2.0;
-
- console.log(`[EnemyInstance] 应用配置 ${this.enemyId}: 移动模式=${this.movementPattern}, 移动类型=${this.moveType}, 速度=${this.speed}, 摆动幅度=${this.swingAmplitude}`);
- }
-
- // 获取敌人配置信息
- public getEnemyConfig(): any {
- return this.enemyConfig;
- }
-
- // 获取敌人名称
- public getEnemyName(): string {
- return this.enemyConfig?.name || '未知敌人';
- }
-
- // 获取敌人类型
- public getEnemyType(): string {
- return this.enemyConfig?.type || 'basic';
- }
-
- // 获取敌人稀有度
- public getEnemyRarity(): string {
- return this.enemyConfig?.rarity || 'common';
- }
-
- // 获取金币奖励
- public getGoldReward(): number {
- return this.enemyConfig?.goldReward || 1;
- }
-
- // 获取掉落金币数量
- public getDropCoins(): number {
- const stats = this.enemyConfig?.stats || {};
- return stats.dropCoins || 1;
- }
-
- // 初始化敌人
- private initializeEnemy() {
- // 确保血量正确设置
- if (this.maxHealth > 0) {
- this.health = this.maxHealth;
- }
-
- this.state = EnemyState.DRIFTING; // 从漂移状态开始
- // 只有在攻击间隔未设置时才使用默认值
- if (this.attackInterval <= 0) {
- this.attackInterval = 2.0; // 默认攻击间隔
- }
- this.attackTimer = 0;
-
- // 初始化血条动画组件
- this.initializeHPBarAnimation();
-
- // 初始化骨骼动画组件 - 从EnemySprite子节点获取
- const enemySprite = this.node.getChildByName('EnemySprite');
- if (enemySprite) {
- this.skeleton = enemySprite.getComponent(sp.Skeleton);
- } else {
- console.error('[EnemyInstance] 未找到EnemySprite子节点,无法获取骨骼动画组件');
- }
- this.playWalkAnimation();
-
- // 计算游戏区域中心
- this.calculateGameAreaCenter();
-
- // 初始化碰撞检测
- 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);
- }
- }
- }
-
- // 设置碰撞器
- setupCollider() {
- // 从EnemySprite子节点获取碰撞器组件
- const enemySprite = this.node.getChildByName('EnemySprite');
- if (!enemySprite) {
- console.error('[EnemyInstance] 未找到EnemySprite子节点,无法设置碰撞器组件');
- return;
- }
-
- // 检查EnemySprite节点是否有碰撞器
- let collider = enemySprite.getComponent(Collider2D);
- if (!collider) {
- console.warn(`[EnemyInstance] 敌人节点 ${this.node.name} 的EnemySprite子节点没有碰撞器组件`);
- return;
- }
-
- // 确保有RigidBody2D组件,这对于碰撞检测是必需的
- let rigidBody = enemySprite.getComponent(RigidBody2D);
- if (!rigidBody) {
- console.log(`[EnemyInstance] 为敌人EnemySprite节点添加RigidBody2D组件`);
- rigidBody = enemySprite.addComponent(RigidBody2D);
- }
-
- // 设置刚体属性
- if (rigidBody) {
- rigidBody.type = ERigidBody2DType.Dynamic; // 动态刚体
- rigidBody.enabledContactListener = true; // 启用碰撞监听
- rigidBody.gravityScale = 0; // 不受重力影响
- rigidBody.linearDamping = 0; // 无线性阻尼
- rigidBody.angularDamping = 0; // 无角阻尼
- rigidBody.allowSleep = false; // 不允许休眠
- rigidBody.fixedRotation = true; // 固定旋转
- }
-
- // 根据ContentSize自适应碰撞体大小
- this.adaptColliderToContentSize(collider);
-
- // 设置碰撞事件监听
- collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
-
- console.log(`[EnemyInstance] 敌人 ${this.node.name} 碰撞器设置完成,碰撞器启用: ${collider.enabled}, 刚体启用: ${rigidBody?.enabled}`);
- }
-
- /**
- * 根据ContentSize自适应碰撞体大小
- */
- private adaptColliderToContentSize(collider: Collider2D): void {
- // 从EnemySprite子节点获取UITransform组件
- const enemySprite = this.node.getChildByName('EnemySprite');
- if (!enemySprite) {
- console.error('[EnemyInstance] 未找到EnemySprite子节点,无法获取UITransform组件');
- return;
- }
-
- const uiTransform = enemySprite.getComponent(UITransform);
- if (!uiTransform) {
- console.warn(`[EnemyInstance] EnemySprite节点没有UITransform组件,无法自适应碰撞体大小`);
- return;
- }
-
- // 尝试获取骨骼动画组件来获取更准确的尺寸
- const skeleton = enemySprite.getComponent(sp.Skeleton);
- let actualWidth = uiTransform.contentSize.width;
- let actualHeight = uiTransform.contentSize.height;
-
- if (skeleton && skeleton.skeletonData) {
- // 如果有骨骼动画,尝试从骨骼数据获取尺寸信息
- try {
- // 使用骨骼数据的默认尺寸,或者根据动画缩放计算
- const skeletonData = skeleton.skeletonData;
- if (skeletonData) {
- // 获取骨骼的缩放比例
- const scaleX = Math.abs(skeleton.node.scale.x);
- const scaleY = Math.abs(skeleton.node.scale.y);
-
- // 使用合理的默认尺寸并应用缩放
- actualWidth = Math.max(60 * scaleX, uiTransform.contentSize.width * 0.8);
- actualHeight = Math.max(80 * scaleY, uiTransform.contentSize.height * 0.8);
-
- console.log(`[EnemyInstance] 使用骨骼动画尺寸 (scale: ${scaleX.toFixed(2)}, ${scaleY.toFixed(2)}): ${actualWidth.toFixed(2)} x ${actualHeight.toFixed(2)}`);
- } else {
- // 如果无法获取骨骼数据,使用默认尺寸
- actualWidth = Math.max(60, uiTransform.contentSize.width * 0.8);
- actualHeight = Math.max(80, uiTransform.contentSize.height * 0.8);
- console.log(`[EnemyInstance] 使用默认骨骼尺寸: ${actualWidth.toFixed(2)} x ${actualHeight.toFixed(2)}`);
- }
- } catch (error) {
- console.warn(`[EnemyInstance] 获取骨骼信息失败,使用默认尺寸:`, error);
- actualWidth = Math.max(60, uiTransform.contentSize.width * 0.8);
- actualHeight = Math.max(80, uiTransform.contentSize.height * 0.8);
- }
- }
-
- console.log(`[EnemyInstance] 敌人 ${this.node.name} 实际尺寸: ${actualWidth.toFixed(2)} x ${actualHeight.toFixed(2)}`);
-
- // 根据碰撞器类型设置大小
- if (collider instanceof BoxCollider2D) {
- // 方形碰撞器:使用实际尺寸,稍微缩小以避免过于敏感的碰撞
- const boxCollider = collider as BoxCollider2D;
- boxCollider.size.width = actualWidth * 0.9; // 缩小10%
- boxCollider.size.height = actualHeight * 0.9; // 缩小10%
-
- // 调整偏移量,使碰撞器居中对齐到EnemySprite
- const anchorPoint = uiTransform.anchorPoint;
- // 对于骨骼动画,通常锚点在底部中心(0.5, 0),需要向上偏移
- boxCollider.offset.x = 0; // X轴居中
- boxCollider.offset.y = actualHeight * (0.5 - anchorPoint.y) * 0.9; // Y轴根据锚点调整,并匹配缩放后的尺寸
-
- console.log(`[EnemyInstance] BoxCollider2D 已自适应: size(${boxCollider.size.width.toFixed(2)}, ${boxCollider.size.height.toFixed(2)}), offset(${boxCollider.offset.x.toFixed(2)}, ${boxCollider.offset.y.toFixed(2)}), anchor(${anchorPoint.x.toFixed(2)}, ${anchorPoint.y.toFixed(2)})`);
- } else if (collider instanceof CircleCollider2D) {
- // 圆形碰撞器:使用实际尺寸的较小值作为直径,稍微缩小
- const circleCollider = collider as CircleCollider2D;
- const radius = Math.min(actualWidth, actualHeight) / 2 * 0.9; // 缩小10%
- circleCollider.radius = radius;
-
- // 调整偏移量,使碰撞器居中对齐到EnemySprite
- const anchorPoint = uiTransform.anchorPoint;
- // 对于骨骼动画,通常锚点在底部中心(0.5, 0),需要向上偏移
- circleCollider.offset.x = 0; // X轴居中
- circleCollider.offset.y = actualHeight * (0.5 - anchorPoint.y) * 0.9; // Y轴根据锚点调整,并匹配缩放后的尺寸
-
- console.log(`[EnemyInstance] CircleCollider2D 已自适应: radius(${radius.toFixed(2)}), offset(${circleCollider.offset.x.toFixed(2)}, ${circleCollider.offset.y.toFixed(2)}), anchor(${anchorPoint.x.toFixed(2)}, ${anchorPoint.y.toFixed(2)})`);
- } else {
- console.warn(`[EnemyInstance] 不支持的碰撞器类型: ${collider.constructor.name}`);
- }
-
- // 碰撞体调整完成后,同步更新血条位置(统一为HPBarAnimation)
- if (this.hpBarAnimation) {
- this.hpBarAnimation.updateBarPosition(true);
- }
- }
- // 已统一到 HPBarAnimation.updateBarPosition,移除本地冗余定位实现
- // 碰撞开始事件
- onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
- const nodeName = otherCollider.node.name;
-
- console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 碰撞检测 - 碰撞对象: ${nodeName}, 当前状态: ${EnemyState[this.state]}`);
-
- // 只有在移动状态或攻击状态下才能响应墙体碰撞,避免重复触发
- if (this.state !== EnemyState.MOVING && this.state !== EnemyState.ATTACKING) {
- return;
- }
-
- // 检查是否碰到墙体(更严格的墙体识别)
- const isWall = this.isWallNode(otherCollider.node);
- if (isWall) {
- console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 碰撞到墙体 ${nodeName},切换到待机状态`);
-
- this.state = EnemyState.IDLE;
- this.attackTimer = 0; // 立即开始攻击
- this.collidedWall = otherCollider.node; // 记录碰撞的墙体
-
- // 停止移动
- this.stopRigidBodyMovement();
-
- // 切换到待机动画
- this.playIdleAnimation();
- }
- }
-
- // 检查节点是否为墙体
- 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;
- }
-
- // 获取节点路径
- getNodePath(node: Node): string {
- let path = node.name;
- let current = node;
-
- while (current.parent) {
- current = current.parent;
- path = current.name + '/' + path;
- }
-
- return path;
- }
- // 计算游戏区域中心
- private calculateGameAreaCenter() {
- const gameArea = find('Canvas/GameLevelUI/GameArea');
- if (gameArea) {
- this.gameAreaCenter = gameArea.worldPosition;
- }
- }
- /**
- * 初始化血条动画组件
- */
- private initializeHPBarAnimation() {
- // 检查是否已经存在血条动画组件,避免重复添加
- if (this.hpBarAnimation) {
- console.log(`[EnemyInstance] 血条动画组件已存在,跳过初始化`);
- return;
- }
-
- const hpBar = this.node.getChildByName('HPBar');
- if (hpBar) {
- // 查找红色和黄色血条节点
- const redBarNode = hpBar.getChildByName('RedBar');
- const yellowBarNode = hpBar.getChildByName('YellowBar');
-
- if (redBarNode && yellowBarNode) {
- // 检查节点是否已经有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;
- this.hpBarAnimation.yellowBarNode = yellowBarNode;
- this.hpBarAnimation.hpBarRootNode = hpBar; // 设置HPBar根节点
- console.log(`[EnemyInstance] 血条动画组件已初始化`);
- }
- } else {
- console.warn(`[EnemyInstance] HPBar下未找到RedBar或YellowBar节点,RedBar: ${!!redBarNode}, YellowBar: ${!!yellowBarNode}`);
- }
- } else {
- console.warn(`[EnemyInstance] 未找到HPBar节点,无法初始化血条动画`);
- }
- }
-
- // 更新血量显示
- updateHealthDisplay(showBar: boolean = false) {
- // 确保血量值在有效范围内
- this.health = Math.max(0, Math.min(this.maxHealth, this.health));
-
- const healthProgress = this.maxHealth > 0 ? this.health / this.maxHealth : 0;
-
- console.log(`[EnemyInstance] 更新血量显示: ${this.health}/${this.maxHealth} (${(healthProgress * 100).toFixed(1)}%)`);
-
- // 使用血条动画组件更新血条
- if (this.hpBarAnimation) {
- this.hpBarAnimation.updateProgress(healthProgress, showBar);
- } else {
- // 备用方案:直接更新血条
- const hpBar = this.node.getChildByName('HPBar');
- if (hpBar) {
- const progressBar = hpBar.getComponent(ProgressBar);
- if (progressBar) {
- progressBar.progress = healthProgress;
- }
- // 根据showBar参数控制血条显示
- hpBar.active = showBar;
- }
- }
-
- // 更新血量数字
- const hpLabel = this.node.getChildByName('HPLabel');
- if (hpLabel) {
- const label = hpLabel.getComponent(Label);
- if (label) {
- // 显示整数血量值
- label.string = Math.ceil(this.health).toString();
- }
- }
- }
- // 受到伤害
- takeDamage(damage: number, isCritical: boolean = false) {
- // 如果已经死亡,不再处理伤害
- if (this.state === EnemyState.DEAD) {
- return;
- }
-
- // 确保伤害值为正数
- if (damage <= 0) {
- console.warn(`[EnemyInstance] 无效的伤害值: ${damage}`);
- return;
- }
-
- // 应用防御力减少伤害
- const enemyComponent = this.getComponent(EnemyComponent);
- const defense = enemyComponent ? enemyComponent.getDefense() : 0;
- let finalDamage = Math.max(1, damage - defense); // 至少造成1点伤害
-
- // 检查格挡
- if (enemyComponent && enemyComponent.canBlock() && this.tryBlock()) {
- finalDamage *= (1 - enemyComponent.getBlockDamageReduction());
- this.showBlockEffect();
- }
-
- // 计算新的血量,确保不会低于0
- const oldHealth = this.health;
- const newHealth = Math.max(0, this.health - finalDamage);
- const actualHealthLoss = oldHealth - newHealth; // 实际血量损失
- this.health = newHealth;
-
- // 检查是否触发狂暴
- this.checkRageTrigger();
-
- // 日志显示武器的真实伤害值,而不是血量差值
- console.log(`[EnemyInstance] 敌人受到伤害: ${damage} (武器伤害), 防御后伤害: ${finalDamage}, 实际血量损失: ${actualHealthLoss}, 剩余血量: ${this.health}/${this.maxHealth}`);
-
- // 受击音效已移除
-
- // 显示伤害数字动画(在敌人头顶)- 显示防御后的实际伤害
- // 优先使用EnemyController节点上的DamageNumberAni组件实例
- if (this.controller) {
- const damageAni = this.controller.getComponent(DamageNumberAni);
- if (damageAni) {
- damageAni.showDamageNumber(finalDamage, this.node.worldPosition, isCritical);
- } else {
- // 如果没有找到组件实例,使用静态方法作为备用
- DamageNumberAni.showDamageNumber(finalDamage, this.node.worldPosition, isCritical);
- }
- } else {
- // 如果没有controller引用,使用静态方法
- DamageNumberAni.showDamageNumber(finalDamage, this.node.worldPosition, isCritical);
- }
-
- // 更新血量显示和动画,受伤时显示血条
- this.updateHealthDisplay(true);
-
- // 如果血量低于等于0,销毁敌人
- if (this.health <= 0) {
- console.log(`[EnemyInstance] 敌人死亡,开始销毁流程`);
- this.state = EnemyState.DEAD;
- this.spawnCoin();
- // 进入死亡流程,禁用碰撞避免重复命中
- const enemySprite = this.node.getChildByName('EnemySprite');
- if (enemySprite) {
- const col = enemySprite.getComponent(Collider2D);
- if (col) col.enabled = false;
- }
- this.playDeathAnimationAndDestroy();
- }
- }
- 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') {
- // 检查控制器是否处于清理状态,避免在清理过程中触发游戏事件
- const isClearing = (this.controller as any).isClearing;
- if (isClearing) {
- console.log(`[EnemyInstance] 控制器处于清理状态,跳过死亡通知`);
- return;
- }
- console.log(`[EnemyInstance] 调用 notifyEnemyDead`);
- (this.controller as any).notifyEnemyDead(this.node);
- } else {
- console.warn(`[EnemyInstance] 无法调用 notifyEnemyDead: controller=${!!this.controller}`);
- }
- }
- update(deltaTime: number) {
- // 如果敌人被暂停,则不执行任何更新逻辑
- if (this.isPaused) {
- return;
- }
-
- if (this.state === EnemyState.DRIFTING) {
- this.updateDrifting(deltaTime);
- // 在漂移状态也检查墙体碰撞
- this.checkWallCollisionByDistance();
- } else if (this.state === EnemyState.MOVING) {
- this.updateMovement(deltaTime);
- // 在移动状态检查墙体碰撞
- this.checkWallCollisionByDistance();
- } else if (this.state === EnemyState.IDLE) {
- this.updateIdle(deltaTime);
- } else if (this.state === EnemyState.ATTACKING) {
- this.updateAttack(deltaTime);
- }
- // 隐身为叠加效果:不改变行为状态,但仍需要计时与退出逻辑
- if (this.stealthActive) {
- this.updateStealth(deltaTime);
- }
- // 不再每帧播放攻击动画,避免日志刷屏
- }
-
- // 更新漂移逻辑(从生成位置移动到线上)
- private updateDrifting(deltaTime: number) {
- if (!this.driftTargetPosition) {
- // 如果没有漂移目标位置,直接切换到移动状态
- this.state = EnemyState.MOVING;
- return;
- }
-
- const currentWorldPos = this.node.worldPosition.clone();
- const targetWorldPos = this.driftTargetPosition.clone();
-
- const dir = targetWorldPos.subtract(currentWorldPos);
- const distanceToTarget = dir.length();
-
- // 如果距离目标很近,切换到移动状态
- const stopDistance = 5; // 减小停止距离以提高精度
- if (distanceToTarget <= stopDistance) {
- this.state = EnemyState.MOVING;
- // 确保刚体速度为零
- this.stopRigidBodyMovement();
- return;
- }
-
- if (distanceToTarget === 0) return;
- dir.normalize();
-
- // 使用代码控制移动,不依赖物理系统
- const driftSpeed = Math.min(this.speed * 8, 500); // 限制最大漂移速度为5500
- const moveDistance = driftSpeed * deltaTime;
- const actualMoveDistance = Math.min(moveDistance, distanceToTarget);
- const newWorldPos = currentWorldPos.add(dir.multiplyScalar(actualMoveDistance));
-
- // 直接设置世界坐标
- this.node.setWorldPosition(newWorldPos);
-
- // 确保刚体速度为零,避免物理系统干扰
- this.stopRigidBodyMovement();
- }
- // 更新移动逻辑
- private updateMovement(deltaTime: number) {
- // 根据配置的移动模式执行不同的移动逻辑
- switch (this.movementPattern) {
- case 'direct':
- this.updateDirectMovement(deltaTime);
- break;
- case 'patrol':
- this.updatePatrolMovement(deltaTime);
- break;
- default:
- this.updateDirectMovement(deltaTime);
- break;
- }
- }
-
- // 直线移动模式
- private updateDirectMovement(deltaTime: number) {
- this.moveTowardsTarget(deltaTime);
- }
-
- // 巡逻移动模式
- private updatePatrolMovement(deltaTime: number) {
- // 更新方向变化计时器
- this.changeDirectionTime += deltaTime;
- console.log(`[EnemyInstance] 摆动 调用,changeDirectionTime=${this.changeDirectionTime}`);
- // 根据巡逻范围决定方向变化
- const enemySprite = this.node.getChildByName('EnemySprite');
- if (enemySprite && this.controller) {
- const currentPos = this.node.worldPosition.clone();
- const bounds = this.controller.gameBounds;
-
- // 检查是否到达巡逻边界
- const leftBound = bounds.left + this.patrolRange;
- const rightBound = bounds.right - this.patrolRange;
-
- if (currentPos.x <= leftBound && this.movingDirection < 0) {
- this.movingDirection = 1; // 向右
- this.changeDirectionTime = 0;
- } else if (currentPos.x >= rightBound && this.movingDirection > 0) {
- this.movingDirection = -1; // 向左
- this.changeDirectionTime = 0;
- }
-
- // 随机改变Y目标位置
- if (this.changeDirectionTime >= 3 + Math.random() * 2) {
- const centerY = (bounds.top + bounds.bottom) / 2;
- const range = (bounds.top - bounds.bottom) * 0.4;
- this.targetY = centerY + (Math.random() - 0.5) * range;
- this.changeDirectionTime = 0;
- }
- }
-
- this.moveTowardsTarget(deltaTime);
- }
- // 向目标移动
- private moveTowardsTarget(deltaTime: number) {
- // 获取当前速度(考虑狂暴加成和速度变化)
- const enemyComponent = this.getComponent(EnemyComponent);
- let currentSpeed = enemyComponent ? enemyComponent.getCurrentSpeed() : this.speed;
-
- // 应用速度变化配置,限制变化幅度防止过大跳跃
- if (this.speedVariation > 0) {
- const maxVariation = Math.min(this.speedVariation, currentSpeed * 0.3); // 限制变化不超过当前速度的30%
- const variation = (Math.random() - 0.5) * 2 * maxVariation;
- currentSpeed = Math.max(currentSpeed + variation, currentSpeed * 0.5); // 确保速度不会过低
- }
-
- // 统一使用主节点的世界坐标,与updateDrifting保持一致
- const currentPos = this.node.worldPosition.clone();
- let dir = new Vec3();
-
- // 水平移动方向
- dir.x = this.movingDirection;
-
- // 垂直移动方向 - 向目标Y位置移动
- const yDiff = this.targetY - currentPos.y;
- if (Math.abs(yDiff) > 10) { // 如果距离目标Y位置超过10像素
- dir.y = yDiff > 0 ? 0.5 : -0.5; // 缓慢向目标Y移动
- } else {
- dir.y = 0;
- }
-
- // 如果有具体的目标位置(墙体),优先移动到墙体
- if (this.targetPosition) {
- const targetDir = new Vec3();
- Vec3.subtract(targetDir, this.targetPosition.clone(), currentPos);
- if (targetDir.length() > 20) { // 距离墙体还有一定距离时
- targetDir.normalize();
- dir.x = targetDir.x * 0.7; // 70%朝向墙体
- dir.y = targetDir.y * 0.7;
- }
- } else if (this.targetFence) {
- const fencePos = this.targetFence.worldPosition.clone();
- const targetDir = new Vec3();
- Vec3.subtract(targetDir, fencePos, currentPos);
- if (targetDir.length() > 20) {
- targetDir.normalize();
- dir.x = targetDir.x * 0.7;
- dir.y = targetDir.y * 0.7;
- }
- }
-
- // 归一化方向向量
- if (dir.length() > 0) {
- dir.normalize();
- }
-
- // 应用摆动效果(根据配置)
- if (this.moveType === 'sway' && this.swingAmplitude > 0) {
- this.swayTime += deltaTime;
- const swayAmount = Math.sin(this.swayTime * this.swingFrequency) * this.swingAmplitude;
-
- // 计算垂直于移动方向的摆动
- const swayDir = new Vec3(-dir.y, dir.x, 0);
- swayDir.multiplyScalar(swayAmount / 100); // 将摆动幅度转换为合适的比例
- dir.add(swayDir);
- }
-
- // 限制单帧移动距离,防止突然跳跃
- const moveDistance = currentSpeed * deltaTime;
- const maxMoveDistance = Math.min(15, currentSpeed * 0.1); // 限制单帧最大移动距离
- const actualMoveDistance = Math.min(moveDistance, maxMoveDistance);
-
- const moveVector = dir.multiplyScalar(actualMoveDistance);
- const newPos = new Vec3();
- Vec3.add(newPos, currentPos, moveVector);
-
- // 统一使用主节点的setWorldPosition,与updateDrifting保持一致
- this.node.setWorldPosition(newPos);
-
- // 确保刚体速度为零,避免物理系统干扰
- this.stopRigidBodyMovement();
- }
- // 更新攻击逻辑
- private updateAttack(deltaTime: number) {
- // 获取敌人的攻击范围配置
- const enemyComponent = this.getComponent(EnemyComponent);
- const attackRange = enemyComponent ? enemyComponent.getAttackRange() : 30;
-
- // 无论近战还是远程攻击,在攻击状态下都停止移动
- const enemySprite = this.node.getChildByName('EnemySprite');
- if (enemySprite) {
- const rigidBody = enemySprite.getComponent(RigidBody2D);
- if (rigidBody) {
- // 停止物理移动
- rigidBody.linearVelocity = new Vec2(0, 0);
- }
- }
-
- this.attackTimer -= deltaTime;
-
- if (this.attackTimer <= 0) {
- // 执行攻击
- this.performAttack();
-
- // 重置攻击计时器
- this.attackTimer = this.attackInterval;
- } else {
- // 攻击冷却期间播放待机动画(只有在不播放攻击动画时)
- if (!this.isPlayingAttackAnimation) {
- this.playIdleAnimationSafe();
- }
- }
- }
- // 待机状态更新(停止移动但可以攻击)
- private updateIdle(deltaTime: number) {
- // 在待机状态下停止移动
- const enemySprite = this.node.getChildByName('EnemySprite');
- if (enemySprite) {
- const rigidBody = enemySprite.getComponent(RigidBody2D);
- if (rigidBody) {
- // 停止物理移动
- rigidBody.linearVelocity = new Vec2(0, 0);
- }
- }
-
- this.attackTimer -= deltaTime;
-
- if (this.attackTimer <= 0) {
- // 执行攻击
- this.performAttack();
-
- // 重置攻击计时器
- this.attackTimer = this.attackInterval;
- } else {
- // 攻击冷却期间播放待机动画(只有在不播放攻击动画时)
- if (!this.isPlayingAttackAnimation) {
- this.playIdleAnimationSafe();
- }
- }
- }
- // 执行攻击
- private performAttack() {
- if (!this.controller) {
- return;
- }
-
- const enemyComponent = this.getComponent(EnemyComponent);
- const attackType = enemyComponent ? enemyComponent.getAttackType() : 'melee';
-
- // 播放攻击音效
- EnemyAudio.playAttackSound(this.enemyConfig);
-
- // 根据攻击类型执行不同的攻击逻辑
- if (attackType === 'ranged' || attackType === 'projectile' || attackType.includes('projectile')) {
- console.log(`[EnemyInstance] 执行远程攻击,攻击类型: ${attackType}`);
- this.performRangedAttack();
- } else {
- // 近战攻击:播放攻击动画,动画结束后造成伤害
- console.log(`[EnemyInstance] 执行近战攻击,攻击类型: ${attackType}`);
- this.playAttackAnimationWithDamage();
- }
- }
- // 执行远程攻击(投掷)
- private performRangedAttack() {
- 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.currentAnimationName = attackName;
- this.isPlayingAttackAnimation = true;
- console.log(`[EnemyInstance] 播放远程攻击动画: ${attackName}`);
-
- // 动画切换后延迟更新血条位置,确保动画尺寸已生效
- this.scheduleOnce(() => {
- this.hpBarAnimation?.updateBarPosition(true);
- }, 0.1);
-
- // 获取动画时长并在动画即将结束时发射抛掷物
- const animationDuration = animation.duration;
- console.log(`[EnemyInstance] 远程攻击动画时长: ${animationDuration}秒`);
-
- // 在动画播放到90%时发射抛掷物,让抛掷物发射与动画同步
- const projectileFireTime = animationDuration * 0.9;
-
- // 在动画播放到90%时发射抛掷物
- this.scheduleOnce(() => {
- // 发射抛掷物
- this.fireProjectile();
- }, projectileFireTime);
-
- // 动画完成后根据当前状态切换动画
- this.scheduleOnce(() => {
- this.isPlayingAttackAnimation = false;
- if (this.state === EnemyState.IDLE || this.state === EnemyState.ATTACKING) {
- // 如果是待机状态或攻击状态,切换回待机动画
- this.playIdleAnimation();
- } else {
- // 其他状态切换回行走动画
- 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}, 伤害: ${attackDamage}`);
-
- // 获取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引用 - 远程敌人攻击无效`);
- }
- }
- // 播放行走动画
- private playWalkAnimation() {
- if (!this.skeleton) return;
- const enemyComp = this.getComponent('EnemyComponent') as any;
- const anims = enemyComp?.getAnimations ? enemyComp.getAnimations() : {};
- const walkName = anims.walk ?? 'walk';
- const idleName = anims.idle ?? 'idle';
- let animationToPlay = '';
- if (this.skeleton.findAnimation(walkName)) {
- animationToPlay = walkName;
- } else if (this.skeleton.findAnimation(idleName)) {
- animationToPlay = idleName;
- }
- if (animationToPlay && this.currentAnimationName !== animationToPlay) {
- this.skeleton.setAnimation(0, animationToPlay, true);
- this.currentAnimationName = animationToPlay;
- this.isPlayingAttackAnimation = false;
- console.log(`[EnemyInstance] 播放行走动画: ${animationToPlay}`);
- }
-
- // 动画切换后延迟更新血条位置,确保动画尺寸已生效
- this.scheduleOnce(() => {
- this.hpBarAnimation?.updateBarPosition(true);
- }, 0.1);
- }
- // 播放攻击动画
- private playAttackAnimation() {
- if (!this.skeleton) return;
- const enemyComp2 = this.getComponent('EnemyComponent') as any;
- const anims2 = enemyComp2?.getAnimations ? enemyComp2.getAnimations() : {};
- const attackName = anims2.attack ?? 'attack';
- if (this.skeleton.findAnimation(attackName) && this.currentAnimationName !== attackName) {
- this.skeleton.setAnimation(0, attackName, true);
- this.currentAnimationName = attackName;
- this.isPlayingAttackAnimation = true;
- console.log(`[EnemyInstance] 播放攻击动画: ${attackName}`);
- }
-
- // 动画切换后延迟更新血条位置,确保动画尺寸已生效
- this.scheduleOnce(() => {
- this.hpBarAnimation?.updateBarPosition(true);
- }, 0.1);
- }
- // 播放待机动画
- private playIdleAnimation() {
- if (!this.skeleton) return;
- const enemyComp = this.getComponent('EnemyComponent') as any;
- const anims = enemyComp?.getAnimations ? enemyComp.getAnimations() : {};
- const idleName = anims.idle ?? 'idle';
- if (this.skeleton.findAnimation(idleName) && this.currentAnimationName !== idleName) {
- this.skeleton.setAnimation(0, idleName, true);
- this.currentAnimationName = idleName;
- this.isPlayingAttackAnimation = false;
- console.log(`[EnemyInstance] 播放待机动画: ${idleName}`);
- }
-
- // 动画切换后延迟更新血条位置,确保动画尺寸已生效
- this.scheduleOnce(() => {
- this.hpBarAnimation?.updateBarPosition(true);
- }, 0.1);
- }
- // 安全播放待机动画(避免重复调用)
- private playIdleAnimationSafe() {
- if (!this.skeleton) return;
- const enemyComp = this.getComponent('EnemyComponent') as any;
- const anims = enemyComp?.getAnimations ? enemyComp.getAnimations() : {};
- const idleName = anims.idle ?? 'idle';
- // 只有当前不是待机动画时才播放
- if (this.skeleton.findAnimation(idleName) && this.currentAnimationName !== idleName && !this.isPlayingAttackAnimation) {
- this.skeleton.setAnimation(0, idleName, true);
- this.currentAnimationName = idleName;
- console.log(`[EnemyInstance] 安全播放待机动画: ${idleName}`);
-
- // 动画切换后延迟更新血条位置,确保动画尺寸已生效
- this.scheduleOnce(() => {
- this.hpBarAnimation?.updateBarPosition(true);
- }, 0.1);
- }
- }
- // 播放攻击动画并在动画结束时造成伤害
- private playAttackAnimationWithDamage() {
- if (!this.skeleton) {
- // 如果没有骨骼动画,直接造成伤害
- this.dealDamageToWall();
- 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.currentAnimationName = attackName;
- this.isPlayingAttackAnimation = true;
- console.log(`[EnemyInstance] 播放近战攻击动画: ${attackName}`);
-
- // 动画切换后延迟更新血条位置,确保动画尺寸已生效
- this.scheduleOnce(() => {
- this.hpBarAnimation?.updateBarPosition(true);
- }, 0.1);
-
- // 获取动画时长并在动画结束时造成伤害
- const animationDuration = animation.duration;
- console.log(`[EnemyInstance] 攻击动画时长: ${animationDuration}秒`);
-
- // 使用定时器在动画结束时造成伤害
- this.scheduleOnce(() => {
- this.dealDamageToWall();
- this.isPlayingAttackAnimation = false;
- // 动画完成后根据当前状态切换动画
- if (this.state === EnemyState.IDLE || this.state === EnemyState.ATTACKING) {
- // 如果是待机状态或攻击状态,切换回待机动画
- this.playIdleAnimation();
- } else {
- // 其他状态切换回行走动画
- this.playWalkAnimation();
- }
- }, animationDuration);
- } else {
- // 如果找不到攻击动画,直接造成伤害
- this.dealDamageToWall();
- }
- }
- // 对墙体造成伤害
- private dealDamageToWall() {
- if (this.controller) {
- // 获取当前攻击力(考虑狂暴加成)
- const enemyComponent = this.getComponent(EnemyComponent);
- const currentAttackPower = enemyComponent ? enemyComponent.getCurrentAttackPower() : this.attackPower;
-
- // 检查是否需要触发画面震动
- 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
- });
- }
- }
- private playDeathAnimationAndDestroy() {
- console.log(`[EnemyInstance] 开始播放死亡动画并销毁`);
-
- // 播放死亡音效
- EnemyAudio.playDeathSound(this.enemyConfig);
-
- if (this.skeleton) {
- const enemyComp = this.getComponent('EnemyComponent') as any;
- const anims = enemyComp?.getAnimations ? enemyComp.getAnimations() : {};
- const deathName = anims.dead ?? 'dead';
- if (this.skeleton.findAnimation(deathName)) {
- this.skeleton.setAnimation(0, deathName, false);
- // 销毁节点在动画完毕后
- this.skeleton.setCompleteListener(() => {
- this.node.destroy();
- });
- return;
- }
- }
- this.node.destroy();
- }
- private spawnCoin() {
- const ctrl = this.controller as any; // EnemyController
- if (!ctrl?.coinPrefab) return;
- // 获取配置中的掉落金币数量
- const dropCoinsCount = this.getDropCoins();
- console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 掉落 ${dropCoinsCount} 个金币`);
-
- // 根据配置掉落相应数量的金币
- for (let i = 0; i < dropCoinsCount; i++) {
- const coin = instantiate(ctrl.coinPrefab);
- find('Canvas')!.addChild(coin); // 放到 UI 层
-
- const pos = new Vec3();
- this.node.getWorldPosition(pos); // 取死亡敌人的世界坐标
-
- // 如果掉落多个金币,添加一些随机偏移避免重叠
- if (dropCoinsCount > 1) {
- const offsetX = (Math.random() - 0.5) * 40; // 随机X偏移 -20到20
- const offsetY = (Math.random() - 0.5) * 40; // 随机Y偏移 -20到20
- pos.x += offsetX;
- pos.y += offsetY;
- }
-
- coin.worldPosition = pos; // 金币就在敌人身上出现
- }
- }
-
- /**
- * 暂停敌人
- */
- public pause(): void {
- this.isPaused = true;
-
- // 暂停物理系统
- const enemySprite = this.node.getChildByName('EnemySprite');
- if (enemySprite) {
- const rigidBody = enemySprite.getComponent(RigidBody2D);
- if (rigidBody) {
- // 保存当前速度并停止物理移动
- (this as any).savedVelocity = rigidBody.linearVelocity;
- rigidBody.linearVelocity = new Vec2(0, 0);
- }
- }
-
- console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 已暂停`);
- }
-
- /**
- * 恢复敌人
- */
- public resume(): void {
- this.isPaused = false;
-
- // 恢复物理系统
- const enemySprite = this.node.getChildByName('EnemySprite');
- if (enemySprite) {
- const rigidBody = enemySprite.getComponent(RigidBody2D);
- if (rigidBody && (this as any).savedVelocity) {
- // 恢复之前保存的速度
- rigidBody.linearVelocity = (this as any).savedVelocity;
- (this as any).savedVelocity = null;
- }
- }
-
- console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 已恢复`);
- }
-
- /**
- * 检查是否暂停
- */
- public isPausedState(): boolean {
- return this.isPaused;
- }
-
- /**
- * 获取敌人当前状态
- */
- public getState(): EnemyState {
- return this.state;
- }
-
- /**
- * 检查敌人是否处于漂移状态
- */
- public isDrifting(): boolean {
- return this.state === EnemyState.DRIFTING;
- }
-
- /**
- * 重置血条状态(满血并隐藏)
- */
- public resetHealthBar(): void {
- if (this.hpBarAnimation) {
- this.hpBarAnimation.resetToFullAndHide();
- } else {
- // 备用方案:直接隐藏血条
- const hpBar = this.node.getChildByName('HPBar');
- if (hpBar) {
- hpBar.active = false;
- const progressBar = hpBar.getComponent(ProgressBar);
- if (progressBar) {
- progressBar.progress = 1.0;
- }
- }
- }
- }
-
- /**
- * 尝试格挡攻击
- */
- private tryBlock(): boolean {
- const enemyComponent = this.getComponent(EnemyComponent);
- if (!enemyComponent) return false;
-
- const blockChance = enemyComponent.getBlockChance();
- // 将百分比值转换为0-1范围(例如:30.0 -> 0.3)
- const blockChanceNormalized = blockChance / 100.0;
- return Math.random() < blockChanceNormalized;
- }
-
- /**
- * 基于距离检查墙体碰撞和攻击范围
- */
- private checkWallCollisionByDistance(): void {
- if (this.state === EnemyState.ATTACKING || this.state === EnemyState.IDLE) {
- return; // 已经在攻击状态或待机状态,不需要再检查
- }
- const enemySprite = this.node.getChildByName('EnemySprite');
- if (!enemySprite) return;
- const currentPos = this.node.worldPosition;
-
- // 获取敌人的攻击范围配置
- 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();
-
- for (const wallNode of wallNodes) {
- const wallPos = wallNode.worldPosition;
- let distance: number;
- const ui = wallNode.getComponent(UITransform);
- if (ui) {
- const width = ui.contentSize.width * wallNode.scale.x;
- const height = ui.contentSize.height * wallNode.scale.y;
- const halfW = width / 2;
- const halfH = height / 2;
- const dx = Math.max(Math.abs(currentPos.x - wallPos.x) - halfW, 0);
- const dy = Math.max(Math.abs(currentPos.y - wallPos.y) - halfH, 0);
- distance = Math.sqrt(dx * dx + dy * dy);
- } else {
- distance = Vec3.distance(currentPos, wallPos);
- }
-
- if (distance <= detectionDistance) {
- const attackTypeStr = isRangedAttack ? '远程攻击' : '近战攻击';
- console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 进入${attackTypeStr}范围,距离墙体 ${wallNode.name}: ${distance.toFixed(2)},攻击范围: ${attackRange}`);
-
- if (isRangedAttack) {
- // 远程攻击:切换到待机状态,停止移动并轮流播放攻击和待机动画
- this.state = EnemyState.IDLE;
- this.attackTimer = 0; // 立即开始攻击
- this.collidedWall = wallNode; // 记录目标墙体
-
- // 停止移动
- this.stopRigidBodyMovement();
-
- // 切换到待机动画
- this.playIdleAnimation();
-
- console.log(`[EnemyInstance] 远程敌人 ${this.getEnemyName()} 进入待机状态,停止移动并准备攻击`);
- } else {
- // 近战攻击:切换到待机状态,停止移动
- this.state = EnemyState.IDLE;
- this.attackTimer = 0; // 立即开始攻击
- this.collidedWall = wallNode; // 记录碰撞的墙体
-
- // 停止移动
- this.stopRigidBodyMovement();
-
- // 切换到待机动画
- this.playIdleAnimation();
- }
-
- return; // 找到一个墙体就停止检查
- }
- }
- }
- /**
- * 获取所有墙体节点
- */
- private getAllWallNodes(): Node[] {
- const wallNodes: Node[] = [];
-
- // 通过EnemyController获取墙体节点
- if (this.controller) {
- if (this.controller.topFenceNode) {
- wallNodes.push(this.controller.topFenceNode);
- }
- if (this.controller.bottomFenceNode) {
- wallNodes.push(this.controller.bottomFenceNode);
- }
- }
-
- // 备用方案:在场景中查找墙体节点
- if (wallNodes.length === 0) {
- 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)) {
- wallNodes.push(child);
- }
- }
- }
- }
-
- return wallNodes;
- }
-
- /**
- * 停止刚体移动,确保物理系统不干扰代码控制的移动
- */
- private stopRigidBodyMovement(): void {
- const enemySprite = this.node.getChildByName('EnemySprite');
- if (enemySprite) {
- const rigidBody = enemySprite.getComponent(RigidBody2D);
- if (rigidBody) {
- rigidBody.linearVelocity = new Vec2(0, 0);
- }
- }
- }
-
- /**
- * 显示格挡效果
- */
- private showBlockEffect(): void {
- console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 格挡了攻击`);
-
- // 显示"格挡"文字效果
- this.showBlockText();
- }
-
- /**
- * 显示格挡文字
- */
- private showBlockText(): void {
- // 使用DamageNumberAni组件来显示格挡文字,就像显示伤害数字一样
- if (this.controller) {
- const damageAni = this.controller.getComponent(DamageNumberAni);
- if (damageAni) {
- damageAni.showBlockText(this.node.worldPosition);
- } else {
- console.warn('[EnemyInstance] 未找到DamageNumberAni组件,无法显示格挡文字');
- }
- } else {
- console.warn('[EnemyInstance] 未找到controller引用,无法显示格挡文字');
- }
- }
-
-
- /**
- * 检查是否触发狂暴状态
- */
- private checkRageTrigger(): void {
- const enemyComponent = this.getComponent(EnemyComponent);
- if (!enemyComponent) return;
-
- const healthPercent = this.health / this.maxHealth;
- const rageTriggerThreshold = enemyComponent.getRageTriggerThreshold();
-
- if (healthPercent <= rageTriggerThreshold && !enemyComponent.isInRage()) {
- enemyComponent.triggerRage();
- 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();
- }
- }
- }
|