HPBarAnimation.ts 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. import { _decorator, Component, Node, ProgressBar, tween } from 'cc';
  2. const { ccclass, property } = _decorator;
  3. /**
  4. * 血条动画组件
  5. * 使用两个重叠的血条实现黄色血皮滑动动画效果
  6. * 红色血条显示当前血量,黄色血条用于滑动动画
  7. * 血条默认隐藏,只在受伤时显示并播放动画
  8. */
  9. @ccclass('HPBarAnimation')
  10. export class HPBarAnimation extends Component {
  11. @property({
  12. type: Node,
  13. tooltip: '红色血条节点'
  14. })
  15. public redBarNode: Node = null;
  16. @property({
  17. type: Node,
  18. tooltip: '黄色血条节点'
  19. })
  20. public yellowBarNode: Node = null;
  21. @property({
  22. type: Node,
  23. tooltip: 'HPBar根节点'
  24. })
  25. public hpBarRootNode: Node = null;
  26. private redProgressBar: ProgressBar = null;
  27. private yellowProgressBar: ProgressBar = null;
  28. private currentProgress: number = 1.0;
  29. private targetProgress: number = 1.0;
  30. private currentTween: any = null; // 当前正在运行的动画
  31. private hideTimer: any = null; // 隐藏血条的定时器
  32. start() {
  33. this.initializeComponents();
  34. }
  35. /**
  36. * 初始化组件
  37. */
  38. private initializeComponents() {
  39. // 获取组件所属的敌人节点信息
  40. const enemyNode = this.node;
  41. const enemyName = enemyNode ? enemyNode.name : 'Unknown';
  42. // 如果没有设置HPBar根节点,尝试自动查找
  43. if (!this.hpBarRootNode) {
  44. this.hpBarRootNode = this.node.getChildByName('HPBar');
  45. }
  46. // 获取红色血条组件
  47. if (this.redBarNode) {
  48. this.redProgressBar = this.redBarNode.getComponent(ProgressBar);
  49. if (this.redProgressBar) {
  50. this.currentProgress = this.redProgressBar.progress;
  51. this.targetProgress = this.currentProgress;
  52. } else {
  53. console.error(`[HPBarAnimation] [${enemyName}] 红色血条节点上未找到ProgressBar组件!`);
  54. }
  55. } else {
  56. console.error(`[HPBarAnimation] [${enemyName}] 红色血条节点未设置!`);
  57. }
  58. // 获取黄色血条组件
  59. if (this.yellowBarNode) {
  60. this.yellowProgressBar = this.yellowBarNode.getComponent(ProgressBar);
  61. if (this.yellowProgressBar) {
  62. // 黄色血条初始进度与红色血条同步
  63. this.yellowProgressBar.progress = this.currentProgress;
  64. } else {
  65. console.error(`[HPBarAnimation] [${enemyName}] 黄色血条节点上未找到ProgressBar组件!`);
  66. }
  67. } else {
  68. console.error(`[HPBarAnimation] [${enemyName}] 黄色血条节点未设置!`);
  69. }
  70. // 初始化时隐藏血条
  71. this.hideHealthBar();
  72. }
  73. /**
  74. * 获取节点路径
  75. */
  76. private getNodePath(node: Node): string {
  77. if (!node) return 'null';
  78. let path = node.name;
  79. let current = node;
  80. while (current.parent) {
  81. current = current.parent;
  82. path = current.name + '/' + path;
  83. }
  84. return path;
  85. }
  86. /**
  87. * 更新血条显示
  88. */
  89. private updateBarDisplay() {
  90. if (!this.redProgressBar || !this.yellowProgressBar) {
  91. return;
  92. }
  93. // 红色血条显示当前血量
  94. if (this.redProgressBar && this.redProgressBar.isValid) {
  95. this.redProgressBar.progress = this.currentProgress;
  96. }
  97. }
  98. /**
  99. * 显示血条
  100. */
  101. public showHealthBar() {
  102. if (this.hpBarRootNode && this.hpBarRootNode.isValid) {
  103. this.hpBarRootNode.active = true;
  104. }
  105. // 清除隐藏定时器
  106. if (this.hideTimer) {
  107. clearTimeout(this.hideTimer);
  108. this.hideTimer = null;
  109. }
  110. }
  111. /**
  112. * 隐藏血条
  113. */
  114. public hideHealthBar() {
  115. if (this.hpBarRootNode && this.hpBarRootNode.isValid) {
  116. this.hpBarRootNode.active = false;
  117. }
  118. }
  119. /**
  120. * 延迟隐藏血条
  121. * @param delay 延迟时间(秒)
  122. */
  123. private scheduleHideHealthBar(delay: number = 3.0) {
  124. // 清除之前的定时器
  125. if (this.hideTimer) {
  126. clearTimeout(this.hideTimer);
  127. }
  128. // 设置新的定时器
  129. this.hideTimer = setTimeout(() => {
  130. this.hideHealthBar();
  131. this.hideTimer = null;
  132. }, delay * 1000);
  133. }
  134. /**
  135. * 更新血条进度并播放动画
  136. * @param newProgress 新的血量百分比 (0-1)
  137. * @param showBar 是否显示血条(默认为true)
  138. */
  139. public updateProgress(newProgress: number, showBar: boolean = true) {
  140. const enemyName = this.node ? this.node.name : 'Unknown';
  141. if (!this.redProgressBar || !this.yellowProgressBar) {
  142. console.warn(`[HPBarAnimation] [${enemyName}] 红色或黄色血条组件未初始化`);
  143. return;
  144. }
  145. // 限制进度范围
  146. newProgress = Math.max(0, Math.min(1, newProgress));
  147. // 如果需要显示血条
  148. if (showBar) {
  149. this.showHealthBar();
  150. }
  151. // 如果血量增加或没有变化,直接更新
  152. if (newProgress >= this.currentProgress) {
  153. this.currentProgress = newProgress;
  154. this.targetProgress = newProgress;
  155. // 同步更新两个血条
  156. if (this.redProgressBar && this.redProgressBar.isValid) {
  157. this.redProgressBar.progress = newProgress;
  158. }
  159. if (this.yellowProgressBar && this.yellowProgressBar.isValid) {
  160. this.yellowProgressBar.progress = newProgress;
  161. }
  162. this.updateBarDisplay();
  163. // 如果血量满了,立即隐藏血条
  164. if (newProgress >= 1.0) {
  165. this.scheduleHideHealthBar(1.0); // 1秒后隐藏
  166. } else if (showBar) {
  167. this.scheduleHideHealthBar(3.0); // 3秒后隐藏
  168. }
  169. return;
  170. }
  171. // 血量减少时播放黄色血皮滑动动画
  172. this.playDamageAnimation(newProgress);
  173. }
  174. /**
  175. * 播放伤害动画
  176. * @param newProgress 新的血量百分比
  177. */
  178. private playDamageAnimation(newProgress: number) {
  179. console.log(`[HPBarAnimation] 开始播放伤害动画:${this.currentProgress} -> ${newProgress}`);
  180. // 停止当前正在运行的动画,避免动画冲突
  181. if (this.currentTween) {
  182. this.currentTween.stop();
  183. this.currentTween = null;
  184. }
  185. this.targetProgress = newProgress;
  186. // 保存原始血量作为黄色血条起始位置
  187. const originalProgress = this.currentProgress;
  188. // 1. 立即更新红色血条到新的血量值
  189. this.currentProgress = newProgress;
  190. if (this.redProgressBar && this.redProgressBar.isValid) {
  191. this.redProgressBar.progress = newProgress;
  192. }
  193. // 2. 黄色血条从原血量滑动到新血量位置
  194. // 黄色血条保持在原始位置
  195. if (this.yellowProgressBar && this.yellowProgressBar.isValid) {
  196. this.yellowProgressBar.progress = originalProgress;
  197. }
  198. this.updateBarDisplay();
  199. // 创建滑动动画,先暂停0.4秒再滑动
  200. this.currentTween = tween({ progress: originalProgress })
  201. .delay(0.4) // 暂停0.4秒
  202. .to(0.3, { progress: newProgress }, {
  203. onUpdate: (target) => {
  204. // 动画过程中更新黄色血条进度
  205. if (this.yellowProgressBar && this.yellowProgressBar.isValid) {
  206. this.yellowProgressBar.progress = target.progress;
  207. this.updateBarDisplay();
  208. }
  209. },
  210. onComplete: () => {
  211. // 动画完成后黄色血条进度等于当前血量
  212. if (this.yellowProgressBar && this.yellowProgressBar.isValid) {
  213. this.yellowProgressBar.progress = newProgress;
  214. this.updateBarDisplay();
  215. }
  216. // 清除动画引用
  217. this.currentTween = null;
  218. // 动画完成后延迟隐藏血条
  219. if (newProgress > 0) {
  220. this.scheduleHideHealthBar(3.0); // 3秒后隐藏血条
  221. }
  222. }
  223. })
  224. .start();
  225. }
  226. /**
  227. * 获取当前血量百分比
  228. */
  229. public getCurrentProgress(): number {
  230. return this.currentProgress;
  231. }
  232. /**
  233. * 重置血条到满血状态
  234. */
  235. public resetToFull() {
  236. this.updateProgress(1.0, false); // 重置时不显示血条
  237. }
  238. /**
  239. * 重置血条到满血状态并隐藏
  240. */
  241. public resetToFullAndHide() {
  242. this.currentProgress = 1.0;
  243. this.targetProgress = 1.0;
  244. // 同步更新两个血条到满血状态
  245. if (this.redProgressBar && this.redProgressBar.isValid) {
  246. this.redProgressBar.progress = 1.0;
  247. }
  248. if (this.yellowProgressBar && this.yellowProgressBar.isValid) {
  249. this.yellowProgressBar.progress = 1.0;
  250. }
  251. this.updateBarDisplay();
  252. // 立即隐藏血条
  253. this.hideHealthBar();
  254. }
  255. onDestroy() {
  256. // 清理正在运行的动画,防止内存泄漏
  257. if (this.currentTween) {
  258. this.currentTween.stop();
  259. this.currentTween = null;
  260. }
  261. // 清理隐藏血条的定时器,防止内存泄漏
  262. if (this.hideTimer) {
  263. clearTimeout(this.hideTimer);
  264. this.hideTimer = null;
  265. }
  266. }
  267. }