ButtonAni.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import { _decorator, Component, Node, Button, Vec3, tween, find } from 'cc';
  2. const { ccclass, property } = _decorator;
  3. /**
  4. * ButtonAni - 通用按钮按压效果管理器
  5. * 自动为场景中所有按钮添加按压缩放效果
  6. * 挂载在场景的根节点或UI管理节点上
  7. */
  8. @ccclass('ButtonAni')
  9. export class ButtonAni extends Component {
  10. @property({ tooltip: '按压时的缩放比例' })
  11. public pressScale: number = 0.9;
  12. @property({ tooltip: '按压动画时长(秒)' })
  13. public pressDuration: number = 0.1;
  14. @property({ tooltip: '释放动画时长(秒)' })
  15. public releaseDuration: number = 0.1;
  16. @property({ tooltip: '是否自动查找所有按钮' })
  17. public autoFindButtons: boolean = true;
  18. @property({ type: [Node], tooltip: '手动指定的按钮节点列表(如果不自动查找)' })
  19. public manualButtons: Node[] = [];
  20. // 存储按钮的原始缩放值
  21. private originalScales: Map<Node, Vec3> = new Map();
  22. // 存储正在播放动画的按钮
  23. private animatingButtons: Set<Node> = new Set();
  24. start() {
  25. this.initializeButtonEffects();
  26. }
  27. /**
  28. * 初始化所有按钮的按压效果
  29. */
  30. private initializeButtonEffects() {
  31. const buttons = this.getAllButtons();
  32. console.log(`[ButtonAni] 找到 ${buttons.length} 个按钮,开始添加按压效果`);
  33. buttons.forEach(buttonNode => {
  34. this.addButtonPressEffect(buttonNode);
  35. });
  36. }
  37. /**
  38. * 获取所有需要添加效果的按钮
  39. */
  40. private getAllButtons(): Node[] {
  41. if (!this.autoFindButtons) {
  42. return this.manualButtons.filter(btn => btn && btn.isValid);
  43. }
  44. const allButtons: Node[] = [];
  45. // 从Canvas开始递归查找所有按钮
  46. const canvas = find('Canvas');
  47. if (canvas) {
  48. this.findButtonsRecursively(canvas, allButtons);
  49. }
  50. // 也查找Canvas-001(如果存在)
  51. const canvas001 = find('Canvas-001');
  52. if (canvas001) {
  53. this.findButtonsRecursively(canvas001, allButtons);
  54. }
  55. return allButtons;
  56. }
  57. /**
  58. * 递归查找节点下的所有按钮组件
  59. */
  60. private findButtonsRecursively(node: Node, buttons: Node[]) {
  61. if (!node || !node.isValid) return;
  62. // 检查当前节点是否有Button组件
  63. const button = node.getComponent(Button);
  64. if (button && button.enabled) {
  65. buttons.push(node);
  66. }
  67. // 递归检查子节点
  68. node.children.forEach(child => {
  69. this.findButtonsRecursively(child, buttons);
  70. });
  71. }
  72. /**
  73. * 为单个按钮添加按压效果
  74. */
  75. private addButtonPressEffect(buttonNode: Node) {
  76. if (!buttonNode || !buttonNode.isValid) return;
  77. const button = buttonNode.getComponent(Button);
  78. if (!button) return;
  79. // 保存原始缩放值
  80. this.originalScales.set(buttonNode, buttonNode.scale.clone());
  81. // 添加按下事件监听
  82. buttonNode.on(Node.EventType.TOUCH_START, this.onButtonPress.bind(this, buttonNode), this);
  83. buttonNode.on(Node.EventType.TOUCH_END, this.onButtonRelease.bind(this, buttonNode), this);
  84. buttonNode.on(Node.EventType.TOUCH_CANCEL, this.onButtonRelease.bind(this, buttonNode), this);
  85. console.log(`[ButtonAni] 已为按钮 ${buttonNode.name} 添加按压效果`);
  86. }
  87. /**
  88. * 按钮按下事件处理
  89. */
  90. private onButtonPress(buttonNode: Node) {
  91. if (!buttonNode || !buttonNode.isValid || this.animatingButtons.has(buttonNode)) {
  92. return;
  93. }
  94. this.animatingButtons.add(buttonNode);
  95. const originalScale = this.originalScales.get(buttonNode) || Vec3.ONE;
  96. const pressedScale = originalScale.clone().multiplyScalar(this.pressScale);
  97. // 停止之前的动画
  98. tween(buttonNode).stop();
  99. // 播放按压动画
  100. tween(buttonNode)
  101. .to(this.pressDuration, { scale: pressedScale }, { easing: 'quadOut' })
  102. .call(() => {
  103. // 动画完成后从集合中移除
  104. this.animatingButtons.delete(buttonNode);
  105. })
  106. .start();
  107. }
  108. /**
  109. * 按钮释放事件处理
  110. */
  111. private onButtonRelease(buttonNode: Node) {
  112. if (!buttonNode || !buttonNode.isValid) {
  113. return;
  114. }
  115. this.animatingButtons.add(buttonNode);
  116. const originalScale = this.originalScales.get(buttonNode) || Vec3.ONE;
  117. // 停止之前的动画
  118. tween(buttonNode).stop();
  119. // 播放释放动画
  120. tween(buttonNode)
  121. .to(this.releaseDuration, { scale: originalScale }, { easing: 'backOut' })
  122. .call(() => {
  123. // 动画完成后从集合中移除
  124. this.animatingButtons.delete(buttonNode);
  125. })
  126. .start();
  127. }
  128. /**
  129. * 手动刷新按钮效果(当有新按钮动态创建时调用)
  130. */
  131. public refreshButtonEffects() {
  132. // 清理之前的监听
  133. this.cleanup();
  134. // 重新初始化
  135. this.initializeButtonEffects();
  136. }
  137. /**
  138. * 清理所有按钮监听和动画
  139. */
  140. private cleanup() {
  141. this.originalScales.forEach((scale, buttonNode) => {
  142. if (buttonNode && buttonNode.isValid) {
  143. // 移除事件监听
  144. buttonNode.off(Node.EventType.TOUCH_START, this.onButtonPress, this);
  145. buttonNode.off(Node.EventType.TOUCH_END, this.onButtonRelease, this);
  146. buttonNode.off(Node.EventType.TOUCH_CANCEL, this.onButtonRelease, this);
  147. // 停止动画并恢复原始缩放
  148. tween(buttonNode).stop();
  149. buttonNode.setScale(scale);
  150. }
  151. });
  152. this.originalScales.clear();
  153. this.animatingButtons.clear();
  154. }
  155. onDestroy() {
  156. this.cleanup();
  157. }
  158. }