【组件】降低Draw Call组件 新增完整实例演示

此组件的实际应用例子:古诗小游戏
游戏不卡有两个主要优化点:

  1. 使用此组件合并draw call
  2. 使用对象池技术

ps 手机上不要打log(log中不带对象会好很多,带对象就卡)

这两天在研究如何降低draw call,看了很多帖子,碰到问题咨询大牛@mister_akai 解答的很详细,还提供了例子(scrollview滚动)。本组件就是源自大牛提供的例子,改写成通用组件。

为了降低广大初学者的学习成本,我整理了一个demo项目,用于演示此组件的使用方式。github地址

使用注意事项:

1.需要优化子节点的节点,需要把子节点用属性引用的方式一只持有被优化节点的对象句柄(这样不会丢失两者的关系)

export default class Word extends cc.Component {

    @property(cc.Sprite)
    bg: cc.Sprite = null

    @property(cc.Label)
    label: cc.Label = null
}

2.节点在销毁(回收)或重用时,需要对被优化节点做处理(销毁同步销毁,回收隐藏去掉监听事件等,重用把子节点先拿回来,设定好位置等,再优化一遍)

// 被优化节点的父节点用对象池的情况
export default class Word extends cc.Component {

    unuse() {
        this.label.enabled = false
        this.bg.enabled = false
        this.correct = false
        // 拿回来,确定位置,再执行优化(外层添加新节点的时候会执行一次优化) 
        this.label.node.parent = this.node
        this.bg.node.parent = this.node
        this.label.node.position = cc.Vec2.ZERO
        this.bg.node.position = cc.Vec2.ZERO
    }

    reuse() {
        // draw call优化后,导致节点分离,要重置下状态
        this.label.enabled = true
        this.bg.enabled = true
    }
}

关于对象复用的时候,计算子节点位置的时候,我纠结了半天,如果直接算,会出很多时序上的问题,很坑。
所以,用这种直接拿回来的方式,再好不过了~

再次执行优化非常简单,而且不损耗性能:

// 组件属性引用
this.optimizeDrawCall.do()

Update:

1. 支持多根节点配置 2019-07-20 13:30

最新配置界面截图:

2. 把组件加到快捷菜单里了 2019-07-21 10:15:00

3. 设定了组件执行顺序,不再需要async await了 2019-07-21 10:15:00

4. 添加了编辑器上快捷的帮助按钮连接到github 2019-07-21 10:15:00

5. 支持隐藏节点配置(例如把节点的所有子节点都优化掉了,则此节点即可隐藏)2019-07-25 01:00:00

6. 内置了一个对象池管理组件。。。assets/Script/Lib/NodePoolManager.ts 2019-07-25 01:00:00

对象池组件配置界面:


对象池使用:

7. 新增了一个排行榜demo,基于ScrollView(这个玩意,坑挺深的。。仔细看例子中各节点的属性)2019-07-25 01:00:00

Ps:后续要支持代理节点模式(父组件可通知已优化节点变更,当前也可以自行实现,比较繁琐而已)

组件核心思路:通过移动相同类型组件到同一个节点下,达到同层合并渲染

    +------+                 +------+              +------+
+---+   A  |             +---+  A   |          +---+   A  |
|   ++-----+             |   +------+          |   +------+
|    |                   |                     |
|    | +------+          |                     |   +------+
|    +-+  B   |          |                     +---+   A  |
|    | +------+          |                     |   +------+
|    |                   |                     |
|    | +------+          |                     |   +-----------+
|    +-+  C   |          |                     +---+ container |
|    | +------+  +--->   |            +--->    |   +-+---------+
|    |           |--->   |            |--->    |     |
|    | +------+  +--->   |            +--->    |     | +------+
|    +-+  B   |          |                     |     +-+  B   |
|      +------+          |                     |     | +------+
|                        |                     |     | +------+
|                        |                     |     +-+  B   |
|   +------+             |   +------+          |     | +------+
+---+   A  |             +---+  A   |          |     | +------+
|   ++-----+             |   +------+          |     +-+  C   |
|    |                   |                     |     | +------+
|    | +------+          |                     |     | +------+
|    +-+  C   |          |                     |     +-+  C   |
|    + +------+          |                     |       +------+
+                        +                     +

效果图:

编辑器界面组件配置

未优化效果 draw call 300+

优化后效果 draw call 5

排行榜例子未优化:

优化后:

40赞

顶技术帖,
有空再来仔细研究

mark一个

mark

马克!!

这个组件能支持scrollView这种会移动的节点吗?比如用的最多的背包物品。

mark

支持!!!

mark技术大牛

支持的~ 打开demo项目试试看~ 也可以查看文章中说的大牛解答帖中大神的demo,就是背包scrollview的~

我的工程中,有十多个 Graphics 组件在不停地绘制,可以用这个组件提高效率吗?

你看看本文上边ascii的原理图~ 把设置的一些节点移动到同节点下进行有序排列。。 我都没用过Graphics。。。 无法回答你的问题。。。

大神,我哪里设置不对吗?Drawcall没降

组件升级支持多个根节点了。 然后, 待节点路径要写完整的路径了。 这个例子应该写

  • flowLine/word/bg
  • flowLine/word/word label

github上的demo更新了~

节点父物体改变,wdige的锚点适配就乱了。。如果是那种同比缩放的适配估计还好

Mark!!

对,这些问题得自行考虑。。 比如父节点destroy了,子节点也要同时处理掉。。。 我现在在想通用的解决方案。。 其实es6支持Proxy类,但是现在用不了。。 typescript中都没支持它。。。

要是这些问题都能解决了,那这个组件算是成熟了 哈哈

想得美。。。 我用了一天时间 终于解决了一个无限文字流点击的小场景。。。
总结下来其实不复杂:

  1. 需要优化子节点的节点,需要把子节点用属性引用的方式一只持有被优化节点的对象句柄
export default class Word extends cc.Component {

    @property(cc.Sprite)
    bg: cc.Sprite = null

    @property(cc.Label)
    label: cc.Label = null
  1. 节点在销毁或重用时,需要对被优化节点做处理(销毁同步销毁,重用把子节点先拿回来,设定好位置等,再优化一遍)
// 被优化节点的父节点用对象池的情况
export default class Word extends cc.Component {

    unuse() {
        this.label.enabled = false
        this.bg.enabled = false
        this.correct = false
        // 拿回来,确定位置,再执行优化(外层添加新节点的时候会执行一次优化) 
        this.label.node.parent = this.node
        this.bg.node.parent = this.node
        this.label.node.position = cc.Vec2.ZERO
        this.bg.node.position = cc.Vec2.ZERO
    }

    reuse() {
        // draw call优化后,导致节点分离,要重置下状态
        this.label.enabled = true
        this.bg.enabled = true
    }
}

关于对象复用的时候,计算子节点位置的时候,我纠结了半天,如果直接算,会出很多时序上的问题,很坑。
所以,用这种直接拿回来的方式,再好不过了~

大家的项目draw call一般是跑到多少?我这边普通的安卓机50多draw call感觉有点卡顿