import { _decorator, Animation, Button, Component, EditBox, editorExtrasTag, find, isValid, js, Label, Layout, Node, ProgressBar, Quat, Slider, sp, Sprite, SpriteFrame, tween, UIOpacity, UITransform, Vec3, } from "cc"; import { ScrollView } from "cc"; import { Prefab } from "cc"; import { instantiate } from "cc"; import BaseUI from "./BaseUI"; export default class UIUtils { static destroyNodeDelay(new_prop: Node, arg1: number) { setTimeout(() => { if (isValid(new_prop)) { new_prop.destroy(); } }, arg1); } static removeEmptyNode(content: Node) { let emptyNode = content.getChildByName("EmptyNode"); if (emptyNode) { emptyNode.destroy(); } } static moveToNewParentAndAnim(parent: Node, animNode: Node, cb: Function) { UIUtils.moveToNewParent(parent, animNode); UIUtils.AnimBezierMoveAndScale( animNode, new Vec3(0, 0, 0), 0.2, 0.8, () => { cb(); } ); } static hideAnim(node: Node, arg1: () => void) { tween(node) .to(0.2, { scale: new Vec3(0, 0, 0) }) .call(() => { node.active = false; node.setScale(new Vec3(1, 1, 1)); arg1(); }) .start(); } static closeUI(node: Node) { if (!isValid(node)) { return; } node.active = false; node.parent = null; node.destroy(); } public static DeepFindChildByName(parentNode: Node, name: string): Node { if (!isValid(parentNode)) { return null; } return UIUtils._findInChildren(parentNode, name); } // 将一个node节点移动到新的父节点 static moveToNewParent(parent: Node, child: Node) { // 先转换child的世界坐标位置 let cp = child.getWorldPosition(new Vec3()); // 更换父节点 child.setParent(parent, false); // 转换世界坐标到新父节点的本地坐标系 const parentUITrans = parent.getComponent(UITransform); // 设置新的本地坐标,使其屏幕位置不变 const newP = parentUITrans.convertToNodeSpaceAR(cp); child.setPosition(newP); } /** * Layout子项目依次播放滑入动画 * @param layout 布局节点 * @param options 动画选项 * @param callback 所有动画完成后的回调 */ static AnimateLayoutItems( layout: Node, options: { offsetX?: number; offsetY?: number; delayInterval?: number; duration?: number; scale?: number; easing?: string; staggerDirection?: "forward" | "reverse" | "random"; onItemComplete?: (index: number, node: Node) => void; } = {}, callback?: () => void ): void { if (!isValid(layout) || !layout.children || layout.children.length === 0) { if (callback) callback(); return; } const { onItemComplete } = options; const children = [...layout.children]; let completedCount = 0; const totalItems = children.length; // 先让所有子元素激活,让布局系统计算最终位置 children.forEach((child) => { if (isValid(child)) { child.active = true; } }); // 强制更新布局 const layoutComp = layout.getComponent(Layout); if (layoutComp) { layoutComp.updateLayout(); } // 等待布局更新完成后再开始动画 setTimeout(() => { this.startSlideInAnimations( children, options, callback, completedCount, totalItems, onItemComplete ); }, 0); } /** * 开始滑入动画 */ private static startSlideInAnimations( children: Node[], options: any, callback?: () => void, completedCount: number = 0, totalItems: number = 0, onItemComplete?: (index: number, node: Node) => void ): void { const { offsetX, offsetY, delayInterval, duration = 0.3, scale = 1, } = options; // 初始化所有子节点 children.forEach((child, index) => { if (!isValid(child)) return; // 获取布局计算后的位置作为目标位置 const targetPos = child.getPosition().clone(); if (index < 8) { // 设置初始偏移位置,增加偏移量使动画更明显 const startPos = new Vec3( targetPos.x + offsetX, targetPos.y +800, targetPos.z ); child.setPosition(startPos); child.setScale(0.8, 0.8, 0.8); // 使用原始的DelayBezierMoveAndScale方法确保位置正确 UIUtils.DelayBezierMoveAndScale( child, targetPos, scale, duration, delayInterval * index ).then(() => { completedCount++; if (onItemComplete) onItemComplete(index, child); if (completedCount >= totalItems && callback) { callback(); } }); } else { child.setPosition(targetPos); child.setScale(1, 1, 1); child.active = true; if (onItemComplete) onItemComplete(index, child); if (completedCount >= totalItems && callback) { callback(); } } }); } static AnimScaleShake( node: Node, scale: number = 1.2, duration: number = 0.6, repeatForever: boolean = true, callback?: () => void ) { const originalScale = node.getScale().clone(); const scaleUp = Vec3.multiplyScalar(new Vec3(), originalScale, scale); const animation = tween(node) .to( duration / 2, { scale: originalScale.clone().multiplyScalar(scale) }, { easing: "quadOut" } ) .to(duration / 2, { scale: originalScale }, { easing: "quadIn" }); if (repeatForever) { animation.repeatForever().start(); } else { animation .call(() => { if (callback) callback(); }) .start(); } } static async AnimShakeUpAndDownByTimes(node: Node, duration: number) { // 根据动画的次数,计算出动画的间隔时间 return new Promise((resolve, reject) => { tween(node) .to( duration / 2, { position: new Vec3(0, 30, 0) }, { easing: "quadOut" } ) .to( duration / 2, { position: new Vec3(0, 150, 0) }, { easing: "quadIn" } ) .call(() => { resolve(node); }) .start(); }); } static MoveXY( node: Node, moveX: number, moveY: number, duration: number, delay: number ) { let toPosition = node.getPosition(); node.setPosition(toPosition.x + moveX, toPosition.y + moveY, toPosition.z); this.DelayBezierMoveAndScale(node, toPosition, 1, duration, delay); } static async DelayBezierMoveAndScale( node: Node, targetPos: Vec3, scale: number, duration: number, delay: number ) { await new Promise((resolve) => setTimeout(resolve, delay)); return new Promise((resolve, reject) => { this.AnimBezierMoveAndScale(node, targetPos, scale, duration, () => { resolve(node); }); }); } static fadeIn(child: Node, duration: number) { if (!isValid(child)) { return; } let opacityComp = child.getComponent(UIOpacity); if (!opacityComp) { opacityComp = child.addComponent(UIOpacity); } child.setScale(0, 0); tween({ t: 0 }) .to( duration, { t: 1 }, { easing: "quadInOut", onUpdate: (obj) => { if (!isValid(child)) { return; } child.setScale(1 * obj.t, 1 * obj.t); opacityComp.opacity = 255 * obj.t; }, } ) .start(); } static AnimBezierMoveAndTranslate( node: Node, targetPos: Vec3, scale: number, duration: number, callback?: Function ) { if (!isValid(node)) { return; } let opacityComp = node.getComponent(UIOpacity); if (!opacityComp) { opacityComp = node.addComponent(UIOpacity); } const startPos = node.getPosition(); const startScale = node.scale.clone(); const ctrl = new Vec3( (startPos.x + targetPos.x) / 2, Math.max(startPos.y, targetPos.y) + 150, (startPos.z + targetPos.z) / 2 ); const tempPos = new Vec3(); const tempScale = new Vec3(); tween({ t: 0 }) .to( duration, { t: 1 }, { easing: "quadInOut", onUpdate: (obj) => { if (!isValid(node)) { return; } const t = obj.t; const oneMinusT = 1 - t; // 贝塞尔曲线计算 tempPos.x = oneMinusT * oneMinusT * startPos.x + 2 * t * oneMinusT * ctrl.x + t * t * targetPos.x; tempPos.y = oneMinusT * oneMinusT * startPos.y + 2 * t * oneMinusT * ctrl.y + t * t * targetPos.y; tempPos.z = oneMinusT * oneMinusT * startPos.z + 2 * t * oneMinusT * ctrl.z + t * t * targetPos.z; node.setPosition(tempPos); // 缩放插值计算 tempScale.set( startScale.x + (scale - startScale.x) * t, startScale.y + (scale - startScale.y) * t, startScale.z + (scale - startScale.z) * t ); node.setScale(tempScale); //设置透明度,越来越透明 opacityComp.opacity = 255 * (1 - t); }, } ) .call(() => { if (callback) callback(); }) .start(); } // 贝塞尔曲线实现移动和缩放动画 static AnimBezierMoveAndScale( node: Node, targetPos: Vec3, scale: number, duration: number, callback?: Function, rotate?: number ) { if (!isValid(node)) { return; } const startPos = node.getPosition(); const startScale = node.scale.clone(); const ctrl = new Vec3( (startPos.x + targetPos.x) / 2, Math.max(startPos.y, targetPos.y) + 150, (startPos.z + targetPos.z) / 2 ); const tempPos = new Vec3(); const tempScale = new Vec3(); tween({ t: 0 }) .to( duration, { t: 1 }, { easing: "quadInOut", onUpdate: (obj) => { if (!isValid(node)) { return; } const t = obj.t; const oneMinusT = 1 - t; // 贝塞尔曲线计算 tempPos.x = oneMinusT * oneMinusT * startPos.x + 2 * t * oneMinusT * ctrl.x + t * t * targetPos.x; tempPos.y = oneMinusT * oneMinusT * startPos.y + 2 * t * oneMinusT * ctrl.y + t * t * targetPos.y; tempPos.z = oneMinusT * oneMinusT * startPos.z + 2 * t * oneMinusT * ctrl.z + t * t * targetPos.z; node.setPosition(tempPos); // 缩放插值计算 tempScale.set( startScale.x + (scale - startScale.x) * t, startScale.y + (scale - startScale.y) * t, startScale.z + (scale - startScale.z) * t ); node.setScale(tempScale); if (rotate) { node.setRotationFromEuler(new Vec3(0, 0, rotate * t)); } }, } ) .call(() => { if (callback) callback(); }) .start(); } public static _findInChildren(node: Node, name: string): Node { var x = node.getChildByName(name); if (x) return x; if (node.children.length == 0) return null; for (var i = 0; i < node.children.length; ++i) { var tmp = this._findInChildren(node.children[i], name); if (tmp) return tmp; } return null; } } /* 使用示例: // 基本用法 - 滑入动画 UIUtils.AnimateLayoutItems(layoutNode); // 自定义选项 UIUtils.AnimateLayoutItems(layoutNode, { duration: 0.5, delayInterval: 0.15, scale: 1.1, easing: 'backOut', staggerDirection: 'reverse' }, () => { console.log('所有动画完成'); }); // 带单个项目完成回调 UIUtils.AnimateLayoutItems(layoutNode, { onItemComplete: (index, node) => { console.log(`第${index}个项目动画完成`); } }, () => { console.log('所有项目动画完成'); }); */