rp_11009/assets/Game/history/scripts/Tools.ts
TJH b3e27eda1b
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 1m18s
正则理解
2026-05-13 14:25:34 +08:00

447 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { _decorator, Director, director, Label, Node, Overflow, RichText, tween, UITransform, Vec3 } from 'cc';
import { StaticTxt } from './StaticTxt';
const { ccclass, property } = _decorator;
export class Tools {
private static _instance: Tools;
ctx: any = null
public static get instance(): Tools {
if (!this._instance) {
this._instance = new Tools();
let canvas = document.createElement('canvas');
this._instance.ctx = canvas.getContext('2d')!;
}
return this._instance;
}
}
/**RichText元素*/
interface Segment {
type: "label" | "image";
value: string;
}
interface WrapRes {
result: string;
lastLineWidth: number;
}
interface LangTxt {
labels: Label[]; // Label 组件数组
richTexts: RichText[]; // RichText 组件数组
}
export let ICON_RATE: number[][] = [
[0, 0, 0], // scatter
[10, 25, 50], // 神灯
[2.5, 10, 25], // 魔毯
[2, 5, 15], // 刀
[1.5, 2, 12], // 戒指
[1, 1.5, 10], // 项链
[0.8, 1.2, 8], // 瓶子
[0.5, 1, 5], // 金币
[0.4, 0.9, 4], // 银币
[0.2, 0.75, 2], // 铜币
]
export function fixNum(num: number): string {
return (num / 10000).toFixed(2)
}
// 需要代码计算空格处理换行的婆罗米系文字
export function isBrahmic(lang: string) {
let arr = ["th", "my"]
return arr.indexOf(lang) != -1
}
export function getLabelsAndRichTexts(node: Node, langText: LangTxt): LangTxt {
const label = node.getComponent(Label);
const richText = node.getComponent(RichText);
const staticTxt = node.getComponent(StaticTxt)
if (label && staticTxt) {
langText.labels.push(label);
}
if (richText && staticTxt) {
langText.richTexts.push(richText);
}
// 遍历所有子节点
node.children.forEach(child => {
getLabelsAndRichTexts(child, langText);
});
return langText
}
/**拿到节点下所有需要翻译的, 挂载了StaticTxt脚本的 Label*/
export function getAllLabels(node: Node, result: Label[] = []): Label[] {
const label = node.getComponent(Label);
const staticTxt = node.getComponent(StaticTxt)
if (label && staticTxt) {
result.push(label);
}
// 遍历所有子节点
node.children.forEach(child => {
getAllLabels(child, result);
});
return result;
}
/**拿到节点下所有需要翻译的, 挂载了StaticTxt脚本的 Label*/
export function getAllRichTexts(node: Node, result: RichText[] = []): RichText[] {
const richText = node.getComponent(RichText);
const staticTxt = node.getComponent(StaticTxt)
if (richText && staticTxt) {
result.push(richText);
}
// 遍历所有子节点
node.children.forEach(child => {
getAllRichTexts(child, result);
});
return result;
}
export function getTranslate(dict, key) {
let str = dict[key] ?? key
return str;
}
export function getTimezoneOffsetString(): string {
const pad = (n: number) => (n < 10 ? '0' + n : String(n));
const offset = -new Date().getTimezoneOffset(); // 单位是分钟,注意要取反
const sign = offset >= 0 ? "+" : "-";
const hours = pad(Math.floor(Math.abs(offset) / 60))
const minutes = pad(Math.abs(offset) % 60);
return `(GMT${sign}${hours}:${minutes})`;
}
export function updateLang(node: Node, dict, lang: string) {
let langText: LangTxt = { labels: [], richTexts: [] }
getLabelsAndRichTexts(node, langText)
langText.labels.forEach((label, i) => {
// console.log(`label.node.name: ${label.node.name}`)
const staticTxt = label.node.getComponent(StaticTxt)
const bResize = label.overflow == Overflow.RESIZE_HEIGHT
let key = staticTxt?.key
if (dict[key]) {
if (isBrahmic(lang) && bResize) {
label.string = wrapTextBySpace(label, dict[key])
} else {
label.string = dict[key]
}
if (label.node.activeInHierarchy) {
label.updateRenderData()
}
}
})
langText.richTexts.forEach((richText, i) => {
const staticTxt = richText.node.getComponent(StaticTxt)
richText.maxWidth = staticTxt.maxWidth
richText.string = wrapRichTextBySpace(richText, staticTxt.key, dict, lang)
})
return langText
}
export function wrapRichTextBySpace(richText: RichText, key: string, dict, lang): string {
// key like this: Label_Rules_6_14_2 <img src='dates' /> Label_Rules_6_14_3
const segmentArr: Segment[] = [];
//Label_[^<\s] 表示由Label开头遇到<或空白(\s)则停止
//+表示前面的字符类可能出现多次
//|表示另一种组合,匹配其中任意一种
//<img[^>]+>表示由img开头遇到>则停止,并在结尾再添加一个>
const regexp = /Label_[^<\s]+|<img[^>]+>/g;
// console.log('返回所有匹配的字符串', key.match(regexp))
// let matchArr = key.match(regexp)
// matchArr.forEach(match => {
// if (match.startsWith("Label_")) {
// let key = match.split("Label_")[1]
// if (dict[key]) {
// segmentArr.push({ type: "label", value: dict[key] });
// }
// } else {
// segmentArr.push({ type: "image", value: match }); // "<img src='dates' />"
// }
// });
// console.log('返回所有匹配的字符串', key.matchAll(regexp))
// let matchArr = [...key.matchAll(regexp)]
// matchArr.forEach(match => {
// if (match[0].startsWith("Label_")) {
// let key = match[0].split("Label_")[1]
// if (dict[key]) {
// segmentArr.push({ type: "label", value: dict[key] });
// }
// } else {
// segmentArr.push({ type: "image", value: match[0] }); // "<img src='dates' />"
// }
// });
let match: RegExpExecArray | null;
while ((match = regexp.exec(key)) !== null) {
if (match[0].startsWith("Label_")) {
let key = match[0].split("Label_")[1]
if (dict[key]) {
segmentArr.push({ type: "label", value: dict[key] });
}
} else {
segmentArr.push({ type: "image", value: match[0] }); // "<img src='dates' />"
}
}
// console.log("segmentArr:", segmentArr);
/*
segmentArr:
[
{
"type": "label",
"value": "Tap"
},
{
"type": "image",
"value": "<img src='dates' />"
},
{
"type": "label",
"value": "to select the dates of games to be shown in History."
}
]
*/
let wrapResult: WrapRes = { result: "", lastLineWidth: 0 }
segmentArr.forEach((part, i) => {
if (isBrahmic(lang)) {
if (part.type == "label") {
wrapResult = wrapTextByRichTextWidth(richText, part.value, wrapResult)
} else if (part.type == "image") {
let img = part.value.match(/src=['"]([^'"]+)['"]/)
const spriteFrame = richText.imageAtlas.getSpriteFrame(img[1]);
if (spriteFrame) {
const rect = spriteFrame.rect; // 原始图片矩形
// console.log("原始图片宽度:", rect.width);
// 图片宽度 + 最后一行宽度 比 最大宽度大,图片换行放到下一行去
if (wrapResult.lastLineWidth + rect.width > richText.maxWidth) {
wrapResult.result += "\n" + part.value
wrapResult.lastLineWidth = rect.width
} else {
// 放得下就放在最后一行
wrapResult.result += part.value
wrapResult.lastLineWidth += rect.width
}
}
}
} else {
// 非婆罗米系文字,引擎自动换行就能处理好
// 不需要手动计算
wrapResult.result += part.value
}
})
return wrapResult.result
}
// 富文本换行
export function wrapTextByRichTextWidth(richText: RichText, str: string, lastWrapResult: WrapRes = { result: "", lastLineWidth: 0 }): WrapRes {
const fontSize = richText.fontSize;
const fontFamily = richText.fontFamily ?? 'Arial'
const maxWidth = richText.maxWidth // richText 最大宽度
let wrapResult = lastWrapResult;
Tools.instance.ctx.font = `${fontSize}px ${fontFamily}`;
const words = str.split(' ')
let line = '';
let result = '';
let endIdx = words.length - 1
for (let i = 0; i < words.length; i++) {
let word = words[i]
console.log("word:", word)
let testLine = line + " " + word
const width = wrapResult.lastLineWidth + Tools.instance.ctx.measureText(testLine).width;
console.log("width = ", width, "maxWidth = ", maxWidth)
// 长度超了放到下一行去
if (width + 5 > maxWidth && line.length > 0) {
if (i == endIdx) {
console.log("最后一个单词长度超了")
result += line + '\n' + word;
wrapResult.lastLineWidth = Tools.instance.ctx.measureText(word).width;
} else {
console.log("单词长度超了,换行")
result += line + '\n';
line = word
wrapResult.lastLineWidth = 0
}
} else {
if (i == endIdx) {
console.log("最后一个单词,拼在屁股后面")
result += testLine;
wrapResult.lastLineWidth = Tools.instance.ctx.measureText(testLine).width;
} else {
console.log("拼在屁股后面")
line = testLine;
}
}
}
console.log("循环结束, ", result)
wrapResult.result += result
return wrapResult;
}
// 按单词换行
export function wrapTextBySpace(label: Label, str: string): string {
const fontSize = label.fontSize;
const fontFamily = label.fontFamily ?? 'Arial'
const maxWidth = label.node.getComponent(UITransform).width; // Label 最大宽度
const words = str.split(' ')
let line = '';
let result = '';
// const canvas = document.createElement('canvas');
// const ctx = canvas.getContext('2d')!;
Tools.instance.ctx.font = `${fontSize}px ${fontFamily}`;
let endIdx = words.length - 1
for (let i = 0; i < words.length; i++) {
let word = words[i]
// console.log("word = ", word)
let testLine = line + " " + word
const width = Tools.instance.ctx.measureText(testLine).width;
// 长度超了放到下一行去
if (width > maxWidth && line.length > 0) {
if (i == endIdx) {
result += line + '\n' + word;
} else {
result += line + '\n';
line = word
}
} else {
if (i == endIdx) {
result += testLine;
} else {
line = testLine;
}
}
}
// console.log("循环结束, ", result)
return result;
}
export function waitNextFrame(): Promise<void> {
return new Promise(resolve => {
director.once(Director.EVENT_BEFORE_UPDATE, () => resolve());
});
}
export function hideAllChildren(node: Node) {
node.children.forEach(child => {
child.active = false
})
}
export function showAllChildren(node: Node) {
node.children.forEach(child => {
child.active = true
})
}
export function showFromBottom(node: Node) {
if (!node) {
return
}
let height = node.getComponent(UITransform)?.height
if (height) {
height = 1920;
}
node.position = new Vec3(0, -height / 2)
tween(node).to(0.15, { position: new Vec3() }).start()
}
export function hideToBottom(node: Node, callFuc = null) {
if (!node) {
return
}
let height = node.getComponent(UITransform)?.height
if (height) {
height = 1920;
}
let endPos = new Vec3(0, -height)
tween(node).to(0.15, { position: endPos }).call(() => {
if (callFuc) {
callFuc()
}
}).start()
}