creatorCocosCreator优化CocosCreator

Cocos Creator ScrollView 优化系列-2-

2019-07-14  本文已影响0人  天煞魔猎手

本系列教程指引:

  1. Cocos Creator ScrollView 优化系列-1-分帧加载
  2. Cocos Creator ScrollView 优化系列-2-可视区域渲染
  3. Cocos Creator ScrollView 优化系列-3-复用实现(待续)
  4. Cocos Creator ScrollView 优化系列-4-合批优化(待续)

本项目中所有图示、代码都在Github仓库中,如果需要运行验证,可直接拉下项目即可,不用自己手撸代码验证

👉👉https://github.com/zhitaocai/CocosCreator-ScrollVIewPlus👈👈

一、前言

在系列上一篇文章中,我们介绍了 「分帧加载」 的技术,最终达到下面效果

Framing Load

但是,在这个过程中,随着我们创建的节点越多,会发现 左下角 Draw call 一直在飙升

这是因为我们的节点在创建并加入到 ScrollView 之后,就一直在显示了,哪怕这个节点不在我们的 ScrollView 的可视区域内,而这正是我们这次要重点解决的问题。

通过阅读本文,你将了解到如何利用 「可视区域渲染」 解决上述问题,最终实现 只渲染 ScrollView 可视区域内的 Item ,不再可视区域内的 Item 不渲染。效果如下:

Visiable Area Render

PS:
1. 注意看左下角的 Draw call 参数
2. 因为录屏软件问题,所以 GIF 看上去可能有点卡顿,实际运行会流畅很多

当然,阅读完本文之后,你还可以 调整姿势,实现诸如下面这种功能:

Effect

二、可视区域渲染实现

在上一节中,我们提及到了 「可视区域渲染」 这个词语。在实现的时候,我们需要拆分两个技术点:

  1. 什么区域才是可视区域?
  2. 节点当前是否在可视区域呢?

2.1 计算 ScrollView 的可视区域

那么,什么区域才算是 ScrollView 的可视区域呢?

其实这反而是一个最简单的问题,因为 ScrollView 本质上是不会移动的,我们一直在移动的只是 ScrollView 中 Content 属性所指定的节点:

ScrollView Content Node

所以 ScrollView 的可视区域就是 ScrollView 本身位置和大小,转换为世界坐标系下, ScrollView 的「可视区域」(也可以叫做碰撞包围盒)就是如下实现代码:

// 获取 ScrollView Node 的左下角坐标在世界坐标系中的坐标
let svLeftBottomPoint = scrollView.node.parent.convertToWorldSpaceAR(
  cc.v2(
    scrollView.node.x - scrollView.node.anchorX * scrollView.node.width,
    scrollView.node.y - scrollView.node.anchorY * scrollView.node.height
  )
);

// 求出 ScrollView 可视区域在世界坐标系中的矩形(碰撞盒)
let svBBoxRect: cc.Rect = cc.rect(
  svLeftBottomPoint.x,
  svLeftBottomPoint.y,
  scrollView.node.width,
  scrollView.node.height
);

2.2 判断 ScrollView Content Node 的子节点是否在可视区域内

知道了 ScrollVIew 的可视区域了,那么,我们又如何检查 ScrollView Content 节点下的子节点是否在「可视区域」内呢?

要知道 ScrollView 的 Content 属性是可以指定不同的 Node ,这个 Node 有可能挂载了不同布局方式的 Layout 组件(水平布局、垂直布局、网格布局),也有可能不挂 Layout 组件,也有可能就是随便乱摆等等的各种情况。

那么问题来了,情况那么复杂,我们又应该怎么计算 ScrollView Content 中的某个子节点在 ScrollView 的「可视区域」内呢?

大部分同学可能就是具体情况具体分析,比如

不得不说,这是一种办法,救急实用,但是问题是不太通用,考虑不全面,比较难以直接复用。

要知道我们现在正在使用的是伟大的 Cocos Creator 游戏引擎呢!那么为什么我们不用游戏该有的碰撞算法方式去计算呢?碰撞的话,我才不管你是怎么排列的呢,是不是!

万幸的是,上一步中,我们知道了 ScrollView 在世界坐标系的「可视区域」(碰撞包围盒),那么如果我们也能求出 Content 中各个子节点在世界坐标系下的碰撞包围盒,然后逐个判断一下是否碰撞,那么岂不是就知道了 Content中的子节点是否在 ScrollView 的「可视区域」内了?!~

好吧,其实好像也没什么难的,就是转换一下思路,然后代码就出来了:

// 遍历 ScrollView Content 内容节点的子节点
scrollView.content.children.forEach((childNode: cc.Node) => {

    // 对每个子节点的包围盒做和 ScrollView 可视区域包围盒做碰撞判断
    // 如果相交了,那么就显示,否则就隐藏
    if (childNode.getBoundingBoxToWorld().intersects(svBBoxRect)) {
        if (childNode.opacity != 255) {
            childNode.opacity = 255;
            // childNode.emit("on_enter_scroll_view");
        }
    } else {
        if (childNode.opacity != 0) {
            childNode.opacity = 0;
            // childNode.emit("on_exit_scroll_view");
        }
    }
});

然后我们只需要在 ScrollView 滚动的时候,一直计算,那么就可以实现「可视区域渲染」了

onEnable() {
    this.node.on("scrolling", this._onScrollingDrawCallOpt, this);
}

onDisable() {
    this.node.off("scrolling", this._onScrollingDrawCallOpt, this);
}

private _onScrollingDrawCallOpt() {
    if (this.content.childrenCount == 0) {
        return;
    }
    // 上文提及到的碰撞检测代码
    // ...
}

至此,好像就已经完成了基本的「可视区域渲染」的功能了!~

三、优化

当然,上面几段代码还是比较简单的,但是它已经把核心的思想(碰撞检测)传递出来了,剩下的我们再优化一下就可以了。

比如:

我们每次碰撞检测都创建了不少对象 cc.Vec2cc.Rect 等等,其实这些我们完全可以优化去掉,不生成引用对象,直接计算

比如:

例子中,我们直接占用了 childNode.opacity ,这是不妥的做法,因为你永远不应该在上层干涉底层的运作,而这里我们已经干涉了子节点的透明度,如果子节点也需要修改透明度,那么就很容易生成找到的Bug了

所以,这里我们可以优化一下

  1. 在进入ScrollView的时候,传递一个事件给子节点处理
  2. 在离开ScrollView的时候,传递一个事件给子节点处理

恩,也就是上面代码中注释掉的两行代码。但是,我注释掉了!因为伟大的 Cocos Creator 游戏引擎是组件化开发的呢~,我们为什么不把事件转换为组件呢?

Talk is cheap, show me the code.

// 遍历 ScrollView Content 内容节点的子节点
// 对每个子节点的包围盒做和 ScrollView 可视区域包围盒做碰撞判断
scrollView.content.children.forEach((childNode: cc.Node) => {
    // 没有绑定指定组件的子节点不处理
    let itemComponent = childNode.getComponent(ScrollViewPlusItem);
    if (itemComponent == null) {
        return;
    }

    // 如果相交了,那么就显示,否则就隐藏
    if (childNode.getBoundingBoxToWorld().intersects(svBBoxRect)) {
        if (!itemComponent.isShowing) {
            itemComponent.isShowing = true;
            itemComponent.publishOnEnterScrollView();
        }
    } else {
        if (itemComponent.isShowing) {
            itemComponent.isShowing = false;
            itemComponent.publishOnExitScrollView();
        }
    }
});

ScrollViewPlusItem 组件代码如下:

@ccclass
export default class ScrollViewPlusItem extends cc.Component {
    @property({
        type: [cc.Component.EventHandler],
        tooltip: "进入ScrollView时回调"
    })
    onEnterScorllViewEvents: cc.Component.EventHandler[] = [];

    @property({
        type: [cc.Component.EventHandler],
        tooltip: "离开ScrollView时回调"
    })
    onExitScorllViewEvents: cc.Component.EventHandler[] = [];

    /**
     * 当前是否在展示中
     *
     * 1. 在进入和离开ScrollView期间,为true
     * 2. 在离开ScrolLView期间,为false
     */
    isShowing: boolean = false;

    /**
     * Item 进入 ScrollView 的时候回调
     */
    publishOnEnterScrollView() {
        if (this.onEnterScorllViewEvents.length == 0) {
            return;
        }
        this.onEnterScorllViewEvents.forEach(event => {
            event.emit([]);
        });
    }

    /**
     * Item 离开 ScrollView 的时候回调
     */
    publishOnExitScrollView() {
        if (this.onExitScorllViewEvents.length == 0) {
            return;
        }
        this.onExitScorllViewEvents.forEach(event => {
            event.emit([]);
        });
    }
}

然后ScrollView的Content的子节点接可以挂在这个 ScrollViewPlusItem 组件,然后绑定事件了。恩,就像Button组件那样子使用就可以:

ScrollViewPlusItem
/**
 * 本Item进入ScrollView的时候回调
 */
onEnterSrcollView() {
    this.node.opacity = 255;
}

/**
 * 本Item离开ScrollView的时候回调
 */
onExitScrollView() {
    this.node.opacity = 0;
}

恩,最后实现的就是我们本文一开始的效果图了:

Visiable Area Render

PS:完整代码可以参考 Github 项目的 ScrollViewPlus 以及 ScrollViewPlusItem 组件

四、总结

看完上面的做法,那么我们总结一下:

这算是 ScrollView 「可视区域渲染」 比较完善的通用解决方案吗?

然而并不是!

如果我们细想的话,就会发现,上面计算 ScrollView 的「可视区域」时,我们只考虑了 anchor(锚点),忽略了 ScrollView 自身的 scale (缩放)rotation(旋转)skew(倾斜) 等几个属性,要知道,这几个属性同样会影响 ScrollView 「可视区域」。

但是因为实际场合中,我们比较少会改动这几个 ScrollView 属性,所以我就没有实现,但是现在指出来了,相信大家应该知道如何继续完善下去,我偷懒了,大家加油~

再总结一下:

如果我们正常使用 ScrollView (不做sacle、rotation、skew修改)的话,本文提及到的方法和代码是够用了

PS:这里特别再特别指出,如果开启了 ScrollView 的 Content 节点挂载了 Layout 组件并且开启了 Affected by Scale 属性,那么此种方案还需要再优化一下的~

五、延伸

5.1 ScrollView 「可视区域渲染」延伸

既然知道了 ScrollView 的「可视区域」以及节点是否在「可视区域」内,那么除了做渲染优化,我们还可以做:

5.2 「可视区域渲染」延伸

这节和5.1节就一个区别,就是没有了 ScrollView 的约束。对的,可视区域渲染是一个能大幅度降低渲染压力的一种方案。 比如:大地图游戏,我们只需要渲染可视区域内的内容就可以,不在可视区域内的物体,我们完全可以通过「碰撞检测」去剔除渲染,又或者「分组渲染」等等其他方案去实现。

核心思想还是:「可视区域渲染」,多理解琢磨这句话,并用到你的游戏上,相信你的游戏品质能更上一层楼

六、进入下一个章节

至此,我们的「可视区域渲染」基本告一段落了。下一个章节,我们见会讲述如何实现「节点复用」,敬请期待

本系列教程指引:

  1. Cocos Creator ScrollView 优化系列-1-分帧加载
  2. Cocos Creator ScrollView 优化系列-2-可视区域渲染
  3. 👉Cocos Creator ScrollView 优化系列-3-复用实现(待续)
  4. Cocos Creator ScrollView 优化系列-4-合批优化(待续)
上一篇 下一篇

猜你喜欢

热点阅读