EnemyInstance.ts 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  1. import { _decorator, Component, Node, ProgressBar, Label, Vec3, find, UITransform, Collider2D, Contact2DType, IPhysics2DContact, instantiate, resources, Prefab, JsonAsset, RigidBody2D, ERigidBody2DType, BoxCollider2D, CircleCollider2D } from 'cc';
  2. import { sp } from 'cc';
  3. import { DamageNumberAni } from '../Animations/DamageNumberAni';
  4. import { HPBarAnimation } from '../Animations/HPBarAnimation';
  5. import { EnemyComponent } from './EnemyComponent';
  6. import { EnemyAudio } from '../AudioManager/EnemyAudios';
  7. const { ccclass, property } = _decorator;
  8. // 前向声明EnemyController接口,避免循环引用
  9. interface EnemyControllerType {
  10. gameBounds: {
  11. left: number;
  12. right: number;
  13. top: number;
  14. bottom: number;
  15. };
  16. damageWall: (damage: number) => void;
  17. getComponent: (componentType: any) => any;
  18. }
  19. // 敌人状态枚举
  20. enum EnemyState {
  21. MOVING, // 移动中
  22. ATTACKING, // 攻击中
  23. DEAD // 死亡
  24. }
  25. // 单个敌人实例的组件
  26. @ccclass('EnemyInstance')
  27. export class EnemyInstance extends Component {
  28. // 敌人属性(从配置文件读取)
  29. public health: number = 0;
  30. public maxHealth: number = 0;
  31. public speed: number = 0;
  32. public attackPower: number = 0;
  33. // 敌人配置ID
  34. public enemyId: string = '';
  35. // 敌人配置数据
  36. private enemyConfig: any = null;
  37. // 敌人配置数据库
  38. private static enemyDatabase: any = null;
  39. // === 新增属性 ===
  40. /** 是否从上方生成 */
  41. public spawnFromTop: boolean = true;
  42. /** 目标 Fence 节点(TopFence / BottomFence) */
  43. public targetFence: Node | null = null;
  44. // 移动相关属性
  45. public movingDirection: number = 1; // 1: 向右, -1: 向左
  46. public targetY: number = 0; // 目标Y位置
  47. public changeDirectionTime: number = 0; // 下次改变方向的时间
  48. // 摆动移动相关属性
  49. private swayTime: number = 0; // 摆动时间累计
  50. private basePosition: Vec3 = new Vec3(); // 基础位置(摆动的中心点)
  51. private swayOffset: Vec3 = new Vec3(); // 当前摆动偏移
  52. // 攻击属性
  53. public attackInterval: number = 0; // 攻击间隔(秒),从配置文件读取
  54. private attackTimer: number = 0;
  55. // 对控制器的引用
  56. public controller: EnemyControllerType = null;
  57. // 敌人当前状态
  58. private state: EnemyState = EnemyState.MOVING;
  59. // 游戏区域中心
  60. private gameAreaCenter: Vec3 = new Vec3();
  61. // 碰撞的墙体
  62. private collidedWall: Node = null;
  63. // 骨骼动画组件
  64. private skeleton: sp.Skeleton | null = null;
  65. // 血条动画组件
  66. private hpBarAnimation: HPBarAnimation | null = null;
  67. // 暂停状态标记
  68. private isPaused: boolean = false;
  69. start() {
  70. // 初始化敌人
  71. this.initializeEnemy();
  72. }
  73. // 静态方法:加载敌人配置数据库
  74. public static async loadEnemyDatabase(): Promise<void> {
  75. if (EnemyInstance.enemyDatabase) return;
  76. return new Promise((resolve, reject) => {
  77. resources.load('data/enemies', JsonAsset, (err, jsonAsset) => {
  78. if (err) {
  79. console.error('[EnemyInstance] 加载敌人配置失败:', err);
  80. reject(err);
  81. return;
  82. }
  83. EnemyInstance.enemyDatabase = jsonAsset.json;
  84. resolve();
  85. });
  86. });
  87. }
  88. // 设置敌人配置
  89. public setEnemyConfig(enemyId: string): void {
  90. this.enemyId = enemyId;
  91. if (!EnemyInstance.enemyDatabase) {
  92. console.error('[EnemyInstance] 敌人配置数据库未加载');
  93. return;
  94. }
  95. // 从数据库中查找敌人配置
  96. // 修复:enemies.json是直接的数组结构,不需要.enemies包装
  97. const enemies = EnemyInstance.enemyDatabase;
  98. this.enemyConfig = enemies.find((enemy: any) => enemy.id === enemyId);
  99. if (!this.enemyConfig) {
  100. console.error(`[EnemyInstance] 未找到敌人配置: ${enemyId}`);
  101. return;
  102. }
  103. // 应用配置到敌人属性
  104. this.applyEnemyConfig();
  105. }
  106. // 应用敌人配置到属性
  107. private applyEnemyConfig(): void {
  108. if (!this.enemyConfig) return;
  109. // 从stats节点读取基础属性
  110. const stats = this.enemyConfig.stats || {};
  111. this.health = stats.health || 30;
  112. this.maxHealth = stats.maxHealth || this.health;
  113. // 从movement节点读取移动速度
  114. const movement = this.enemyConfig.movement || {};
  115. this.speed = movement.speed || 50;
  116. // 从combat节点读取攻击力
  117. const combat = this.enemyConfig.combat || {};
  118. this.attackPower = combat.attackDamage || 10;
  119. // 设置攻击间隔
  120. this.attackInterval = combat.attackCooldown || 2.0;
  121. }
  122. // 获取敌人配置信息
  123. public getEnemyConfig(): any {
  124. return this.enemyConfig;
  125. }
  126. // 获取敌人名称
  127. public getEnemyName(): string {
  128. return this.enemyConfig?.name || '未知敌人';
  129. }
  130. // 获取敌人类型
  131. public getEnemyType(): string {
  132. return this.enemyConfig?.type || 'basic';
  133. }
  134. // 获取敌人稀有度
  135. public getEnemyRarity(): string {
  136. return this.enemyConfig?.rarity || 'common';
  137. }
  138. // 获取金币奖励
  139. public getGoldReward(): number {
  140. return this.enemyConfig?.goldReward || 1;
  141. }
  142. // 初始化敌人
  143. private initializeEnemy() {
  144. // 确保血量正确设置
  145. if (this.maxHealth > 0) {
  146. this.health = this.maxHealth;
  147. }
  148. this.state = EnemyState.MOVING;
  149. // 只有在攻击间隔未设置时才使用默认值
  150. if (this.attackInterval <= 0) {
  151. this.attackInterval = 2.0; // 默认攻击间隔
  152. }
  153. this.attackTimer = 0;
  154. // 初始化血条动画组件
  155. this.initializeHPBarAnimation();
  156. // 获取骨骼动画组件
  157. this.skeleton = this.getComponent(sp.Skeleton);
  158. this.playWalkAnimation();
  159. // 计算游戏区域中心
  160. this.calculateGameAreaCenter();
  161. // 初始化碰撞检测
  162. this.setupCollider();
  163. }
  164. // 设置碰撞器
  165. setupCollider() {
  166. // 检查节点是否有碰撞器
  167. let collider = this.node.getComponent(Collider2D);
  168. if (!collider) {
  169. console.warn(`[EnemyInstance] 敌人节点 ${this.node.name} 没有碰撞器组件`);
  170. return;
  171. }
  172. // 确保有RigidBody2D组件,这对于碰撞检测是必需的
  173. let rigidBody = this.node.getComponent(RigidBody2D);
  174. if (!rigidBody) {
  175. console.log(`[EnemyInstance] 为敌人节点 ${this.node.name} 添加RigidBody2D组件`);
  176. rigidBody = this.node.addComponent(RigidBody2D);
  177. }
  178. // 设置刚体属性
  179. if (rigidBody) {
  180. rigidBody.type = ERigidBody2DType.Dynamic; // 动态刚体
  181. rigidBody.enabledContactListener = true; // 启用碰撞监听
  182. rigidBody.gravityScale = 0; // 不受重力影响
  183. rigidBody.linearDamping = 0; // 无线性阻尼
  184. rigidBody.angularDamping = 0; // 无角阻尼
  185. rigidBody.allowSleep = false; // 不允许休眠
  186. rigidBody.fixedRotation = true; // 固定旋转
  187. }
  188. // 根据ContentSize自适应碰撞体大小
  189. this.adaptColliderToContentSize(collider);
  190. // 设置碰撞事件监听
  191. collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
  192. console.log(`[EnemyInstance] 敌人 ${this.node.name} 碰撞器设置完成,碰撞器启用: ${collider.enabled}, 刚体启用: ${rigidBody?.enabled}`);
  193. }
  194. /**
  195. * 根据ContentSize自适应碰撞体大小
  196. */
  197. private adaptColliderToContentSize(collider: Collider2D): void {
  198. const uiTransform = this.node.getComponent(UITransform);
  199. if (!uiTransform) {
  200. console.warn(`[EnemyInstance] 敌人节点 ${this.node.name} 没有UITransform组件,无法自适应碰撞体大小`);
  201. return;
  202. }
  203. const contentSize = uiTransform.contentSize;
  204. console.log(`[EnemyInstance] 敌人 ${this.node.name} ContentSize: ${contentSize.width} x ${contentSize.height}`);
  205. // 根据碰撞器类型设置大小
  206. if (collider instanceof BoxCollider2D) {
  207. // 方形碰撞器:直接使用ContentSize
  208. const boxCollider = collider as BoxCollider2D;
  209. boxCollider.size.width = contentSize.width;
  210. boxCollider.size.height = contentSize.height;
  211. // 调整偏移量,使碰撞器居中
  212. const anchorPoint = uiTransform.anchorPoint;
  213. boxCollider.offset.x = (0.5 - anchorPoint.x) * contentSize.width;
  214. boxCollider.offset.y = (0.5 - anchorPoint.y) * contentSize.height;
  215. console.log(`[EnemyInstance] BoxCollider2D 已自适应: size(${boxCollider.size.width}, ${boxCollider.size.height}), offset(${boxCollider.offset.x.toFixed(2)}, ${boxCollider.offset.y.toFixed(2)})`);
  216. } else if (collider instanceof CircleCollider2D) {
  217. // 圆形碰撞器:使用ContentSize的较小值作为直径
  218. const circleCollider = collider as CircleCollider2D;
  219. const radius = Math.min(contentSize.width, contentSize.height) / 2;
  220. circleCollider.radius = radius;
  221. // 调整偏移量,使碰撞器居中
  222. const anchorPoint = uiTransform.anchorPoint;
  223. circleCollider.offset.x = (0.5 - anchorPoint.x) * contentSize.width;
  224. circleCollider.offset.y = (0.5 - anchorPoint.y) * contentSize.height;
  225. console.log(`[EnemyInstance] CircleCollider2D 已自适应: radius(${radius.toFixed(2)}), offset(${circleCollider.offset.x.toFixed(2)}, ${circleCollider.offset.y.toFixed(2)})`);
  226. } else {
  227. console.warn(`[EnemyInstance] 不支持的碰撞器类型: ${collider.constructor.name}`);
  228. }
  229. }
  230. // 碰撞开始事件
  231. onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
  232. const nodeName = otherCollider.node.name;
  233. // 如果碰到墙体,停止移动并开始攻击
  234. if (nodeName.includes('Wall') || nodeName.includes('wall') || nodeName.includes('Fence') || nodeName.includes('Jiguang')) {
  235. this.state = EnemyState.ATTACKING;
  236. this.attackTimer = 0; // 立即开始攻击
  237. // 切换攻击动画
  238. this.playAttackAnimation();
  239. }
  240. }
  241. // 获取节点路径
  242. getNodePath(node: Node): string {
  243. let path = node.name;
  244. let current = node;
  245. while (current.parent) {
  246. current = current.parent;
  247. path = current.name + '/' + path;
  248. }
  249. return path;
  250. }
  251. // 计算游戏区域中心
  252. private calculateGameAreaCenter() {
  253. const gameArea = find('Canvas/GameLevelUI/GameArea');
  254. if (gameArea) {
  255. this.gameAreaCenter = gameArea.worldPosition;
  256. }
  257. }
  258. /**
  259. * 初始化血条动画组件
  260. */
  261. private initializeHPBarAnimation() {
  262. const hpBar = this.node.getChildByName('HPBar');
  263. if (hpBar) {
  264. // 查找红色和黄色血条节点
  265. const redBarNode = hpBar.getChildByName('RedBar');
  266. const yellowBarNode = hpBar.getChildByName('YellowBar');
  267. if (redBarNode && yellowBarNode) {
  268. // 添加血条动画组件
  269. this.hpBarAnimation = this.node.addComponent(HPBarAnimation);
  270. if (this.hpBarAnimation) {
  271. // 正确设置红色和黄色血条节点引用
  272. this.hpBarAnimation.redBarNode = redBarNode;
  273. this.hpBarAnimation.yellowBarNode = yellowBarNode;
  274. this.hpBarAnimation.hpBarRootNode = hpBar; // 设置HPBar根节点
  275. console.log(`[EnemyInstance] 血条动画组件已初始化`);
  276. }
  277. } else {
  278. console.warn(`[EnemyInstance] HPBar下未找到RedBar或YellowBar节点,RedBar: ${!!redBarNode}, YellowBar: ${!!yellowBarNode}`);
  279. }
  280. } else {
  281. console.warn(`[EnemyInstance] 未找到HPBar节点,无法初始化血条动画`);
  282. }
  283. }
  284. // 更新血量显示
  285. updateHealthDisplay(showBar: boolean = false) {
  286. // 确保血量值在有效范围内
  287. this.health = Math.max(0, Math.min(this.maxHealth, this.health));
  288. const healthProgress = this.maxHealth > 0 ? this.health / this.maxHealth : 0;
  289. console.log(`[EnemyInstance] 更新血量显示: ${this.health}/${this.maxHealth} (${(healthProgress * 100).toFixed(1)}%)`);
  290. // 使用血条动画组件更新血条
  291. if (this.hpBarAnimation) {
  292. this.hpBarAnimation.updateProgress(healthProgress, showBar);
  293. } else {
  294. // 备用方案:直接更新血条
  295. const hpBar = this.node.getChildByName('HPBar');
  296. if (hpBar) {
  297. const progressBar = hpBar.getComponent(ProgressBar);
  298. if (progressBar) {
  299. progressBar.progress = healthProgress;
  300. }
  301. // 根据showBar参数控制血条显示
  302. hpBar.active = showBar;
  303. }
  304. }
  305. // 更新血量数字
  306. const hpLabel = this.node.getChildByName('HPLabel');
  307. if (hpLabel) {
  308. const label = hpLabel.getComponent(Label);
  309. if (label) {
  310. // 显示整数血量值
  311. label.string = Math.ceil(this.health).toString();
  312. }
  313. }
  314. }
  315. // 受到伤害
  316. takeDamage(damage: number, isCritical: boolean = false) {
  317. // 如果已经死亡,不再处理伤害
  318. if (this.state === EnemyState.DEAD) {
  319. return;
  320. }
  321. // 确保伤害值为正数
  322. if (damage <= 0) {
  323. console.warn(`[EnemyInstance] 无效的伤害值: ${damage}`);
  324. return;
  325. }
  326. // 应用防御力减少伤害
  327. const enemyComponent = this.getComponent(EnemyComponent);
  328. const defense = enemyComponent ? enemyComponent.getDefense() : 0;
  329. let finalDamage = Math.max(1, damage - defense); // 至少造成1点伤害
  330. // 检查格挡
  331. if (enemyComponent && enemyComponent.canBlock() && this.tryBlock()) {
  332. finalDamage *= (1 - enemyComponent.getBlockDamageReduction());
  333. this.showBlockEffect();
  334. }
  335. // 计算新的血量,确保不会低于0
  336. const oldHealth = this.health;
  337. const newHealth = Math.max(0, this.health - finalDamage);
  338. const actualHealthLoss = oldHealth - newHealth; // 实际血量损失
  339. this.health = newHealth;
  340. // 检查是否触发狂暴
  341. this.checkRageTrigger();
  342. // 日志显示武器的真实伤害值,而不是血量差值
  343. console.log(`[EnemyInstance] 敌人受到伤害: ${damage} (武器伤害), 防御后伤害: ${finalDamage}, 实际血量损失: ${actualHealthLoss}, 剩余血量: ${this.health}/${this.maxHealth}`);
  344. // 受击音效已移除
  345. // 显示伤害数字动画(在敌人头顶)- 显示防御后的实际伤害
  346. // 优先使用EnemyController节点上的DamageNumberAni组件实例
  347. if (this.controller) {
  348. const damageAni = this.controller.getComponent(DamageNumberAni);
  349. if (damageAni) {
  350. damageAni.showDamageNumber(finalDamage, this.node.worldPosition, isCritical);
  351. } else {
  352. // 如果没有找到组件实例,使用静态方法作为备用
  353. DamageNumberAni.showDamageNumber(finalDamage, this.node.worldPosition, isCritical);
  354. }
  355. } else {
  356. // 如果没有controller引用,使用静态方法
  357. DamageNumberAni.showDamageNumber(finalDamage, this.node.worldPosition, isCritical);
  358. }
  359. // 更新血量显示和动画,受伤时显示血条
  360. this.updateHealthDisplay(true);
  361. // 如果血量低于等于0,销毁敌人
  362. if (this.health <= 0) {
  363. console.log(`[EnemyInstance] 敌人死亡,开始销毁流程`);
  364. this.state = EnemyState.DEAD;
  365. this.spawnCoin();
  366. // 进入死亡流程,禁用碰撞避免重复命中
  367. const col = this.getComponent(Collider2D);
  368. if (col) col.enabled = false;
  369. this.playDeathAnimationAndDestroy();
  370. }
  371. }
  372. onDestroy() {
  373. console.log(`[EnemyInstance] onDestroy 被调用,准备通知控制器`);
  374. // 通知控制器 & GameManager
  375. if (this.controller && typeof (this.controller as any).notifyEnemyDead === 'function') {
  376. // 检查控制器是否处于清理状态,避免在清理过程中触发游戏事件
  377. const isClearing = (this.controller as any).isClearing;
  378. if (isClearing) {
  379. console.log(`[EnemyInstance] 控制器处于清理状态,跳过死亡通知`);
  380. return;
  381. }
  382. console.log(`[EnemyInstance] 调用 notifyEnemyDead`);
  383. (this.controller as any).notifyEnemyDead(this.node);
  384. } else {
  385. console.warn(`[EnemyInstance] 无法调用 notifyEnemyDead: controller=${!!this.controller}`);
  386. }
  387. }
  388. update(deltaTime: number) {
  389. // 如果敌人被暂停,则不执行任何更新逻辑
  390. if (this.isPaused) {
  391. return;
  392. }
  393. if (this.state === EnemyState.MOVING) {
  394. this.updateMovement(deltaTime);
  395. } else if (this.state === EnemyState.ATTACKING) {
  396. this.updateAttack(deltaTime);
  397. }
  398. // 不再每帧播放攻击动画,避免日志刷屏
  399. }
  400. // 更新移动逻辑
  401. private updateMovement(deltaTime: number) {
  402. // 继续移动到目标墙体
  403. this.moveTowardsTarget(deltaTime);
  404. }
  405. // 移动到目标位置
  406. private moveTowardsTarget(deltaTime: number) {
  407. // 使用世界坐标进行移动计算,确保不受父节点坐标系影响
  408. const currentWorldPos = this.node.worldPosition.clone();
  409. // 目标世界坐标:优先使用指定的 Fence,其次退化到游戏区域中心
  410. let targetWorldPos: Vec3;
  411. if (this.targetFence && this.targetFence.isValid) {
  412. targetWorldPos = this.targetFence.worldPosition.clone();
  413. } else {
  414. targetWorldPos = this.gameAreaCenter.clone();
  415. }
  416. const dir = targetWorldPos.subtract(currentWorldPos);
  417. if (dir.length() === 0) return;
  418. dir.normalize();
  419. // 获取当前速度(考虑狂暴加成)
  420. const enemyComponent = this.getComponent(EnemyComponent);
  421. const currentSpeed = enemyComponent ? enemyComponent.getCurrentSpeed() : this.speed;
  422. const moveDistance = currentSpeed * deltaTime;
  423. let newWorldPos = currentWorldPos.add(dir.multiplyScalar(moveDistance));
  424. // 应用摆动效果
  425. if (enemyComponent) {
  426. const swayAmplitude = enemyComponent.getSwingAmplitude();
  427. const swayFrequency = enemyComponent.getSwingFrequency();
  428. if (swayAmplitude > 0 && swayFrequency > 0) {
  429. this.swayTime += deltaTime;
  430. // 计算垂直于移动方向的摆动偏移
  431. const swayOffset = Math.sin(this.swayTime * swayFrequency * 2 * Math.PI) * swayAmplitude;
  432. // 计算垂直于移动方向的向量
  433. const perpDir = new Vec3(-dir.y, dir.x, 0);
  434. const swayVector = perpDir.multiplyScalar(swayOffset);
  435. newWorldPos = newWorldPos.add(swayVector);
  436. }
  437. }
  438. // 直接设置世界坐标
  439. this.node.setWorldPosition(newWorldPos);
  440. }
  441. // 更新攻击逻辑
  442. private updateAttack(deltaTime: number) {
  443. this.attackTimer -= deltaTime;
  444. if (this.attackTimer <= 0) {
  445. // 执行攻击
  446. this.performAttack();
  447. // 重置攻击计时器
  448. this.attackTimer = this.attackInterval;
  449. }
  450. }
  451. // 执行攻击
  452. private performAttack() {
  453. if (!this.controller) {
  454. return;
  455. }
  456. const enemyComponent = this.getComponent(EnemyComponent);
  457. const attackType = enemyComponent ? enemyComponent.getAttackType() : 'melee';
  458. // 播放攻击音效
  459. EnemyAudio.playAttackSound(this.enemyConfig);
  460. // 根据攻击类型执行不同的攻击逻辑
  461. if (attackType === 'ranged' || attackType === 'projectile') {
  462. this.performRangedAttack();
  463. } else {
  464. // 近战攻击:播放攻击动画,动画结束后造成伤害
  465. this.playAttackAnimationWithDamage();
  466. }
  467. }
  468. // 执行远程攻击(投掷)
  469. private performRangedAttack() {
  470. const enemyComponent = this.getComponent(EnemyComponent);
  471. if (!enemyComponent) return;
  472. const projectileType = enemyComponent.getProjectileType();
  473. const projectileSpeed = enemyComponent.getProjectileSpeed();
  474. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 发射投掷物: ${projectileType}, 速度: ${projectileSpeed}`);
  475. // TODO: 实际创建和发射投掷物的逻辑
  476. // 这里需要根据项目的投掷物系统来实现
  477. // 例如:创建投掷物预制体,设置速度和方向,添加碰撞检测等
  478. // 暂时直接造成伤害作为占位符
  479. this.scheduleOnce(() => {
  480. this.dealDamageToWall();
  481. }, 1.0); // 1秒后造成伤害,模拟投掷物飞行时间
  482. }
  483. // 播放行走动画
  484. private playWalkAnimation() {
  485. if (!this.skeleton) return;
  486. const enemyComp = this.getComponent('EnemyComponent') as any;
  487. const anims = enemyComp?.getAnimations ? enemyComp.getAnimations() : {};
  488. const walkName = anims.walk ?? 'walk';
  489. const idleName = anims.idle ?? 'idle';
  490. if (this.skeleton.findAnimation(walkName)) {
  491. this.skeleton.setAnimation(0, walkName, true);
  492. // 行走音效已移除
  493. } else if (this.skeleton.findAnimation(idleName)) {
  494. this.skeleton.setAnimation(0, idleName, true);
  495. }
  496. }
  497. // 播放攻击动画
  498. private playAttackAnimation() {
  499. if (!this.skeleton) return;
  500. const enemyComp2 = this.getComponent('EnemyComponent') as any;
  501. const anims2 = enemyComp2?.getAnimations ? enemyComp2.getAnimations() : {};
  502. const attackName = anims2.attack ?? 'attack';
  503. // 移除频繁打印
  504. if (this.skeleton.findAnimation(attackName)) {
  505. this.skeleton.setAnimation(0, attackName, true);
  506. }
  507. }
  508. // 播放攻击动画并在动画结束时造成伤害
  509. private playAttackAnimationWithDamage() {
  510. if (!this.skeleton) {
  511. // 如果没有骨骼动画,直接造成伤害
  512. this.dealDamageToWall();
  513. return;
  514. }
  515. const enemyComp = this.getComponent('EnemyComponent') as any;
  516. const anims = enemyComp?.getAnimations ? enemyComp.getAnimations() : {};
  517. const attackName = anims.attack ?? 'attack';
  518. const animation = this.skeleton.findAnimation(attackName);
  519. if (animation) {
  520. // 播放攻击动画(不循环)
  521. this.skeleton.setAnimation(0, attackName, false);
  522. // 获取动画时长并在动画结束时造成伤害
  523. const animationDuration = animation.duration;
  524. console.log(`[EnemyInstance] 攻击动画时长: ${animationDuration}秒`);
  525. // 使用定时器在动画结束时造成伤害
  526. this.scheduleOnce(() => {
  527. this.dealDamageToWall();
  528. // 动画完成后切换回行走动画
  529. this.playWalkAnimation();
  530. }, animationDuration);
  531. } else {
  532. // 如果找不到攻击动画,直接造成伤害
  533. this.dealDamageToWall();
  534. }
  535. }
  536. // 对墙体造成伤害
  537. private dealDamageToWall() {
  538. if (this.controller) {
  539. // 获取当前攻击力(考虑狂暴加成)
  540. const enemyComponent = this.getComponent(EnemyComponent);
  541. const currentAttackPower = enemyComponent ? enemyComponent.getCurrentAttackPower() : this.attackPower;
  542. this.controller.damageWall(currentAttackPower);
  543. }
  544. }
  545. private playDeathAnimationAndDestroy() {
  546. console.log(`[EnemyInstance] 开始播放死亡动画并销毁`);
  547. // 播放死亡音效
  548. EnemyAudio.playDeathSound(this.enemyConfig);
  549. if (this.skeleton) {
  550. const enemyComp = this.getComponent('EnemyComponent') as any;
  551. const anims = enemyComp?.getAnimations ? enemyComp.getAnimations() : {};
  552. const deathName = anims.dead ?? 'dead';
  553. if (this.skeleton.findAnimation(deathName)) {
  554. this.skeleton.setAnimation(0, deathName, false);
  555. // 销毁节点在动画完毕后
  556. this.skeleton.setCompleteListener(() => {
  557. this.node.destroy();
  558. });
  559. return;
  560. }
  561. }
  562. this.node.destroy();
  563. }
  564. private spawnCoin() {
  565. const ctrl = this.controller as any; // EnemyController
  566. if (!ctrl?.coinPrefab) return;
  567. const coin = instantiate(ctrl.coinPrefab);
  568. find('Canvas')!.addChild(coin); // 放到 UI 层
  569. const pos = new Vec3();
  570. this.node.getWorldPosition(pos); // 取死亡敌人的世界坐标
  571. coin.worldPosition = pos; // 金币就在敌人身上出现
  572. }
  573. /**
  574. * 暂停敌人
  575. */
  576. public pause(): void {
  577. this.isPaused = true;
  578. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 已暂停`);
  579. }
  580. /**
  581. * 恢复敌人
  582. */
  583. public resume(): void {
  584. this.isPaused = false;
  585. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 已恢复`);
  586. }
  587. /**
  588. * 检查是否暂停
  589. */
  590. public isPausedState(): boolean {
  591. return this.isPaused;
  592. }
  593. /**
  594. * 重置血条状态(满血并隐藏)
  595. */
  596. public resetHealthBar(): void {
  597. if (this.hpBarAnimation) {
  598. this.hpBarAnimation.resetToFullAndHide();
  599. } else {
  600. // 备用方案:直接隐藏血条
  601. const hpBar = this.node.getChildByName('HPBar');
  602. if (hpBar) {
  603. hpBar.active = false;
  604. const progressBar = hpBar.getComponent(ProgressBar);
  605. if (progressBar) {
  606. progressBar.progress = 1.0;
  607. }
  608. }
  609. }
  610. }
  611. /**
  612. * 尝试格挡攻击
  613. */
  614. private tryBlock(): boolean {
  615. const enemyComponent = this.getComponent(EnemyComponent);
  616. if (!enemyComponent) return false;
  617. const blockChance = enemyComponent.getBlockChance();
  618. return Math.random() < blockChance;
  619. }
  620. /**
  621. * 显示格挡效果
  622. */
  623. private showBlockEffect(): void {
  624. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 格挡了攻击`);
  625. // TODO: 添加格挡视觉效果
  626. }
  627. /**
  628. * 检查是否触发狂暴状态
  629. */
  630. private checkRageTrigger(): void {
  631. const enemyComponent = this.getComponent(EnemyComponent);
  632. if (!enemyComponent) return;
  633. const healthPercent = this.health / this.maxHealth;
  634. const rageTriggerThreshold = enemyComponent.getRageTriggerThreshold();
  635. if (healthPercent <= rageTriggerThreshold && !enemyComponent.isInRage()) {
  636. enemyComponent.triggerRage();
  637. console.log(`[EnemyInstance] 敌人 ${this.getEnemyName()} 进入狂暴状态`);
  638. }
  639. }
  640. }