import { _decorator, Component, Node, Tween, tween, Vec3 } from 'cc'; const { ccclass, property } = _decorator; /** * 通用弹出动画控制器 * 负责管理UI面板的显示和隐藏动画 */ @ccclass('PopUPAni') export class PopUPAni extends Component { // Gray节点引用 @property(Node) grayNode: Node = null; /** * 显示面板动画 * 面板从小到大的缩放动画,从屏幕中央弹出 */ public showPanel(): Promise { return new Promise((resolve) => { // 设置Gray节点为true if (this.grayNode) { this.grayNode.active = true; } // 设置初始状态:位置居中,缩小到0 this.node.setPosition(0, 0, 0); // 先移动到屏幕正中间 this.node.setScale(Vec3.ZERO); // 然后设置缩放为0 // 缩放动画:从0放大到1 tween(this.node) .to(0.3, { scale: new Vec3(1, 1, 1) }, { easing: 'backOut' // 使用回弹缓动效果 }) .call(() => { resolve(); }) .start(); }); } /** * 隐藏面板动画 * 面板从大到小的缩放动画,然后回到原来的画面外右侧位置并恢复原来大小 * 注意:不操作面板的active状态,面板始终保持active=true */ public hidePanel(): Promise { return new Promise((resolve) => { // 缩放动画:从1缩小到0 tween(this.node) .to(0.2, { scale: Vec3.ZERO }, { easing: 'backIn' // 使用回弹缓动效果 }) .call(() => { // 设置Gray节点为false if (this.grayNode) { this.grayNode.active = false; } // 面板缩放到0后,回到原来的画面外右侧位置并恢复原来大小 // 假设原来位置在画面外右侧,这里设置一个合理的右侧位置 this.node.setPosition(1000, 0, 0); // 移动到画面外右侧 this.node.setScale(Vec3.ONE); // 恢复原来大小 // 不操作 this.node.active,保持面板原有的active状态 resolve(); }) .start(); }); } /** * 立即隐藏面板(无动画) * 注意:不操作面板的active状态,面板始终保持active=true */ public hidePanelImmediate(): void { // 设置Gray节点为false if (this.grayNode) { this.grayNode.active = false; } this.node.setScale(Vec3.ZERO); // 立即移动到画面外右侧位置并恢复原来大小 this.node.setPosition(1000, 0, 0); // 移动到画面外右侧 this.node.setScale(Vec3.ONE); // 恢复原来大小 // 不操作 this.node.active,保持面板原有的active状态 } /** * 立即显示面板(无动画) * 注意:不操作面板的active状态,面板始终保持active=true */ public showPanelImmediate(): void { // 设置Gray节点为true if (this.grayNode) { this.grayNode.active = true; } this.node.setPosition(0, 0, 0); // 先移动到屏幕正中间 this.node.setScale(Vec3.ONE); // 然后设置缩放为1 // 不操作 this.node.active,保持面板原有的active状态 } /** * 图标升级成功动画 * 播放图标的放大缩小动画 * @param iconNode 图标节点 */ public playIconUpgradeAnimation(iconNode: Node): Promise { return new Promise((resolve) => { if (!iconNode) { resolve(); return; } // 停止之前的动画,防止快速点击时动画冲突 Tween.stopAllByTarget(iconNode); // 重置到原始缩放状态,确保动画从正确的状态开始 iconNode.setScale(Vec3.ONE); // 保存原始缩放值 const originalScale = Vec3.ONE.clone(); // 创建缩放动画:放大到1.5倍再立即缩小回原始大小 const scaleAnimation = tween(iconNode) .to(0.25, { scale: new Vec3(originalScale.x * 1.5, originalScale.y * 1.5, originalScale.z) }, { easing: 'sineOut' }) .to(0.25, { scale: originalScale }, { easing: 'sineIn' }); // 只播放缩放动画 scaleAnimation.call(() => resolve()).start(); }); } /** * 提示图标动画 * 播放缩小到0.7倍再放大到原来大小的重复动画,直到条件不满足为止 * @param iconNode 图标节点 * @param checkCondition 检查条件的回调函数,返回false时停止动画 */ public playIconTipAnimation(iconNode: Node, checkCondition?: () => boolean): void { if (!iconNode) return; // 停止之前的动画 Tween.stopAllByTarget(iconNode); // 保存原始缩放值 const originalScale = iconNode.scale.clone(); // 如果没有提供检查条件,使用默认的无限重复动画 if (!checkCondition) { const scaleAnimation = tween(iconNode) .to(0.5, { scale: new Vec3(originalScale.x * 0.7, originalScale.y * 0.7, originalScale.z) }, { easing: 'sineInOut' }) .to(0.5, { scale: originalScale }, { easing: 'sineInOut' }) .repeatForever(); // 无限重复 scaleAnimation.start(); return; } // 带条件检查的动画循环 const playAnimationCycle = () => { // 在每次动画循环开始前检查条件 if (!checkCondition()) { // 条件不满足,停止动画并恢复原始缩放 this.stopIconTipAnimation(iconNode); return; } // 创建一次完整的缩放动画循环 const scaleAnimation = tween(iconNode) .to(0.5, { scale: new Vec3(originalScale.x * 0.7, originalScale.y * 0.7, originalScale.z) }, { easing: 'sineInOut' }) .to(0.5, { scale: originalScale }, { easing: 'sineInOut' }) .call(() => { // 一次循环完成后,递归调用下一次循环 playAnimationCycle(); }); scaleAnimation.start(); }; // 开始第一次动画循环 playAnimationCycle(); } /** * 停止提示图标动画 * @param iconNode 图标节点 */ public stopIconTipAnimation(iconNode: Node): void { if (!iconNode) return; // 停止所有动画 Tween.stopAllByTarget(iconNode); // 恢复原始缩放 iconNode.setScale(Vec3.ONE); } }