EnemyInstance.ts 71 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771
  1. 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';
  2. import { JsonConfigLoader } from '../Core/JsonConfigLoader';
  3. import { sp } from 'cc';
  4. import { DamageNumberAni } from '../Animations/DamageNumberAni';
  5. import { HPBarAnimation } from '../Animations/HPBarAnimation';
  6. import { EnemyComponent } from './EnemyComponent';
  7. import { EnemyAudio } from '../AudioManager/EnemyAudios';
  8. import { ScreenShakeManager } from './EnemyWeapon/ScreenShakeManager';
  9. import { EnemyAttackStateManager } from './EnemyAttackStateManager';
  10. import EventBus, { GameEvents } from '../Core/EventBus';
  11. const { ccclass, property } = _decorator;
  12. // 前向声明EnemyController接口,避免循环引用
  13. interface EnemyControllerType {
  14. gameBounds: {
  15. left: number;
  16. right: number;
  17. top: number;
  18. bottom: number;
  19. };
  20. topFenceNode: Node;
  21. bottomFenceNode: Node;
  22. damageWall: (damage: number) => void;
  23. getComponent: (componentType: any) => any;
  24. }
  25. // 敌人状态枚举
  26. enum EnemyState {
  27. DRIFTING, // 漂移中(从生成位置移动到线上)
  28. MOVING, // 移动中
  29. IDLE, // 待机中(碰到墙体后停止移动但可以攻击)
  30. ATTACKING, // 攻击中
  31. STEALTH, // 隐身中(不被方块武器检测到)
  32. DEAD // 死亡
  33. }
  34. // 单个敌人实例的组件
  35. @ccclass('EnemyInstance')
  36. export class EnemyInstance extends Component {
  37. // 敌人属性(从配置文件读取)
  38. public health: number = 0;
  39. public maxHealth: number = 0;
  40. public speed: number = 0;
  41. public attackPower: number = 0;
  42. // 敌人配置ID
  43. public enemyId: string = '';
  44. // 敌人配置数据
  45. private enemyConfig: any = null;
  46. // 敌人配置数据库
  47. private static enemyDatabase: any = null;
  48. // === 新增属性 ===
  49. /** 是否从上方生成 */
  50. public spawnFromTop: boolean = true;
  51. /** 目标 Fence 节点(TopFence / BottomFence) */
  52. public targetFence: Node | null = null;
  53. /** 目标位置(墙体上的随机点) */
  54. public targetPosition: Vec3 | null = null;
  55. /** 漂移目标位置(线上的位置) */
  56. public driftTargetPosition: Vec3 | null = null;
  57. // 移动相关属性
  58. public movingDirection: number = 1; // 1: 向右, -1: 向左
  59. public targetY: number = 0; // 目标Y位置
  60. public changeDirectionTime: number = 0; // 下次改变方向的时间
  61. // 移动配置属性(从配置文件读取)
  62. public movementPattern: string = 'direct'; // 移动模式:direct, patrol等
  63. public moveType: string = 'straight'; // 移动类型:straight, sway等
  64. public patrolRange: number = 100; // 巡逻范围
  65. public rotationSpeed: number = 90; // 旋转速度
  66. public speedVariation: number = 0; // 速度变化
  67. public swingAmplitude: number = 0; // 摆动幅度
  68. public swingFrequency: number = 1; // 摆动频率
  69. // 摆动移动相关属性
  70. private swayTime: number = 0; // 摆动时间累计
  71. private basePosition: Vec3 = new Vec3(); // 基础位置(摆动的中心点)
  72. private swayOffset: Vec3 = new Vec3(); // 当前摆动偏移
  73. // 攻击属性
  74. public attackInterval: number = 0; // 攻击间隔(秒),从配置文件读取
  75. public attackRange: number = 0; // 攻击范围,从配置文件读取
  76. private attackTimer: number = 0;
  77. // 对控制器的引用
  78. public controller: EnemyControllerType = null;
  79. // 敌人当前状态
  80. private state: EnemyState = EnemyState.DRIFTING;
  81. // 游戏区域中心
  82. private gameAreaCenter: Vec3 = new Vec3();
  83. // 碰撞的墙体
  84. private collidedWall: Node = null;
  85. // 骨骼动画组件
  86. private skeleton: sp.Skeleton | null = null;
  87. // 血条动画组件
  88. private hpBarAnimation: HPBarAnimation | null = null;
  89. // 暂停状态标记
  90. private isPaused: boolean = false;
  91. // 当前播放的动画名称,用于避免重复播放
  92. private currentAnimationName: string = '';
  93. // 攻击状态下的动画管理
  94. private isPlayingAttackAnimation: boolean = false;
  95. // 隐身相关属性
  96. private stealthDuration: number = 0; // 隐身持续时间
  97. private stealthTimer: number = 0; // 隐身计时器
  98. private originalOpacity: number = 255; // 原始透明度
  99. // 隐身循环相关
  100. private hasStealthAbility: boolean = false;
  101. private stealthCooldown: number = 5.0;
  102. private initialStealthScheduled: boolean = false; // 首次隐身定时是否已安排
  103. private stealthActive: boolean = false; // 隐身为叠加标记,不改变行为状态
  104. start() {
  105. // 初始化敌人
  106. this.initializeEnemy();
  107. }
  108. // 静态方法:加载敌人配置数据库
  109. public static async loadEnemyDatabase(): Promise<void> {
  110. if (EnemyInstance.enemyDatabase) return;
  111. try {
  112. EnemyInstance.enemyDatabase = await JsonConfigLoader.getInstance().loadConfig('enemies');
  113. if (!EnemyInstance.enemyDatabase) {
  114. throw new Error('敌人配置文件内容为空');
  115. }
  116. } catch (error) {
  117. console.error('[EnemyInstance] 加载敌人配置失败:', error);
  118. throw error;
  119. }
  120. }
  121. // 设置敌人配置
  122. public setEnemyConfig(enemyId: string): void {
  123. this.enemyId = enemyId;
  124. if (!EnemyInstance.enemyDatabase) {
  125. console.error('[EnemyInstance] 敌人配置数据库未加载');
  126. return;
  127. }
  128. // 从数据库中查找敌人配置
  129. // 修复:enemies.json是直接的数组结构,不需要.enemies包装
  130. const enemies = EnemyInstance.enemyDatabase;
  131. this.enemyConfig = enemies.find((enemy: any) => enemy.id === enemyId);
  132. if (!this.enemyConfig) {
  133. console.error(`[EnemyInstance] 未找到敌人配置: ${enemyId}`);
  134. return;
  135. }
  136. // 应用配置到敌人属性
  137. this.applyEnemyConfig();
  138. // 应用配置后,重新初始化特殊能力(确保在运行时添加组件后也能按配置触发)
  139. this.initializeSpecialAbilities();
  140. }
  141. // 应用敌人配置到属性
  142. private applyEnemyConfig(): void {
  143. if (!this.enemyConfig) return;
  144. // 从stats节点读取基础属性
  145. const stats = this.enemyConfig.stats || {};
  146. this.health = stats.health || 30;
  147. this.maxHealth = stats.maxHealth || this.health;
  148. // 从movement节点读取移动配置
  149. const movement = this.enemyConfig.movement || {};
  150. this.speed = movement.speed || 50;
  151. // 读取移动模式配置
  152. this.movementPattern = movement.pattern || 'direct';
  153. this.moveType = movement.moveType || 'straight';
  154. this.patrolRange = movement.patrolRange || 100;
  155. this.rotationSpeed = movement.rotationSpeed || 90;
  156. this.speedVariation = movement.speedVariation || 0;
  157. // 读取摆动配置
  158. this.swingAmplitude = movement.swingAmplitude || 0;
  159. this.swingFrequency = movement.swingFrequency || 1;
  160. // 从combat节点读取攻击力
  161. const combat = this.enemyConfig.combat || {};
  162. this.attackPower = combat.attackDamage || 10;
  163. // 设置攻击间隔
  164. this.attackInterval = combat.attackCooldown || 2.0;
  165. console.log(`[EnemyInstance] 应用配置 ${this.enemyId}: 移动模式=${this.movementPattern}, 移动类型=${this.moveType}, 速度=${this.speed}, 摆动幅度=${this.swingAmplitude}`);
  166. }
  167. // 获取敌人配置信息
  168. public getEnemyConfig(): any {
  169. return this.enemyConfig;
  170. }
  171. // 获取敌人名称
  172. public getEnemyName(): string {
  173. return this.enemyConfig?.name || '未知敌人';
  174. }
  175. // 获取敌人类型
  176. public getEnemyType(): string {
  177. return this.enemyConfig?.type || 'basic';
  178. }
  179. // 获取敌人稀有度
  180. public getEnemyRarity(): string {
  181. return this.enemyConfig?.rarity || 'common';
  182. }
  183. // 获取金币奖励
  184. public getGoldReward(): number {
  185. return this.enemyConfig?.goldReward || 1;
  186. }
  187. // 获取掉落金币数量
  188. public getDropCoins(): number {
  189. const stats = this.enemyConfig?.stats || {};
  190. return stats.dropCoins || 1;
  191. }
  192. // 初始化敌人
  193. private initializeEnemy() {
  194. // 确保血量正确设置
  195. if (this.maxHealth > 0) {
  196. this.health = this.maxHealth;
  197. }
  198. this.state = EnemyState.DRIFTING; // 从漂移状态开始
  199. // 只有在攻击间隔未设置时才使用默认值
  200. if (this.attackInterval <= 0) {
  201. this.attackInterval = 2.0; // 默认攻击间隔
  202. }
  203. this.attackTimer = 0;
  204. // 初始化血条动画组件
  205. this.initializeHPBarAnimation();
  206. // 初始化骨骼动画组件 - 从EnemySprite子节点获取
  207. const enemySprite = this.node.getChildByName('EnemySprite');
  208. if (enemySprite) {
  209. this.skeleton = enemySprite.getComponent(sp.Skeleton);
  210. } else {
  211. console.error('[EnemyInstance] 未找到EnemySprite子节点,无法获取骨骼动画组件');
  212. }
  213. this.playWalkAnimation();
  214. // 计算游戏区域中心
  215. this.calculateGameAreaCenter();
  216. // 初始化碰撞检测
  217. this.setupCollider();
  218. // 注册到攻击状态管理器
  219. const attackStateManager = EnemyAttackStateManager.getInstance();
  220. attackStateManager.registerEnemy(this.node, true); // 初始状态为可攻击
  221. // 初始化特殊能力
  222. console.log(`[EnemyInstance] 准备初始化特殊能力 - 敌人节点: ${this.node.name}`);
  223. this.initializeSpecialAbilities();
  224. }
  225. // 初始化特殊能力
  226. private initializeSpecialAbilities(): void {
  227. console.log(`[EnemyInstance] 初始化特殊能力 - 敌人: ${this.getEnemyName()}, 配置存在: ${!!this.enemyConfig}, 特殊能力: ${this.enemyConfig?.special_abilities ? JSON.stringify(this.enemyConfig.special_abilities) : '无'}`);
  228. if (!this.enemyConfig || !this.enemyConfig.special_abilities) {
  229. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 没有特殊能力配置`);
  230. return;
  231. }
  232. // 检查是否有隐身能力
  233. const stealthAbility = this.enemyConfig.special_abilities.find(ability => ability.type === 'stealth');
  234. if (stealthAbility) {
  235. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 具有隐身能力,冷却时间: ${stealthAbility.cooldown}秒`);
  236. this.hasStealthAbility = true;
  237. this.stealthCooldown = typeof stealthAbility.cooldown === 'number' ? stealthAbility.cooldown : 5.0;
  238. // 生成后按冷却时间开始循环隐身:首次隐身在 cooldown 秒后触发
  239. if (!this.initialStealthScheduled) {
  240. this.initialStealthScheduled = true;
  241. this.scheduleOnce(() => {
  242. if (!this.node || !this.node.isValid) return;
  243. if (this.state === EnemyState.DEAD) return;
  244. this.enterStealth(this.stealthCooldown);
  245. }, this.stealthCooldown);
  246. }
  247. }
  248. }
  249. // 设置碰撞器
  250. setupCollider() {
  251. // 从EnemySprite子节点获取碰撞器组件
  252. const enemySprite = this.node.getChildByName('EnemySprite');
  253. if (!enemySprite) {
  254. console.error('[EnemyInstance] 未找到EnemySprite子节点,无法设置碰撞器组件');
  255. return;
  256. }
  257. // 检查EnemySprite节点是否有碰撞器
  258. let collider = enemySprite.getComponent(Collider2D);
  259. if (!collider) {
  260. console.warn(`[EnemyInstance] 敌人节点 ${this.node.name} 的EnemySprite子节点没有碰撞器组件`);
  261. return;
  262. }
  263. // 确保有RigidBody2D组件,这对于碰撞检测是必需的
  264. let rigidBody = enemySprite.getComponent(RigidBody2D);
  265. if (!rigidBody) {
  266. console.log(`[EnemyInstance] 为敌人EnemySprite节点添加RigidBody2D组件`);
  267. rigidBody = enemySprite.addComponent(RigidBody2D);
  268. }
  269. // 设置刚体属性
  270. if (rigidBody) {
  271. rigidBody.type = ERigidBody2DType.Dynamic; // 动态刚体
  272. rigidBody.enabledContactListener = true; // 启用碰撞监听
  273. rigidBody.gravityScale = 0; // 不受重力影响
  274. rigidBody.linearDamping = 0; // 无线性阻尼
  275. rigidBody.angularDamping = 0; // 无角阻尼
  276. rigidBody.allowSleep = false; // 不允许休眠
  277. rigidBody.fixedRotation = true; // 固定旋转
  278. }
  279. // 根据ContentSize自适应碰撞体大小
  280. this.adaptColliderToContentSize(collider);
  281. // 设置碰撞事件监听
  282. collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
  283. console.log(`[EnemyInstance] 敌人 ${this.node.name} 碰撞器设置完成,碰撞器启用: ${collider.enabled}, 刚体启用: ${rigidBody?.enabled}`);
  284. }
  285. /**
  286. * 根据ContentSize自适应碰撞体大小
  287. */
  288. private adaptColliderToContentSize(collider: Collider2D): void {
  289. // 从EnemySprite子节点获取UITransform组件
  290. const enemySprite = this.node.getChildByName('EnemySprite');
  291. if (!enemySprite) {
  292. console.error('[EnemyInstance] 未找到EnemySprite子节点,无法获取UITransform组件');
  293. return;
  294. }
  295. const uiTransform = enemySprite.getComponent(UITransform);
  296. if (!uiTransform) {
  297. console.warn(`[EnemyInstance] EnemySprite节点没有UITransform组件,无法自适应碰撞体大小`);
  298. return;
  299. }
  300. // 尝试获取骨骼动画组件来获取更准确的尺寸
  301. const skeleton = enemySprite.getComponent(sp.Skeleton);
  302. let actualWidth = uiTransform.contentSize.width;
  303. let actualHeight = uiTransform.contentSize.height;
  304. if (skeleton && skeleton.skeletonData) {
  305. // 如果有骨骼动画,尝试从骨骼数据获取尺寸信息
  306. try {
  307. // 使用骨骼数据的默认尺寸,或者根据动画缩放计算
  308. const skeletonData = skeleton.skeletonData;
  309. if (skeletonData) {
  310. // 获取骨骼的缩放比例
  311. const scaleX = Math.abs(skeleton.node.scale.x);
  312. const scaleY = Math.abs(skeleton.node.scale.y);
  313. // 使用合理的默认尺寸并应用缩放
  314. actualWidth = Math.max(60 * scaleX, uiTransform.contentSize.width * 0.8);
  315. actualHeight = Math.max(80 * scaleY, uiTransform.contentSize.height * 0.8);
  316. console.log(`[EnemyInstance] 使用骨骼动画尺寸 (scale: ${scaleX.toFixed(2)}, ${scaleY.toFixed(2)}): ${actualWidth.toFixed(2)} x ${actualHeight.toFixed(2)}`);
  317. } else {
  318. // 如果无法获取骨骼数据,使用默认尺寸
  319. actualWidth = Math.max(60, uiTransform.contentSize.width * 0.8);
  320. actualHeight = Math.max(80, uiTransform.contentSize.height * 0.8);
  321. console.log(`[EnemyInstance] 使用默认骨骼尺寸: ${actualWidth.toFixed(2)} x ${actualHeight.toFixed(2)}`);
  322. }
  323. } catch (error) {
  324. console.warn(`[EnemyInstance] 获取骨骼信息失败,使用默认尺寸:`, error);
  325. actualWidth = Math.max(60, uiTransform.contentSize.width * 0.8);
  326. actualHeight = Math.max(80, uiTransform.contentSize.height * 0.8);
  327. }
  328. }
  329. console.log(`[EnemyInstance] 敌人 ${this.node.name} 实际尺寸: ${actualWidth.toFixed(2)} x ${actualHeight.toFixed(2)}`);
  330. // 根据碰撞器类型设置大小
  331. if (collider instanceof BoxCollider2D) {
  332. // 方形碰撞器:使用实际尺寸,稍微缩小以避免过于敏感的碰撞
  333. const boxCollider = collider as BoxCollider2D;
  334. boxCollider.size.width = actualWidth * 0.9; // 缩小10%
  335. boxCollider.size.height = actualHeight * 0.9; // 缩小10%
  336. // 调整偏移量,使碰撞器居中对齐到EnemySprite
  337. const anchorPoint = uiTransform.anchorPoint;
  338. // 对于骨骼动画,通常锚点在底部中心(0.5, 0),需要向上偏移
  339. boxCollider.offset.x = 0; // X轴居中
  340. boxCollider.offset.y = actualHeight * (0.5 - anchorPoint.y) * 0.9; // Y轴根据锚点调整,并匹配缩放后的尺寸
  341. 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)})`);
  342. } else if (collider instanceof CircleCollider2D) {
  343. // 圆形碰撞器:使用实际尺寸的较小值作为直径,稍微缩小
  344. const circleCollider = collider as CircleCollider2D;
  345. const radius = Math.min(actualWidth, actualHeight) / 2 * 0.9; // 缩小10%
  346. circleCollider.radius = radius;
  347. // 调整偏移量,使碰撞器居中对齐到EnemySprite
  348. const anchorPoint = uiTransform.anchorPoint;
  349. // 对于骨骼动画,通常锚点在底部中心(0.5, 0),需要向上偏移
  350. circleCollider.offset.x = 0; // X轴居中
  351. circleCollider.offset.y = actualHeight * (0.5 - anchorPoint.y) * 0.9; // Y轴根据锚点调整,并匹配缩放后的尺寸
  352. 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)})`);
  353. } else {
  354. console.warn(`[EnemyInstance] 不支持的碰撞器类型: ${collider.constructor.name}`);
  355. }
  356. // 碰撞体调整完成后,同步更新血条位置(统一为HPBarAnimation)
  357. if (this.hpBarAnimation) {
  358. this.hpBarAnimation.updateBarPosition(true);
  359. }
  360. }
  361. /**
  362. * 动态调整血条位置,使其始终显示在碰撞体上方
  363. */
  364. private updateHPBarPosition(): void {
  365. // 获取血条根节点
  366. const hpBarRoot = this.node.getChildByName('HPBar');
  367. if (!hpBarRoot) {
  368. console.warn('[EnemyInstance] 未找到HPBar节点,无法调整血条位置');
  369. return;
  370. }
  371. // 获取EnemySprite节点和其碰撞器
  372. const enemySprite = this.node.getChildByName('EnemySprite');
  373. if (!enemySprite) {
  374. console.error('[EnemyInstance] 未找到EnemySprite子节点,无法计算血条位置');
  375. return;
  376. }
  377. const collider = enemySprite.getComponent(Collider2D);
  378. const uiTransform = enemySprite.getComponent(UITransform);
  379. if (!collider || !uiTransform) {
  380. console.warn('[EnemyInstance] EnemySprite节点缺少必要组件,无法计算血条位置');
  381. return;
  382. }
  383. // 计算碰撞体的顶部位置
  384. let colliderTopY = 0;
  385. const anchorPoint = uiTransform.anchorPoint;
  386. if (collider instanceof BoxCollider2D) {
  387. const boxCollider = collider as BoxCollider2D;
  388. // 碰撞体顶部 = 碰撞体偏移Y + 碰撞体高度的一半
  389. colliderTopY = boxCollider.offset.y + boxCollider.size.height / 2;
  390. } else if (collider instanceof CircleCollider2D) {
  391. const circleCollider = collider as CircleCollider2D;
  392. // 碰撞体顶部 = 碰撞体偏移Y + 半径
  393. colliderTopY = circleCollider.offset.y + circleCollider.radius;
  394. }
  395. // 血条位置设置为碰撞体顶部上方一定距离
  396. const hpBarOffset = -35; // 血条距离碰撞体顶部的距离
  397. const hpBarY = colliderTopY + hpBarOffset;
  398. // 设置血条位置
  399. const hpBarTransform = hpBarRoot.getComponent(UITransform);
  400. if (hpBarTransform) {
  401. hpBarRoot.setPosition(0, hpBarY, 0);
  402. console.log(`[EnemyInstance] 血条位置已更新: Y = ${hpBarY.toFixed(2)} (碰撞体顶部: ${colliderTopY.toFixed(2)})`);
  403. }
  404. }
  405. // 碰撞开始事件
  406. onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
  407. const nodeName = otherCollider.node.name;
  408. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 碰撞检测 - 碰撞对象: ${nodeName}, 当前状态: ${EnemyState[this.state]}`);
  409. // 只有在移动状态或攻击状态下才能响应墙体碰撞,避免重复触发
  410. if (this.state !== EnemyState.MOVING && this.state !== EnemyState.ATTACKING) {
  411. return;
  412. }
  413. // 检查是否碰到墙体(更严格的墙体识别)
  414. const isWall = this.isWallNode(otherCollider.node);
  415. if (isWall) {
  416. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 碰撞到墙体 ${nodeName},切换到待机状态`);
  417. this.state = EnemyState.IDLE;
  418. this.attackTimer = 0; // 立即开始攻击
  419. this.collidedWall = otherCollider.node; // 记录碰撞的墙体
  420. // 停止移动
  421. this.stopRigidBodyMovement();
  422. // 切换到待机动画
  423. this.playIdleAnimation();
  424. }
  425. }
  426. // 检查节点是否为墙体
  427. private isWallNode(node: Node): boolean {
  428. const nodeName = node.name.toLowerCase();
  429. // 检查节点名称是否包含墙体关键词
  430. const wallKeywords = ['wall', 'fence', 'jiguang', '墙', '围栏'];
  431. const isWallByName = wallKeywords.some(keyword => nodeName.includes(keyword));
  432. // 检查节点是否有Wall组件
  433. const hasWallComponent = node.getComponent('Wall') !== null;
  434. return isWallByName || hasWallComponent;
  435. }
  436. // 获取节点路径
  437. getNodePath(node: Node): string {
  438. let path = node.name;
  439. let current = node;
  440. while (current.parent) {
  441. current = current.parent;
  442. path = current.name + '/' + path;
  443. }
  444. return path;
  445. }
  446. // 计算游戏区域中心
  447. private calculateGameAreaCenter() {
  448. const gameArea = find('Canvas/GameLevelUI/GameArea');
  449. if (gameArea) {
  450. this.gameAreaCenter = gameArea.worldPosition;
  451. }
  452. }
  453. /**
  454. * 初始化血条动画组件
  455. */
  456. private initializeHPBarAnimation() {
  457. // 检查是否已经存在血条动画组件,避免重复添加
  458. if (this.hpBarAnimation) {
  459. console.log(`[EnemyInstance] 血条动画组件已存在,跳过初始化`);
  460. return;
  461. }
  462. const hpBar = this.node.getChildByName('HPBar');
  463. if (hpBar) {
  464. // 查找红色和黄色血条节点
  465. const redBarNode = hpBar.getChildByName('RedBar');
  466. const yellowBarNode = hpBar.getChildByName('YellowBar');
  467. if (redBarNode && yellowBarNode) {
  468. // 检查节点是否已经有HPBarAnimation组件
  469. let existingHPBarAnimation = this.node.getComponent(HPBarAnimation);
  470. if (existingHPBarAnimation) {
  471. this.hpBarAnimation = existingHPBarAnimation;
  472. console.log(`[EnemyInstance] 使用已存在的血条动画组件`);
  473. } else {
  474. // 添加血条动画组件
  475. this.hpBarAnimation = this.node.addComponent(HPBarAnimation);
  476. console.log(`[EnemyInstance] 新建血条动画组件`);
  477. }
  478. if (this.hpBarAnimation) {
  479. // 正确设置红色和黄色血条节点引用
  480. this.hpBarAnimation.redBarNode = redBarNode;
  481. this.hpBarAnimation.yellowBarNode = yellowBarNode;
  482. this.hpBarAnimation.hpBarRootNode = hpBar; // 设置HPBar根节点
  483. console.log(`[EnemyInstance] 血条动画组件已初始化`);
  484. }
  485. } else {
  486. console.warn(`[EnemyInstance] HPBar下未找到RedBar或YellowBar节点,RedBar: ${!!redBarNode}, YellowBar: ${!!yellowBarNode}`);
  487. }
  488. } else {
  489. console.warn(`[EnemyInstance] 未找到HPBar节点,无法初始化血条动画`);
  490. }
  491. }
  492. // 更新血量显示
  493. updateHealthDisplay(showBar: boolean = false) {
  494. // 确保血量值在有效范围内
  495. this.health = Math.max(0, Math.min(this.maxHealth, this.health));
  496. const healthProgress = this.maxHealth > 0 ? this.health / this.maxHealth : 0;
  497. console.log(`[EnemyInstance] 更新血量显示: ${this.health}/${this.maxHealth} (${(healthProgress * 100).toFixed(1)}%)`);
  498. // 使用血条动画组件更新血条
  499. if (this.hpBarAnimation) {
  500. this.hpBarAnimation.updateProgress(healthProgress, showBar);
  501. } else {
  502. // 备用方案:直接更新血条
  503. const hpBar = this.node.getChildByName('HPBar');
  504. if (hpBar) {
  505. const progressBar = hpBar.getComponent(ProgressBar);
  506. if (progressBar) {
  507. progressBar.progress = healthProgress;
  508. }
  509. // 根据showBar参数控制血条显示
  510. hpBar.active = showBar;
  511. }
  512. }
  513. // 更新血量数字
  514. const hpLabel = this.node.getChildByName('HPLabel');
  515. if (hpLabel) {
  516. const label = hpLabel.getComponent(Label);
  517. if (label) {
  518. // 显示整数血量值
  519. label.string = Math.ceil(this.health).toString();
  520. }
  521. }
  522. }
  523. // 受到伤害
  524. takeDamage(damage: number, isCritical: boolean = false) {
  525. // 如果已经死亡,不再处理伤害
  526. if (this.state === EnemyState.DEAD) {
  527. return;
  528. }
  529. // 确保伤害值为正数
  530. if (damage <= 0) {
  531. console.warn(`[EnemyInstance] 无效的伤害值: ${damage}`);
  532. return;
  533. }
  534. // 应用防御力减少伤害
  535. const enemyComponent = this.getComponent(EnemyComponent);
  536. const defense = enemyComponent ? enemyComponent.getDefense() : 0;
  537. let finalDamage = Math.max(1, damage - defense); // 至少造成1点伤害
  538. // 检查格挡
  539. if (enemyComponent && enemyComponent.canBlock() && this.tryBlock()) {
  540. finalDamage *= (1 - enemyComponent.getBlockDamageReduction());
  541. this.showBlockEffect();
  542. }
  543. // 计算新的血量,确保不会低于0
  544. const oldHealth = this.health;
  545. const newHealth = Math.max(0, this.health - finalDamage);
  546. const actualHealthLoss = oldHealth - newHealth; // 实际血量损失
  547. this.health = newHealth;
  548. // 检查是否触发狂暴
  549. this.checkRageTrigger();
  550. // 日志显示武器的真实伤害值,而不是血量差值
  551. console.log(`[EnemyInstance] 敌人受到伤害: ${damage} (武器伤害), 防御后伤害: ${finalDamage}, 实际血量损失: ${actualHealthLoss}, 剩余血量: ${this.health}/${this.maxHealth}`);
  552. // 受击音效已移除
  553. // 显示伤害数字动画(在敌人头顶)- 显示防御后的实际伤害
  554. // 优先使用EnemyController节点上的DamageNumberAni组件实例
  555. if (this.controller) {
  556. const damageAni = this.controller.getComponent(DamageNumberAni);
  557. if (damageAni) {
  558. damageAni.showDamageNumber(finalDamage, this.node.worldPosition, isCritical);
  559. } else {
  560. // 如果没有找到组件实例,使用静态方法作为备用
  561. DamageNumberAni.showDamageNumber(finalDamage, this.node.worldPosition, isCritical);
  562. }
  563. } else {
  564. // 如果没有controller引用,使用静态方法
  565. DamageNumberAni.showDamageNumber(finalDamage, this.node.worldPosition, isCritical);
  566. }
  567. // 更新血量显示和动画,受伤时显示血条
  568. this.updateHealthDisplay(true);
  569. // 如果血量低于等于0,销毁敌人
  570. if (this.health <= 0) {
  571. console.log(`[EnemyInstance] 敌人死亡,开始销毁流程`);
  572. this.state = EnemyState.DEAD;
  573. this.spawnCoin();
  574. // 进入死亡流程,禁用碰撞避免重复命中
  575. const enemySprite = this.node.getChildByName('EnemySprite');
  576. if (enemySprite) {
  577. const col = enemySprite.getComponent(Collider2D);
  578. if (col) col.enabled = false;
  579. }
  580. this.playDeathAnimationAndDestroy();
  581. }
  582. }
  583. onDestroy() {
  584. console.log(`[EnemyInstance] onDestroy 被调用,准备通知控制器`);
  585. // 取消所有调度,避免隐身循环在销毁后继续
  586. this.unscheduleAllCallbacks();
  587. // 从攻击状态管理器中移除敌人
  588. const attackStateManager = EnemyAttackStateManager.getInstance();
  589. attackStateManager.unregisterEnemy(this.node);
  590. // 通知控制器 & GameManager
  591. if (this.controller && typeof (this.controller as any).notifyEnemyDead === 'function') {
  592. // 检查控制器是否处于清理状态,避免在清理过程中触发游戏事件
  593. const isClearing = (this.controller as any).isClearing;
  594. if (isClearing) {
  595. console.log(`[EnemyInstance] 控制器处于清理状态,跳过死亡通知`);
  596. return;
  597. }
  598. console.log(`[EnemyInstance] 调用 notifyEnemyDead`);
  599. (this.controller as any).notifyEnemyDead(this.node);
  600. } else {
  601. console.warn(`[EnemyInstance] 无法调用 notifyEnemyDead: controller=${!!this.controller}`);
  602. }
  603. }
  604. update(deltaTime: number) {
  605. // 如果敌人被暂停,则不执行任何更新逻辑
  606. if (this.isPaused) {
  607. return;
  608. }
  609. if (this.state === EnemyState.DRIFTING) {
  610. this.updateDrifting(deltaTime);
  611. // 在漂移状态也检查墙体碰撞
  612. this.checkWallCollisionByDistance();
  613. } else if (this.state === EnemyState.MOVING) {
  614. this.updateMovement(deltaTime);
  615. // 在移动状态检查墙体碰撞
  616. this.checkWallCollisionByDistance();
  617. } else if (this.state === EnemyState.IDLE) {
  618. this.updateIdle(deltaTime);
  619. } else if (this.state === EnemyState.ATTACKING) {
  620. this.updateAttack(deltaTime);
  621. }
  622. // 隐身为叠加效果:不改变行为状态,但仍需要计时与退出逻辑
  623. if (this.stealthActive) {
  624. this.updateStealth(deltaTime);
  625. }
  626. // 不再每帧播放攻击动画,避免日志刷屏
  627. }
  628. // 更新漂移逻辑(从生成位置移动到线上)
  629. private updateDrifting(deltaTime: number) {
  630. if (!this.driftTargetPosition) {
  631. // 如果没有漂移目标位置,直接切换到移动状态
  632. this.state = EnemyState.MOVING;
  633. return;
  634. }
  635. const currentWorldPos = this.node.worldPosition.clone();
  636. const targetWorldPos = this.driftTargetPosition.clone();
  637. const dir = targetWorldPos.subtract(currentWorldPos);
  638. const distanceToTarget = dir.length();
  639. // 如果距离目标很近,切换到移动状态
  640. const stopDistance = 5; // 减小停止距离以提高精度
  641. if (distanceToTarget <= stopDistance) {
  642. this.state = EnemyState.MOVING;
  643. // 确保刚体速度为零
  644. this.stopRigidBodyMovement();
  645. return;
  646. }
  647. if (distanceToTarget === 0) return;
  648. dir.normalize();
  649. // 使用代码控制移动,不依赖物理系统
  650. const driftSpeed = Math.min(this.speed * 8, 500); // 限制最大漂移速度为5500
  651. const moveDistance = driftSpeed * deltaTime;
  652. const actualMoveDistance = Math.min(moveDistance, distanceToTarget);
  653. const newWorldPos = currentWorldPos.add(dir.multiplyScalar(actualMoveDistance));
  654. // 直接设置世界坐标
  655. this.node.setWorldPosition(newWorldPos);
  656. // 确保刚体速度为零,避免物理系统干扰
  657. this.stopRigidBodyMovement();
  658. }
  659. // 更新移动逻辑
  660. private updateMovement(deltaTime: number) {
  661. // 根据配置的移动模式执行不同的移动逻辑
  662. switch (this.movementPattern) {
  663. case 'direct':
  664. this.updateDirectMovement(deltaTime);
  665. break;
  666. case 'patrol':
  667. this.updatePatrolMovement(deltaTime);
  668. break;
  669. default:
  670. this.updateDirectMovement(deltaTime);
  671. break;
  672. }
  673. }
  674. // 直线移动模式
  675. private updateDirectMovement(deltaTime: number) {
  676. this.moveTowardsTarget(deltaTime);
  677. }
  678. // 巡逻移动模式
  679. private updatePatrolMovement(deltaTime: number) {
  680. // 更新方向变化计时器
  681. this.changeDirectionTime += deltaTime;
  682. console.log(`[EnemyInstance] 摆动 调用,changeDirectionTime=${this.changeDirectionTime}`);
  683. // 根据巡逻范围决定方向变化
  684. const enemySprite = this.node.getChildByName('EnemySprite');
  685. if (enemySprite && this.controller) {
  686. const currentPos = this.node.worldPosition.clone();
  687. const bounds = this.controller.gameBounds;
  688. // 检查是否到达巡逻边界
  689. const leftBound = bounds.left + this.patrolRange;
  690. const rightBound = bounds.right - this.patrolRange;
  691. if (currentPos.x <= leftBound && this.movingDirection < 0) {
  692. this.movingDirection = 1; // 向右
  693. this.changeDirectionTime = 0;
  694. } else if (currentPos.x >= rightBound && this.movingDirection > 0) {
  695. this.movingDirection = -1; // 向左
  696. this.changeDirectionTime = 0;
  697. }
  698. // 随机改变Y目标位置
  699. if (this.changeDirectionTime >= 3 + Math.random() * 2) {
  700. const centerY = (bounds.top + bounds.bottom) / 2;
  701. const range = (bounds.top - bounds.bottom) * 0.4;
  702. this.targetY = centerY + (Math.random() - 0.5) * range;
  703. this.changeDirectionTime = 0;
  704. }
  705. }
  706. this.moveTowardsTarget(deltaTime);
  707. }
  708. // 向目标移动
  709. private moveTowardsTarget(deltaTime: number) {
  710. // 获取当前速度(考虑狂暴加成和速度变化)
  711. const enemyComponent = this.getComponent(EnemyComponent);
  712. let currentSpeed = enemyComponent ? enemyComponent.getCurrentSpeed() : this.speed;
  713. // 应用速度变化配置,限制变化幅度防止过大跳跃
  714. if (this.speedVariation > 0) {
  715. const maxVariation = Math.min(this.speedVariation, currentSpeed * 0.3); // 限制变化不超过当前速度的30%
  716. const variation = (Math.random() - 0.5) * 2 * maxVariation;
  717. currentSpeed = Math.max(currentSpeed + variation, currentSpeed * 0.5); // 确保速度不会过低
  718. }
  719. // 统一使用主节点的世界坐标,与updateDrifting保持一致
  720. const currentPos = this.node.worldPosition.clone();
  721. let dir = new Vec3();
  722. // 水平移动方向
  723. dir.x = this.movingDirection;
  724. // 垂直移动方向 - 向目标Y位置移动
  725. const yDiff = this.targetY - currentPos.y;
  726. if (Math.abs(yDiff) > 10) { // 如果距离目标Y位置超过10像素
  727. dir.y = yDiff > 0 ? 0.5 : -0.5; // 缓慢向目标Y移动
  728. } else {
  729. dir.y = 0;
  730. }
  731. // 如果有具体的目标位置(墙体),优先移动到墙体
  732. if (this.targetPosition) {
  733. const targetDir = new Vec3();
  734. Vec3.subtract(targetDir, this.targetPosition.clone(), currentPos);
  735. if (targetDir.length() > 20) { // 距离墙体还有一定距离时
  736. targetDir.normalize();
  737. dir.x = targetDir.x * 0.7; // 70%朝向墙体
  738. dir.y = targetDir.y * 0.7;
  739. }
  740. } else if (this.targetFence) {
  741. const fencePos = this.targetFence.worldPosition.clone();
  742. const targetDir = new Vec3();
  743. Vec3.subtract(targetDir, fencePos, currentPos);
  744. if (targetDir.length() > 20) {
  745. targetDir.normalize();
  746. dir.x = targetDir.x * 0.7;
  747. dir.y = targetDir.y * 0.7;
  748. }
  749. }
  750. // 归一化方向向量
  751. if (dir.length() > 0) {
  752. dir.normalize();
  753. }
  754. // 应用摆动效果(根据配置)
  755. if (this.moveType === 'sway' && this.swingAmplitude > 0) {
  756. this.swayTime += deltaTime;
  757. const swayAmount = Math.sin(this.swayTime * this.swingFrequency) * this.swingAmplitude;
  758. // 计算垂直于移动方向的摆动
  759. const swayDir = new Vec3(-dir.y, dir.x, 0);
  760. swayDir.multiplyScalar(swayAmount / 100); // 将摆动幅度转换为合适的比例
  761. dir.add(swayDir);
  762. }
  763. // 限制单帧移动距离,防止突然跳跃
  764. const moveDistance = currentSpeed * deltaTime;
  765. const maxMoveDistance = Math.min(15, currentSpeed * 0.1); // 限制单帧最大移动距离
  766. const actualMoveDistance = Math.min(moveDistance, maxMoveDistance);
  767. const moveVector = dir.multiplyScalar(actualMoveDistance);
  768. const newPos = new Vec3();
  769. Vec3.add(newPos, currentPos, moveVector);
  770. // 统一使用主节点的setWorldPosition,与updateDrifting保持一致
  771. this.node.setWorldPosition(newPos);
  772. // 确保刚体速度为零,避免物理系统干扰
  773. this.stopRigidBodyMovement();
  774. }
  775. // 更新攻击逻辑
  776. private updateAttack(deltaTime: number) {
  777. // 获取敌人的攻击范围配置
  778. const enemyComponent = this.getComponent(EnemyComponent);
  779. const attackRange = enemyComponent ? enemyComponent.getAttackRange() : 30;
  780. // 无论近战还是远程攻击,在攻击状态下都停止移动
  781. const enemySprite = this.node.getChildByName('EnemySprite');
  782. if (enemySprite) {
  783. const rigidBody = enemySprite.getComponent(RigidBody2D);
  784. if (rigidBody) {
  785. // 停止物理移动
  786. rigidBody.linearVelocity = new Vec2(0, 0);
  787. }
  788. }
  789. this.attackTimer -= deltaTime;
  790. if (this.attackTimer <= 0) {
  791. // 执行攻击
  792. this.performAttack();
  793. // 重置攻击计时器
  794. this.attackTimer = this.attackInterval;
  795. } else {
  796. // 攻击冷却期间播放待机动画(只有在不播放攻击动画时)
  797. if (!this.isPlayingAttackAnimation) {
  798. this.playIdleAnimationSafe();
  799. }
  800. }
  801. }
  802. // 待机状态更新(停止移动但可以攻击)
  803. private updateIdle(deltaTime: number) {
  804. // 在待机状态下停止移动
  805. const enemySprite = this.node.getChildByName('EnemySprite');
  806. if (enemySprite) {
  807. const rigidBody = enemySprite.getComponent(RigidBody2D);
  808. if (rigidBody) {
  809. // 停止物理移动
  810. rigidBody.linearVelocity = new Vec2(0, 0);
  811. }
  812. }
  813. this.attackTimer -= deltaTime;
  814. if (this.attackTimer <= 0) {
  815. // 执行攻击
  816. this.performAttack();
  817. // 重置攻击计时器
  818. this.attackTimer = this.attackInterval;
  819. } else {
  820. // 攻击冷却期间播放待机动画(只有在不播放攻击动画时)
  821. if (!this.isPlayingAttackAnimation) {
  822. this.playIdleAnimationSafe();
  823. }
  824. }
  825. }
  826. // 执行攻击
  827. private performAttack() {
  828. if (!this.controller) {
  829. return;
  830. }
  831. const enemyComponent = this.getComponent(EnemyComponent);
  832. const attackType = enemyComponent ? enemyComponent.getAttackType() : 'melee';
  833. // 播放攻击音效
  834. EnemyAudio.playAttackSound(this.enemyConfig);
  835. // 根据攻击类型执行不同的攻击逻辑
  836. if (attackType === 'ranged' || attackType === 'projectile' || attackType.includes('projectile')) {
  837. console.log(`[EnemyInstance] 执行远程攻击,攻击类型: ${attackType}`);
  838. this.performRangedAttack();
  839. } else {
  840. // 近战攻击:播放攻击动画,动画结束后造成伤害
  841. console.log(`[EnemyInstance] 执行近战攻击,攻击类型: ${attackType}`);
  842. this.playAttackAnimationWithDamage();
  843. }
  844. }
  845. // 执行远程攻击(投掷)
  846. private performRangedAttack() {
  847. const enemyComponent = this.getComponent(EnemyComponent);
  848. if (!enemyComponent) return;
  849. // 远程攻击也需要播放攻击动画,在动画即将结束时发射抛掷物
  850. this.playRangedAttackAnimationWithProjectile();
  851. }
  852. // 播放远程攻击动画并在动画结束时发射抛掷物
  853. private playRangedAttackAnimationWithProjectile() {
  854. if (!this.skeleton) {
  855. // 如果没有骨骼动画,直接发射抛掷物
  856. this.fireProjectile();
  857. return;
  858. }
  859. const enemyComp = this.getComponent('EnemyComponent') as any;
  860. const anims = enemyComp?.getAnimations ? enemyComp.getAnimations() : {};
  861. const attackName = anims.attack ?? 'attack';
  862. const animation = this.skeleton.findAnimation(attackName);
  863. if (animation) {
  864. // 播放攻击动画(不循环)
  865. this.skeleton.setAnimation(0, attackName, false);
  866. this.currentAnimationName = attackName;
  867. this.isPlayingAttackAnimation = true;
  868. console.log(`[EnemyInstance] 播放远程攻击动画: ${attackName}`);
  869. // 动画切换后延迟更新血条位置,确保动画尺寸已生效
  870. this.scheduleOnce(() => {
  871. this.updateHPBarPosition();
  872. }, 0.1);
  873. // 获取动画时长并在动画即将结束时发射抛掷物
  874. const animationDuration = animation.duration;
  875. console.log(`[EnemyInstance] 远程攻击动画时长: ${animationDuration}秒`);
  876. // 在动画播放到90%时发射抛掷物,让抛掷物发射与动画同步
  877. const projectileFireTime = animationDuration * 0.9;
  878. // 在动画播放到90%时发射抛掷物
  879. this.scheduleOnce(() => {
  880. // 发射抛掷物
  881. this.fireProjectile();
  882. }, projectileFireTime);
  883. // 动画完成后根据当前状态切换动画
  884. this.scheduleOnce(() => {
  885. this.isPlayingAttackAnimation = false;
  886. if (this.state === EnemyState.IDLE || this.state === EnemyState.ATTACKING) {
  887. // 如果是待机状态或攻击状态,切换回待机动画
  888. this.playIdleAnimation();
  889. } else {
  890. // 其他状态切换回行走动画
  891. this.playWalkAnimation();
  892. }
  893. }, animationDuration);
  894. } else {
  895. // 如果找不到攻击动画,直接发射抛掷物
  896. this.fireProjectile();
  897. }
  898. }
  899. // 发射抛掷物
  900. private fireProjectile() {
  901. const enemyComponent = this.getComponent(EnemyComponent);
  902. if (!enemyComponent) return;
  903. const projectileType = enemyComponent.getProjectileType();
  904. const projectileSpeed = enemyComponent.getProjectileSpeed();
  905. const attackDamage = enemyComponent.getDamage();
  906. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 发射投掷物: ${projectileType}, 速度: ${projectileSpeed}, 伤害: ${attackDamage}`);
  907. // 获取EnemyProjectile组件来创建抛掷物
  908. if (this.controller) {
  909. console.log('[EnemyInstance] 找到controller,开始获取EnemyProjectile组件');
  910. const enemyProjectile = this.controller.getComponent('EnemyProjectile') as any;
  911. if (enemyProjectile) {
  912. console.log('[EnemyInstance] 找到EnemyProjectile组件,准备创建抛掷物');
  913. // 计算抛掷物发射方向(朝向目标墙体)
  914. let direction = new Vec3(0, -1, 0); // 默认向下
  915. if (this.collidedWall) {
  916. const currentPos = this.node.worldPosition;
  917. const wallPos = this.collidedWall.worldPosition;
  918. direction = wallPos.clone().subtract(currentPos).normalize();
  919. console.log('[EnemyInstance] 计算抛掷物方向,目标墙体:', this.collidedWall.name, '方向:', direction);
  920. } else {
  921. console.log('[EnemyInstance] 未找到目标墙体,使用默认方向');
  922. }
  923. // 创建抛掷物配置
  924. const projectileConfig = {
  925. damage: attackDamage,
  926. speed: projectileSpeed,
  927. direction: direction,
  928. projectileType: projectileType,
  929. startPosition: this.node.worldPosition.clone()
  930. };
  931. console.log('[EnemyInstance] 抛掷物配置:', projectileConfig);
  932. // 创建抛掷物
  933. const projectileNode = enemyProjectile.createProjectile(projectileConfig);
  934. if (projectileNode) {
  935. console.log(`[EnemyInstance] 成功创建抛掷物,目标方向: ${direction.toString()}`);
  936. } else {
  937. console.error(`[EnemyInstance] 创建抛掷物失败 - 远程敌人攻击无效`);
  938. }
  939. } else {
  940. console.error(`[EnemyInstance] 未找到EnemyProjectile组件 - 远程敌人攻击无效`);
  941. }
  942. } else {
  943. console.error(`[EnemyInstance] 没有controller引用 - 远程敌人攻击无效`);
  944. }
  945. }
  946. // 播放行走动画
  947. private playWalkAnimation() {
  948. if (!this.skeleton) return;
  949. const enemyComp = this.getComponent('EnemyComponent') as any;
  950. const anims = enemyComp?.getAnimations ? enemyComp.getAnimations() : {};
  951. const walkName = anims.walk ?? 'walk';
  952. const idleName = anims.idle ?? 'idle';
  953. let animationToPlay = '';
  954. if (this.skeleton.findAnimation(walkName)) {
  955. animationToPlay = walkName;
  956. } else if (this.skeleton.findAnimation(idleName)) {
  957. animationToPlay = idleName;
  958. }
  959. if (animationToPlay && this.currentAnimationName !== animationToPlay) {
  960. this.skeleton.setAnimation(0, animationToPlay, true);
  961. this.currentAnimationName = animationToPlay;
  962. this.isPlayingAttackAnimation = false;
  963. console.log(`[EnemyInstance] 播放行走动画: ${animationToPlay}`);
  964. }
  965. // 动画切换后延迟更新血条位置,确保动画尺寸已生效
  966. this.scheduleOnce(() => {
  967. this.updateHPBarPosition();
  968. }, 0.1);
  969. }
  970. // 播放攻击动画
  971. private playAttackAnimation() {
  972. if (!this.skeleton) return;
  973. const enemyComp2 = this.getComponent('EnemyComponent') as any;
  974. const anims2 = enemyComp2?.getAnimations ? enemyComp2.getAnimations() : {};
  975. const attackName = anims2.attack ?? 'attack';
  976. if (this.skeleton.findAnimation(attackName) && this.currentAnimationName !== attackName) {
  977. this.skeleton.setAnimation(0, attackName, true);
  978. this.currentAnimationName = attackName;
  979. this.isPlayingAttackAnimation = true;
  980. console.log(`[EnemyInstance] 播放攻击动画: ${attackName}`);
  981. }
  982. // 动画切换后延迟更新血条位置,确保动画尺寸已生效
  983. this.scheduleOnce(() => {
  984. this.updateHPBarPosition();
  985. }, 0.1);
  986. }
  987. // 播放待机动画
  988. private playIdleAnimation() {
  989. if (!this.skeleton) return;
  990. const enemyComp = this.getComponent('EnemyComponent') as any;
  991. const anims = enemyComp?.getAnimations ? enemyComp.getAnimations() : {};
  992. const idleName = anims.idle ?? 'idle';
  993. if (this.skeleton.findAnimation(idleName) && this.currentAnimationName !== idleName) {
  994. this.skeleton.setAnimation(0, idleName, true);
  995. this.currentAnimationName = idleName;
  996. this.isPlayingAttackAnimation = false;
  997. console.log(`[EnemyInstance] 播放待机动画: ${idleName}`);
  998. }
  999. // 动画切换后延迟更新血条位置,确保动画尺寸已生效
  1000. this.scheduleOnce(() => {
  1001. this.updateHPBarPosition();
  1002. }, 0.1);
  1003. }
  1004. // 安全播放待机动画(避免重复调用)
  1005. private playIdleAnimationSafe() {
  1006. if (!this.skeleton) return;
  1007. const enemyComp = this.getComponent('EnemyComponent') as any;
  1008. const anims = enemyComp?.getAnimations ? enemyComp.getAnimations() : {};
  1009. const idleName = anims.idle ?? 'idle';
  1010. // 只有当前不是待机动画时才播放
  1011. if (this.skeleton.findAnimation(idleName) && this.currentAnimationName !== idleName && !this.isPlayingAttackAnimation) {
  1012. this.skeleton.setAnimation(0, idleName, true);
  1013. this.currentAnimationName = idleName;
  1014. console.log(`[EnemyInstance] 安全播放待机动画: ${idleName}`);
  1015. // 动画切换后延迟更新血条位置,确保动画尺寸已生效
  1016. this.scheduleOnce(() => {
  1017. this.updateHPBarPosition();
  1018. }, 0.1);
  1019. }
  1020. }
  1021. // 播放攻击动画并在动画结束时造成伤害
  1022. private playAttackAnimationWithDamage() {
  1023. if (!this.skeleton) {
  1024. // 如果没有骨骼动画,直接造成伤害
  1025. this.dealDamageToWall();
  1026. return;
  1027. }
  1028. const enemyComp = this.getComponent('EnemyComponent') as any;
  1029. const anims = enemyComp?.getAnimations ? enemyComp.getAnimations() : {};
  1030. const attackName = anims.attack ?? 'attack';
  1031. const animation = this.skeleton.findAnimation(attackName);
  1032. if (animation) {
  1033. // 播放攻击动画(不循环)
  1034. this.skeleton.setAnimation(0, attackName, false);
  1035. this.currentAnimationName = attackName;
  1036. this.isPlayingAttackAnimation = true;
  1037. console.log(`[EnemyInstance] 播放近战攻击动画: ${attackName}`);
  1038. // 动画切换后延迟更新血条位置,确保动画尺寸已生效
  1039. this.scheduleOnce(() => {
  1040. this.updateHPBarPosition();
  1041. }, 0.1);
  1042. // 获取动画时长并在动画结束时造成伤害
  1043. const animationDuration = animation.duration;
  1044. console.log(`[EnemyInstance] 攻击动画时长: ${animationDuration}秒`);
  1045. // 使用定时器在动画结束时造成伤害
  1046. this.scheduleOnce(() => {
  1047. this.dealDamageToWall();
  1048. this.isPlayingAttackAnimation = false;
  1049. // 动画完成后根据当前状态切换动画
  1050. if (this.state === EnemyState.IDLE || this.state === EnemyState.ATTACKING) {
  1051. // 如果是待机状态或攻击状态,切换回待机动画
  1052. this.playIdleAnimation();
  1053. } else {
  1054. // 其他状态切换回行走动画
  1055. this.playWalkAnimation();
  1056. }
  1057. }, animationDuration);
  1058. } else {
  1059. // 如果找不到攻击动画,直接造成伤害
  1060. this.dealDamageToWall();
  1061. }
  1062. }
  1063. // 对墙体造成伤害
  1064. private dealDamageToWall() {
  1065. if (this.controller) {
  1066. // 获取当前攻击力(考虑狂暴加成)
  1067. const enemyComponent = this.getComponent(EnemyComponent);
  1068. const currentAttackPower = enemyComponent ? enemyComponent.getCurrentAttackPower() : this.attackPower;
  1069. // 检查是否需要触发画面震动
  1070. if (enemyComponent && enemyComponent.getCausesWallShake()) {
  1071. const attackType = enemyComponent.getAttackType();
  1072. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 攻击墙体,触发画面震动,攻击类型: ${attackType}`);
  1073. // 根据攻击类型触发对应的震动效果
  1074. ScreenShakeManager.getInstance().playShakeByAttackType(attackType);
  1075. }
  1076. // 使用统一的事件机制触发墙体伤害
  1077. EventBus.getInstance().emit(GameEvents.ENEMY_DAMAGE_WALL, {
  1078. damage: currentAttackPower,
  1079. source: this.node
  1080. });
  1081. }
  1082. }
  1083. private playDeathAnimationAndDestroy() {
  1084. console.log(`[EnemyInstance] 开始播放死亡动画并销毁`);
  1085. // 播放死亡音效
  1086. EnemyAudio.playDeathSound(this.enemyConfig);
  1087. if (this.skeleton) {
  1088. const enemyComp = this.getComponent('EnemyComponent') as any;
  1089. const anims = enemyComp?.getAnimations ? enemyComp.getAnimations() : {};
  1090. const deathName = anims.dead ?? 'dead';
  1091. if (this.skeleton.findAnimation(deathName)) {
  1092. this.skeleton.setAnimation(0, deathName, false);
  1093. // 销毁节点在动画完毕后
  1094. this.skeleton.setCompleteListener(() => {
  1095. this.node.destroy();
  1096. });
  1097. return;
  1098. }
  1099. }
  1100. this.node.destroy();
  1101. }
  1102. private spawnCoin() {
  1103. const ctrl = this.controller as any; // EnemyController
  1104. if (!ctrl?.coinPrefab) return;
  1105. // 获取配置中的掉落金币数量
  1106. const dropCoinsCount = this.getDropCoins();
  1107. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 掉落 ${dropCoinsCount} 个金币`);
  1108. // 根据配置掉落相应数量的金币
  1109. for (let i = 0; i < dropCoinsCount; i++) {
  1110. const coin = instantiate(ctrl.coinPrefab);
  1111. find('Canvas')!.addChild(coin); // 放到 UI 层
  1112. const pos = new Vec3();
  1113. this.node.getWorldPosition(pos); // 取死亡敌人的世界坐标
  1114. // 如果掉落多个金币,添加一些随机偏移避免重叠
  1115. if (dropCoinsCount > 1) {
  1116. const offsetX = (Math.random() - 0.5) * 40; // 随机X偏移 -20到20
  1117. const offsetY = (Math.random() - 0.5) * 40; // 随机Y偏移 -20到20
  1118. pos.x += offsetX;
  1119. pos.y += offsetY;
  1120. }
  1121. coin.worldPosition = pos; // 金币就在敌人身上出现
  1122. }
  1123. }
  1124. /**
  1125. * 暂停敌人
  1126. */
  1127. public pause(): void {
  1128. this.isPaused = true;
  1129. // 暂停物理系统
  1130. const enemySprite = this.node.getChildByName('EnemySprite');
  1131. if (enemySprite) {
  1132. const rigidBody = enemySprite.getComponent(RigidBody2D);
  1133. if (rigidBody) {
  1134. // 保存当前速度并停止物理移动
  1135. (this as any).savedVelocity = rigidBody.linearVelocity;
  1136. rigidBody.linearVelocity = new Vec2(0, 0);
  1137. }
  1138. }
  1139. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 已暂停`);
  1140. }
  1141. /**
  1142. * 恢复敌人
  1143. */
  1144. public resume(): void {
  1145. this.isPaused = false;
  1146. // 恢复物理系统
  1147. const enemySprite = this.node.getChildByName('EnemySprite');
  1148. if (enemySprite) {
  1149. const rigidBody = enemySprite.getComponent(RigidBody2D);
  1150. if (rigidBody && (this as any).savedVelocity) {
  1151. // 恢复之前保存的速度
  1152. rigidBody.linearVelocity = (this as any).savedVelocity;
  1153. (this as any).savedVelocity = null;
  1154. }
  1155. }
  1156. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 已恢复`);
  1157. }
  1158. /**
  1159. * 检查是否暂停
  1160. */
  1161. public isPausedState(): boolean {
  1162. return this.isPaused;
  1163. }
  1164. /**
  1165. * 获取敌人当前状态
  1166. */
  1167. public getState(): EnemyState {
  1168. return this.state;
  1169. }
  1170. /**
  1171. * 检查敌人是否处于漂移状态
  1172. */
  1173. public isDrifting(): boolean {
  1174. return this.state === EnemyState.DRIFTING;
  1175. }
  1176. /**
  1177. * 重置血条状态(满血并隐藏)
  1178. */
  1179. public resetHealthBar(): void {
  1180. if (this.hpBarAnimation) {
  1181. this.hpBarAnimation.resetToFullAndHide();
  1182. } else {
  1183. // 备用方案:直接隐藏血条
  1184. const hpBar = this.node.getChildByName('HPBar');
  1185. if (hpBar) {
  1186. hpBar.active = false;
  1187. const progressBar = hpBar.getComponent(ProgressBar);
  1188. if (progressBar) {
  1189. progressBar.progress = 1.0;
  1190. }
  1191. }
  1192. }
  1193. }
  1194. /**
  1195. * 尝试格挡攻击
  1196. */
  1197. private tryBlock(): boolean {
  1198. const enemyComponent = this.getComponent(EnemyComponent);
  1199. if (!enemyComponent) return false;
  1200. const blockChance = enemyComponent.getBlockChance();
  1201. // 将百分比值转换为0-1范围(例如:30.0 -> 0.3)
  1202. const blockChanceNormalized = blockChance / 100.0;
  1203. return Math.random() < blockChanceNormalized;
  1204. }
  1205. /**
  1206. * 基于距离检查墙体碰撞和攻击范围
  1207. */
  1208. private checkWallCollisionByDistance(): void {
  1209. if (this.state === EnemyState.ATTACKING || this.state === EnemyState.IDLE) {
  1210. return; // 已经在攻击状态或待机状态,不需要再检查
  1211. }
  1212. const enemySprite = this.node.getChildByName('EnemySprite');
  1213. if (!enemySprite) return;
  1214. const currentPos = this.node.worldPosition;
  1215. // 获取敌人的攻击范围配置
  1216. const enemyComponent = this.getComponent(EnemyComponent);
  1217. const attackRange = enemyComponent ? enemyComponent.getAttackRange() : 30;
  1218. // 根据攻击范围确定检测距离和攻击类型
  1219. let detectionDistance: number;
  1220. let isRangedAttack: boolean;
  1221. if (attackRange <= 0) {
  1222. // 近战攻击:需要接近墙体
  1223. detectionDistance = 30;
  1224. isRangedAttack = false;
  1225. } else {
  1226. // 远程攻击:在攻击范围内就开始攻击
  1227. detectionDistance = attackRange;
  1228. isRangedAttack = true;
  1229. }
  1230. // 获取所有墙体节点进行距离检测
  1231. const wallNodes = this.getAllWallNodes();
  1232. for (const wallNode of wallNodes) {
  1233. const wallPos = wallNode.worldPosition;
  1234. let distance: number;
  1235. const ui = wallNode.getComponent(UITransform);
  1236. if (ui) {
  1237. const width = ui.contentSize.width * wallNode.scale.x;
  1238. const height = ui.contentSize.height * wallNode.scale.y;
  1239. const halfW = width / 2;
  1240. const halfH = height / 2;
  1241. const dx = Math.max(Math.abs(currentPos.x - wallPos.x) - halfW, 0);
  1242. const dy = Math.max(Math.abs(currentPos.y - wallPos.y) - halfH, 0);
  1243. distance = Math.sqrt(dx * dx + dy * dy);
  1244. } else {
  1245. distance = Vec3.distance(currentPos, wallPos);
  1246. }
  1247. if (distance <= detectionDistance) {
  1248. const attackTypeStr = isRangedAttack ? '远程攻击' : '近战攻击';
  1249. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 进入${attackTypeStr}范围,距离墙体 ${wallNode.name}: ${distance.toFixed(2)},攻击范围: ${attackRange}`);
  1250. if (isRangedAttack) {
  1251. // 远程攻击:切换到待机状态,停止移动并轮流播放攻击和待机动画
  1252. this.state = EnemyState.IDLE;
  1253. this.attackTimer = 0; // 立即开始攻击
  1254. this.collidedWall = wallNode; // 记录目标墙体
  1255. // 停止移动
  1256. this.stopRigidBodyMovement();
  1257. // 切换到待机动画
  1258. this.playIdleAnimation();
  1259. console.log(`[EnemyInstance] 远程敌人 ${this.getEnemyName()} 进入待机状态,停止移动并准备攻击`);
  1260. } else {
  1261. // 近战攻击:切换到待机状态,停止移动
  1262. this.state = EnemyState.IDLE;
  1263. this.attackTimer = 0; // 立即开始攻击
  1264. this.collidedWall = wallNode; // 记录碰撞的墙体
  1265. // 停止移动
  1266. this.stopRigidBodyMovement();
  1267. // 切换到待机动画
  1268. this.playIdleAnimation();
  1269. }
  1270. return; // 找到一个墙体就停止检查
  1271. }
  1272. }
  1273. }
  1274. /**
  1275. * 获取所有墙体节点
  1276. */
  1277. private getAllWallNodes(): Node[] {
  1278. const wallNodes: Node[] = [];
  1279. // 通过EnemyController获取墙体节点
  1280. if (this.controller) {
  1281. if (this.controller.topFenceNode) {
  1282. wallNodes.push(this.controller.topFenceNode);
  1283. }
  1284. if (this.controller.bottomFenceNode) {
  1285. wallNodes.push(this.controller.bottomFenceNode);
  1286. }
  1287. }
  1288. // 备用方案:在场景中查找墙体节点
  1289. if (wallNodes.length === 0) {
  1290. const gameArea = find('Canvas/GameLevelUI/GameArea');
  1291. if (gameArea) {
  1292. for (let i = 0; i < gameArea.children.length; i++) {
  1293. const child = gameArea.children[i];
  1294. if (this.isWallNode(child)) {
  1295. wallNodes.push(child);
  1296. }
  1297. }
  1298. }
  1299. }
  1300. return wallNodes;
  1301. }
  1302. /**
  1303. * 停止刚体移动,确保物理系统不干扰代码控制的移动
  1304. */
  1305. private stopRigidBodyMovement(): void {
  1306. const enemySprite = this.node.getChildByName('EnemySprite');
  1307. if (enemySprite) {
  1308. const rigidBody = enemySprite.getComponent(RigidBody2D);
  1309. if (rigidBody) {
  1310. rigidBody.linearVelocity = new Vec2(0, 0);
  1311. }
  1312. }
  1313. }
  1314. /**
  1315. * 显示格挡效果
  1316. */
  1317. private showBlockEffect(): void {
  1318. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 格挡了攻击`);
  1319. // 显示"格挡"文字效果
  1320. this.showBlockText();
  1321. }
  1322. /**
  1323. * 显示格挡文字
  1324. */
  1325. private showBlockText(): void {
  1326. // 使用DamageNumberAni组件来显示格挡文字,就像显示伤害数字一样
  1327. if (this.controller) {
  1328. const damageAni = this.controller.getComponent(DamageNumberAni);
  1329. if (damageAni) {
  1330. damageAni.showBlockText(this.node.worldPosition);
  1331. } else {
  1332. console.warn('[EnemyInstance] 未找到DamageNumberAni组件,无法显示格挡文字');
  1333. }
  1334. } else {
  1335. console.warn('[EnemyInstance] 未找到controller引用,无法显示格挡文字');
  1336. }
  1337. }
  1338. /**
  1339. * 检查是否触发狂暴状态
  1340. */
  1341. private checkRageTrigger(): void {
  1342. const enemyComponent = this.getComponent(EnemyComponent);
  1343. if (!enemyComponent) return;
  1344. const healthPercent = this.health / this.maxHealth;
  1345. const rageTriggerThreshold = enemyComponent.getRageTriggerThreshold();
  1346. if (healthPercent <= rageTriggerThreshold && !enemyComponent.isInRage()) {
  1347. enemyComponent.triggerRage();
  1348. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 进入狂暴状态`);
  1349. }
  1350. }
  1351. // ===== 隐身相关方法 =====
  1352. /**
  1353. * 进入隐身状态
  1354. * @param duration 隐身持续时间(秒)
  1355. */
  1356. public enterStealth(duration: number = 5.0): void {
  1357. if (this.state === EnemyState.DEAD) {
  1358. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 已死亡,无法进入隐身状态`);
  1359. return; // 死亡状态下不能隐身
  1360. }
  1361. if (this.stealthActive) {
  1362. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 已经处于隐身状态,跳过重复进入`);
  1363. return; // 已经在隐身状态,不重复进入
  1364. }
  1365. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 进入隐身状态,持续时间: ${duration}秒,当前状态: ${this.state}`);
  1366. // 隐身为叠加标记,不改变行为状态
  1367. this.stealthActive = true;
  1368. this.stealthDuration = duration;
  1369. this.stealthTimer = 0;
  1370. // 应用隐身视觉效果
  1371. this.applyStealthVisualEffect();
  1372. // 直接更新攻击状态,确保不依赖事件监听顺序
  1373. const attackStateManager = EnemyAttackStateManager.getInstance();
  1374. attackStateManager.setEnemyAttackable(this.node, false);
  1375. console.log(`[EnemyInstance] 已直接将 ${this.getEnemyName()} 标记为不可攻击(隐身中)`);
  1376. // 触发隐身事件
  1377. EventBus.getInstance().emit(GameEvents.ENEMY_STEALTH_START, {
  1378. enemy: this,
  1379. duration: duration
  1380. });
  1381. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 隐身状态激活,透明度已调整`);
  1382. }
  1383. /**
  1384. * 退出隐身状态
  1385. */
  1386. public exitStealth(): void {
  1387. if (!this.stealthActive) {
  1388. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 不在隐身状态(当前状态: ${this.state}),无需退出`);
  1389. return; // 不在隐身状态
  1390. }
  1391. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 退出隐身状态`);
  1392. // 结束隐身:行为状态不变,仅关闭叠加标记
  1393. this.stealthActive = false;
  1394. this.stealthTimer = 0;
  1395. this.stealthDuration = 0;
  1396. // 移除隐身视觉效果
  1397. this.removeStealthVisualEffect();
  1398. // 触发隐身结束事件
  1399. EventBus.getInstance().emit(GameEvents.ENEMY_STEALTH_END, {
  1400. enemy: this
  1401. });
  1402. // 直接更新攻击状态,确保不依赖事件监听顺序
  1403. const attackStateManager2 = EnemyAttackStateManager.getInstance();
  1404. attackStateManager2.setEnemyAttackable(this.node, true);
  1405. console.log(`[EnemyInstance] 已直接将 ${this.getEnemyName()} 标记为可攻击(退出隐身)`);
  1406. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 隐身状态结束,透明度已恢复`);
  1407. // 若具备隐身能力,按照冷却时间再次尝试进入隐身(在回调中再检查是否已死亡/节点失效)
  1408. if (this.hasStealthAbility) {
  1409. this.scheduleOnce(() => {
  1410. if (!this.node || !this.node.isValid) return;
  1411. if (this.state === EnemyState.DEAD) return;
  1412. this.enterStealth(this.stealthCooldown);
  1413. }, this.stealthCooldown);
  1414. }
  1415. }
  1416. /**
  1417. * 检查是否处于隐身状态
  1418. * @returns 是否隐身
  1419. */
  1420. public isStealth(): boolean {
  1421. return this.stealthActive;
  1422. }
  1423. /**
  1424. * 应用隐身视觉效果
  1425. */
  1426. private applyStealthVisualEffect(): void {
  1427. const enemySprite = this.node.getChildByName('EnemySprite');
  1428. if (!enemySprite) {
  1429. console.warn('[EnemyInstance] 未找到EnemySprite节点,无法应用隐身视觉效果');
  1430. return;
  1431. }
  1432. // 保存原始透明度
  1433. const uiOpacity = enemySprite.getComponent(UIOpacity);
  1434. if (uiOpacity) {
  1435. this.originalOpacity = uiOpacity.opacity;
  1436. // 使用缓动动画降低透明度到30%
  1437. tween(uiOpacity)
  1438. .to(0.5, { opacity: 77 }) // 30% 透明度 (255 * 0.3 ≈ 77)
  1439. .start();
  1440. } else {
  1441. // 如果没有UIOpacity组件,添加一个
  1442. const newUIOpacity = enemySprite.addComponent(UIOpacity);
  1443. this.originalOpacity = 255;
  1444. newUIOpacity.opacity = 255;
  1445. tween(newUIOpacity)
  1446. .to(0.5, { opacity: 77 })
  1447. .start();
  1448. }
  1449. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 应用隐身视觉效果`);
  1450. }
  1451. /**
  1452. * 移除隐身视觉效果
  1453. */
  1454. private removeStealthVisualEffect(): void {
  1455. const enemySprite = this.node.getChildByName('EnemySprite');
  1456. if (!enemySprite) {
  1457. console.warn('[EnemyInstance] 未找到EnemySprite节点,无法移除隐身视觉效果');
  1458. return;
  1459. }
  1460. const uiOpacity = enemySprite.getComponent(UIOpacity);
  1461. if (uiOpacity) {
  1462. // 使用缓动动画恢复原始透明度
  1463. tween(uiOpacity)
  1464. .to(0.5, { opacity: this.originalOpacity })
  1465. .start();
  1466. }
  1467. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 移除隐身视觉效果`);
  1468. }
  1469. /**
  1470. * 更新隐身状态
  1471. * @param deltaTime 帧时间间隔
  1472. */
  1473. private updateStealth(deltaTime: number): void {
  1474. if (!this.stealthActive) {
  1475. return;
  1476. }
  1477. this.stealthTimer += deltaTime;
  1478. // 检查隐身时间是否结束
  1479. if (this.stealthTimer >= this.stealthDuration) {
  1480. this.exitStealth();
  1481. }
  1482. }
  1483. }