|
|
@@ -0,0 +1,198 @@
|
|
|
+import { _decorator, Component, Node, Button, Vec3, tween, find } from 'cc';
|
|
|
+const { ccclass, property } = _decorator;
|
|
|
+
|
|
|
+/**
|
|
|
+ * ButtonAni - 通用按钮按压效果管理器
|
|
|
+ * 自动为场景中所有按钮添加按压缩放效果
|
|
|
+ * 挂载在场景的根节点或UI管理节点上
|
|
|
+ */
|
|
|
+@ccclass('ButtonAni')
|
|
|
+export class ButtonAni extends Component {
|
|
|
+
|
|
|
+ @property({ tooltip: '按压时的缩放比例' })
|
|
|
+ public pressScale: number = 0.9;
|
|
|
+
|
|
|
+ @property({ tooltip: '按压动画时长(秒)' })
|
|
|
+ public pressDuration: number = 0.1;
|
|
|
+
|
|
|
+ @property({ tooltip: '释放动画时长(秒)' })
|
|
|
+ public releaseDuration: number = 0.1;
|
|
|
+
|
|
|
+ @property({ tooltip: '是否自动查找所有按钮' })
|
|
|
+ public autoFindButtons: boolean = true;
|
|
|
+
|
|
|
+ @property({ type: [Node], tooltip: '手动指定的按钮节点列表(如果不自动查找)' })
|
|
|
+ public manualButtons: Node[] = [];
|
|
|
+
|
|
|
+ // 存储按钮的原始缩放值
|
|
|
+ private originalScales: Map<Node, Vec3> = new Map();
|
|
|
+
|
|
|
+ // 存储正在播放动画的按钮
|
|
|
+ private animatingButtons: Set<Node> = new Set();
|
|
|
+
|
|
|
+ start() {
|
|
|
+ this.initializeButtonEffects();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 初始化所有按钮的按压效果
|
|
|
+ */
|
|
|
+ private initializeButtonEffects() {
|
|
|
+ const buttons = this.getAllButtons();
|
|
|
+ console.log(`[ButtonAni] 找到 ${buttons.length} 个按钮,开始添加按压效果`);
|
|
|
+
|
|
|
+ buttons.forEach(buttonNode => {
|
|
|
+ this.addButtonPressEffect(buttonNode);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取所有需要添加效果的按钮
|
|
|
+ */
|
|
|
+ private getAllButtons(): Node[] {
|
|
|
+ if (!this.autoFindButtons) {
|
|
|
+ return this.manualButtons.filter(btn => btn && btn.isValid);
|
|
|
+ }
|
|
|
+
|
|
|
+ const allButtons: Node[] = [];
|
|
|
+
|
|
|
+ // 从Canvas开始递归查找所有按钮
|
|
|
+ const canvas = find('Canvas');
|
|
|
+ if (canvas) {
|
|
|
+ this.findButtonsRecursively(canvas, allButtons);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 也查找Canvas-001(如果存在)
|
|
|
+ const canvas001 = find('Canvas-001');
|
|
|
+ if (canvas001) {
|
|
|
+ this.findButtonsRecursively(canvas001, allButtons);
|
|
|
+ }
|
|
|
+
|
|
|
+ return allButtons;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 递归查找节点下的所有按钮组件
|
|
|
+ */
|
|
|
+ private findButtonsRecursively(node: Node, buttons: Node[]) {
|
|
|
+ if (!node || !node.isValid) return;
|
|
|
+
|
|
|
+ // 检查当前节点是否有Button组件
|
|
|
+ const button = node.getComponent(Button);
|
|
|
+ if (button && button.enabled) {
|
|
|
+ buttons.push(node);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 递归检查子节点
|
|
|
+ node.children.forEach(child => {
|
|
|
+ this.findButtonsRecursively(child, buttons);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 为单个按钮添加按压效果
|
|
|
+ */
|
|
|
+ private addButtonPressEffect(buttonNode: Node) {
|
|
|
+ if (!buttonNode || !buttonNode.isValid) return;
|
|
|
+
|
|
|
+ const button = buttonNode.getComponent(Button);
|
|
|
+ if (!button) return;
|
|
|
+
|
|
|
+ // 保存原始缩放值
|
|
|
+ this.originalScales.set(buttonNode, buttonNode.scale.clone());
|
|
|
+
|
|
|
+ // 添加按下事件监听
|
|
|
+ buttonNode.on(Node.EventType.TOUCH_START, this.onButtonPress.bind(this, buttonNode), this);
|
|
|
+ buttonNode.on(Node.EventType.TOUCH_END, this.onButtonRelease.bind(this, buttonNode), this);
|
|
|
+ buttonNode.on(Node.EventType.TOUCH_CANCEL, this.onButtonRelease.bind(this, buttonNode), this);
|
|
|
+
|
|
|
+ console.log(`[ButtonAni] 已为按钮 ${buttonNode.name} 添加按压效果`);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按钮按下事件处理
|
|
|
+ */
|
|
|
+ private onButtonPress(buttonNode: Node) {
|
|
|
+ if (!buttonNode || !buttonNode.isValid || this.animatingButtons.has(buttonNode)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.animatingButtons.add(buttonNode);
|
|
|
+
|
|
|
+ const originalScale = this.originalScales.get(buttonNode) || Vec3.ONE;
|
|
|
+ const pressedScale = originalScale.clone().multiplyScalar(this.pressScale);
|
|
|
+
|
|
|
+ // 停止之前的动画
|
|
|
+ tween(buttonNode).stop();
|
|
|
+
|
|
|
+ // 播放按压动画
|
|
|
+ tween(buttonNode)
|
|
|
+ .to(this.pressDuration, { scale: pressedScale }, { easing: 'quadOut' })
|
|
|
+ .call(() => {
|
|
|
+ // 动画完成后从集合中移除
|
|
|
+ this.animatingButtons.delete(buttonNode);
|
|
|
+ })
|
|
|
+ .start();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按钮释放事件处理
|
|
|
+ */
|
|
|
+ private onButtonRelease(buttonNode: Node) {
|
|
|
+ if (!buttonNode || !buttonNode.isValid) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.animatingButtons.add(buttonNode);
|
|
|
+
|
|
|
+ const originalScale = this.originalScales.get(buttonNode) || Vec3.ONE;
|
|
|
+
|
|
|
+ // 停止之前的动画
|
|
|
+ tween(buttonNode).stop();
|
|
|
+
|
|
|
+ // 播放释放动画
|
|
|
+ tween(buttonNode)
|
|
|
+ .to(this.releaseDuration, { scale: originalScale }, { easing: 'backOut' })
|
|
|
+ .call(() => {
|
|
|
+ // 动画完成后从集合中移除
|
|
|
+ this.animatingButtons.delete(buttonNode);
|
|
|
+ })
|
|
|
+ .start();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 手动刷新按钮效果(当有新按钮动态创建时调用)
|
|
|
+ */
|
|
|
+ public refreshButtonEffects() {
|
|
|
+ // 清理之前的监听
|
|
|
+ this.cleanup();
|
|
|
+
|
|
|
+ // 重新初始化
|
|
|
+ this.initializeButtonEffects();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 清理所有按钮监听和动画
|
|
|
+ */
|
|
|
+ private cleanup() {
|
|
|
+ this.originalScales.forEach((scale, buttonNode) => {
|
|
|
+ if (buttonNode && buttonNode.isValid) {
|
|
|
+ // 移除事件监听
|
|
|
+ buttonNode.off(Node.EventType.TOUCH_START, this.onButtonPress, this);
|
|
|
+ buttonNode.off(Node.EventType.TOUCH_END, this.onButtonRelease, this);
|
|
|
+ buttonNode.off(Node.EventType.TOUCH_CANCEL, this.onButtonRelease, this);
|
|
|
+
|
|
|
+ // 停止动画并恢复原始缩放
|
|
|
+ tween(buttonNode).stop();
|
|
|
+ buttonNode.setScale(scale);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ this.originalScales.clear();
|
|
|
+ this.animatingButtons.clear();
|
|
|
+ }
|
|
|
+
|
|
|
+ onDestroy() {
|
|
|
+ this.cleanup();
|
|
|
+ }
|
|
|
+}
|