| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597 |
- import { Label, Node, Sprite, Tween, tween, UIOpacity, UITransform, v2, v3, Vec3, view, Widget } from "cc";
- import { ch } from "../../ch/ch";
- //单个节点进场动画,退场动画
- export function toggleSlideOutAndBack(t: Node, offset: Vec3, duration: number = 0.5, onFinish?: () => void) {
- if (!t['__originPos']) {
- t['__originPos'] = t.position.clone(); // 初始位置
- t['__isHidden'] = false;
- }
- const originPos: Vec3 = t['__originPos'];
- if (t['__targetHidden'] === undefined) {
- t['__targetHidden'] = t['__isHidden'];
- }
- const isHidden = t['__isHidden'];
- const targetHidden = !t['__targetHidden']; // 翻转目标
- t['__targetHidden'] = targetHidden; // 标记目标
- const targetPos = targetHidden ? originPos.clone().add(offset) : originPos.clone();
- // 停止旧动画(彻底打断)
- if (t['__slideTween']) {
- t['__slideTween'].stop();
- t['__slideTween'] = null;
- }
- const currentPos = t.position.clone();
- const posDelta = targetPos.clone().subtract(currentPos).length();
- const isReversing = posDelta > 1 && isHidden !== targetHidden;
- const actualDuration = isReversing ? duration * 0.4 : duration;
- const tweenInst = tween(t)
- .to(actualDuration, { position: new Vec3(targetPos) }, { easing: "quadInOut" })
- .call(() => {
- // 动画未被中断才更新最终状态
- if (t['__targetHidden'] === targetHidden) {
- t['__isHidden'] = targetHidden;
- t['__slideTween'] = null;
- onFinish?.();
- }
- });
- t['__slideTween'] = tweenInst;
- tweenInst.start();
- }
- //节点下所有子节点进场动画,退场动画
- export function toggleSlideOutAndBack_Nodes(t: Node, offset: Vec3, duration: number = 0.5, onFinish?: () => void) {
- const children = t.children;
- if (children.length === 0) {
- onFinish?.();
- return;
- }
- let completedCount = 0;
- children.forEach((node) => {
- toggleSlideOutAndBack(node, offset, duration, () => {
- completedCount++;
- if (completedCount === children.length) {
- onFinish?.();
- }
- });
- });
- }
- //向上滑动
- export function toggleSlideInAndOut(t: Node, duration: number = 0.5, onFinish?: () => void) {
- if (!t['__originPos']) {
- t['__originPos'] = t.position.clone();
- }
- const originPos: Vec3 = t['__originPos'];
- const showPos = originPos.clone().add(new Vec3(0, 1334, 0));//可优化
- let uiOpacity = t.getComponent(UIOpacity);
- if (!uiOpacity) {
- uiOpacity = t.addComponent(UIOpacity);
- uiOpacity.opacity = 0;
- }
- // 初始化状态记录
- if (t['__isHidden'] === undefined) {
- t['__isHidden'] = true;
- }
- if (t['__targetHidden'] === undefined) {
- t['__targetHidden'] = t['__isHidden'];
- }
- // 计算新的目标状态(翻转)
- const nextTargetHidden = !t['__targetHidden'];
- t['__targetHidden'] = nextTargetHidden;
- const targetPos = nextTargetHidden ? originPos : showPos;
- const targetOpacity = nextTargetHidden ? 0 : 255;
- // 当前状态
- const currentPos = t.position.clone();
- const currentOpacity = uiOpacity.opacity;
- const posDiff = targetPos.clone().subtract(currentPos).length();
- const isMidTween = currentOpacity > 0 && currentOpacity < 255 && posDiff > 0;
- const actualDuration = isMidTween ? duration * 0.4 : duration;
- // 停止旧动画
- Tween.stopAllByTarget(t);
- Tween.stopAllByTarget(uiOpacity);
- tween(t)
- .parallel(
- tween(t).to(actualDuration, { position: targetPos.clone() }, { easing: 'quadInOut' }),
- tween(uiOpacity).to(actualDuration, { opacity: targetOpacity }, { easing: 'quadInOut' })
- )
- .call(() => {
- // 只有目标状态未被再次更改时,才确认最终状态
- if (t['__targetHidden'] === nextTargetHidden) {
- t['__isHidden'] = nextTargetHidden;
- onFinish?.();
- }
- })
- .start();
- }
- //呼吸动画
- export function toggleBreath(t: Node, duration: number = 0.5, onFinish?: () => void) {
- if (!t['__originalScale']) {
- t['__originalScale'] = t.scale.clone(); // 缓存初始缩放
- }
- const originalScale: Vec3 = t['__originalScale'];
- //停止之前的 tween,并还原缩放
- if (t['__breathTween']) {
- t['__breathTween'].stop();
- t.scale = originalScale.clone(); //强制还原到初始scale
- }
- const scaleUp = originalScale.clone().multiplyScalar(1.1);
- const scaleDown = originalScale;
- const breathTween = tween(t)
- .repeatForever(
- tween(t)
- .to(duration, { scale: scaleUp }, { easing: 'sineInOut' })
- .to(duration, { scale: scaleDown }, { easing: 'sineInOut' })
- .call(() => {
- console.log('breath');
- if (onFinish) onFinish();
- })
- );
- t['__breathTween'] = breathTween;
- breathTween.start();
- }
- //提醒动画
- export function toggleRemind(t: Node, duration: number = 0.2, delay: number = 1.5, onFinish?: () => void) {
- if (!t['__originScale']) {
- t['__originScale'] = t.scale.clone(); // 记录初始缩放
- }
- if (t['__remindTween']) {
- t['__remindTween'].stop();
- }
- const originalScale = t['__originScale'];
- const scaleUp = originalScale.clone().multiplyScalar(1.2);
- const remindTween = tween(t)
- .repeatForever(
- tween()
- .to(duration, { scale: scaleUp }, { easing: 'sineOut' })
- .to(duration, { scale: originalScale }, { easing: 'sineIn' })
- .delay(delay)
- .call(() => {
- if (onFinish) onFinish();
- })
- );
- t['__remindTween'] = remindTween;
- remindTween.start();
- }
- //平移至指定点位动画
- export function toggleMoveTo(t: Node, toPos?: Vec3, duration: number = 0.5, onFinish?: () => void) {
- if (!t['__originPos']) {
- t['__originPos'] = t.position.clone();
- }
- if (t['__moveTween']) {
- t['__moveTween'].stop();
- t['__moveTween'] = null;
- }
- // 没传目标位置时表示还原
- const targetPos = toPos ? toPos : t['__originPos'];
- const moveTween = tween(t)
- .to(duration, { position: targetPos }, { easing: 'quadOut' })
- .call(() => {
- if (onFinish) onFinish();
- });
- t['__moveTween'] = moveTween;
- moveTween.start();
- }
- // 根据指定位移距离进行平移
- export function toggleMoveBy(t: Node, offset: Vec3, duration: number = 0.5, onFinish?: () => void) {
- if (!t['__originPos']) {
- t['__originPos'] = t.position.clone();
- }
- if (t['__moveTween']) {
- t['__moveTween'].stop();
- t['__moveTween'] = null;
- }
- const originPos: Vec3 = t['__originPos'];
- const targetPos = t.position.equals(originPos) ? originPos.clone().add(offset) : originPos;
- t.position = targetPos;
- const moveTween = tween(t)
- .to(duration, { position: targetPos }, { easing: "quadInOut" })
- .call(() => {
- if (onFinish) onFinish();
- });
- t['__moveTween'] = moveTween;
- moveTween.start();
- }
- //图片填充动画
- export function fillImage(t: Node, duration: number, start: number, target: number, onFinish?: () => {}) {
- Tween.stopAllByTarget(t);
- let sprite = t.getComponent(Sprite);
- sprite.fillRange = start;
- tween(sprite)
- .to(duration, { fillRange: target }, { easing: 'quadOut' }).call(onFinish)
- .start();
- }
- //数字过渡动画
- export function number_fraction_ani(t: Node, duration: number, startNumerator: number, endNumerator: number, denominator: number, onFinish?: () => void) {
- Tween.stopAllByTarget(t);
- const label = t.getComponent(Label);
- if (!label) return;
- const obj = { value: startNumerator };
- tween(obj)
- .to(duration, { value: endNumerator }, {
- easing: 'quadOut',
- onUpdate: () => {
- const numerator = Math.floor(obj.value);
- label.string = `${numerator}/${denominator}`;
- }
- })
- .call(() => {
- label.string = `${endNumerator}/${denominator}`;
- onFinish?.();
- })
- .start();
- }
- //弹窗动画
- export function ani_ui(node: Node, end: number = 1.0): void {
- Tween.stopAllByTarget(node); // 打断旧动画
- //重置scale
- node.setScale(0.5, 0.5, 1);
- tween(node)
- .to(1.2, { scale: v3(end, end, 1) }, { easing: 'elasticOut' })
- .start();
- }
- //淡入淡出动画
- export function fade_anim(node: Node, duration: number = 0.2, curOpacity: number = 0, targetOpacity: number = 255, delay: number = 0, onFinish?: () => void) {
- let uiOpacity = node.getComponent(UIOpacity);
- if (!uiOpacity) {
- node.addComponent(UIOpacity).opacity = curOpacity;
- uiOpacity = node.getComponent(UIOpacity);
- }
- tween(uiOpacity).to(duration, { opacity: targetOpacity }).delay(delay).call(onFinish).start();
- }
- //放大缩小动画
- export function scale_anim(node: Node, duration: number = 0.2, curScale: Vec3 = new Vec3(0, 0, 0), targetScale: Vec3 = new Vec3(1, 1, 1), delay: number = 0, onFinish?: () => void, loop: boolean = false, backDuration: number = 0.1) {
- Tween.stopAllByTarget(node);
- // 平滑缩回初始 scale
- tween(node)
- .to(backDuration, { scale: curScale })
- .call(() => {
- const anim = tween(node).to(duration, { scale: targetScale }).delay(delay);
- if (loop) {
- anim
- .to(duration, { scale: curScale })
- .union()
- .repeatForever()
- .start();
- } else {
- anim.call(() => {
- if (onFinish) onFinish();
- }).start();
- }
- })
- .start();
- }
- export type RotateAnimType = 'once' | 'loop' | 'swing';
- export interface RotateAnimOptions {
- duration?: number;
- angle?: number;
- delay?: number;
- onFinish?: () => void;
- swingCount?: number;
- direction?: 'y' | 'x' | 'z';
- }
- // 旋转动画
- export function rotate_anim_ext(node: Node, type: RotateAnimType, options: RotateAnimOptions = {}) {
- const {
- duration = 3.5,
- angle = 30,
- delay = 0,
- onFinish,
- swingCount = 3,
- direction = 'z',
- } = options;
- const getAngleVec = (value: number): Vec3 => {
- switch (direction) {
- case 'x': return new Vec3(value, 0, 0);
- case 'z': return new Vec3(0, 0, value);
- case 'y':
- default: return new Vec3(0, value, 0);
- }
- };
- //停止旧动画
- if ((node as any).__rotate_tween) {
- (node as any).__rotate_tween.stop();
- }
- //保存原始角度
- (node as any).__rotate_origin = node.eulerAngles.clone();
- let rotateTween: Tween<Node>;
- switch (type) {
- case 'once': {
- const original = node.eulerAngles.clone();
- const offset = getAngleVec(angle); // 例如 Vec3(0, 15, 0)
- const left = original.clone().subtract(offset);
- const right = original.clone().add(offset);
- const swingCount = 3; // 摆动次数(一次来回为一次)
- const singleSwingDuration = duration / (swingCount * 2); // 一次半摆(左或右)用的时间
- let t = tween(node);
- for (let i = 0; i < swingCount; i++) {
- t = t
- .to(singleSwingDuration, { eulerAngles: left }, { easing: 'sineInOut' })
- .to(singleSwingDuration, { eulerAngles: right }, { easing: 'sineInOut' });
- }
- // 最后回中
- t = t
- .to(singleSwingDuration, { eulerAngles: original }, { easing: 'sineOut' })
- .delay(delay)
- .call(onFinish);
- rotateTween = t;
- break;
- }
- case 'loop': {
- const delta: any = {};
- switch (direction) {
- case 'x': delta.eulerAngles = { x: angle }; break;
- case 'z': delta.eulerAngles = { z: angle }; break;
- case 'y':
- default: delta.eulerAngles = { y: angle }; break;
- }
- rotateTween = tween(node)
- .delay(delay)
- .by(duration, delta)
- .repeatForever();
- break;
- }
- case 'swing': {
- const original = node.eulerAngles.clone();
- const left = original.clone().subtract(getAngleVec(angle));
- const right = original.clone().add(getAngleVec(angle));
- let swing = tween(node);
- for (let i = 0; i < swingCount; i++) {
- swing = swing
- .to(duration / 2, { eulerAngles: right }, { easing: 'sineOut' })
- .to(duration / 2, { eulerAngles: left }, { easing: 'sineIn' });
- }
- swing = swing
- .to(duration / 2, { eulerAngles: original }, { easing: 'sineOut' })
- .call(onFinish);
- rotateTween = tween(node)
- .delay(delay)
- .then(swing);
- break;
- }
- }
- //启动动画并保存引用
- (node as any).__rotate_tween = rotateTween;
- rotateTween.start();
- }
- //停止旋转动画(不还原)
- export function rotate_anim_stop(node: Node) {
- const t = (node as any).__rotate_tween;
- if (t) {
- t.stop();
- (node as any).__rotate_tween = null;
- }
- }
- //还原旋转角度(平滑返回初始值)
- export function rotate_anim_restore(node: Node, duration: number = 0.3) {
- const origin = (node as any).__rotate_origin as Vec3;
- if (!origin) return;
- rotate_anim_stop(node);
- const backTween = tween(node)
- .to(duration, { eulerAngles: origin }, { easing: 'quadOut' });
- (node as any).__rotate_tween = backTween;
- backTween.start();
- }
- //上下抖动动画
- export interface BounceYOptions {
- distance?: number; // 向上抖动的距离(默认20)
- durationMin?: number; // 最短时长(默认 0.3)
- durationMax?: number; // 最长时长(默认 0.5)
- delay?: number; // 延迟(默认0)
- onFinish?: () => void; // 动画完成回调
- }
- export function bounce_y_anim(node: Node, options: BounceYOptions = {}) {
- const {
- distance = 20,
- durationMin = 0.3,
- durationMax = 0.5,
- delay = 0,
- onFinish,
- } = options;
- //在 min~max 范围内生成随机时长
- const duration = ch.util.getRandom(0.2, 0.35);
- const originalPos = node.position.clone();
- const upPos = originalPos.clone().add(new Vec3(0, distance, 0));
- tween(node)
- .delay(delay)
- .to(duration, { position: upPos }, { easing: 'quadOut' })
- .to(duration, { position: originalPos }, { easing: 'quadIn' })
- .call(onFinish)
- .start();
- }
- export interface JellyAnimOptions {
- scaleX?: number;
- scaleY?: number;
- duration?: number;
- delay?: number;
- onFinish?: () => void;
- loop?: boolean;
- }
- export function jelly_anim_ext(node: Node, options: JellyAnimOptions = {}) {
- const {
- scaleX = 1.2,
- scaleY = 0.8,
- duration = 0.4,
- delay = 0.5,
- onFinish,
- loop = true,
- } = options;
- // 停止旧动画
- if ((node as any).__jelly_tween) {
- (node as any).__jelly_tween.stop();
- }
- // 保存原始 scale
- const original = node.scale.clone();
- (node as any).__jelly_origin = original;
- const stretch = new Vec3(scaleX, scaleY, original.z);
- const fullTween = tween(node).sequence(
- tween(node).to(duration / 2, { scale: stretch }, { easing: 'sineOut' }),
- tween(node).to(duration / 2, { scale: original }, { easing: 'backOut' }),
- tween(node).delay(delay)
- );
- let finalTween: Tween<Node>;
- if (loop) {
- finalTween = fullTween.repeatForever();
- } else {
- finalTween = tween(node)
- .then(fullTween)
- .call(onFinish || (() => { }));
- }
- (node as any).__jelly_tween = finalTween;
- finalTween.start();
- }
- //直接打断
- export function jelly_anim_stop(node: Node) {
- const t = (node as any).__jelly_tween;
- if (t) {
- t.stop();
- (node as any).__jelly_tween = null;
- }
- }
- //打断且复原
- export function jelly_anim_restore(node: Node, duration: number = 0.3) {
- const origin = (node as any).__jelly_origin as Vec3;
- if (!origin) return;
- jelly_anim_stop(node);
- const backTween = tween(node).to(duration, { scale: origin }, { easing: 'quadOut' });
- (node as any).__jelly_tween = backTween;
- backTween.start();
- }
- /**
- * 贝塞尔加缩放
- * @param tweenObj
- * @param target
- * @param startPos
- * @param targetPos
- * @param time
- * @param scale
- * @param px1
- * @param py1
- * @param px2
- * @param py2
- * @returns
- */
- export function targetScaleBezier(tweenObj: any, target: Node, startPos: Vec3, targetPos: Vec3, time: number, scale: number, px1: number, py1: number, px2: number, py2: number, delay: number, onHalf?: () => void, onFinish?: () => void) {
- let t1 = tween(tweenObj).to(time, { t: 1 }, {
- onUpdate: (tar, t) => {
- const p0 = v2(0, 0);
- const p1 = v2(px1, py1);
- const p2 = v2(px2, py2);
- const p3 = v2(1, 1);
- const x = p0.x * (1 - t) * (1 - t) * (1 - t) + 3 * p1.x * t * (1 - t) * (1 - t) + 3 * p2.x * t * t * (1 - t) + p3.x * t * t * t;
- const y = p0.y * (1 - t) * (1 - t) * (1 - t) + 3 * p1.y * t * (1 - t) * (1 - t) + 3 * p2.y * t * t * (1 - t) + p3.y * t * t * t;
- let posx = (targetPos.x - startPos.x) * x + startPos.x;
- let posy = (targetPos.y - startPos.y) * y + startPos.y;
- target.setPosition(new Vec3(posx, posy, 0));
- }
- });
- let t2 = tween(tweenObj).to(time, { scale: scale }, {
- onUpdate: (tar: any, t) => {
- target.setScale(tar.scale, tar.scale, tar.scale);
- }
- });
- tween(tweenObj).parallel(t1, t2).call(onHalf).delay(delay).call(onFinish).start();
- }
|