rp_11001/assets/Game/scripts/game/Tools.ts
2025-12-26 11:23:09 +08:00

401 lines
11 KiB
TypeScript

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 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[] = [];
const regex = /Label_[^<\s]+|<img[^>]+>/g;
let match: RegExpExecArray | null;
while ((match = regex.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()
}