Utils.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. import {
  2. Prefab,
  3. resources,
  4. Node,
  5. find,
  6. instantiate,
  7. Vec3,
  8. log,
  9. Component,
  10. Button,
  11. warn,
  12. Asset,
  13. error,
  14. v2,
  15. UITransform,
  16. tween,
  17. v3,
  18. Vec2,
  19. director,
  20. ImageAsset,
  21. assetManager,
  22. } from "cc";
  23. import BigNumber from "./BigNumber";
  24. import { EDITOR } from "cc/env";
  25. import { LanguageManager } from "./LanguageManager";
  26. import PoolMgr from "./PoolMgr";
  27. import BaseUI from "../base/BaseUI";
  28. import { Tips } from "../mgr/Tips";
  29. export enum EFLY_TYPE {
  30. 金币,
  31. 体力,
  32. 钻石,
  33. }
  34. export default class Utils {
  35. public static copyText(text: string) {
  36. navigator.clipboard.writeText(text).then(
  37. (res) => {
  38. console.log(" writeText success: ", res);
  39. // Tips.show(Utils.setI18nLabel("Share.LinkCopied"));
  40. },
  41. (err) => {
  42. console.log(" writeText error: ", err);
  43. this.copyTextFallback(text);
  44. }
  45. );
  46. }
  47. private static copyTextFallback(text) {
  48. const textArea = document.createElement("textarea");
  49. textArea.value = text;
  50. document.body.appendChild(textArea);
  51. textArea.select();
  52. try {
  53. document.execCommand("copy");
  54. Tips.show(Utils.setI18nLabel("Share.LinkCopied")); //"Link copied!");
  55. } catch (err) {
  56. Tips.show(Utils.setI18nLabel("Share.Title1"));
  57. }
  58. document.body.removeChild(textArea);
  59. }
  60. static sleep(ms: number) {
  61. return new Promise((resolve) => setTimeout(resolve, ms));
  62. }
  63. static padNumber(num, length): string {
  64. let str = num.toString();
  65. while (str.length < length) {
  66. str = "0" + str;
  67. }
  68. return str;
  69. }
  70. static log(o: any) {
  71. if ("1" == this.getQueryString("log")) {
  72. console.log(o);
  73. }
  74. }
  75. // 创建UI
  76. static createUI(
  77. filepath: string,
  78. parent: Node = null,
  79. callback: Function = null
  80. ): Promise<Node> {
  81. return new Promise((resolve, reject) => {
  82. resources.load(filepath, Prefab, (err, ret) => {
  83. log("加载UI:" + filepath);
  84. if (err) {
  85. console.error(err);
  86. reject();
  87. return;
  88. }
  89. if (parent == null) {
  90. parent = find("Canvas/popupNode");
  91. }
  92. let index = filepath.lastIndexOf("/");
  93. let name = filepath.substr(index + 1, filepath.length - index);
  94. if (parent.getComponentInChildren(name)) {
  95. console.log("重复UI跳过");
  96. return;
  97. }
  98. var tmp: Node = instantiate(ret);
  99. tmp.parent = parent;
  100. if (callback) callback(tmp);
  101. resolve(tmp);
  102. });
  103. });
  104. }
  105. // 创建预制体
  106. static createPrefab(
  107. filepath: string,
  108. parent: Node = null,
  109. callback: Function = null,
  110. pos: Vec3 = null
  111. ) {
  112. return new Promise((resolve, reject) => {
  113. resources.load(filepath, Prefab, (err, ret) => {
  114. if (err) {
  115. console.error(err);
  116. reject();
  117. return;
  118. }
  119. if (parent == null) {
  120. parent = find("Canvas");
  121. }
  122. var tmp: Node = instantiate(ret);
  123. // tmp.opacity = 0;
  124. // tmp.runAction(
  125. // sequence(
  126. // delayTime(0.01),
  127. // callFunc(() => {
  128. // tmp.opacity = 255;
  129. // })
  130. // )
  131. // );
  132. tmp.parent = parent;
  133. if (pos) {
  134. tmp.position = pos;
  135. }
  136. if (callback) callback(tmp);
  137. resolve(tmp);
  138. });
  139. });
  140. }
  141. // 获取随机数
  142. public static getRandom(lower, upper): number {
  143. return Math.random() * (upper - lower) + lower;
  144. }
  145. // 获取随机整数
  146. public static getRandomInt(lower, upper): number {
  147. return Math.floor(Math.random() * (upper - lower)) + lower;
  148. }
  149. // 获取随机整数
  150. public static seedRandomInt(lower, upper): number {
  151. return Utils.getRandomInt(lower, upper);
  152. }
  153. // 格式化数字
  154. public static formatNumber(num: number, afterdot: number = 2): string {
  155. num = Number(num);
  156. num = Number(num.toFixed(afterdot));
  157. if (num < 1000) {
  158. return num.toString();
  159. }
  160. return BigNumber.getLargeString(num);
  161. }
  162. public static getPowNum(p) {
  163. return Math.pow(10, p);
  164. }
  165. // 设置服务器时间
  166. public static setServerTime(time: number) {
  167. Utils.timeOffset = time - new Date().getTime();
  168. }
  169. // 获取服务器时间
  170. public static timeOffset: number = 0;
  171. public static getServerTime() {
  172. return new Date().getTime() + Utils.timeOffset;
  173. }
  174. // 添加点击事件
  175. public static addClickEvent(
  176. node,
  177. target,
  178. component,
  179. handler,
  180. customEventData
  181. ) {
  182. var eventHandler = new Component.EventHandler();
  183. eventHandler.target = target;
  184. eventHandler.component = component;
  185. eventHandler.handler = handler;
  186. if (customEventData) eventHandler.customEventData = customEventData;
  187. var clickEvents = node.getComponent(Button).clickEvents;
  188. if (clickEvents.length > 0) {
  189. // if (!EDITOR) warn("按钮已经存在绑定,跳过自动绑定", node.name);
  190. return;
  191. }
  192. console.log(node.name, target.name, component);
  193. clickEvents.push(eventHandler);
  194. }
  195. public static secondsToDHMS(seconds) {
  196. if (seconds <= 0) {
  197. return `00d 00h:00m:00s`;
  198. }
  199. const days = Math.floor(seconds / (24 * 3600));
  200. let d = days < 9 ? "0" + days : days;
  201. const hours = Math.floor((seconds % (24 * 3600)) / 3600);
  202. let h = hours < 9 ? "0" + hours : hours;
  203. const minutes = Math.floor((seconds % 3600) / 60);
  204. let min = minutes < 9 ? "0" + minutes : minutes;
  205. const second = Math.floor(seconds % 60);
  206. let s = second < 9 ? "0" + second : second;
  207. let str = days > 0 ? `${d}d ${h}h:${min}m:${s}s` : `${h}h:${min}m:${s}s`;
  208. return str;
  209. }
  210. public static formatTimestamp(timestamp) {}
  211. // 秒转换为时分秒
  212. public static getToTimeByS(second: number) {
  213. const totalSeconds = Math.floor(second);
  214. const hours = Math.floor(totalSeconds / 3600) % 24;
  215. const minutes = Math.floor((totalSeconds % 3600) / 60);
  216. const seconds = totalSeconds % 60;
  217. return [hours, minutes, seconds]
  218. .map((unit) => (unit < 10 ? "0" + unit : unit.toString())) // 确保每个部分都是两位数
  219. .join(":"); // 拼接为字符串
  220. }
  221. private static padStartCustom(str, targetLength, padString) {
  222. str = String(str);
  223. padString = String(padString);
  224. if (str.length >= targetLength) {
  225. return str;
  226. }
  227. targetLength -= str.length;
  228. if (targetLength > padString.length) {
  229. padString = padString.repeat(Math.ceil(targetLength / padString.length));
  230. }
  231. return padString.slice(0, targetLength) + str;
  232. }
  233. public static formatTimeRemaining(targetTimestamp) {
  234. // 获取当前时间戳
  235. const now = Date.now();
  236. // 计算剩余时间的毫秒数
  237. let remainingTime = targetTimestamp - now;
  238. // 如果剩余时间小于等于 0,表示目标时间已经过去
  239. if (remainingTime <= 0) {
  240. return "00:00:00";
  241. }
  242. // 计算小时、分钟、秒
  243. const hours = Math.floor(remainingTime / (1000 * 60 * 60));
  244. remainingTime -= hours * 1000 * 60 * 60;
  245. const minutes = Math.floor(remainingTime / (1000 * 60));
  246. remainingTime -= minutes * 1000 * 60;
  247. const seconds = Math.floor(remainingTime / 1000);
  248. // 格式化为两位数的字符串
  249. const formattedHours = Utils.padStartCustom(hours, 2, "0");
  250. const formattedMinutes = Utils.padStartCustom(minutes, 2, "0");
  251. const formattedSeconds = Utils.padStartCustom(seconds, 2, "0");
  252. return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
  253. }
  254. // 秒转换为时分秒
  255. public static getTimeStrByS(second: number) {
  256. second = Math.floor(second);
  257. if (second < 0) second = 0;
  258. var d = Math.floor(second / 3600 / 24);
  259. second -= d * 3600 * 24;
  260. var h = Math.floor(second / 3600);
  261. second -= h * 3600;
  262. var m = Math.floor(second / 60);
  263. second -= m * 60;
  264. var front = "00";
  265. if (h > 9) {
  266. front = "" + h;
  267. } else {
  268. front = "0" + h;
  269. }
  270. var mid = "00";
  271. if (m > 9) {
  272. mid = "" + m;
  273. } else {
  274. mid = "0" + m;
  275. }
  276. var back = "00";
  277. if (second > 9) {
  278. back = "" + second;
  279. } else {
  280. back = "0" + second;
  281. }
  282. if (d > 0) {
  283. return d + ":" + h + ":" + m;
  284. } else {
  285. var longTime = h > 0;
  286. if (longTime) {
  287. return front + ":" + mid;
  288. } else {
  289. return mid + ":" + back; //+ '秒';
  290. }
  291. }
  292. }
  293. public static formattedDate(timestamp) {
  294. const date = new Date(Number(timestamp));
  295. // 获取年月日
  296. const year = date.getFullYear();
  297. let month = date.getMonth() + 1;
  298. let monthS = month < 9 ? "0" + month : month;
  299. let day = date.getDate();
  300. let dayS = day < 9 ? "0" + day : day;
  301. const hours = date.getHours();
  302. let hoursS = hours < 9 ? "0" + hours : hours;
  303. const minutes = date.getMinutes();
  304. let minutesS = minutes < 9 ? "0" + minutes : minutes;
  305. const seconds = date.getSeconds();
  306. let secondsS = seconds < 9 ? "0" + seconds : seconds;
  307. const formattedDate = `${year}-${monthS}-${dayS} ${hoursS}:${minutesS}:${secondsS}`;
  308. return formattedDate;
  309. }
  310. // 格式化金币
  311. public static formatCoin(num: number) {
  312. num = Math.floor(num);
  313. return BigNumber.getLargeString(num);
  314. }
  315. // 加载资源
  316. public static loadRes(path: string, type: typeof Asset): Promise<Asset> {
  317. return new Promise((resolve, reject) => {
  318. resources.load(path, type, (err, ret) => {
  319. if (err) {
  320. error(path, err);
  321. reject(null);
  322. } else {
  323. resolve(ret);
  324. }
  325. });
  326. });
  327. }
  328. public static loadRemote(path: string, type: typeof ImageAsset) {
  329. return new Promise((resolve, reject) => {
  330. assetManager.loadRemote(path, type, (err, ret) => {
  331. if (err) {
  332. error(path, err);
  333. reject(null);
  334. } else {
  335. resolve(ret);
  336. }
  337. });
  338. });
  339. }
  340. // 权重
  341. public static weight(v: number[]): number {
  342. var mTotalWeight = 0;
  343. for (var i = 0; i < v.length; ++i) {
  344. mTotalWeight += v[i];
  345. }
  346. if (mTotalWeight <= 0) return -1;
  347. var randnum = Math.round(Math.random() * Number.MAX_VALUE) % mTotalWeight;
  348. for (var i = 0; i < v.length; ++i) {
  349. if (randnum < v[i]) {
  350. return i;
  351. } else {
  352. randnum -= v[i];
  353. }
  354. }
  355. return -1;
  356. }
  357. //定点数
  358. public static fixFloat(val: number, count: number = 2) {
  359. var a = count * 100;
  360. return Math.floor(val * a) / a;
  361. }
  362. // 在子节点中查找
  363. private static _findInChildren(node: Node, name: string): Node {
  364. var x = node.getChildByName(name);
  365. if (x) return x;
  366. if (node.children.length == 0) return null;
  367. for (var i = 0; i < node.children.length; ++i) {
  368. var tmp = this._findInChildren(node.children[i], name);
  369. if (tmp) return tmp;
  370. }
  371. return null;
  372. }
  373. // 飞动动画
  374. public static flyAnim(
  375. type: number,
  376. startNode: Node,
  377. targetNodeName: string,
  378. count: number,
  379. radius: number,
  380. callback: Function
  381. ) {
  382. let srcNode = this._findInChildren(director.getScene(), targetNodeName);
  383. if (!srcNode) {
  384. notPlay = false;
  385. callback();
  386. return;
  387. }
  388. let getPoint = (r, ox, oy, count) => {
  389. var point = []; //结果
  390. var radians = (Math.PI / 180) * Math.round(360 / count), //弧度
  391. i = 0;
  392. for (; i < count; i++) {
  393. var x = ox + r * Math.sin(radians * i),
  394. y = oy + r * Math.cos(radians * i);
  395. point.unshift(v2(x, y)); //为保持数据顺时针
  396. }
  397. return point;
  398. };
  399. let createNode = (type) => {
  400. if (type == 0) return PoolMgr.Instance().get("Coin");
  401. if (type == 1) return PoolMgr.Instance().get("Gem");
  402. if (type == 2) return PoolMgr.Instance().get("Zp");
  403. if (type == 3) return PoolMgr.Instance().get("ZpGold");
  404. };
  405. let start = startNode.parent
  406. .getComponent(UITransform)
  407. .convertToWorldSpaceAR(startNode.position);
  408. start = find("Canvas/flyNode")
  409. .getComponent(UITransform)
  410. .convertToNodeSpaceAR(start);
  411. var array = getPoint(radius, start.x, start.y, count);
  412. var nodeArray = new Array();
  413. for (var i = 0; i < array.length; i++) {
  414. var gold = createNode(type);
  415. gold.parent = find("Canvas/flyNode");
  416. var randPos = v2(
  417. array[i].x + Utils.getRandomInt(0, 50),
  418. array[i].y + Utils.getRandomInt(0, 50)
  419. );
  420. gold.setPosition(start);
  421. nodeArray.push({ gold, randPos });
  422. }
  423. var notPlay = false;
  424. let dstPos = srcNode.parent
  425. .getComponent(UITransform)
  426. .convertToWorldSpaceAR(srcNode.position);
  427. dstPos = find("Canvas/flyNode")
  428. .getComponent(UITransform)
  429. .convertToNodeSpaceAR(dstPos);
  430. var targetGoldNode = srcNode;
  431. for (var i = 0; i < nodeArray.length; i++) {
  432. var pos = nodeArray[i].randPos;
  433. var node = nodeArray[i].gold;
  434. nodeArray[i].gold.id = i;
  435. tween(node)
  436. .to(0.2, { position: pos })
  437. .delay(i * 0.03)
  438. .to(0.5, { position: v2(dstPos.x, dstPos.y) })
  439. .call(() => {
  440. if (!notPlay) {
  441. targetGoldNode.scale = v3(1, 1, 1);
  442. notPlay = true;
  443. tween(targetGoldNode)
  444. .to(0.1, { scale: v3(2, 2, 2) })
  445. .to(0.1, { scale: v3(1, 1, 1) })
  446. .call(() => {
  447. notPlay = false;
  448. });
  449. }
  450. callback(node._id == nodeArray.length - 1);
  451. PoolMgr.Instance().put(node.name, node);
  452. if (node._id == nodeArray.length - 1) {
  453. find("Canvas/flyNode").removeAllChildren();
  454. }
  455. })
  456. .start();
  457. }
  458. }
  459. /**
  460. * 深度复制
  461. * @param value
  462. */
  463. public static deepClone(value: any) {
  464. // 处理基本数据类型和特殊情况
  465. if (value === null || typeof value !== "object") {
  466. return value; // 基本数据类型和 null
  467. }
  468. // 处理数组
  469. if (Array.isArray(value)) {
  470. return value.map((item) => Utils.deepClone(item)); // 递归拷贝每个元素
  471. }
  472. // 处理对象
  473. const copy = {};
  474. for (const key in value) {
  475. if (value.hasOwnProperty(key)) {
  476. copy[key] = Utils.deepClone(value[key]); // 递归拷贝每个属性
  477. }
  478. }
  479. return copy;
  480. }
  481. /**
  482. * 获取url参数
  483. * @param name
  484. * @returns
  485. */
  486. public static getQueryString(name: any) {
  487. var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
  488. var r = window.location.search.substr(1).match(reg);
  489. if (r != null) return unescape(r[2]);
  490. return null;
  491. }
  492. /**
  493. * 贝塞尔曲线计算
  494. * @param points
  495. * @param t
  496. */
  497. public static calculateBezier(points: Vec2[], t: number) {
  498. const n = points.length;
  499. if (n === 0) return v2(0, 0); // 如果没有控制点,返回 (0,0)
  500. if (n === 1) return points[0]; // 如果只有一个控制点,返回该点
  501. let tempPoints = points.slice(); // 创建控制点的副本
  502. // De Casteljau's algorithm
  503. for (let r = 1; r < n; r++) {
  504. for (let i = 0; i < n - r; i++) {
  505. tempPoints[i] = v2(
  506. (1 - t) * tempPoints[i].x + t * tempPoints[i + 1].x,
  507. (1 - t) * tempPoints[i].y + t * tempPoints[i + 1].y
  508. );
  509. }
  510. }
  511. return tempPoints[0]; // 返回计算得到的点
  512. }
  513. /**
  514. * 是否为某个链接
  515. * @param url
  516. * @param hostname
  517. * @returns
  518. */
  519. static isNameLink(url: string | URL, hostname: string) {
  520. try {
  521. const parsedUrl = new URL(url);
  522. return parsedUrl.hostname === hostname;
  523. } catch (e) {
  524. console.error("Invalid URL");
  525. return false;
  526. }
  527. }
  528. // 将用户 ID 转换为 26 进制字符串
  529. public static convertTo26(userId) {
  530. let sb = "";
  531. while (userId > 0) {
  532. userId--;
  533. let ch = String.fromCharCode((userId % 26) + "A".charCodeAt(0));
  534. sb = ch + sb;
  535. userId = Math.floor(userId / 26);
  536. }
  537. return sb;
  538. }
  539. // 生成邀请代码
  540. public static generateInviteCode(channelId, userId) {
  541. return channelId + this.convertTo26(userId);
  542. }
  543. // 取后缀
  544. public static getSuffix(idx) {
  545. idx = Math.floor(idx);
  546. if (idx <= 0) {
  547. return "";
  548. }
  549. idx = idx - 1;
  550. let result = "";
  551. while (idx >= 0) {
  552. result = String.fromCharCode((idx % 26) + 97) + result;
  553. idx = Math.floor(idx / 26) - 1;
  554. }
  555. result = "";
  556. return result;
  557. }
  558. // max: 10 ==> 1->1,2->2,..10->10,11->1
  559. public static getMappedValue(idx, max) {
  560. let result;
  561. result = idx % max == 0 ? max : idx % max;
  562. return result;
  563. }
  564. //
  565. public static getStarValue(idx, max) {
  566. let result = Math.floor((idx - 1) / max);
  567. return result;
  568. }
  569. // 格式化地址
  570. static formAddress(address: string) {
  571. if (
  572. address == null ||
  573. address == "" ||
  574. address == undefined ||
  575. address.length < 10
  576. ) {
  577. return " ";
  578. }
  579. return (
  580. address.substring(0, 8) +
  581. "..." +
  582. address.substring(address.length - 8, address.length)
  583. );
  584. }
  585. //
  586. public static updateLabelText(fullText, maxWidth, fontSize) {
  587. let str = "";
  588. // 计算文本的实际宽度
  589. let textWidth = this.getTextWidth(fullText, fontSize);
  590. if (textWidth > maxWidth) {
  591. // 超过最大宽度,显示截断后的文本并加上省略号
  592. let truncatedText = this.truncateText(fullText, maxWidth, fontSize);
  593. str = truncatedText + "...";
  594. } else {
  595. // 显示完整文本
  596. str = fullText;
  597. }
  598. return str;
  599. }
  600. // 获取文本的宽度
  601. private static getTextWidth(text, fontSize) {
  602. let canvas = document.createElement("canvas");
  603. let ctx = canvas.getContext("2d");
  604. ctx.font = fontSize + "px Arial"; // 设置字体大小
  605. return ctx.measureText(text).width;
  606. }
  607. // 截断文本并返回适合的文本
  608. private static truncateText(text, maxWidth, fontSize) {
  609. let i = 0;
  610. let truncatedText = text;
  611. // 循环截取字符直到文本超出最大宽度
  612. while (
  613. this.getTextWidth(truncatedText, fontSize) > maxWidth &&
  614. i < text.length
  615. ) {
  616. i++;
  617. truncatedText = text.slice(0, text.length - i);
  618. }
  619. return truncatedText;
  620. }
  621. // 多语言
  622. static setI18nLabel(str: string, value?: string, value2?: string) {
  623. let i18nStr = str;
  624. i18nStr = LanguageManager.getText(str);
  625. if (value) {
  626. i18nStr = i18nStr.replace("{value}", value);
  627. }
  628. if (value2) {
  629. i18nStr = i18nStr.replace("{value2}", value2);
  630. }
  631. return i18nStr;
  632. }
  633. }