Cocos Creator ScrollView 优化系列-2-可视区域渲染

本系列教程指引:

  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 的「可视区域」内呢?

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

  • 如果Content Node 采用水平 Layout 的时候,用计算x偏移量去判断
  • 如果Content Node 采用垂直 Layout 的时候,用计算y偏移量去判断
  • ......

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

要知道我们现在正在使用的是伟大的 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 的「可视区域」以及节点是否在「可视区域」内,那么除了做渲染优化,我们还可以做:

  • 加载优化。比如,只有Item进入到可视区域内,我们才加载该Item的网络图片之类的逻辑,效果类似上面的演示图那样子
  • 高级动效。比如,下面的这些高级动效,都可以实现,具体代码可以见我的 Github 仓库
    Effect
  • ...

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-合批优化(待续)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345