Customize_Ani.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. import { Label, Node, Sprite, Tween, tween, UIOpacity, UITransform, v2, v3, Vec3, view, Widget } from "cc";
  2. import { ch } from "../../ch/ch";
  3. //单个节点进场动画,退场动画
  4. export function toggleSlideOutAndBack(t: Node, offset: Vec3, duration: number = 0.5, onFinish?: () => void) {
  5. if (!t['__originPos']) {
  6. t['__originPos'] = t.position.clone(); // 初始位置
  7. t['__isHidden'] = false;
  8. }
  9. const originPos: Vec3 = t['__originPos'];
  10. if (t['__targetHidden'] === undefined) {
  11. t['__targetHidden'] = t['__isHidden'];
  12. }
  13. const isHidden = t['__isHidden'];
  14. const targetHidden = !t['__targetHidden']; // 翻转目标
  15. t['__targetHidden'] = targetHidden; // 标记目标
  16. const targetPos = targetHidden ? originPos.clone().add(offset) : originPos.clone();
  17. // 停止旧动画(彻底打断)
  18. if (t['__slideTween']) {
  19. t['__slideTween'].stop();
  20. t['__slideTween'] = null;
  21. }
  22. const currentPos = t.position.clone();
  23. const posDelta = targetPos.clone().subtract(currentPos).length();
  24. const isReversing = posDelta > 1 && isHidden !== targetHidden;
  25. const actualDuration = isReversing ? duration * 0.4 : duration;
  26. const tweenInst = tween(t)
  27. .to(actualDuration, { position: new Vec3(targetPos) }, { easing: "quadInOut" })
  28. .call(() => {
  29. // 动画未被中断才更新最终状态
  30. if (t['__targetHidden'] === targetHidden) {
  31. t['__isHidden'] = targetHidden;
  32. t['__slideTween'] = null;
  33. onFinish?.();
  34. }
  35. });
  36. t['__slideTween'] = tweenInst;
  37. tweenInst.start();
  38. }
  39. //节点下所有子节点进场动画,退场动画
  40. export function toggleSlideOutAndBack_Nodes(t: Node, offset: Vec3, duration: number = 0.5, onFinish?: () => void) {
  41. const children = t.children;
  42. if (children.length === 0) {
  43. onFinish?.();
  44. return;
  45. }
  46. let completedCount = 0;
  47. children.forEach((node) => {
  48. toggleSlideOutAndBack(node, offset, duration, () => {
  49. completedCount++;
  50. if (completedCount === children.length) {
  51. onFinish?.();
  52. }
  53. });
  54. });
  55. }
  56. //向上滑动
  57. export function toggleSlideInAndOut(t: Node, duration: number = 0.5, onFinish?: () => void) {
  58. if (!t['__originPos']) {
  59. t['__originPos'] = t.position.clone();
  60. }
  61. const originPos: Vec3 = t['__originPos'];
  62. const showPos = originPos.clone().add(new Vec3(0, 1334, 0));//可优化
  63. let uiOpacity = t.getComponent(UIOpacity);
  64. if (!uiOpacity) {
  65. uiOpacity = t.addComponent(UIOpacity);
  66. uiOpacity.opacity = 0;
  67. }
  68. // 初始化状态记录
  69. if (t['__isHidden'] === undefined) {
  70. t['__isHidden'] = true;
  71. }
  72. if (t['__targetHidden'] === undefined) {
  73. t['__targetHidden'] = t['__isHidden'];
  74. }
  75. // 计算新的目标状态(翻转)
  76. const nextTargetHidden = !t['__targetHidden'];
  77. t['__targetHidden'] = nextTargetHidden;
  78. const targetPos = nextTargetHidden ? originPos : showPos;
  79. const targetOpacity = nextTargetHidden ? 0 : 255;
  80. // 当前状态
  81. const currentPos = t.position.clone();
  82. const currentOpacity = uiOpacity.opacity;
  83. const posDiff = targetPos.clone().subtract(currentPos).length();
  84. const isMidTween = currentOpacity > 0 && currentOpacity < 255 && posDiff > 0;
  85. const actualDuration = isMidTween ? duration * 0.4 : duration;
  86. // 停止旧动画
  87. Tween.stopAllByTarget(t);
  88. Tween.stopAllByTarget(uiOpacity);
  89. tween(t)
  90. .parallel(
  91. tween(t).to(actualDuration, { position: targetPos.clone() }, { easing: 'quadInOut' }),
  92. tween(uiOpacity).to(actualDuration, { opacity: targetOpacity }, { easing: 'quadInOut' })
  93. )
  94. .call(() => {
  95. // 只有目标状态未被再次更改时,才确认最终状态
  96. if (t['__targetHidden'] === nextTargetHidden) {
  97. t['__isHidden'] = nextTargetHidden;
  98. onFinish?.();
  99. }
  100. })
  101. .start();
  102. }
  103. //呼吸动画
  104. export function toggleBreath(t: Node, duration: number = 0.5, onFinish?: () => void) {
  105. if (!t['__originalScale']) {
  106. t['__originalScale'] = t.scale.clone(); // 缓存初始缩放
  107. }
  108. const originalScale: Vec3 = t['__originalScale'];
  109. //停止之前的 tween,并还原缩放
  110. if (t['__breathTween']) {
  111. t['__breathTween'].stop();
  112. t.scale = originalScale.clone(); //强制还原到初始scale
  113. }
  114. const scaleUp = originalScale.clone().multiplyScalar(1.1);
  115. const scaleDown = originalScale;
  116. const breathTween = tween(t)
  117. .repeatForever(
  118. tween(t)
  119. .to(duration, { scale: scaleUp }, { easing: 'sineInOut' })
  120. .to(duration, { scale: scaleDown }, { easing: 'sineInOut' })
  121. .call(() => {
  122. console.log('breath');
  123. if (onFinish) onFinish();
  124. })
  125. );
  126. t['__breathTween'] = breathTween;
  127. breathTween.start();
  128. }
  129. //提醒动画
  130. export function toggleRemind(t: Node, duration: number = 0.2, delay: number = 1.5, onFinish?: () => void) {
  131. if (!t['__originScale']) {
  132. t['__originScale'] = t.scale.clone(); // 记录初始缩放
  133. }
  134. if (t['__remindTween']) {
  135. t['__remindTween'].stop();
  136. }
  137. const originalScale = t['__originScale'];
  138. const scaleUp = originalScale.clone().multiplyScalar(1.2);
  139. const remindTween = tween(t)
  140. .repeatForever(
  141. tween()
  142. .to(duration, { scale: scaleUp }, { easing: 'sineOut' })
  143. .to(duration, { scale: originalScale }, { easing: 'sineIn' })
  144. .delay(delay)
  145. .call(() => {
  146. if (onFinish) onFinish();
  147. })
  148. );
  149. t['__remindTween'] = remindTween;
  150. remindTween.start();
  151. }
  152. //平移至指定点位动画
  153. export function toggleMoveTo(t: Node, toPos?: Vec3, duration: number = 0.5, onFinish?: () => void) {
  154. if (!t['__originPos']) {
  155. t['__originPos'] = t.position.clone();
  156. }
  157. if (t['__moveTween']) {
  158. t['__moveTween'].stop();
  159. t['__moveTween'] = null;
  160. }
  161. // 没传目标位置时表示还原
  162. const targetPos = toPos ? toPos : t['__originPos'];
  163. const moveTween = tween(t)
  164. .to(duration, { position: targetPos }, { easing: 'quadOut' })
  165. .call(() => {
  166. if (onFinish) onFinish();
  167. });
  168. t['__moveTween'] = moveTween;
  169. moveTween.start();
  170. }
  171. // 根据指定位移距离进行平移
  172. export function toggleMoveBy(t: Node, offset: Vec3, duration: number = 0.5, onFinish?: () => void) {
  173. if (!t['__originPos']) {
  174. t['__originPos'] = t.position.clone();
  175. }
  176. if (t['__moveTween']) {
  177. t['__moveTween'].stop();
  178. t['__moveTween'] = null;
  179. }
  180. const originPos: Vec3 = t['__originPos'];
  181. const targetPos = t.position.equals(originPos) ? originPos.clone().add(offset) : originPos;
  182. t.position = targetPos;
  183. const moveTween = tween(t)
  184. .to(duration, { position: targetPos }, { easing: "quadInOut" })
  185. .call(() => {
  186. if (onFinish) onFinish();
  187. });
  188. t['__moveTween'] = moveTween;
  189. moveTween.start();
  190. }
  191. //图片填充动画
  192. export function fillImage(t: Node, duration: number, start: number, target: number, onFinish?: () => {}) {
  193. Tween.stopAllByTarget(t);
  194. let sprite = t.getComponent(Sprite);
  195. sprite.fillRange = start;
  196. tween(sprite)
  197. .to(duration, { fillRange: target }, { easing: 'quadOut' }).call(onFinish)
  198. .start();
  199. }
  200. //数字过渡动画
  201. export function number_fraction_ani(t: Node, duration: number, startNumerator: number, endNumerator: number, denominator: number, onFinish?: () => void) {
  202. Tween.stopAllByTarget(t);
  203. const label = t.getComponent(Label);
  204. if (!label) return;
  205. const obj = { value: startNumerator };
  206. tween(obj)
  207. .to(duration, { value: endNumerator }, {
  208. easing: 'quadOut',
  209. onUpdate: () => {
  210. const numerator = Math.floor(obj.value);
  211. label.string = `${numerator}/${denominator}`;
  212. }
  213. })
  214. .call(() => {
  215. label.string = `${endNumerator}/${denominator}`;
  216. onFinish?.();
  217. })
  218. .start();
  219. }
  220. //弹窗动画
  221. export function ani_ui(node: Node, end: number = 1.0): void {
  222. Tween.stopAllByTarget(node); // 打断旧动画
  223. //重置scale
  224. node.setScale(0.5, 0.5, 1);
  225. tween(node)
  226. .to(1.2, { scale: v3(end, end, 1) }, { easing: 'elasticOut' })
  227. .start();
  228. }
  229. //淡入淡出动画
  230. export function fade_anim(node: Node, duration: number = 0.2, curOpacity: number = 0, targetOpacity: number = 255, delay: number = 0, onFinish?: () => void) {
  231. let uiOpacity = node.getComponent(UIOpacity);
  232. if (!uiOpacity) {
  233. node.addComponent(UIOpacity).opacity = curOpacity;
  234. uiOpacity = node.getComponent(UIOpacity);
  235. }
  236. tween(uiOpacity).to(duration, { opacity: targetOpacity }).delay(delay).call(onFinish).start();
  237. }
  238. //放大缩小动画
  239. 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) {
  240. Tween.stopAllByTarget(node);
  241. // 平滑缩回初始 scale
  242. tween(node)
  243. .to(backDuration, { scale: curScale })
  244. .call(() => {
  245. const anim = tween(node).to(duration, { scale: targetScale }).delay(delay);
  246. if (loop) {
  247. anim
  248. .to(duration, { scale: curScale })
  249. .union()
  250. .repeatForever()
  251. .start();
  252. } else {
  253. anim.call(() => {
  254. if (onFinish) onFinish();
  255. }).start();
  256. }
  257. })
  258. .start();
  259. }
  260. export type RotateAnimType = 'once' | 'loop' | 'swing';
  261. export interface RotateAnimOptions {
  262. duration?: number;
  263. angle?: number;
  264. delay?: number;
  265. onFinish?: () => void;
  266. swingCount?: number;
  267. direction?: 'y' | 'x' | 'z';
  268. }
  269. // 旋转动画
  270. export function rotate_anim_ext(node: Node, type: RotateAnimType, options: RotateAnimOptions = {}) {
  271. const {
  272. duration = 3.5,
  273. angle = 30,
  274. delay = 0,
  275. onFinish,
  276. swingCount = 3,
  277. direction = 'z',
  278. } = options;
  279. const getAngleVec = (value: number): Vec3 => {
  280. switch (direction) {
  281. case 'x': return new Vec3(value, 0, 0);
  282. case 'z': return new Vec3(0, 0, value);
  283. case 'y':
  284. default: return new Vec3(0, value, 0);
  285. }
  286. };
  287. //停止旧动画
  288. if ((node as any).__rotate_tween) {
  289. (node as any).__rotate_tween.stop();
  290. }
  291. //保存原始角度
  292. (node as any).__rotate_origin = node.eulerAngles.clone();
  293. let rotateTween: Tween<Node>;
  294. switch (type) {
  295. case 'once': {
  296. const original = node.eulerAngles.clone();
  297. const offset = getAngleVec(angle); // 例如 Vec3(0, 15, 0)
  298. const left = original.clone().subtract(offset);
  299. const right = original.clone().add(offset);
  300. const swingCount = 3; // 摆动次数(一次来回为一次)
  301. const singleSwingDuration = duration / (swingCount * 2); // 一次半摆(左或右)用的时间
  302. let t = tween(node);
  303. for (let i = 0; i < swingCount; i++) {
  304. t = t
  305. .to(singleSwingDuration, { eulerAngles: left }, { easing: 'sineInOut' })
  306. .to(singleSwingDuration, { eulerAngles: right }, { easing: 'sineInOut' });
  307. }
  308. // 最后回中
  309. t = t
  310. .to(singleSwingDuration, { eulerAngles: original }, { easing: 'sineOut' })
  311. .delay(delay)
  312. .call(onFinish);
  313. rotateTween = t;
  314. break;
  315. }
  316. case 'loop': {
  317. const delta: any = {};
  318. switch (direction) {
  319. case 'x': delta.eulerAngles = { x: angle }; break;
  320. case 'z': delta.eulerAngles = { z: angle }; break;
  321. case 'y':
  322. default: delta.eulerAngles = { y: angle }; break;
  323. }
  324. rotateTween = tween(node)
  325. .delay(delay)
  326. .by(duration, delta)
  327. .repeatForever();
  328. break;
  329. }
  330. case 'swing': {
  331. const original = node.eulerAngles.clone();
  332. const left = original.clone().subtract(getAngleVec(angle));
  333. const right = original.clone().add(getAngleVec(angle));
  334. let swing = tween(node);
  335. for (let i = 0; i < swingCount; i++) {
  336. swing = swing
  337. .to(duration / 2, { eulerAngles: right }, { easing: 'sineOut' })
  338. .to(duration / 2, { eulerAngles: left }, { easing: 'sineIn' });
  339. }
  340. swing = swing
  341. .to(duration / 2, { eulerAngles: original }, { easing: 'sineOut' })
  342. .call(onFinish);
  343. rotateTween = tween(node)
  344. .delay(delay)
  345. .then(swing);
  346. break;
  347. }
  348. }
  349. //启动动画并保存引用
  350. (node as any).__rotate_tween = rotateTween;
  351. rotateTween.start();
  352. }
  353. //停止旋转动画(不还原)
  354. export function rotate_anim_stop(node: Node) {
  355. const t = (node as any).__rotate_tween;
  356. if (t) {
  357. t.stop();
  358. (node as any).__rotate_tween = null;
  359. }
  360. }
  361. //还原旋转角度(平滑返回初始值)
  362. export function rotate_anim_restore(node: Node, duration: number = 0.3) {
  363. const origin = (node as any).__rotate_origin as Vec3;
  364. if (!origin) return;
  365. rotate_anim_stop(node);
  366. const backTween = tween(node)
  367. .to(duration, { eulerAngles: origin }, { easing: 'quadOut' });
  368. (node as any).__rotate_tween = backTween;
  369. backTween.start();
  370. }
  371. //上下抖动动画
  372. export interface BounceYOptions {
  373. distance?: number; // 向上抖动的距离(默认20)
  374. durationMin?: number; // 最短时长(默认 0.3)
  375. durationMax?: number; // 最长时长(默认 0.5)
  376. delay?: number; // 延迟(默认0)
  377. onFinish?: () => void; // 动画完成回调
  378. }
  379. export function bounce_y_anim(node: Node, options: BounceYOptions = {}) {
  380. const {
  381. distance = 20,
  382. durationMin = 0.3,
  383. durationMax = 0.5,
  384. delay = 0,
  385. onFinish,
  386. } = options;
  387. //在 min~max 范围内生成随机时长
  388. const duration = ch.util.getRandom(0.2, 0.35);
  389. const originalPos = node.position.clone();
  390. const upPos = originalPos.clone().add(new Vec3(0, distance, 0));
  391. tween(node)
  392. .delay(delay)
  393. .to(duration, { position: upPos }, { easing: 'quadOut' })
  394. .to(duration, { position: originalPos }, { easing: 'quadIn' })
  395. .call(onFinish)
  396. .start();
  397. }
  398. export interface JellyAnimOptions {
  399. scaleX?: number;
  400. scaleY?: number;
  401. duration?: number;
  402. delay?: number;
  403. onFinish?: () => void;
  404. loop?: boolean;
  405. }
  406. export function jelly_anim_ext(node: Node, options: JellyAnimOptions = {}) {
  407. const {
  408. scaleX = 1.2,
  409. scaleY = 0.8,
  410. duration = 0.4,
  411. delay = 0.5,
  412. onFinish,
  413. loop = true,
  414. } = options;
  415. // 停止旧动画
  416. if ((node as any).__jelly_tween) {
  417. (node as any).__jelly_tween.stop();
  418. }
  419. // 保存原始 scale
  420. const original = node.scale.clone();
  421. (node as any).__jelly_origin = original;
  422. const stretch = new Vec3(scaleX, scaleY, original.z);
  423. const fullTween = tween(node).sequence(
  424. tween(node).to(duration / 2, { scale: stretch }, { easing: 'sineOut' }),
  425. tween(node).to(duration / 2, { scale: original }, { easing: 'backOut' }),
  426. tween(node).delay(delay)
  427. );
  428. let finalTween: Tween<Node>;
  429. if (loop) {
  430. finalTween = fullTween.repeatForever();
  431. } else {
  432. finalTween = tween(node)
  433. .then(fullTween)
  434. .call(onFinish || (() => { }));
  435. }
  436. (node as any).__jelly_tween = finalTween;
  437. finalTween.start();
  438. }
  439. //直接打断
  440. export function jelly_anim_stop(node: Node) {
  441. const t = (node as any).__jelly_tween;
  442. if (t) {
  443. t.stop();
  444. (node as any).__jelly_tween = null;
  445. }
  446. }
  447. //打断且复原
  448. export function jelly_anim_restore(node: Node, duration: number = 0.3) {
  449. const origin = (node as any).__jelly_origin as Vec3;
  450. if (!origin) return;
  451. jelly_anim_stop(node);
  452. const backTween = tween(node).to(duration, { scale: origin }, { easing: 'quadOut' });
  453. (node as any).__jelly_tween = backTween;
  454. backTween.start();
  455. }
  456. /**
  457. * 贝塞尔加缩放
  458. * @param tweenObj
  459. * @param target
  460. * @param startPos
  461. * @param targetPos
  462. * @param time
  463. * @param scale
  464. * @param px1
  465. * @param py1
  466. * @param px2
  467. * @param py2
  468. * @returns
  469. */
  470. 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) {
  471. let t1 = tween(tweenObj).to(time, { t: 1 }, {
  472. onUpdate: (tar, t) => {
  473. const p0 = v2(0, 0);
  474. const p1 = v2(px1, py1);
  475. const p2 = v2(px2, py2);
  476. const p3 = v2(1, 1);
  477. 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;
  478. 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;
  479. let posx = (targetPos.x - startPos.x) * x + startPos.x;
  480. let posy = (targetPos.y - startPos.y) * y + startPos.y;
  481. target.setPosition(new Vec3(posx, posy, 0));
  482. }
  483. });
  484. let t2 = tween(tweenObj).to(time, { scale: scale }, {
  485. onUpdate: (tar: any, t) => {
  486. target.setScale(tar.scale, tar.scale, tar.scale);
  487. }
  488. });
  489. tween(tweenObj).parallel(t1, t2).call(onHalf).delay(delay).call(onFinish).start();
  490. }