一、类概述
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;
}