TextRenderManager
是 Cocos Creator 中实现动态艺术字体渲染的核心单例类。它通过整合资源加载、缓存管理、异步队列和自动布局等功能,支持普通字符模式和图集模式两种渲染方案,适用于游戏中的动态文本(如聊天内容、排行榜)和静态艺术字(如UI标题)的高效渲染。
资源预加载:分批次异步加载字符纹理或图集,避免主线程阻塞。
动态缓存管理:通过LRUCache
实现字符纹理的智能缓存与淘汰。
自动布局排版:支持水平对齐(左/中/右)、垂直对齐(上/中/下)及字符间距调整。
多模式渲染:
普通模式:从独立文件加载每个字符的纹理。
图集模式:从预生成的图集(如BMFont输出)中提取字符。
高性能:通过异步加载、LRU缓存减少资源重复请求。
易扩展:模块化设计,支持自定义布局策略与渲染模式。
内存安全:动态释放无用资源,防止内存泄漏。
// 字符纹理缓存(LRU策略)
private _spriteCache = new LRUCache(100);
// 加载队列(防止重复请求)
private _loadingQueue = new Map>();
// 渲染请求队列(异步任务调度)
private _requestQueue: Array<{ text: string, config: TextRenderConfig, resolve: Function }> = [];
// 占位符纹理(加载失败时使用)
private _placeholderCache: SpriteFrame | null = null;
renderText()
/**
* 渲染文本
* @param text 文本内容
* @param config 渲染配置
* @returns 包含所有字符节点的容器
*/
public async renderText(text: string, config: TextRenderConfig = {}): Promise {
return new Promise((resolve) => {
// 将请求加入队列,触发异步处理
this._requestQueue.push({ text, config, resolve });
this._processQueue();
});
}
流程:
将渲染请求推入队列,确保异步任务按顺序执行。
调用_processQueue()
处理队列中的任务。
_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
状态锁防止并发处理。
通过递归调用实现队列的持续消费。
_doRender()
/** 执行实际渲染逻辑 */
private async _doRender(text: string, config: TextRenderConfig): Promise {
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节点。
布局计算:根据对齐方式排列字符位置。
_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缓存管理。
_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坐标。
动态间距:累加每个字符的宽度与间距,实现自动换行(如需)。
_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错乱。
使用透明纹理减少视觉干扰。
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;
}