一、類概述
TextRenderManager
?是 Cocos Creator 中實現動態藝術字體渲染的核心單例類。它通過整合資源加載、緩存管理、異步隊列和自動布局等功能,支持普通字符模式和圖集模式兩種渲染方案,適用于游戲中的動態文本(如聊天內容、排行榜)和靜態藝術字(如UI標題)的高效渲染。
二、核心功能與設計理念
1.?核心功能
-
資源預加載:分批次異步加載字符紋理或圖集,避免主線程阻塞。
-
動態緩存管理:通過
LRUCache
實現字符紋理的智能緩存與淘汰。 -
自動布局排版:支持水平對齊(左/中/右)、垂直對齊(上/中/下)及字符間距調整。
-
多模式渲染:
-
普通模式:從獨立文件加載每個字符的紋理。
-
圖集模式:從預生成的圖集(如BMFont輸出)中提取字符。
-
2.?設計目標
-
高性能:通過異步加載、LRU緩存減少資源重復請求。
-
易擴展:模塊化設計,支持自定義布局策略與渲染模式。
-
內存安全:動態釋放無用資源,防止內存泄漏。
三、代碼結構與核心方法詳解
1.?私有屬性與緩存機制
// 字符紋理緩存(LRU策略)
private _spriteCache = new LRUCache<string, SpriteFrame>(100);
// 加載隊列(防止重復請求)
private _loadingQueue = new Map<string, Promise<void>>();
// 渲染請求隊列(異步任務調度)
private _requestQueue: Array<{ text: string, config: TextRenderConfig, resolve: Function }> = [];
// 占位符紋理(加載失敗時使用)
private _placeholderCache: SpriteFrame | null = null;
2.?核心方法解析
(1) 文本渲染入口:renderText()
/*** 渲染文本* @param text 文本內容* @param config 渲染配置* @returns 包含所有字符節點的容器*/
public async renderText(text: string, config: TextRenderConfig = {}): Promise<Node> {return new Promise((resolve) => {// 將請求加入隊列,觸發異步處理this._requestQueue.push({ text, config, resolve });this._processQueue();});
}
-
流程:
-
將渲染請求推入隊列,確保異步任務按順序執行。
-
調用
_processQueue()
處理隊列中的任務。
-
(2) 隊列處理器:_processQueue()
/** 處理渲染隊列(單線程異步執行) */
private async _processQueue() {if (this._isProcessing || this._requestQueue.length === 0) return;this._isProcessing = true;const { text, config, resolve } = this._requestQueue.shift()!;try {const container = await this._doRender(text, config);resolve(container);} finally {this._isProcessing = false;this._processQueue(); // 遞歸處理下一個任務}
}
-
關鍵點:
-
使用
_isProcessing
狀態鎖防止并發處理。 -
通過遞歸調用實現隊列的持續消費。
-
(3) 實際渲染邏輯:_doRender()
/** 執行實際渲染邏輯 */
private async _doRender(text: string, config: TextRenderConfig): Promise<Node> {const container = new Node('TextContainer');this._applyConfig(config, container); // 應用配置(位置、父節點等)// 預加載資源(字符或圖集)try {if (config.useAtlas) {await this._loadAtlas(config);} else {await this._preloadCharacters(text, config);}} finally {if (config.preRender) {container.destroy(); // 預渲染模式直接銷毀容器return;}}// 創建字符節點并布局const nodes = await this._createCharacterNodes(text, config);this._layoutNodes(nodes, config);// 設置字符縮放并添加到容器nodes.forEach(node => {container.addChild(node);if (config.fontSize) {node.setScale(new Vec3(config.fontSize / 100, config.fontSize / 100, 1));}});return container;
}
-
流程:
-
配置初始化:設置容器位置、父節點等基礎屬性。
-
資源預加載:根據配置選擇加載圖集或獨立字符。
-
字符節點創建:生成所有字符的Sprite節點。
-
布局計算:根據對齊方式排列字符位置。
-
(4) 字符預加載:_preloadCharacters()
/** 預加載字符資源(分批次加載) */
private async _preloadCharacters(text: string, config: TextRenderConfig) {const uniqueChars = [...new Set(text.split(''))];// 分批次加載(每批5個字符)for (let i = 0; i < uniqueChars.length; i += 5) {const batch = uniqueChars.slice(i, i + 5);await Promise.all(batch.map(char => this._loadWithCacheControl(char, config)));}
}
-
優化策略:
-
分批加載減少瞬時資源請求壓力。
-
使用
_loadWithCacheControl
結合LRU緩存管理。
-
(5) 布局計算:_layoutNodes()
/** 自動布局字符節點 */
private _layoutNodes(nodes: Node[], config: TextRenderConfig) {const firstNode = nodes[0]?.getComponent(Sprite);if (!firstNode?.spriteFrame) return;// 計算總寬度和基礎高度const baseHeight = firstNode.spriteFrame.rect.height;const totalWidth = nodes.reduce((sum, node) => sum + node.getComponent(Sprite)!.spriteFrame.rect.width, 0) + (nodes.length - 1) * (config.spacing || 0);// 計算起始位置let xPos = this._calculateXPosition(totalWidth, config.alignment);const yPos = this._calculateYPosition(baseHeight, config.verticalAlign);// 排列節點nodes.forEach(node => {const width = node.getComponent(Sprite)!.spriteFrame.rect.width;node.setPosition(xPos, yPos, 0);xPos += width + (config.spacing || 0);});
}
-
布局邏輯:
-
水平對齊:根據
alignment
計算起始X坐標。 -
垂直對齊:根據
verticalAlign
計算起始Y坐標。 -
動態間距:累加每個字符的寬度與間距,實現自動換行(如需)。
-
(6) 占位符生成:_getPlaceholder()
/** 生成透明占位符紋理 */
private _getPlaceholder(): SpriteFrame {if (!this._placeholderCache) {const frame = new SpriteFrame();const charWidth = 30, charHeight = 30; // 與實際字符尺寸一致const texture = new Texture2D();texture.create(charWidth, charHeight, Texture2D.PixelFormat.RGBA8888);const data = new Uint8Array(charWidth * charHeight * 4).fill(150); // 半透明灰色texture.uploadData(data);frame.rect = new Rect(0, 0, charWidth, charHeight);frame.texture = texture;this._placeholderCache = frame;}return this._placeholderCache;
}
-
作用:
-
在字符加載失敗時提供占位顯示,避免UI錯亂。
-
使用透明紋理減少視覺干擾。
-
3.?關鍵配置參數:TextRenderConfig
/** 生成透明占位符紋理 */
private _getPlaceholder(): SpriteFrame {if (!this._placeholderCache) {const frame = new SpriteFrame();const charWidth = 30, charHeight = 30; // 與實際字符尺寸一致const texture = new Texture2D();texture.create(charWidth, charHeight, Texture2D.PixelFormat.RGBA8888);const data = new Uint8Array(charWidth * charHeight * 4).fill(150); // 半透明灰色texture.uploadData(data);frame.rect = new Rect(0, 0, charWidth, charHeight);frame.texture = texture;this._placeholderCache = frame;}return this._placeholderCache;
}