关于场景渲染机制提个建议

通过我长久的观察,发现这么一个问题:一个场景在运行时加进来一个节点,在这一帧,这个节点的所有组件都不会update,但有个例外,这个节点的渲染相关组件在这一帧就已经开始渲染了,所以,导致现在在运行时加入节点会在加入的一瞬间渲染一个你并不想看到的东西,求改进

1赞

可以举一个具体的案例吗?

这个使用NodePool应该很常见,每次从pool里取得一个节点后再加进场景的一瞬间,它都会显示它最后被加进pool时的模样,因为reuse里所做的修改还来不及在update里实现

@Knox 也看看

的确好烦,一添加某个渲染节点就闪一下,关键不知道为什么有些还带错位闪,就是第一帧某个不远的位置好像已经渲染了某个东西,下一帧马上正确渲染

有这个问题,都是手动设置到屏幕区域外,让第一帧看不见。。。

应该是节点的 world transform 没有更新,但是更新的时机是在 update 之后,渲染之前,理论上渲染时候获得的位置应该已经是正确的,我想知道你的 reuse 逻辑和期望的位置是怎么设置的

位置是没问题,是其他的东西,比如reuse里修改了某个属性,这个属性在update里会去调整这个节点的渲染,比如将sprite的spriteFrame换一下等等

我也遇到了这个问题,特别是使用了NodePool后非常明显

update 逻辑中的修改,应该都是会在渲染前被更新的,如果发生意外,应该是我们没有测试出来的 bug,能不能提供一个 demo 呢?

NewProject.zip (2.8 MB)
临时写了个Demo,很明显

看了你的 Demo,表现是正常的,这个和 main loop 中的调用顺序有关系,Director 的 main loop 是按下面的顺序来执行的:

  1. cc.Director.EVENT_BEFORE_UPDATE (Component start)
  2. cc.Director.EVENT_COMPONENT_UPDATE (Component update)
  3. Scheduler update
  4. cc.Director.EVENT_COMPONENT_LATE_UPDATE (Component lateUpdate)
  5. cc.Director.EVENT_AFTER_UPDATE
  6. cc.Director.EVENT_BEFORE_VISIT
  7. Visit node tree
  8. cc.Director.EVENT_AFTER_VISIT
  9. Renderer rendering
  10. cc.Director.EVENT_AFTER_DRAW

所以在你的 schedule 中创建了新的 Prefab 对象后,会开始当前帧的 visit 和渲染,导致 Prefab 默认的贴图被渲染了一次。

然后在下一帧当中,你的 Prefab 的 HelloWorld Component 中的 update 才会执行,去替换 sprite frame,这一帧才会渲染你替换后的贴图。

有两个办法可以解决这个示例中的问题:

  1. 在 useFrame1 的 notify 中,直接修改 Sprite 的 spriteFrame 属性
  2. 在 lateUpdate 中替换 spriteFrame
2赞

其实我就是觉得这个调用顺序有问题,按照你给你的这个顺序,运行时加进来的节点肯定是先走渲染的,然后第二帧才会走update

是这样没错,Component update 在 scheduler update 之前是有道理的

Component update 相当于用户逻辑,scheduler update 包含系统逻辑(比如 ActionManager),如果将 update 放在 scheduler update 之后,你在 update 中执行的 action 就会被延迟到下一帧。

这也是为什么我们也有 lateUpdate 回调

我觉得你给的第二种解决方案也没用吧,lateUpdate运行的时候还没有visit,我刚试了一下,确实也不行,按你给的顺序,只有在draw之前才会visit,那就是无论如何都会先daw

我觉得改成这样反而可能会友好一点。一般 action 也不需要立刻执行,做成异步执行反而可能符合用户预期。

刚刚检查了一下,根本问题在于 update 和 lateUpdate 是在添加后下一帧才开始监听的,所以这里只能用我刚刚说的第一个方案解决。

@jare 修改 schedule update 和 component update 的顺序也解决不了问题

我这只是给的一个demo,在notify里很容易更新,但我实际的工作中有些是必须在所有属性的修改完成后再一次性在update里或lateUpdate里更新的。

能举个例子吗?为什么不能在 onLoad 或者 reuse 里面直接完成呢?

这种模式不是很常见么,就像cc.Layout,为什么实际的doLayout要嵌进循环里去,不是在每次属性变化的时候直接调用