小心 3.6 Mask

也遇到了这个问题 需要判断mask 节点下子节点的数量 现在就会多一个

cocos的产品经理就是个***,做事情不靠动脑筋,完全是靠拍脑袋,这种入侵用户节点树的鬼东西居然还发版本了,简直离谱。
还是说你们也没有请一个产品经理,就是程序员自己想,自己做,做完就丢上线的

api的接口说改就改 资源输出路径说改就改

这个问题确实对用户体验影响挺大,直到公测的时候也才注意到这个反馈,非常抱歉我们没有及早暴露并优化这个问题。我们内部也在持续沟通中,看看技术侧是否有办法绕过。目前会在 3.6.1 先把前面的 PR 进行合并,至少确保引擎自己的组件之间不打架。

你现在干产品了么,git提交就应该打回去,狠狠的打回去

是啊,干产品了。产品不审核代码的,除非特殊情况帮忙看一眼。

很抱歉对各位的使用体验造成了破坏性影响,我来解释下我们为何会产生这种结构。
在我们之前的设计中,mask 作为一个渲染组件,本身包含了一些对 stencilBuffer 的处理的操作。但由于我们的组件规则,一个节点上不允许存在多个渲染组件,而且在原生化中我们尽量保持了渲染流程的一致,移除了之前侵入到底层的修改,那么这样,利用子节点来实现即可分离不同的渲染组件并简化底层流程。

但对于各位的使用方式,是我们考虑不周了,我们正在积极讨论新的解决方案,用更底层的方式来解决这个问题,并预计将于 3.6.1 中上线。

2赞

这个设计确实有点不合理

已经被坑了一波。

路径修改和侵入式组件确实不合理,想想就知道会影响大范围 :joy:。我写的话,都不用产品经理来,我自己都不敢下手,我肯定到处咨询这种修改能不能做 :rofl:

而且mask的属性必须在加入父节点后才能设置,否则会报错

哈哈,被背刺了吧,我也是差点,好在我有检查的习惯。 :rofl:

哈哈,还好我没用3.6

被Mask背刺+1

抱歉给大家带来的困扰!我在这里详细解释一下我们在 3.6 版本做出这个调整背后的逻辑。并且把目前的绕过方案与后续修改的方向也一并说明。

首先,从技术层面上来看,这里的本质问题,在于我们使用组合的设计模式进行工作,Mask 组件本身无法一次性把所有事情做完,Mask 本身其实只有控制状态的能力,并不具备渲染能力,所以 Mask 组件必须依赖一个渲染组件来进行渲染。组合模式是 Creator 一直以来最重要的设计模式之一,通过拆分各个组件的功能,从而使的每个组件的功能更加简单和纯粹,然后再通过组合的方式来实现复杂应用的设计和实现。除了 Mask 之外,用组合的组件还包括 rich text, edit box 等等。这些组件都有创建子节点的效果。

了解了这个原则之后,我们在来看 Mask 的问题,因为历史原因(从 2.x 继承过来的), Mask 明明只是一个管理状态的组件,但在最开始的时候(2.x 时代)Mask 被设计为了一个渲染组件。这里是一个根本设计上的错误。然后因为一个节点上只能有一个渲染组件的原则,Mask 本身是一个渲染组件,当他需要再引用一个渲染组件的时候只能用一些奇技淫巧了。

如果大家看过之前版本(包括 2.x)中 Mask 的实现代码,可以发现下面这个非常非常离奇的实现方式。https://github.com/cocos/cocos-engine/blob/v3.5.0/cocos/2d/components/mask.ts#L488


Mask 内部居然创建了一个 不挂在节点上 的 Graphics 组件,从而去绕过了一个节点只能有一个渲染组件的原则。这里是一个巨大的隐患,因为这个 Graphics 组件不挂在节点上了,所以他的生命周期函数无法正确被调用,很容易出现各种离奇的 内存泄露,状态不同步的问题。

更严重的是这对 3.6 的原生化结构是个很大的阻碍,在 3.6 上,我们实现了 2D 的原生化版本来优化性能,我们将 2D 的渲染合批流程挪到了 C++,在原生平台上,性能有很大的提升,在多个测试例里面,3.6 帧率比 2.x 还高出了 20-30%。而性能提升的一个原因在于,在 3.6 的原生化的合批流程中,我们将原本 面向对象 的合批流程,修改为了 面向数据 的合批流程(DOP),我们会将所有渲染组件的数据收集起来,底层合批流程中快速地对所有数据进行遍历与合批。依赖于这个层级的切分,甚至在部分 web 平台上都有性能提升

而 Mask 这个不挂在节点上的 Graphics 组件对面向数据的方式造成了很大的障碍。因为这个组件不挂在节点上,同时没有正确的生命周期回调,导致他的渲染数据无法被正常的初始化,收集,渲染。

考虑到上面的各种问题,所以我们必须要修复 Mask 内的这个 Graphics 的错误的实现方式,修改为使用一个 正常 的组件来完成正确的流程。我们需要重构 Mask , 而在重构 Mask 的过程因为一些时间上还有流程上的限制,我们有了以下约束的原则:

  • Mask 的 API 尽可能地保持和之前版本一致,保证兼容性
  • 较小的代价 实现将 Mask 内部的 Graphics 变成一个正常的组件,并且对老用户无感知

在这两个原则的约束下,我们做出了新增一个隐藏的子节点,由子节点去挂载 Graphics 组件的决定,当然这里我们 错误地估计 了 Mask 新增一个子节点对布局组件所造成的影响,导致了 Mask 和布局组件混合起来使用时出现了问题。这里有考虑上的不周。

现阶段,你的项目中如果有 Mask 和布局组件混合在一起使用而导致错误效果的情况,你可以在 Mask 节点下先增加一个子节点,并在子节点上添加布局组件来绕过此问题

我们后续的修改方向是,我们不会放弃使用组合的设计模式来实现 Mask,但会对结构进行一些调整,避免对布局组件的影响

  • Mask 不再继承自渲染组件,转变为一个状态组件,这里将引入一些无法被兼容的 Breaking Change,比如 Mask 内无法再调用渲染组件的 API.
  • 将 Mask 子节点上的 Graphics 组件或 Sprite 组件移到 Mask 节点本身上
  • 移除 Mask 子节点

做了这个改动之后,就能在修复 Graphics 问题的同时避免对布局组件的影响。

上面就是这个改动的始末。

最后补充一点,这个改动对部分现有项目确实造成了影响,对给大家造成的影响,我们很抱歉!但希望大家能理解,这个改动只是对之前版本的错误的实现方式和错误的设计下的一次不太完美的重构,我们会继续重构并达到最终目标!

另外,Creator 到了 3.x 时代,我们想做的更多了,项目组对我们的需求也更多了,我们需要更加健壮的架构和更合理的设计才能支撑起更多的应用,所以很多之前遗留下来的错误设计与 Hack 会被我们推翻与重做。在这个过程中,我们会尽可能地保证兼容性,但我们不会为了兼容之前的错误设计和错误实现去修改我们新的架构方案,也不会为了兼容错误的实现而故步自封地放弃拥抱一些更前沿的设计与架构,否则 Creator 将无法承担得起未来更多的内容。所以引入一些破坏性的改动,请大家多谅解,我们也会把所有破坏性质的改动的说明更加完善,尽量避免大家升级项目的过程中踩坑。同时如果有既不破坏兼容性,又能实现我们架构目标的方案,我们也会积极拥抱!感谢!

16赞

为这个处理方式点赞!
另外,语言组织的不错,你真的是写代码的么

1赞

什么都做,擅长打杂!

1赞

你们不觉得 Graphics 占用显存有点多么, 最少也有 1~2M 样子… Mask 我都不敢用 RECT, 而是用 IMAGE 替代了.

好像程序的尽头都是转行当产品策划运营 :fearful:

我 3.3 的项目升级上来需要遍历子节点用 name 做一些操作,本来是数字的,被 MASK_CHILD 背刺了,一定要把这个解决了,不然稍微大点的项目升级上来一堆错