Godot version:
3.0.6-stable
OS/device including version:
Windows 10
Issue description:
Hidden nodes still recieve _unhandled_input(). Not only could this lead to performance issues, but it leads to totally unexpected behaviours. Hidden nodes should not do anything.
In my case I have two nodes with a GDScript attached. Both handle _unhandled_input(event) and consume the event with get_tree().set_input_as_handled(). I use unhandled because I want the UI handle input first, but this doesn't matter for this issue. Although the other node was hidden, it still stole the input events, the other node never received anything and I was wondering what was going on until I finally figured out the problem. Only one node can be visible at the same time. I switch them on and off with show() and hide().
My Workaround for now is if(not visible): return in every event handling method.
Steps to reproduce:
In attached project if you move the mouse in the empty game window you see "HiddenNode" steals all events. Only if you delete it, "VisibleNode" prints "I got the event" in the console.
Minimal reproduction project:
HidenNodesStillReceiveEvents.zip
This isn't an issue. If a node is inside the scene tree it should be processed whether it's visible or not. If you want it to stop processing while hidden that should be up to you to code. You can set it to stop handling inputs by setting set_process_inputs or set_process_unhandled_inputs when the visibility changes.
same for _process and _physics_process as well. you can disable/toggle inputs, processing,etc when you hide teh node
That's what I did in the end. I made my own deactivate() and activate() methods which I expected from show/hide. Is there any reason for processing hidden nodes? I think its not intuitive and from a design standpoint it leads to unexpected behaviour and performance leaks especially if you are new to godot and don't know the specific "workarounds". If you hide a button you can't press it anymore. If you hide a sprite why should it collide with something or receive input etc. :)
Here is the extra code needed for deactivating a node:
func _ready():
if(not visible):
deactivate()
func deactivate():
hide()
set_process(false)
set_physics_process(false)
set_process_unhandled_input(false)
set_process_input(false)
func activate():
show()
set_process(true)
set_physics_process(true)
set_process_unhandled_input(true)
set_process_input(true)
power of gdscript. working as intended imo. just part of learning the engine. very flexible in the choices we make, we can do whatever we want
If you hide a sprite why should it collide with something or receive input etc. :)
Maybe you want to hide an entity but still process it's behaviour e.g. they have an ability to turn invisible but that shouldn't mean they can't move
I like that Godot is that flexible and I understand now, there may be use cases, but these seem intentional - meaning you know you want them still processing even if hidden and therefore should require addional methods for enable processing. The default behavior shoud in my opinion be the intuitive case - disabling everything, because someone would not expect something to do anything if its not there - like the button example I made. Maybe I have a different intuition, but that's just me being confused with the design choice :)
@Skyfish1 yeah there's good points on both sides.. just try to adapt (what i did)
For me the intuitive case is that when a node is inside the scene tree is can be processed if the relevant processing methods are enabled. The visibility of a node has nothing to do with its logic processing, just whether it is rendered or not. For example look at Timer nodes, they have no rendering at all, but they need to process to count down their timer.
For the GUI nodes it makes sense because to interact with them they need to be visible. But even then my game has a pause menu which is a Popup node that stays hidden and processes input waiting for the "pause game" input event, which will then make it pop up and pause the game.
As I have a complex tree of Node2Ds and Sprites in a multilayer Map I had to come up with a flexible solution to deactivate all subnodes if I hide the parent (Map layer) node. Here is my script in case someone has the same Issue:
You need to extend all Nodes you want to be deactivatable with this script:
extends "res://scripts/DeactivatableNode2D.gd"
if you also want access to Sprite methods like texture in your script, you also need a second script which extends Sprite instead of Node2D because you can not extend Sprite and my script at the same time as far as I know.
extends Node2D
# mask for activation eg. (A_PROCESS | A_INPUT)
const A_PROCESS = 0x1
const A_PHYSICS = 0x2
const A_INPUT = 0x4
const A_UNHANDLED_INPUT = 0x8
const A_ALL = A_PROCESS | A_PHYSICS | A_INPUT | A_UNHANDLED_INPUT
var _mask = A_ALL
# to override call super(=dot) method: ._ready()
func _ready():
if(not visible):
_deactivate()
connect("visibility_changed", self, "on_visibility_changed")
func set_mask(mask):
_mask = mask
func _deactivate():
# hide() -> will trigger on_visibility_changed which will call this method
set_process(false)
set_physics_process(false)
set_process_unhandled_input(false)
set_process_input(false)
func _activate():
# show() -> will trigger on_visibility_changed which will call this method
if(_mask & A_PROCESS): set_process(true)
if(_mask & A_PHYSICS): set_physics_process(true)
if(_mask & A_UNHANDLED_INPUT): set_process_unhandled_input(true)
if(_mask & A_INPUT): set_process_input(true)
func on_visibility_changed():
if(is_visible_in_tree()): # This does also consider inherited visible
_activate()
else:
_deactivate()
All you need in your code is then:
extends "res://scripts/DeactivatableNode2D.gd"
func _ready():
# ._ready() #call super method seems to happen automatically (tested on Godot 3.1)
set_mask(A_PROCESS|A_UNHANDLED_INPUT | ...) # optional. default is ALL
and if you hide() a node the script and the visibility_changed event does the rest for this and all subnodes in whatever complex tree.
Update
You actually don't need to extend grouping nodes or go down the tree, because of the event on_visibility_changed already triggers all the deactivatable nodes. So I shortend the code above accordingly.
Update2
I added is_visible_in_tree() as this also considers parent visibility. This helped in cases where visible alone somehow did not work.
Update 3
Following updated script also lets you keep some processing eg. you just want to disable input processing but keep processing in the background.
# mask for activation eg. (A_PROCESS | A_INPUT)
const A_PROCESS = 0x1
const A_PHYSICS = 0x2
const A_INPUT = 0x4
const A_UNHANDLED_INPUT = 0x8
const A_UNHANDLED_KEY_INPUT = 0x10
const A_ALL = A_PROCESS | A_PHYSICS | A_INPUT | A_UNHANDLED_INPUT | A_UNHANDLED_KEY_INPUT
var _mask = A_ALL
var _keep = 0x00 # Handlers that should NOT be deactivated on hide(), they keep processing
# to override call super(=dot) method: ._ready()
func _ready():
_deactivate() # Default: ALL OFF. Let parent activate because otherwise children already process input
connect("visibility_changed", self, "on_visibility_changed")
func set_mask(mask):
_mask = mask
# Set the handlers that should not be deactivated
func set_keep_on_deactivate(keep):
_keep = keep
func _deactivate():
# hide() -> will trigger on_visibility_changed which will call this method
var mask = A_ALL & ~_keep
if(mask & A_PROCESS): set_process(false)
if(mask & A_PHYSICS): set_physics_process(false)
if(mask & A_INPUT): set_process_input(false)
if(mask & A_UNHANDLED_INPUT): set_process_unhandled_input(false)
if(mask & A_UNHANDLED_KEY_INPUT): set_process_unhandled_key_input(false)
# print("%s deactivated, mask: %s, is_processing_unhandled_input %s" % [name, _keep, is_processing_unhandled_input()])
func _activate():
# show() -> will trigger on_visibility_changed which will call this method
if(_mask & A_PROCESS): set_process(true)
if(_mask & A_PHYSICS): set_physics_process(true)
if(_mask & A_INPUT): set_process_input(true)
if(_mask & A_UNHANDLED_INPUT): set_process_unhandled_input(true)
if(_mask & A_UNHANDLED_KEY_INPUT): set_process_unhandled_key_input(true)
# print("%s activated, mask: %s, is_processing_unhandled_input %s" % [name, _keep, is_processing_unhandled_input()])
func on_visibility_changed():
if(is_visible_in_tree()): # This does also consider inherited visible
_activate()
else:
_deactivate()
Thanks for sharing that code, @Skyfish1.
In my case I have a Control that handles input and that Control is a child of a Popup. I noticed that the visible property of the Control was always true in on_visibility_changed, so I had to use is_visible_in_tree() instead, as that would return the effective visibility of the Control:
extends Control
func _ready():
if (!visible):
_disable_input()
connect("visibility_changed", self, "on_visibility_changed")
func _disable_input():
set_process_input(false)
func _enable_input():
set_process_input(true)
func on_visibility_changed():
if (is_visible_in_tree()):
_enable_input()
else:
_disable_input()
While browsing the docs I also noticed this:
https://docs.godotengine.org/en/3.1/classes/class_visibilityenabler2d.html
It doesn't have any way to disable input though, so I guess it's useless here?
@mitchcurtis I noticed the same thing. I updated my code above. And yes visibilityenabler2d does not help in my case. I have sprites that can be clicked that live on different maps which zoom on mouse wheel and drag on mouse move when clicked. I don't want them to process input when hidden.
Most helpful comment
This isn't an issue. If a node is inside the scene tree it should be processed whether it's visible or not. If you want it to stop processing while hidden that should be up to you to code. You can set it to stop handling inputs by setting
set_process_inputsorset_process_unhandled_inputswhen the visibility changes.