DamageNumberAni.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. import { _decorator, Component, Node, Label, Vec3, tween, Tween, UITransform, UIOpacity, instantiate, resources, Prefab, Color, find } from 'cc';
  2. import { EnemyController } from '../CombatSystem/EnemyController';
  3. const { ccclass, property } = _decorator;
  4. /**
  5. * 伤害数字动画组件
  6. * 实现伤害数字从小变大再缩小消失的动画效果
  7. * 可挂载到EnemyController节点上,通过装饰器获取EnemyController组件
  8. */
  9. @ccclass('DamageNumberAni')
  10. export class DamageNumberAni extends Component {
  11. @property({ type: EnemyController, tooltip: 'EnemyController组件引用' })
  12. private enemyController: EnemyController = null;
  13. @property({ type: Node, tooltip: 'GameArea节点引用' })
  14. private gameArea: Node = null;
  15. @property({ type: Prefab, tooltip: 'DamageNumber预制体引用' })
  16. private damageNumberPrefab: Prefab = null;
  17. onLoad() {
  18. // 自动获取同节点上的EnemyController组件
  19. if (!this.enemyController) {
  20. this.enemyController = this.getComponent(EnemyController);
  21. }
  22. // 只有在需要使用EnemyController功能时才报错
  23. // 动态创建的伤害数字节点不需要EnemyController组件
  24. if (!this.enemyController && this.damageNumberPrefab) {
  25. console.warn('[DamageNumberAni] 未找到EnemyController组件,某些功能可能不可用');
  26. }
  27. }
  28. /**
  29. * 创建并显示伤害数字
  30. * @param damage 伤害值
  31. * @param worldPosition 世界坐标位置(敌人头顶)
  32. * @param isCritical 是否暴击
  33. */
  34. public showDamageNumber(damage: number, worldPosition: Vec3, isCritical: boolean = false) {
  35. // 检查预制体引用
  36. if (!this.damageNumberPrefab) {
  37. console.error('[DamageNumberAni] DamageNumber预制体引用未设置');
  38. return;
  39. }
  40. // 实例化伤害数字节点
  41. const damageNode = instantiate(this.damageNumberPrefab);
  42. // 使用装饰器引用的GameArea节点
  43. if (!this.gameArea) {
  44. console.error('[DamageNumberAni] GameArea节点引用未设置');
  45. damageNode.destroy();
  46. return;
  47. }
  48. // 添加到GameArea
  49. this.gameArea.addChild(damageNode);
  50. // 转换世界坐标到GameArea的本地坐标
  51. const gameAreaTransform = this.gameArea.getComponent(UITransform);
  52. if (gameAreaTransform) {
  53. const localPos = gameAreaTransform.convertToNodeSpaceAR(worldPosition);
  54. // 稍微向上偏移,显示在敌人头顶
  55. localPos.y += 30;
  56. damageNode.position = localPos;
  57. }
  58. // 设置伤害数字文本
  59. const label = damageNode.getComponent(Label);
  60. if (label) {
  61. // 显示精确的伤害值,保留一位小数(如果有小数部分)
  62. const damageText = damage % 1 === 0 ? damage.toString() : damage.toFixed(1);
  63. label.string = damageText;
  64. // 暴击时使用不同颜色
  65. if (isCritical) {
  66. label.color = Color.YELLOW; // 暴击显示黄色
  67. label.fontSize = 24; // 暴击字体更大
  68. } else {
  69. label.color = Color.WHITE; // 普通伤害白色
  70. label.fontSize = 20;
  71. }
  72. }
  73. // 将DamageNumberAni组件添加到伤害数字节点
  74. const aniComponent = damageNode.addComponent(DamageNumberAni);
  75. aniComponent.playAnimation(isCritical);
  76. }
  77. /**
  78. * 静态方法保持兼容性,但推荐使用实例方法
  79. * @param damage 伤害值
  80. * @param worldPosition 世界坐标位置(敌人头顶)
  81. * @param isCritical 是否暴击
  82. */
  83. public static showDamageNumber(damage: number, worldPosition: Vec3, isCritical: boolean = false) {
  84. // 尝试找到EnemyController节点上的DamageNumberAni组件
  85. const enemyControllerNode = find('Canvas/GameLevelUI/EnemyController');
  86. if (enemyControllerNode) {
  87. const damageAni = enemyControllerNode.getComponent(DamageNumberAni);
  88. if (damageAni) {
  89. damageAni.showDamageNumber(damage, worldPosition, isCritical);
  90. return;
  91. }
  92. }
  93. console.warn('[DamageNumberAni] 未找到挂载的DamageNumberAni组件,请确保组件已挂载到EnemyController节点');
  94. }
  95. /**
  96. * 播放伤害数字动画
  97. * @param isCritical 是否暴击
  98. */
  99. private playAnimation(isCritical: boolean = false) {
  100. // 初始状态:缩放为0,透明度为1
  101. this.node.setScale(0, 0, 1);
  102. const uiTransform = this.node.getComponent(UITransform);
  103. const uiOpacity = this.node.getComponent(UIOpacity);
  104. if (uiOpacity) {
  105. uiOpacity.opacity = 255;
  106. }
  107. // 动画参数
  108. const maxScale = isCritical ? 1.3 : 1.0; // 暴击时放大更多
  109. const growDuration = 0.2; // 放大阶段持续时间
  110. const stayDuration = 0.3; // 停留时间
  111. const shrinkDuration = 0.4; // 缩小消失时间
  112. const floatDistance = 50; // 向上漂浮距离
  113. // 第一阶段:从0放大到最大尺寸(从底部开始放大)
  114. tween(this.node)
  115. .to(growDuration, {
  116. scale: new Vec3(maxScale, maxScale, 1)
  117. }, {
  118. easing: 'backOut' // 弹性效果
  119. })
  120. .call(() => {
  121. // 第二阶段:保持大小,开始向上漂浮并逐渐缩小透明
  122. const startPos = this.node.position.clone();
  123. const endPos = startPos.clone();
  124. endPos.y += floatDistance;
  125. // 位置动画
  126. tween(this.node)
  127. .to(stayDuration + shrinkDuration, {
  128. position: endPos
  129. }, {
  130. easing: 'sineOut'
  131. })
  132. .start();
  133. // 延迟后开始缩小和淡出
  134. tween(this.node)
  135. .delay(stayDuration)
  136. .to(shrinkDuration, {
  137. scale: new Vec3(0.3, 0.3, 1)
  138. }, {
  139. easing: 'sineIn'
  140. })
  141. .start();
  142. // 透明度动画
  143. if (uiOpacity) {
  144. tween(uiOpacity)
  145. .delay(stayDuration)
  146. .to(shrinkDuration, {
  147. opacity: 0
  148. }, {
  149. easing: 'sineIn'
  150. })
  151. .call(() => {
  152. // 动画完成,销毁节点
  153. if (this.node && this.node.isValid) {
  154. this.node.destroy();
  155. }
  156. })
  157. .start();
  158. }
  159. })
  160. .start();
  161. }
  162. onDestroy() {
  163. // 清理所有tween动画
  164. Tween.stopAllByTarget(this.node);
  165. const uiTransform = this.node.getComponent(UITransform);
  166. const uiOpacity = this.node.getComponent(UIOpacity);
  167. if (uiTransform) {
  168. Tween.stopAllByTarget(uiTransform);
  169. }
  170. if (uiOpacity) {
  171. Tween.stopAllByTarget(uiOpacity);
  172. }
  173. }
  174. }