EnemyInstance.ts 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312
  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. const { ccclass, property } = _decorator;
  9. // 前向声明EnemyController接口,避免循环引用
  10. interface EnemyControllerType {
  11. gameBounds: {
  12. left: number;
  13. right: number;
  14. top: number;
  15. bottom: number;
  16. };
  17. topFenceNode: Node;
  18. bottomFenceNode: Node;
  19. damageWall: (damage: number) => void;
  20. getComponent: (componentType: any) => any;
  21. }
  22. // 敌人状态枚举
  23. enum EnemyState {
  24. DRIFTING, // 漂移中(从生成位置移动到线上)
  25. MOVING, // 移动中
  26. IDLE, // 待机中(碰到墙体后停止移动但可以攻击)
  27. ATTACKING, // 攻击中
  28. DEAD // 死亡
  29. }
  30. // 单个敌人实例的组件
  31. @ccclass('EnemyInstance')
  32. export class EnemyInstance extends Component {
  33. // 敌人属性(从配置文件读取)
  34. public health: number = 0;
  35. public maxHealth: number = 0;
  36. public speed: number = 0;
  37. public attackPower: number = 0;
  38. // 敌人配置ID
  39. public enemyId: string = '';
  40. // 敌人配置数据
  41. private enemyConfig: any = null;
  42. // 敌人配置数据库
  43. private static enemyDatabase: any = null;
  44. // === 新增属性 ===
  45. /** 是否从上方生成 */
  46. public spawnFromTop: boolean = true;
  47. /** 目标 Fence 节点(TopFence / BottomFence) */
  48. public targetFence: Node | null = null;
  49. /** 目标位置(墙体上的随机点) */
  50. public targetPosition: Vec3 | null = null;
  51. /** 漂移目标位置(线上的位置) */
  52. public driftTargetPosition: Vec3 | null = null;
  53. // 移动相关属性
  54. public movingDirection: number = 1; // 1: 向右, -1: 向左
  55. public targetY: number = 0; // 目标Y位置
  56. public changeDirectionTime: number = 0; // 下次改变方向的时间
  57. // 移动配置属性(从配置文件读取)
  58. public movementPattern: string = 'direct'; // 移动模式:direct, patrol等
  59. public moveType: string = 'straight'; // 移动类型:straight, sway等
  60. public patrolRange: number = 100; // 巡逻范围
  61. public rotationSpeed: number = 90; // 旋转速度
  62. public speedVariation: number = 0; // 速度变化
  63. public swingAmplitude: number = 0; // 摆动幅度
  64. public swingFrequency: number = 1; // 摆动频率
  65. // 摆动移动相关属性
  66. private swayTime: number = 0; // 摆动时间累计
  67. private basePosition: Vec3 = new Vec3(); // 基础位置(摆动的中心点)
  68. private swayOffset: Vec3 = new Vec3(); // 当前摆动偏移
  69. // 攻击属性
  70. public attackInterval: number = 0; // 攻击间隔(秒),从配置文件读取
  71. private attackTimer: number = 0;
  72. // 对控制器的引用
  73. public controller: EnemyControllerType = null;
  74. // 敌人当前状态
  75. private state: EnemyState = EnemyState.DRIFTING;
  76. // 游戏区域中心
  77. private gameAreaCenter: Vec3 = new Vec3();
  78. // 碰撞的墙体
  79. private collidedWall: Node = null;
  80. // 骨骼动画组件
  81. private skeleton: sp.Skeleton | null = null;
  82. // 血条动画组件
  83. private hpBarAnimation: HPBarAnimation | null = null;
  84. // 暂停状态标记
  85. private isPaused: boolean = false;
  86. start() {
  87. // 初始化敌人
  88. this.initializeEnemy();
  89. }
  90. // 静态方法:加载敌人配置数据库
  91. public static async loadEnemyDatabase(): Promise<void> {
  92. if (EnemyInstance.enemyDatabase) return;
  93. try {
  94. EnemyInstance.enemyDatabase = await JsonConfigLoader.getInstance().loadConfig('enemies');
  95. if (!EnemyInstance.enemyDatabase) {
  96. throw new Error('敌人配置文件内容为空');
  97. }
  98. } catch (error) {
  99. console.error('[EnemyInstance] 加载敌人配置失败:', error);
  100. throw error;
  101. }
  102. }
  103. // 设置敌人配置
  104. public setEnemyConfig(enemyId: string): void {
  105. this.enemyId = enemyId;
  106. if (!EnemyInstance.enemyDatabase) {
  107. console.error('[EnemyInstance] 敌人配置数据库未加载');
  108. return;
  109. }
  110. // 从数据库中查找敌人配置
  111. // 修复:enemies.json是直接的数组结构,不需要.enemies包装
  112. const enemies = EnemyInstance.enemyDatabase;
  113. this.enemyConfig = enemies.find((enemy: any) => enemy.id === enemyId);
  114. if (!this.enemyConfig) {
  115. console.error(`[EnemyInstance] 未找到敌人配置: ${enemyId}`);
  116. return;
  117. }
  118. // 应用配置到敌人属性
  119. this.applyEnemyConfig();
  120. }
  121. // 应用敌人配置到属性
  122. private applyEnemyConfig(): void {
  123. if (!this.enemyConfig) return;
  124. // 从stats节点读取基础属性
  125. const stats = this.enemyConfig.stats || {};
  126. this.health = stats.health || 30;
  127. this.maxHealth = stats.maxHealth || this.health;
  128. // 从movement节点读取移动配置
  129. const movement = this.enemyConfig.movement || {};
  130. this.speed = movement.speed || 50;
  131. // 读取移动模式配置
  132. this.movementPattern = movement.pattern || 'direct';
  133. this.moveType = movement.moveType || 'straight';
  134. this.patrolRange = movement.patrolRange || 100;
  135. this.rotationSpeed = movement.rotationSpeed || 90;
  136. this.speedVariation = movement.speedVariation || 0;
  137. // 读取摆动配置
  138. this.swingAmplitude = movement.swingAmplitude || 0;
  139. this.swingFrequency = movement.swingFrequency || 1;
  140. // 从combat节点读取攻击力
  141. const combat = this.enemyConfig.combat || {};
  142. this.attackPower = combat.attackDamage || 10;
  143. // 设置攻击间隔
  144. this.attackInterval = combat.attackCooldown || 2.0;
  145. console.log(`[EnemyInstance] 应用配置 ${this.enemyId}: 移动模式=${this.movementPattern}, 移动类型=${this.moveType}, 速度=${this.speed}, 摆动幅度=${this.swingAmplitude}`);
  146. }
  147. // 获取敌人配置信息
  148. public getEnemyConfig(): any {
  149. return this.enemyConfig;
  150. }
  151. // 获取敌人名称
  152. public getEnemyName(): string {
  153. return this.enemyConfig?.name || '未知敌人';
  154. }
  155. // 获取敌人类型
  156. public getEnemyType(): string {
  157. return this.enemyConfig?.type || 'basic';
  158. }
  159. // 获取敌人稀有度
  160. public getEnemyRarity(): string {
  161. return this.enemyConfig?.rarity || 'common';
  162. }
  163. // 获取金币奖励
  164. public getGoldReward(): number {
  165. return this.enemyConfig?.goldReward || 1;
  166. }
  167. // 获取掉落金币数量
  168. public getDropCoins(): number {
  169. const stats = this.enemyConfig?.stats || {};
  170. return stats.dropCoins || 1;
  171. }
  172. // 初始化敌人
  173. private initializeEnemy() {
  174. // 确保血量正确设置
  175. if (this.maxHealth > 0) {
  176. this.health = this.maxHealth;
  177. }
  178. this.state = EnemyState.DRIFTING; // 从漂移状态开始
  179. // 只有在攻击间隔未设置时才使用默认值
  180. if (this.attackInterval <= 0) {
  181. this.attackInterval = 2.0; // 默认攻击间隔
  182. }
  183. this.attackTimer = 0;
  184. // 初始化血条动画组件
  185. this.initializeHPBarAnimation();
  186. // 初始化骨骼动画组件 - 从EnemySprite子节点获取
  187. const enemySprite = this.node.getChildByName('EnemySprite');
  188. if (enemySprite) {
  189. this.skeleton = enemySprite.getComponent(sp.Skeleton);
  190. } else {
  191. console.error('[EnemyInstance] 未找到EnemySprite子节点,无法获取骨骼动画组件');
  192. }
  193. this.playWalkAnimation();
  194. // 计算游戏区域中心
  195. this.calculateGameAreaCenter();
  196. // 初始化碰撞检测
  197. this.setupCollider();
  198. }
  199. // 设置碰撞器
  200. setupCollider() {
  201. // 从EnemySprite子节点获取碰撞器组件
  202. const enemySprite = this.node.getChildByName('EnemySprite');
  203. if (!enemySprite) {
  204. console.error('[EnemyInstance] 未找到EnemySprite子节点,无法设置碰撞器组件');
  205. return;
  206. }
  207. // 检查EnemySprite节点是否有碰撞器
  208. let collider = enemySprite.getComponent(Collider2D);
  209. if (!collider) {
  210. console.warn(`[EnemyInstance] 敌人节点 ${this.node.name} 的EnemySprite子节点没有碰撞器组件`);
  211. return;
  212. }
  213. // 确保有RigidBody2D组件,这对于碰撞检测是必需的
  214. let rigidBody = enemySprite.getComponent(RigidBody2D);
  215. if (!rigidBody) {
  216. console.log(`[EnemyInstance] 为敌人EnemySprite节点添加RigidBody2D组件`);
  217. rigidBody = enemySprite.addComponent(RigidBody2D);
  218. }
  219. // 设置刚体属性
  220. if (rigidBody) {
  221. rigidBody.type = ERigidBody2DType.Dynamic; // 动态刚体
  222. rigidBody.enabledContactListener = true; // 启用碰撞监听
  223. rigidBody.gravityScale = 0; // 不受重力影响
  224. rigidBody.linearDamping = 0; // 无线性阻尼
  225. rigidBody.angularDamping = 0; // 无角阻尼
  226. rigidBody.allowSleep = false; // 不允许休眠
  227. rigidBody.fixedRotation = true; // 固定旋转
  228. }
  229. // 根据ContentSize自适应碰撞体大小
  230. this.adaptColliderToContentSize(collider);
  231. // 设置碰撞事件监听
  232. collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
  233. console.log(`[EnemyInstance] 敌人 ${this.node.name} 碰撞器设置完成,碰撞器启用: ${collider.enabled}, 刚体启用: ${rigidBody?.enabled}`);
  234. }
  235. /**
  236. * 根据ContentSize自适应碰撞体大小
  237. */
  238. private adaptColliderToContentSize(collider: Collider2D): void {
  239. // 从EnemySprite子节点获取UITransform组件
  240. const enemySprite = this.node.getChildByName('EnemySprite');
  241. if (!enemySprite) {
  242. console.error('[EnemyInstance] 未找到EnemySprite子节点,无法获取UITransform组件');
  243. return;
  244. }
  245. const uiTransform = enemySprite.getComponent(UITransform);
  246. if (!uiTransform) {
  247. console.warn(`[EnemyInstance] EnemySprite节点没有UITransform组件,无法自适应碰撞体大小`);
  248. return;
  249. }
  250. // 尝试获取骨骼动画组件来获取更准确的尺寸
  251. const skeleton = enemySprite.getComponent(sp.Skeleton);
  252. let actualWidth = uiTransform.contentSize.width;
  253. let actualHeight = uiTransform.contentSize.height;
  254. if (skeleton && skeleton.skeletonData) {
  255. // 如果有骨骼动画,尝试从骨骼数据获取尺寸信息
  256. try {
  257. // 使用骨骼数据的默认尺寸,或者根据动画缩放计算
  258. const skeletonData = skeleton.skeletonData;
  259. if (skeletonData) {
  260. // 获取骨骼的缩放比例
  261. const scaleX = Math.abs(skeleton.node.scale.x);
  262. const scaleY = Math.abs(skeleton.node.scale.y);
  263. // 使用合理的默认尺寸并应用缩放
  264. actualWidth = Math.max(60 * scaleX, uiTransform.contentSize.width * 0.8);
  265. actualHeight = Math.max(80 * scaleY, uiTransform.contentSize.height * 0.8);
  266. console.log(`[EnemyInstance] 使用骨骼动画尺寸 (scale: ${scaleX.toFixed(2)}, ${scaleY.toFixed(2)}): ${actualWidth.toFixed(2)} x ${actualHeight.toFixed(2)}`);
  267. } else {
  268. // 如果无法获取骨骼数据,使用默认尺寸
  269. actualWidth = Math.max(60, uiTransform.contentSize.width * 0.8);
  270. actualHeight = Math.max(80, uiTransform.contentSize.height * 0.8);
  271. console.log(`[EnemyInstance] 使用默认骨骼尺寸: ${actualWidth.toFixed(2)} x ${actualHeight.toFixed(2)}`);
  272. }
  273. } catch (error) {
  274. console.warn(`[EnemyInstance] 获取骨骼信息失败,使用默认尺寸:`, error);
  275. actualWidth = Math.max(60, uiTransform.contentSize.width * 0.8);
  276. actualHeight = Math.max(80, uiTransform.contentSize.height * 0.8);
  277. }
  278. }
  279. console.log(`[EnemyInstance] 敌人 ${this.node.name} 实际尺寸: ${actualWidth.toFixed(2)} x ${actualHeight.toFixed(2)}`);
  280. // 根据碰撞器类型设置大小
  281. if (collider instanceof BoxCollider2D) {
  282. // 方形碰撞器:使用实际尺寸,稍微缩小以避免过于敏感的碰撞
  283. const boxCollider = collider as BoxCollider2D;
  284. boxCollider.size.width = actualWidth * 0.9; // 缩小10%
  285. boxCollider.size.height = actualHeight * 0.9; // 缩小10%
  286. // 调整偏移量,使碰撞器居中对齐到EnemySprite
  287. const anchorPoint = uiTransform.anchorPoint;
  288. // 对于骨骼动画,通常锚点在底部中心(0.5, 0),需要向上偏移
  289. boxCollider.offset.x = 0; // X轴居中
  290. boxCollider.offset.y = actualHeight * (0.5 - anchorPoint.y) * 0.9; // Y轴根据锚点调整,并匹配缩放后的尺寸
  291. 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)})`);
  292. } else if (collider instanceof CircleCollider2D) {
  293. // 圆形碰撞器:使用实际尺寸的较小值作为直径,稍微缩小
  294. const circleCollider = collider as CircleCollider2D;
  295. const radius = Math.min(actualWidth, actualHeight) / 2 * 0.9; // 缩小10%
  296. circleCollider.radius = radius;
  297. // 调整偏移量,使碰撞器居中对齐到EnemySprite
  298. const anchorPoint = uiTransform.anchorPoint;
  299. // 对于骨骼动画,通常锚点在底部中心(0.5, 0),需要向上偏移
  300. circleCollider.offset.x = 0; // X轴居中
  301. circleCollider.offset.y = actualHeight * (0.5 - anchorPoint.y) * 0.9; // Y轴根据锚点调整,并匹配缩放后的尺寸
  302. 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)})`);
  303. } else {
  304. console.warn(`[EnemyInstance] 不支持的碰撞器类型: ${collider.constructor.name}`);
  305. }
  306. // 碰撞体调整完成后,同步更新血条位置
  307. this.updateHPBarPosition();
  308. }
  309. /**
  310. * 动态调整血条位置,使其始终显示在碰撞体上方
  311. */
  312. private updateHPBarPosition(): void {
  313. // 获取血条根节点
  314. const hpBarRoot = this.node.getChildByName('HPBar');
  315. if (!hpBarRoot) {
  316. console.warn('[EnemyInstance] 未找到HPBar节点,无法调整血条位置');
  317. return;
  318. }
  319. // 获取EnemySprite节点和其碰撞器
  320. const enemySprite = this.node.getChildByName('EnemySprite');
  321. if (!enemySprite) {
  322. console.error('[EnemyInstance] 未找到EnemySprite子节点,无法计算血条位置');
  323. return;
  324. }
  325. const collider = enemySprite.getComponent(Collider2D);
  326. const uiTransform = enemySprite.getComponent(UITransform);
  327. if (!collider || !uiTransform) {
  328. console.warn('[EnemyInstance] EnemySprite节点缺少必要组件,无法计算血条位置');
  329. return;
  330. }
  331. // 计算碰撞体的顶部位置
  332. let colliderTopY = 0;
  333. const anchorPoint = uiTransform.anchorPoint;
  334. if (collider instanceof BoxCollider2D) {
  335. const boxCollider = collider as BoxCollider2D;
  336. // 碰撞体顶部 = 碰撞体偏移Y + 碰撞体高度的一半
  337. colliderTopY = boxCollider.offset.y + boxCollider.size.height / 2;
  338. } else if (collider instanceof CircleCollider2D) {
  339. const circleCollider = collider as CircleCollider2D;
  340. // 碰撞体顶部 = 碰撞体偏移Y + 半径
  341. colliderTopY = circleCollider.offset.y + circleCollider.radius;
  342. }
  343. // 血条位置设置为碰撞体顶部上方一定距离
  344. const hpBarOffset = -35; // 血条距离碰撞体顶部的距离
  345. const hpBarY = colliderTopY + hpBarOffset;
  346. // 设置血条位置
  347. const hpBarTransform = hpBarRoot.getComponent(UITransform);
  348. if (hpBarTransform) {
  349. hpBarRoot.setPosition(0, hpBarY, 0);
  350. console.log(`[EnemyInstance] 血条位置已更新: Y = ${hpBarY.toFixed(2)} (碰撞体顶部: ${colliderTopY.toFixed(2)})`);
  351. }
  352. }
  353. // 碰撞开始事件
  354. onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
  355. const nodeName = otherCollider.node.name;
  356. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 碰撞检测 - 碰撞对象: ${nodeName}, 当前状态: ${EnemyState[this.state]}`);
  357. // 只有在移动状态下才能切换到攻击状态,避免重复触发
  358. if (this.state !== EnemyState.MOVING) {
  359. return;
  360. }
  361. // 检查是否碰到墙体(更严格的墙体识别)
  362. const isWall = this.isWallNode(otherCollider.node);
  363. if (isWall) {
  364. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 碰撞到墙体 ${nodeName},切换到待机状态`);
  365. this.state = EnemyState.IDLE;
  366. this.attackTimer = 0; // 立即开始攻击
  367. this.collidedWall = otherCollider.node; // 记录碰撞的墙体
  368. // 停止移动
  369. this.stopRigidBodyMovement();
  370. // 切换到待机动画
  371. this.playIdleAnimation();
  372. }
  373. }
  374. // 检查节点是否为墙体
  375. private isWallNode(node: Node): boolean {
  376. const nodeName = node.name.toLowerCase();
  377. // 检查节点名称是否包含墙体关键词
  378. const wallKeywords = ['wall', 'fence', 'jiguang', '墙', '围栏'];
  379. const isWallByName = wallKeywords.some(keyword => nodeName.includes(keyword));
  380. // 检查节点是否有Wall组件
  381. const hasWallComponent = node.getComponent('Wall') !== null;
  382. return isWallByName || hasWallComponent;
  383. }
  384. // 获取节点路径
  385. getNodePath(node: Node): string {
  386. let path = node.name;
  387. let current = node;
  388. while (current.parent) {
  389. current = current.parent;
  390. path = current.name + '/' + path;
  391. }
  392. return path;
  393. }
  394. // 计算游戏区域中心
  395. private calculateGameAreaCenter() {
  396. const gameArea = find('Canvas/GameLevelUI/GameArea');
  397. if (gameArea) {
  398. this.gameAreaCenter = gameArea.worldPosition;
  399. }
  400. }
  401. /**
  402. * 初始化血条动画组件
  403. */
  404. private initializeHPBarAnimation() {
  405. const hpBar = this.node.getChildByName('HPBar');
  406. if (hpBar) {
  407. // 查找红色和黄色血条节点
  408. const redBarNode = hpBar.getChildByName('RedBar');
  409. const yellowBarNode = hpBar.getChildByName('YellowBar');
  410. if (redBarNode && yellowBarNode) {
  411. // 添加血条动画组件
  412. this.hpBarAnimation = this.node.addComponent(HPBarAnimation);
  413. if (this.hpBarAnimation) {
  414. // 正确设置红色和黄色血条节点引用
  415. this.hpBarAnimation.redBarNode = redBarNode;
  416. this.hpBarAnimation.yellowBarNode = yellowBarNode;
  417. this.hpBarAnimation.hpBarRootNode = hpBar; // 设置HPBar根节点
  418. console.log(`[EnemyInstance] 血条动画组件已初始化`);
  419. }
  420. } else {
  421. console.warn(`[EnemyInstance] HPBar下未找到RedBar或YellowBar节点,RedBar: ${!!redBarNode}, YellowBar: ${!!yellowBarNode}`);
  422. }
  423. } else {
  424. console.warn(`[EnemyInstance] 未找到HPBar节点,无法初始化血条动画`);
  425. }
  426. }
  427. // 更新血量显示
  428. updateHealthDisplay(showBar: boolean = false) {
  429. // 确保血量值在有效范围内
  430. this.health = Math.max(0, Math.min(this.maxHealth, this.health));
  431. const healthProgress = this.maxHealth > 0 ? this.health / this.maxHealth : 0;
  432. console.log(`[EnemyInstance] 更新血量显示: ${this.health}/${this.maxHealth} (${(healthProgress * 100).toFixed(1)}%)`);
  433. // 使用血条动画组件更新血条
  434. if (this.hpBarAnimation) {
  435. this.hpBarAnimation.updateProgress(healthProgress, showBar);
  436. } else {
  437. // 备用方案:直接更新血条
  438. const hpBar = this.node.getChildByName('HPBar');
  439. if (hpBar) {
  440. const progressBar = hpBar.getComponent(ProgressBar);
  441. if (progressBar) {
  442. progressBar.progress = healthProgress;
  443. }
  444. // 根据showBar参数控制血条显示
  445. hpBar.active = showBar;
  446. }
  447. }
  448. // 更新血量数字
  449. const hpLabel = this.node.getChildByName('HPLabel');
  450. if (hpLabel) {
  451. const label = hpLabel.getComponent(Label);
  452. if (label) {
  453. // 显示整数血量值
  454. label.string = Math.ceil(this.health).toString();
  455. }
  456. }
  457. }
  458. // 受到伤害
  459. takeDamage(damage: number, isCritical: boolean = false) {
  460. // 如果已经死亡,不再处理伤害
  461. if (this.state === EnemyState.DEAD) {
  462. return;
  463. }
  464. // 确保伤害值为正数
  465. if (damage <= 0) {
  466. console.warn(`[EnemyInstance] 无效的伤害值: ${damage}`);
  467. return;
  468. }
  469. // 应用防御力减少伤害
  470. const enemyComponent = this.getComponent(EnemyComponent);
  471. const defense = enemyComponent ? enemyComponent.getDefense() : 0;
  472. let finalDamage = Math.max(1, damage - defense); // 至少造成1点伤害
  473. // 检查格挡
  474. if (enemyComponent && enemyComponent.canBlock() && this.tryBlock()) {
  475. finalDamage *= (1 - enemyComponent.getBlockDamageReduction());
  476. this.showBlockEffect();
  477. }
  478. // 计算新的血量,确保不会低于0
  479. const oldHealth = this.health;
  480. const newHealth = Math.max(0, this.health - finalDamage);
  481. const actualHealthLoss = oldHealth - newHealth; // 实际血量损失
  482. this.health = newHealth;
  483. // 检查是否触发狂暴
  484. this.checkRageTrigger();
  485. // 日志显示武器的真实伤害值,而不是血量差值
  486. console.log(`[EnemyInstance] 敌人受到伤害: ${damage} (武器伤害), 防御后伤害: ${finalDamage}, 实际血量损失: ${actualHealthLoss}, 剩余血量: ${this.health}/${this.maxHealth}`);
  487. // 受击音效已移除
  488. // 显示伤害数字动画(在敌人头顶)- 显示防御后的实际伤害
  489. // 优先使用EnemyController节点上的DamageNumberAni组件实例
  490. if (this.controller) {
  491. const damageAni = this.controller.getComponent(DamageNumberAni);
  492. if (damageAni) {
  493. damageAni.showDamageNumber(finalDamage, this.node.worldPosition, isCritical);
  494. } else {
  495. // 如果没有找到组件实例,使用静态方法作为备用
  496. DamageNumberAni.showDamageNumber(finalDamage, this.node.worldPosition, isCritical);
  497. }
  498. } else {
  499. // 如果没有controller引用,使用静态方法
  500. DamageNumberAni.showDamageNumber(finalDamage, this.node.worldPosition, isCritical);
  501. }
  502. // 更新血量显示和动画,受伤时显示血条
  503. this.updateHealthDisplay(true);
  504. // 如果血量低于等于0,销毁敌人
  505. if (this.health <= 0) {
  506. console.log(`[EnemyInstance] 敌人死亡,开始销毁流程`);
  507. this.state = EnemyState.DEAD;
  508. this.spawnCoin();
  509. // 进入死亡流程,禁用碰撞避免重复命中
  510. const enemySprite = this.node.getChildByName('EnemySprite');
  511. if (enemySprite) {
  512. const col = enemySprite.getComponent(Collider2D);
  513. if (col) col.enabled = false;
  514. }
  515. this.playDeathAnimationAndDestroy();
  516. }
  517. }
  518. onDestroy() {
  519. console.log(`[EnemyInstance] onDestroy 被调用,准备通知控制器`);
  520. // 通知控制器 & GameManager
  521. if (this.controller && typeof (this.controller as any).notifyEnemyDead === 'function') {
  522. // 检查控制器是否处于清理状态,避免在清理过程中触发游戏事件
  523. const isClearing = (this.controller as any).isClearing;
  524. if (isClearing) {
  525. console.log(`[EnemyInstance] 控制器处于清理状态,跳过死亡通知`);
  526. return;
  527. }
  528. console.log(`[EnemyInstance] 调用 notifyEnemyDead`);
  529. (this.controller as any).notifyEnemyDead(this.node);
  530. } else {
  531. console.warn(`[EnemyInstance] 无法调用 notifyEnemyDead: controller=${!!this.controller}`);
  532. }
  533. }
  534. update(deltaTime: number) {
  535. // 如果敌人被暂停,则不执行任何更新逻辑
  536. if (this.isPaused) {
  537. return;
  538. }
  539. if (this.state === EnemyState.DRIFTING) {
  540. this.updateDrifting(deltaTime);
  541. // 在漂移状态也检查墙体碰撞
  542. this.checkWallCollisionByDistance();
  543. } else if (this.state === EnemyState.MOVING) {
  544. this.updateMovement(deltaTime);
  545. // 在移动状态检查墙体碰撞
  546. this.checkWallCollisionByDistance();
  547. } else if (this.state === EnemyState.IDLE) {
  548. this.updateIdle(deltaTime);
  549. } else if (this.state === EnemyState.ATTACKING) {
  550. this.updateAttack(deltaTime);
  551. }
  552. // 不再每帧播放攻击动画,避免日志刷屏
  553. }
  554. // 更新漂移逻辑(从生成位置移动到线上)
  555. private updateDrifting(deltaTime: number) {
  556. if (!this.driftTargetPosition) {
  557. // 如果没有漂移目标位置,直接切换到移动状态
  558. this.state = EnemyState.MOVING;
  559. return;
  560. }
  561. const currentWorldPos = this.node.worldPosition.clone();
  562. const targetWorldPos = this.driftTargetPosition.clone();
  563. const dir = targetWorldPos.subtract(currentWorldPos);
  564. const distanceToTarget = dir.length();
  565. // 如果距离目标很近,切换到移动状态
  566. const stopDistance = 5; // 减小停止距离以提高精度
  567. if (distanceToTarget <= stopDistance) {
  568. this.state = EnemyState.MOVING;
  569. // 确保刚体速度为零
  570. this.stopRigidBodyMovement();
  571. return;
  572. }
  573. if (distanceToTarget === 0) return;
  574. dir.normalize();
  575. // 使用代码控制移动,不依赖物理系统
  576. const driftSpeed = Math.min(this.speed * 8, 500); // 限制最大漂移速度为5500
  577. const moveDistance = driftSpeed * deltaTime;
  578. const actualMoveDistance = Math.min(moveDistance, distanceToTarget);
  579. const newWorldPos = currentWorldPos.add(dir.multiplyScalar(actualMoveDistance));
  580. // 直接设置世界坐标
  581. this.node.setWorldPosition(newWorldPos);
  582. // 确保刚体速度为零,避免物理系统干扰
  583. this.stopRigidBodyMovement();
  584. }
  585. // 更新移动逻辑
  586. private updateMovement(deltaTime: number) {
  587. // 根据配置的移动模式执行不同的移动逻辑
  588. switch (this.movementPattern) {
  589. case 'direct':
  590. this.updateDirectMovement(deltaTime);
  591. break;
  592. case 'patrol':
  593. this.updatePatrolMovement(deltaTime);
  594. break;
  595. default:
  596. this.updateDirectMovement(deltaTime);
  597. break;
  598. }
  599. }
  600. // 直线移动模式
  601. private updateDirectMovement(deltaTime: number) {
  602. this.moveTowardsTarget(deltaTime);
  603. }
  604. // 巡逻移动模式
  605. private updatePatrolMovement(deltaTime: number) {
  606. // 更新方向变化计时器
  607. this.changeDirectionTime += deltaTime;
  608. console.log(`[EnemyInstance] 摆动 调用,changeDirectionTime=${this.changeDirectionTime}`);
  609. // 根据巡逻范围决定方向变化
  610. const enemySprite = this.node.getChildByName('EnemySprite');
  611. if (enemySprite && this.controller) {
  612. const currentPos = enemySprite.worldPosition;
  613. const bounds = this.controller.gameBounds;
  614. // 检查是否到达巡逻边界
  615. const leftBound = bounds.left + this.patrolRange;
  616. const rightBound = bounds.right - this.patrolRange;
  617. if (currentPos.x <= leftBound && this.movingDirection < 0) {
  618. this.movingDirection = 1; // 向右
  619. this.changeDirectionTime = 0;
  620. } else if (currentPos.x >= rightBound && this.movingDirection > 0) {
  621. this.movingDirection = -1; // 向左
  622. this.changeDirectionTime = 0;
  623. }
  624. // 随机改变Y目标位置
  625. if (this.changeDirectionTime >= 3 + Math.random() * 2) {
  626. const centerY = (bounds.top + bounds.bottom) / 2;
  627. const range = (bounds.top - bounds.bottom) * 0.4;
  628. this.targetY = centerY + (Math.random() - 0.5) * range;
  629. this.changeDirectionTime = 0;
  630. }
  631. }
  632. this.moveTowardsTarget(deltaTime);
  633. }
  634. // 向目标移动
  635. private moveTowardsTarget(deltaTime: number) {
  636. // 获取当前速度(考虑狂暴加成和速度变化)
  637. const enemyComponent = this.getComponent(EnemyComponent);
  638. let currentSpeed = enemyComponent ? enemyComponent.getCurrentSpeed() : this.speed;
  639. // 应用速度变化配置,限制变化幅度防止过大跳跃
  640. if (this.speedVariation > 0) {
  641. const maxVariation = Math.min(this.speedVariation, currentSpeed * 0.3); // 限制变化不超过当前速度的30%
  642. const variation = (Math.random() - 0.5) * 2 * maxVariation;
  643. currentSpeed = Math.max(currentSpeed + variation, currentSpeed * 0.5); // 确保速度不会过低
  644. }
  645. // 统一使用主节点的世界坐标,与updateDrifting保持一致
  646. const currentPos = this.node.worldPosition.clone();
  647. let dir = new Vec3();
  648. // 水平移动方向
  649. dir.x = this.movingDirection;
  650. // 垂直移动方向 - 向目标Y位置移动
  651. const yDiff = this.targetY - currentPos.y;
  652. if (Math.abs(yDiff) > 10) { // 如果距离目标Y位置超过10像素
  653. dir.y = yDiff > 0 ? 0.5 : -0.5; // 缓慢向目标Y移动
  654. } else {
  655. dir.y = 0;
  656. }
  657. // 如果有具体的目标位置(墙体),优先移动到墙体
  658. if (this.targetPosition) {
  659. const targetDir = new Vec3();
  660. Vec3.subtract(targetDir, this.targetPosition.clone(), currentPos);
  661. if (targetDir.length() > 20) { // 距离墙体还有一定距离时
  662. targetDir.normalize();
  663. dir.x = targetDir.x * 0.7; // 70%朝向墙体
  664. dir.y = targetDir.y * 0.7;
  665. }
  666. } else if (this.targetFence) {
  667. const fencePos = this.targetFence.worldPosition.clone();
  668. const targetDir = new Vec3();
  669. Vec3.subtract(targetDir, fencePos, currentPos);
  670. if (targetDir.length() > 20) {
  671. targetDir.normalize();
  672. dir.x = targetDir.x * 0.7;
  673. dir.y = targetDir.y * 0.7;
  674. }
  675. }
  676. // 归一化方向向量
  677. if (dir.length() > 0) {
  678. dir.normalize();
  679. }
  680. // 应用摆动效果(根据配置)
  681. if (this.moveType === 'sway' && this.swingAmplitude > 0) {
  682. this.swayTime += deltaTime;
  683. const swayAmount = Math.sin(this.swayTime * this.swingFrequency) * this.swingAmplitude;
  684. // 计算垂直于移动方向的摆动
  685. const swayDir = new Vec3(-dir.y, dir.x, 0);
  686. swayDir.multiplyScalar(swayAmount / 100); // 将摆动幅度转换为合适的比例
  687. dir.add(swayDir);
  688. }
  689. // 限制单帧移动距离,防止突然跳跃
  690. const moveDistance = currentSpeed * deltaTime;
  691. const maxMoveDistance = Math.min(15, currentSpeed * 0.1); // 限制单帧最大移动距离
  692. const actualMoveDistance = Math.min(moveDistance, maxMoveDistance);
  693. const moveVector = dir.multiplyScalar(actualMoveDistance);
  694. const newPos = new Vec3();
  695. Vec3.add(newPos, currentPos, moveVector);
  696. // 统一使用主节点的setWorldPosition,与updateDrifting保持一致
  697. this.node.setWorldPosition(newPos);
  698. // 确保刚体速度为零,避免物理系统干扰
  699. this.stopRigidBodyMovement();
  700. }
  701. // 更新攻击逻辑
  702. private updateAttack(deltaTime: number) {
  703. // 在攻击状态下停止移动
  704. const enemySprite = this.node.getChildByName('EnemySprite');
  705. if (enemySprite) {
  706. const rigidBody = enemySprite.getComponent(RigidBody2D);
  707. if (rigidBody) {
  708. // 停止物理移动
  709. rigidBody.linearVelocity = new Vec2(0, 0);
  710. }
  711. }
  712. this.attackTimer -= deltaTime;
  713. if (this.attackTimer <= 0) {
  714. // 执行攻击
  715. this.performAttack();
  716. // 重置攻击计时器
  717. this.attackTimer = this.attackInterval;
  718. }
  719. }
  720. // 待机状态更新(停止移动但可以攻击)
  721. private updateIdle(deltaTime: number) {
  722. // 在待机状态下停止移动
  723. const enemySprite = this.node.getChildByName('EnemySprite');
  724. if (enemySprite) {
  725. const rigidBody = enemySprite.getComponent(RigidBody2D);
  726. if (rigidBody) {
  727. // 停止物理移动
  728. rigidBody.linearVelocity = new Vec2(0, 0);
  729. }
  730. }
  731. this.attackTimer -= deltaTime;
  732. if (this.attackTimer <= 0) {
  733. // 执行攻击
  734. this.performAttack();
  735. // 重置攻击计时器
  736. this.attackTimer = this.attackInterval;
  737. }
  738. }
  739. // 执行攻击
  740. private performAttack() {
  741. if (!this.controller) {
  742. return;
  743. }
  744. const enemyComponent = this.getComponent(EnemyComponent);
  745. const attackType = enemyComponent ? enemyComponent.getAttackType() : 'melee';
  746. // 播放攻击音效
  747. EnemyAudio.playAttackSound(this.enemyConfig);
  748. // 根据攻击类型执行不同的攻击逻辑
  749. if (attackType === 'ranged' || attackType === 'projectile') {
  750. this.performRangedAttack();
  751. } else {
  752. // 近战攻击:播放攻击动画,动画结束后造成伤害
  753. this.playAttackAnimationWithDamage();
  754. }
  755. }
  756. // 执行远程攻击(投掷)
  757. private performRangedAttack() {
  758. const enemyComponent = this.getComponent(EnemyComponent);
  759. if (!enemyComponent) return;
  760. const projectileType = enemyComponent.getProjectileType();
  761. const projectileSpeed = enemyComponent.getProjectileSpeed();
  762. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 发射投掷物: ${projectileType}, 速度: ${projectileSpeed}`);
  763. // TODO: 实际创建和发射投掷物的逻辑
  764. // 这里需要根据项目的投掷物系统来实现
  765. // 例如:创建投掷物预制体,设置速度和方向,添加碰撞检测等
  766. // 暂时直接造成伤害作为占位符
  767. this.scheduleOnce(() => {
  768. this.dealDamageToWall();
  769. }, 1.0); // 1秒后造成伤害,模拟投掷物飞行时间
  770. }
  771. // 播放行走动画
  772. private playWalkAnimation() {
  773. if (!this.skeleton) return;
  774. const enemyComp = this.getComponent('EnemyComponent') as any;
  775. const anims = enemyComp?.getAnimations ? enemyComp.getAnimations() : {};
  776. const walkName = anims.walk ?? 'walk';
  777. const idleName = anims.idle ?? 'idle';
  778. if (this.skeleton.findAnimation(walkName)) {
  779. this.skeleton.setAnimation(0, walkName, true);
  780. // 行走音效已移除
  781. } else if (this.skeleton.findAnimation(idleName)) {
  782. this.skeleton.setAnimation(0, idleName, true);
  783. }
  784. // 动画切换后延迟更新血条位置,确保动画尺寸已生效
  785. this.scheduleOnce(() => {
  786. this.updateHPBarPosition();
  787. }, 0.1);
  788. }
  789. // 播放攻击动画
  790. private playAttackAnimation() {
  791. if (!this.skeleton) return;
  792. const enemyComp2 = this.getComponent('EnemyComponent') as any;
  793. const anims2 = enemyComp2?.getAnimations ? enemyComp2.getAnimations() : {};
  794. const attackName = anims2.attack ?? 'attack';
  795. // 移除频繁打印
  796. if (this.skeleton.findAnimation(attackName)) {
  797. this.skeleton.setAnimation(0, attackName, true);
  798. }
  799. // 动画切换后延迟更新血条位置,确保动画尺寸已生效
  800. this.scheduleOnce(() => {
  801. this.updateHPBarPosition();
  802. }, 0.1);
  803. }
  804. // 播放待机动画
  805. private playIdleAnimation() {
  806. if (!this.skeleton) return;
  807. const enemyComp = this.getComponent('EnemyComponent') as any;
  808. const anims = enemyComp?.getAnimations ? enemyComp.getAnimations() : {};
  809. const idleName = anims.idle ?? 'idle';
  810. if (this.skeleton.findAnimation(idleName)) {
  811. this.skeleton.setAnimation(0, idleName, true);
  812. }
  813. // 动画切换后延迟更新血条位置,确保动画尺寸已生效
  814. this.scheduleOnce(() => {
  815. this.updateHPBarPosition();
  816. }, 0.1);
  817. }
  818. // 播放攻击动画并在动画结束时造成伤害
  819. private playAttackAnimationWithDamage() {
  820. if (!this.skeleton) {
  821. // 如果没有骨骼动画,直接造成伤害
  822. this.dealDamageToWall();
  823. return;
  824. }
  825. const enemyComp = this.getComponent('EnemyComponent') as any;
  826. const anims = enemyComp?.getAnimations ? enemyComp.getAnimations() : {};
  827. const attackName = anims.attack ?? 'attack';
  828. const animation = this.skeleton.findAnimation(attackName);
  829. if (animation) {
  830. // 播放攻击动画(不循环)
  831. this.skeleton.setAnimation(0, attackName, false);
  832. // 动画切换后延迟更新血条位置,确保动画尺寸已生效
  833. this.scheduleOnce(() => {
  834. this.updateHPBarPosition();
  835. }, 0.1);
  836. // 获取动画时长并在动画结束时造成伤害
  837. const animationDuration = animation.duration;
  838. console.log(`[EnemyInstance] 攻击动画时长: ${animationDuration}秒`);
  839. // 使用定时器在动画结束时造成伤害
  840. this.scheduleOnce(() => {
  841. this.dealDamageToWall();
  842. // 动画完成后根据当前状态切换动画
  843. if (this.state === EnemyState.IDLE) {
  844. // 如果是待机状态,切换回待机动画
  845. this.playIdleAnimation();
  846. } else {
  847. // 其他状态切换回行走动画
  848. this.playWalkAnimation();
  849. }
  850. }, animationDuration);
  851. } else {
  852. // 如果找不到攻击动画,直接造成伤害
  853. this.dealDamageToWall();
  854. }
  855. }
  856. // 对墙体造成伤害
  857. private dealDamageToWall() {
  858. if (this.controller) {
  859. // 获取当前攻击力(考虑狂暴加成)
  860. const enemyComponent = this.getComponent(EnemyComponent);
  861. const currentAttackPower = enemyComponent ? enemyComponent.getCurrentAttackPower() : this.attackPower;
  862. this.controller.damageWall(currentAttackPower);
  863. }
  864. }
  865. private playDeathAnimationAndDestroy() {
  866. console.log(`[EnemyInstance] 开始播放死亡动画并销毁`);
  867. // 播放死亡音效
  868. EnemyAudio.playDeathSound(this.enemyConfig);
  869. if (this.skeleton) {
  870. const enemyComp = this.getComponent('EnemyComponent') as any;
  871. const anims = enemyComp?.getAnimations ? enemyComp.getAnimations() : {};
  872. const deathName = anims.dead ?? 'dead';
  873. if (this.skeleton.findAnimation(deathName)) {
  874. this.skeleton.setAnimation(0, deathName, false);
  875. // 销毁节点在动画完毕后
  876. this.skeleton.setCompleteListener(() => {
  877. this.node.destroy();
  878. });
  879. return;
  880. }
  881. }
  882. this.node.destroy();
  883. }
  884. private spawnCoin() {
  885. const ctrl = this.controller as any; // EnemyController
  886. if (!ctrl?.coinPrefab) return;
  887. // 获取配置中的掉落金币数量
  888. const dropCoinsCount = this.getDropCoins();
  889. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 掉落 ${dropCoinsCount} 个金币`);
  890. // 根据配置掉落相应数量的金币
  891. for (let i = 0; i < dropCoinsCount; i++) {
  892. const coin = instantiate(ctrl.coinPrefab);
  893. find('Canvas')!.addChild(coin); // 放到 UI 层
  894. const pos = new Vec3();
  895. this.node.getWorldPosition(pos); // 取死亡敌人的世界坐标
  896. // 如果掉落多个金币,添加一些随机偏移避免重叠
  897. if (dropCoinsCount > 1) {
  898. const offsetX = (Math.random() - 0.5) * 40; // 随机X偏移 -20到20
  899. const offsetY = (Math.random() - 0.5) * 40; // 随机Y偏移 -20到20
  900. pos.x += offsetX;
  901. pos.y += offsetY;
  902. }
  903. coin.worldPosition = pos; // 金币就在敌人身上出现
  904. }
  905. }
  906. /**
  907. * 暂停敌人
  908. */
  909. public pause(): void {
  910. this.isPaused = true;
  911. // 暂停物理系统
  912. const enemySprite = this.node.getChildByName('EnemySprite');
  913. if (enemySprite) {
  914. const rigidBody = enemySprite.getComponent(RigidBody2D);
  915. if (rigidBody) {
  916. // 保存当前速度并停止物理移动
  917. (this as any).savedVelocity = rigidBody.linearVelocity;
  918. rigidBody.linearVelocity = new Vec2(0, 0);
  919. }
  920. }
  921. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 已暂停`);
  922. }
  923. /**
  924. * 恢复敌人
  925. */
  926. public resume(): void {
  927. this.isPaused = false;
  928. // 恢复物理系统
  929. const enemySprite = this.node.getChildByName('EnemySprite');
  930. if (enemySprite) {
  931. const rigidBody = enemySprite.getComponent(RigidBody2D);
  932. if (rigidBody && (this as any).savedVelocity) {
  933. // 恢复之前保存的速度
  934. rigidBody.linearVelocity = (this as any).savedVelocity;
  935. (this as any).savedVelocity = null;
  936. }
  937. }
  938. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 已恢复`);
  939. }
  940. /**
  941. * 检查是否暂停
  942. */
  943. public isPausedState(): boolean {
  944. return this.isPaused;
  945. }
  946. /**
  947. * 获取敌人当前状态
  948. */
  949. public getState(): EnemyState {
  950. return this.state;
  951. }
  952. /**
  953. * 检查敌人是否处于漂移状态
  954. */
  955. public isDrifting(): boolean {
  956. return this.state === EnemyState.DRIFTING;
  957. }
  958. /**
  959. * 重置血条状态(满血并隐藏)
  960. */
  961. public resetHealthBar(): void {
  962. if (this.hpBarAnimation) {
  963. this.hpBarAnimation.resetToFullAndHide();
  964. } else {
  965. // 备用方案:直接隐藏血条
  966. const hpBar = this.node.getChildByName('HPBar');
  967. if (hpBar) {
  968. hpBar.active = false;
  969. const progressBar = hpBar.getComponent(ProgressBar);
  970. if (progressBar) {
  971. progressBar.progress = 1.0;
  972. }
  973. }
  974. }
  975. }
  976. /**
  977. * 尝试格挡攻击
  978. */
  979. private tryBlock(): boolean {
  980. const enemyComponent = this.getComponent(EnemyComponent);
  981. if (!enemyComponent) return false;
  982. const blockChance = enemyComponent.getBlockChance();
  983. // 将百分比值转换为0-1范围(例如:30.0 -> 0.3)
  984. const blockChanceNormalized = blockChance / 100.0;
  985. return Math.random() < blockChanceNormalized;
  986. }
  987. /**
  988. * 基于距离检查墙体碰撞
  989. */
  990. private checkWallCollisionByDistance(): void {
  991. if (this.state === EnemyState.ATTACKING || this.state === EnemyState.IDLE) {
  992. return; // 已经在攻击状态或待机状态,不需要再检查
  993. }
  994. const enemySprite = this.node.getChildByName('EnemySprite');
  995. if (!enemySprite) return;
  996. const currentPos = enemySprite.worldPosition;
  997. const collisionDistance = 30; // 碰撞检测距离
  998. // 获取所有墙体节点进行距离检测
  999. const wallNodes = this.getAllWallNodes();
  1000. for (const wallNode of wallNodes) {
  1001. const wallPos = wallNode.worldPosition;
  1002. const distance = Vec3.distance(currentPos, wallPos);
  1003. if (distance <= collisionDistance) {
  1004. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 距离检测到墙体 ${wallNode.name},距离: ${distance.toFixed(2)},切换到待机状态`);
  1005. this.state = EnemyState.IDLE;
  1006. this.attackTimer = 0; // 立即开始攻击
  1007. this.collidedWall = wallNode; // 记录碰撞的墙体
  1008. // 停止移动
  1009. this.stopRigidBodyMovement();
  1010. // 切换到待机动画
  1011. this.playIdleAnimation();
  1012. return; // 找到一个墙体就停止检查
  1013. }
  1014. }
  1015. }
  1016. /**
  1017. * 获取所有墙体节点
  1018. */
  1019. private getAllWallNodes(): Node[] {
  1020. const wallNodes: Node[] = [];
  1021. // 通过EnemyController获取墙体节点
  1022. if (this.controller) {
  1023. if (this.controller.topFenceNode) {
  1024. wallNodes.push(this.controller.topFenceNode);
  1025. }
  1026. if (this.controller.bottomFenceNode) {
  1027. wallNodes.push(this.controller.bottomFenceNode);
  1028. }
  1029. }
  1030. // 备用方案:在场景中查找墙体节点
  1031. if (wallNodes.length === 0) {
  1032. const gameArea = find('Canvas/GameLevelUI/GameArea');
  1033. if (gameArea) {
  1034. for (let i = 0; i < gameArea.children.length; i++) {
  1035. const child = gameArea.children[i];
  1036. if (this.isWallNode(child)) {
  1037. wallNodes.push(child);
  1038. }
  1039. }
  1040. }
  1041. }
  1042. return wallNodes;
  1043. }
  1044. /**
  1045. * 停止刚体移动,确保物理系统不干扰代码控制的移动
  1046. */
  1047. private stopRigidBodyMovement(): void {
  1048. const enemySprite = this.node.getChildByName('EnemySprite');
  1049. if (enemySprite) {
  1050. const rigidBody = enemySprite.getComponent(RigidBody2D);
  1051. if (rigidBody) {
  1052. rigidBody.linearVelocity = new Vec2(0, 0);
  1053. }
  1054. }
  1055. }
  1056. /**
  1057. * 显示格挡效果
  1058. */
  1059. private showBlockEffect(): void {
  1060. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 格挡了攻击`);
  1061. // 显示"格挡"文字效果
  1062. this.showBlockText();
  1063. }
  1064. /**
  1065. * 显示格挡文字
  1066. */
  1067. private showBlockText(): void {
  1068. // 使用DamageNumberAni组件来显示格挡文字,就像显示伤害数字一样
  1069. if (this.controller) {
  1070. const damageAni = this.controller.getComponent(DamageNumberAni);
  1071. if (damageAni) {
  1072. damageAni.showBlockText(this.node.worldPosition);
  1073. } else {
  1074. console.warn('[EnemyInstance] 未找到DamageNumberAni组件,无法显示格挡文字');
  1075. }
  1076. } else {
  1077. console.warn('[EnemyInstance] 未找到controller引用,无法显示格挡文字');
  1078. }
  1079. }
  1080. /**
  1081. * 检查是否触发狂暴状态
  1082. */
  1083. private checkRageTrigger(): void {
  1084. const enemyComponent = this.getComponent(EnemyComponent);
  1085. if (!enemyComponent) return;
  1086. const healthPercent = this.health / this.maxHealth;
  1087. const rageTriggerThreshold = enemyComponent.getRageTriggerThreshold();
  1088. if (healthPercent <= rageTriggerThreshold && !enemyComponent.isInRage()) {
  1089. enemyComponent.triggerRage();
  1090. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 进入狂暴状态`);
  1091. }
  1092. }
  1093. }