mirror of
https://github.com/smallmain/cocos-enhance-kit.git
synced 2025-12-25 02:46:52 +00:00
创建 v3.0.0 文档
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"label": "文本渲染",
|
||||
"position": 2,
|
||||
"collapsed": true,
|
||||
"link": {
|
||||
"type": "doc",
|
||||
"id": "text-render-intro"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 116 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 255 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
@@ -0,0 +1,187 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
description: "详细了解如何通过烘焙提升运行时文本渲染性能。"
|
||||
---
|
||||
# 文本渲染烘焙
|
||||
|
||||
社区版为引擎增加了文本渲染烘焙的能力。
|
||||
|
||||
以某个项目的界面打开操作为例,通过分析 CPU Profile 可以看到,当界面节点激活时,绝大部分的耗时都来自于文本渲染函数(_applyFontTexture)。
|
||||
|
||||

|
||||
|
||||
但这个界面的文本量并不多,在低端机上的耗时却有 1.06s。
|
||||
|
||||
通过文本渲染烘焙优化后,耗时降至 60ms!
|
||||
|
||||

|
||||
|
||||
文本渲染烘焙分为三个部分:
|
||||
|
||||
- **预加载 Label Canvas**
|
||||
- **烘焙文本测量值**
|
||||
- **预加载 `Char` 图集**
|
||||
|
||||
我们将逐个说明。
|
||||
|
||||
## 预加载 Label Canvas
|
||||
|
||||
引擎通过离屏 Canvas 渲染文本,内部维护着一个 Canvas 池以作复用。
|
||||
|
||||
每个非 Char 模式的 Label 都会占用一个 Canvas 对象,直到 Label 销毁才会将 Canvas 对象放回池中。
|
||||
|
||||
默认情况下,Canvas 池不预先创建对象,而是在使用时进行创建,在部分低端设备上,这会增加文本渲染时的卡顿。
|
||||
|
||||
并且该 Canvas 池默认最大缓存数量为 32,超过该数量的对象不会放入池中而是直接销毁,这也导致大量使用 Label 的项目可能会因为持续创建 Canvas 而造成卡顿。
|
||||
|
||||
现在,社区版开放了这部分的接口,可通过 `cc.Label._canvasPool` 访问该对象池。
|
||||
|
||||
可以调整最大缓存数量:
|
||||
|
||||
```ts
|
||||
cc.Label._canvasPool.max = 64;
|
||||
```
|
||||
|
||||
可以预先缓存 Canvas 对象:
|
||||
|
||||
```ts
|
||||
cc.Label._canvasPool.cache(Number.MAX_SAFE_INTEGER);
|
||||
```
|
||||
|
||||
预先缓存接口需传入要缓存的对象数量,无论如何,实际缓存的对象数量都不会超过设置的最大数量。
|
||||
|
||||
## 烘焙文本测量值
|
||||
|
||||
引擎内部在渲染文本前需要先测量字符的宽高,这是通过 Canvas 上的 `measureText` 接口实现的。
|
||||
|
||||
如果你对性能进行了分析,那么可以看出,该接口占据单次文本渲染总耗时的 80% 以上。
|
||||
|
||||
引擎会对此接口的结果进行缓存,相同样式的文本会从缓存中读取,避免了部分性能消耗。
|
||||
|
||||
但默认情况下,该缓存仅会保留最后 100 次的测量值,也就是说,如果所有 Label 使用 Char 模式渲染,在渲染 100 个不同的字符后,缓存就会被逐个移除,测量值又会有大量性能消耗。
|
||||
|
||||
所以,社区版对这部分接口进行了优化,开放了 `cc.textUtils` 的部分接口来控制测量值缓存。
|
||||
|
||||
首先,你可以调整缓存容量,容量越大,就能避免更多测量值的性能消耗,但内存占用会上升,请视情况而定:
|
||||
|
||||
```ts
|
||||
cc.textUtils.measureCache.limit = Number.MAX_SAFE_INTEGER;
|
||||
```
|
||||
|
||||
其次,我们开放了操作缓存的接口,意味着你可以烘焙测量值,将测量值数据保存到项目中,完全避免测量值的性能消耗!
|
||||
|
||||
获取与应用缓存对象:
|
||||
|
||||
```ts
|
||||
// 获取
|
||||
const data = cc.textUtils.getMeasureCache();
|
||||
|
||||
// 应用
|
||||
cc.textUtils.applyMeasureCache(data);
|
||||
```
|
||||
|
||||
我们推荐的用法是:
|
||||
|
||||
- 在开发测试的版本中收集缓存数据并序列化到项目的 JSON 文件中(因为收集会有性能消耗,所以线上版本不进行收集)
|
||||
- 在游戏启动前应用缓存数据
|
||||
|
||||
**注意**
|
||||
|
||||
经过我们的测试,同个字符在微信小游戏平台与 Web 端得出的测量值是一致的。
|
||||
|
||||
但可能会出现其它不同平台或者设备对同个字符的测量值是不一致的,建议针对这种情况烘焙多个 JSON 文件并分别应用即可。
|
||||
|
||||
## 预加载 `Char` 图集
|
||||
|
||||
建议先阅读 [烘焙文本测量值](#烘焙文本测量值),再阅读本节内容。
|
||||
|
||||
如果你对性能进行了分析,那么可以看出,即使我们烘焙了文本测量值,依旧会有将文本绘制到纹理上的性能消耗,那么这剩下 20% 的性能消耗如果无法避免,能控制它消耗的时机也是很有意义的。
|
||||
|
||||
所以,社区版提供了 Char 图集的预加载接口,我们可以在特定时机(比如游戏启动时)就加载好会用到的所有字符,避免游戏中的卡顿。
|
||||
|
||||
引擎的 Char 图集只会在用到时才会创建,所以我们在调用任何接口前需要初始化:
|
||||
|
||||
```ts
|
||||
cc.Label.LetterAtlases.init();
|
||||
```
|
||||
|
||||
由于引擎并不会持有每个字符信息的缓存,所以需要通过社区版新增的开启缓存接口来保留字符信息:
|
||||
|
||||
```ts
|
||||
cc.Label._shareAtlas.enableLetterCache = true;
|
||||
```
|
||||
|
||||
和烘焙文本测量值一样,我们只推荐在开发测试版本收集缓存,因为保留缓存和收集操作都会有性能消耗。
|
||||
|
||||
以下两个接口可以获取与应用字符缓存:
|
||||
|
||||
```ts
|
||||
// 获取
|
||||
const data = cc.Label._shareAtlas.getLetterCache();
|
||||
|
||||
// 应用
|
||||
cc.Label._shareAtlas.applyLetterCache(data);
|
||||
```
|
||||
|
||||
需要注意的是,我们只是将渲染过的字符信息缓存下来,而不是纹理本身,然后在特定时机调用接口来批量渲染字符到纹理上。
|
||||
|
||||
并且,这里的缓存信息已经包括了每个字符的测量值,所以如果你的项目只使用 `CHAR` 模式的 Label 组件,那么你不用再单独烘焙文本测量值。
|
||||
|
||||
## 实际应用
|
||||
|
||||
你可以参考以下代码,在游戏开发测试版本中将缓存数据上传至一个后端服务器。
|
||||
|
||||
```ts
|
||||
// 上报 Label 烘焙信息
|
||||
if (!CC_EDITOR && game.TEST) {
|
||||
cc.director.once(cc.Director.EVENT_AFTER_SCENE_LAUNCH, () => {
|
||||
// 启用缓存
|
||||
cc.Label._shareAtlas.enableLetterCache = true;
|
||||
|
||||
setInterval(() => {
|
||||
const unCaches = {};
|
||||
for (const key in cc.textUtils.measureCache.datas) {
|
||||
if (!(key in _measureCache)) {
|
||||
unCaches[key] = cc.textUtils.measureCache.datas[key].value;
|
||||
_measureCache[key] = unCaches[key];
|
||||
console.warn("未收集的测量值:", key, unCaches[key]);
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(unCaches).length > 0) {
|
||||
sendToServer(JSON.stringify(unCaches));
|
||||
}
|
||||
|
||||
const unLetters = {};
|
||||
for (const key in cc.Label._shareAtlas.letterCache) {
|
||||
if (!(key in _letterCache)) {
|
||||
unLetters[key] = cc.Label._shareAtlas.letterCache[key];
|
||||
_letterCache[key] = unLetters[key];
|
||||
console.warn("未收集的 Char 字符:", key, unLetters[key]);
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(unLetters).length > 0) {
|
||||
sendToServer(JSON.stringify(unLetters));
|
||||
}
|
||||
}, 10000);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
然后在每个版本上线前,从后端下载缓存数据到项目中,并用代码读取并在游戏开始前应用:
|
||||
|
||||
```ts
|
||||
cc.director.once(cc.Director.EVENT_AFTER_SCENE_LAUNCH, () => {
|
||||
// 提前缓存 Label Canvas
|
||||
cc.Label._canvasPool.cache(Number.MAX_SAFE_INTEGER);
|
||||
|
||||
// 提高文本测量值缓存容量
|
||||
cc.textUtils.measureCache.limit = Number.MAX_SAFE_INTEGER;
|
||||
cc.textUtils.applyMeasureCache(_measureCache);
|
||||
|
||||
// 烘焙 CHAR 字符
|
||||
cc.Label.LetterAtlases.init();
|
||||
cc.Label._shareAtlas.applyLetterCache(_letterCache);
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,97 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
description: "详细了解该缓存模式重构后的所有新特性。"
|
||||
---
|
||||
# Char 缓存模式
|
||||
|
||||
在 [提升游戏性能](../../best-practices/performance-guide) 中,我们提到了社区版使 Bitmap 与 Char 缓存模式都支持了废弃字符空间复用的特性。
|
||||
|
||||
除此之外,Char 缓存模式还提供了一些可调整的设置。
|
||||
|
||||
## 在场景切换时清空所有字符图集
|
||||
|
||||
控制在场景切换时是否会清空所有的字符图集,考虑到旧项目兼容,默认为开启状态。
|
||||
|
||||
```js
|
||||
cc.sp.charAtlasAutoResetBeforeSceneLoad = false;
|
||||
```
|
||||
|
||||
在引擎原来的设计中,该机制不可被关闭,现在推荐关闭该机制。
|
||||
|
||||
## 字符图集的数量与内置材质
|
||||
|
||||
现在在内部最多会创建 8 张字符图集,与多纹理材质的最大纹理插槽数一致,如果合理使用缓存模式,8 张应该对所有项目都是足够的。
|
||||
|
||||
Char 缓存模式会在内部维护一个使用内置多纹理 Effect 着色器的材质,Label 在渲染时会先判断当前所使用的是否为多纹理材质,是的话则判断是否能在材质纹理插槽中找到字符图集纹理。
|
||||
|
||||
若任何判断不满足则会将组件的材质设为内部维护的材质。
|
||||
|
||||
如果你有特殊的用途,可以通过以下代码获取:
|
||||
|
||||
```js
|
||||
cc.Label._shareAtlas.material;
|
||||
```
|
||||
|
||||
需注意 Char 字符图集是运行时才创建的,所以现在**暂时无法在 Char 缓存模式下设置组件的自定义材质**。
|
||||
|
||||
要解决这个问题,你可以通过代码创建含有当前所有字符图集纹理的自定义材质来解决这个问题,但内部会不断创建新的字符图集(直到 8 张),所以需注意同步更新这个自定义材质,建议有自定义材质需求时使用 Bitmap 缓存模式。
|
||||
|
||||
## 与动态图集合批的注意事项
|
||||
|
||||
多纹理材质只有 8 个纹理插槽,默认情况下字符图集自动多纹理合批的数量为 1,也就是说会将第 1 张字符图集纹理放入材质。
|
||||
|
||||
**你可以自己调整这个分配值,但请注意调整的时机。**
|
||||
|
||||
下面举一个例子,假设有一个脚本 `example.ts` 的部分内容是:
|
||||
|
||||
```js
|
||||
|
||||
class Example extends cc.Component {
|
||||
|
||||
onLoad() {
|
||||
// 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 2
|
||||
|
||||
```
|
||||
|
||||
一般情况下,代码位置 2 是当用户脚本被加载时就会被执行,而代码位置 1 可能需要等到引擎首场景加载后的某个时间执行。
|
||||
|
||||
社区版会自动调整动态图集的最大数量,这个调整的时机是在代码位置 2 之后的,所以比如你的项目对 Char 缓存模式使用量比较大时,想尝试将动态图集最大数量调整为 6,自动合批的字符图集数量调整为 2,那么你只需要在代码位置 2 修改字符图集自动多纹理合批的数量:
|
||||
|
||||
```js
|
||||
cc.sp.charAtlasAutoBatchCount = 2;
|
||||
```
|
||||
|
||||
之后社区版会自动将动态图集的最大数量调整为 `8 - 2`,即 6。
|
||||
|
||||
这个自动调整的时机并不意味着你在代码位置 2 修改动态图集的最大数量是无效的,因为一开始动态图集的最大数量为 `-1`,你打印一下可以看到
|
||||
|
||||
```js
|
||||
console.log(cc.dynamicAtlasManager.maxAtlasCount); // -1
|
||||
```
|
||||
|
||||
如果你在代码位置 2 修改了动态图集的最大数量,社区版就不会调整该值了。
|
||||
|
||||
```js
|
||||
cc.dynamicAtlasManager.maxAtlasCount = 5;
|
||||
```
|
||||
|
||||
这时候动态图集的最大数量是 5,字符图集自动多纹理合批的数量依旧默认为 1,多纹理材质会有 2 个空纹理插槽。
|
||||
|
||||
如果这两个数量加起来超过 8 就会使用更多的材质进行渲染,这会导致项目的 Draw Call 数量升高,建议保持加起来的数量不超过 8 张,能保持 1 Draw Call。
|
||||
|
||||
```js
|
||||
cc.dynamicAtlasManager.maxAtlasCount = 13;
|
||||
cc.sp.charAtlasAutoBatchCount = 3;
|
||||
```
|
||||
|
||||
比如上面这个设置,这会使得引擎需要用 2 个材质进行渲染,但是可用的动态图集扩充到了 13 张,Char 能自动合批的图集数量扩充到了 3 张,对于某些项目来说可能并不是一件坏事。
|
||||
|
||||
社区版使用这个 “7 + 1” 的默认值有以下几点原因:
|
||||
|
||||
- 引擎原本就只有 1 张 Char 字符图集
|
||||
- 大多数项目使用 1 张 Char 字符图集是足够的
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
description: "一行代码开启高清文本渲染。"
|
||||
---
|
||||
# 高 DPI 支持
|
||||
|
||||
可阅读 [提升游戏质量](../../best-practices/quality-guide) 了解基本的使用方法。
|
||||
|
||||
## 调整全局开关
|
||||
|
||||
使用下面的代码控制所有组件是否默认开启高 DPI 支持:
|
||||
|
||||
```js
|
||||
cc.sp.enableLabelRetina = false;
|
||||
```
|
||||
|
||||
## 调整渲染缩放比例
|
||||
|
||||
使用下面的代码调整内部渲染的缩放倍数:
|
||||
|
||||
```js
|
||||
cc.sp.labelRetinaScale = 2;
|
||||
```
|
||||
|
||||
## 控制单个组件开关
|
||||
|
||||

|
||||
|
||||
除了在编辑器调整,也可以通过代码控制:
|
||||
|
||||
```js
|
||||
// cc.RenderComponent.EnableType
|
||||
// GLOBAL: 全局默认值
|
||||
// ENABLE: 开启
|
||||
// DISABLE: 关闭
|
||||
label.enableRetina = cc.RenderComponent.EnableType.ENABLE;
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
|
||||
|
||||
# 文本渲染
|
||||
|
||||
文本渲染一般是游戏性能优化需要重点关注的地方,并且其显示效果也非常重要,所以社区版提供了以下新特性:
|
||||
|
||||
<DocCardList items={useCurrentSidebarCategory().items}/>
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
description: "就像在其它组件里一样使用自定义材质。"
|
||||
---
|
||||
# RichText 自定义材质
|
||||
|
||||

|
||||
|
||||
像往常一样使用即可。
|
||||
Reference in New Issue
Block a user