1. 性能瓶颈分析
通过性能分析工具可以得知
技能列表刷新一次,需要耗时 650 ms 左右
刷新列表时,刷新了 5 个 item,每个 item 的刷新时间平均为 130 ms。
查看单个 item 的刷新,分为 3 部分,getObject
,addChild
和 renderListItem
。
其中 getObject
和 addChild
是虚拟列表 GList
添加 item 时必要的步骤, renderListItem
是自己写的渲染 item 的函数。
而在 renderListItem
中,可以发现,updateText
函数占用了大量的时间,达到了总耗时的 93.7%
。
所以优化 updateText
函数成了技能系统优化的重点。
2. 优化方法
查阅底层代码可以知道,updateText
函数是富文本组件 fgui.GRichTextField
在渲染时候调用的。
所以优化的重点变成了 富文本组件的渲染问题。
2.1 使用普通文本替代富文本
富文本在底层是调用了多个普通文本拼接而成,尤其是使用了 UBB
和 模板
的情况下,渲染非常耗性能。
所以如非必要,可以改为使用普通文本替代。
let label: fgui.GRichTextField = this.getchild("label")?.asRichTextField;
label.text = "恭喜玩家{userName}获得装备{equipName}x{num}"
let userName = "龙傲天";
let equipName = "屠龙刀";
let equipNum = 1;
// 富文本使用模板
label.setVar("userName", userName)
.setVar("equipName", equipName)
.setVar("num", `${equipNum}`)
.flushVars();
使用富文本的模板有很大的好处,就是不需要关注具体的文本内容是什么,只需要关注自己需要设置的内容即可。
是一种很好的 UI
和 逻辑
分离的思路,只可惜刷新显示文本时性能消耗有点多。
可以改成普通文本,这样可以节省很多性能。
let label: fgui.GTextField = this.getchild("label")?.asTextField;
let userName = "龙傲天";
let equipName = "屠龙刀";
let equipNum = 1;
// 普通文本字符串拼接
label.text = `恭喜玩家${userName}获得装备${equipName}x${equipNum}`
2.2 分帧加载
但是并非所有的 RichTextField
都可以使用 TextField
替代。
比如技能描述文本中,需要使用 UBB
语法进行排版,直接将富文本替换成普通文本,显然是无法达到要求的。
也就是说,富文本渲染的消耗是不可避免了。
但是,使用分帧加载的思路,还是有优化的空间的。
一帧内要刷新 5 个富文本组件,会很卡,但是如果每帧只刷新一个富文本组件,就会流畅很多了。
一帧的任务分摊到 5 帧执行完成,时间上相差并不大,但是每帧的任务压力都减轻了很多。
示例代码如下:
定义一个渲染任务队列管理类 renderTaskQueue
,用来分帧处理比较耗时的渲染任务。
class renderTaskQueue {
// 单例模式
private static ins: renderTaskQueue;
static instance(): renderTaskQueue {
if (!this.ins) {
this.ins = new renderTaskQueue();
}
return this.ins;
}
// 渲染任务队列
private m_queue: Array<Function> = [];
// 添加一个任务到队列里
addToQueue(func: Function) {
if (!func || this.m_queue.indexOf(func) < 0) {
this.m_queue.push(func);
}
}
// 从队列中取一个任务并执行
executeOne() {
if (this.m_queue.length > 0) {
let func = this.m_queue.shift();
func && func();
}
}
// 执行队列中所有任务
executeAll() {
this.m_queue.forEach(func => {
func && func();
});
this.m_queue = [];
}
}
将渲染耗时较长,时效性要求又不是特别高的任务拆分出来,放入渲染队列中执行
// 修改前的 refreshWnd 函数,富文本渲染和图片加载耗时较长
refreshWnd() {
if (this.textField) {
this.textField.text = "这个是普通文本";
}
if (this.richTextField) {
this.richTextField.text = "这个是[color=#d18888]富文本[/color]";
}
if (this.loader) {
this.loader.url = "ui://commonPackage/testImage";
}
}
// 优化后的 refreshWnd 函数,将耗时较长的任务拆分出来,放入渲染队列中
setRichTextField() {
if (this.richTextField) {
this.richTextField.text = "这个是[color=#d18888]富文本[/color]";
}
}
setLoader() {
if (this.loader) {
this.loader.url = "ui://commonPackage/testImage";
}
}
refreshWnd() {
if (this.textField) {
this.textField.text = "这个是普通文本";
}
renderTaskQueue.instance().addToQueue(this.setRichTextField.bind(this));
renderTaskQueue.instance().addToQueue(this.setLoader.bind(this));
}
在 OnUpdate()
中调用 renderTaskQueue
的方法,每帧执行一个任务
OnUpdate() {
// 每帧执行一次 executeOne() 方法,处理一个任务
renderTaskQueue.instance().executeOne();
}
3. 优化效果
优化后,使用性能分析工具检查可以知道,任务被拆分到多帧执行了。
技能列表刷新时间,由原先的 650 ms 左右,优化到 290 ms 左右,节省了 55% 的时间。
此处评论已关闭