anatolbogun/godot-smoother-node

is_on_screen() returns always false in _enter_tree()

Closed this issue · 3 comments

You write under Performance Optimisations:

One caveat is that on entering the tree, the VisibleOnScreenNotifier2D/VisibleOnScreenNotifier3D do not fire the screen_exited signal, so you may have to emit this in a Node's _enter_tree

Then you use the method is_on_screen() in the example.

But the API states:

Note: It takes one frame for the node's visibility to be assessed once added to the scene tree, so this method will return false right after it is instantiated, even if it will be on screen in the draw pass.

I think the notifier just needs a frame and then catches up with the signal. So you should be able to simply omit the _enter_tree() method here.

Ah, thanks for that @greycheeked . So it sounds like even after an await owner.ready this wouldn't work. Good to know. I probably have some time tomorrow to clean up a few bits in this project. Thanks for your help!

@greycheeked , I had a look at this. There is one way to get a proper result for VisibleOnScreenNotifier2D.is_on_screen(), which is:

func _enter_tree() -> void:
	await RenderingServer.frame_post_draw

	if !$VisibleOnScreenNotifier2D.is_on_screen():
		_on_screen_exited()

But since this awaits until the first frame was drawn, even though technically the await delivers the correct is_on_screen() result, for the purpose of the sample project it's the same as:

func _enter_tree() -> void:
	_on_screen_exited()

So it'll be one frame behind either case.

I was curious what was really happening, so the former with await RenderingServer.frame_post_draw results in this debug log:

ready: Player
ready: RigidBody
ready: Enemy1
ready: Enemy2
ready: Enemy3
frame 0 - node entered screen: Player
frame 0 - smoothed nodes: [&"Player", &"Enemy1", &"Enemy2", &"Enemy3"]
frame 0 - node exited screen: Enemy1
frame 0 - smoothed nodes: [&"Player", &"Enemy2", &"Enemy3"]
frame 0 - node exited screen: Enemy2
frame 0 - smoothed nodes: [&"Player", &"Enemy3"]
frame 0 - node exited screen: Enemy3
frame 0 - smoothed nodes: [&"Player"]

while the latter without await RenderingServer.frame_post_draw it is:

frame 0 - node exited screen: Player
frame 0 - smoothed nodes: [&"Enemy1", &"Enemy2", &"Enemy3"]
frame 0 - node exited screen: Enemy1
frame 0 - smoothed nodes: [&"Enemy2", &"Enemy3"]
frame 0 - node exited screen: Enemy2
frame 0 - smoothed nodes: [&"Enemy3"]
frame 0 - node exited screen: Enemy3
frame 0 - smoothed nodes: []
ready: Player
ready: RigidBody
ready: Enemy1
ready: Enemy2
ready: Enemy3
frame 0 - node entered screen: Player
frame 0 - smoothed nodes: [&"Player"]

I decided to add await RenderingServer.frame_post_draw to the code simply because it's a bit more accurate, so that after I forgot everything about this project I guess I'd be less confused by that code than just emitting screen_exited in _enter_tree. Also, as a reminder for myself because this await may be helpful in other use cases.

For the optimisation method to update the Smoother.excludes array I can, however, not just omit the above _enter_tree function because nodes don't seem to fire the on_screen_exited signal when they are initialised or enter the tree. As a result, without emitting screen_exited all relevant nodes would be smoothed initially until they enter and then exit the screen again at a later stage.

Any on-screen node on the other hand does fire the on_screen_entered signal once the first frame is drawn.

I guess I can close this issue. Thanks again for pointing that out. It was helpful for me to learn more about Godot.