EnemyInstance.ts 48 KB

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