Hover animation works in item scene, but not when item is instanced in mainscene

I’m using Godot 4.4.1.stable and have an issue with UI.
I have three scenes:

  • fish_inventory.tscn — this is the inventory background with a frame and some containers (for items).
extends Control
var is_open := false
var tween: Tween

@onready var chat_ui = null
var input_cooldown := 0.0

func _ready():
visible = false
modulate.a = 0.0
scale = Vector2(1.0, 1.0)
await get_tree().process_frame
chat_ui = get_tree().get_first_node_in_group("chat_ui")
if not chat_ui:
chat_ui = get_tree().get_root().find_child("ChatUI", true, false)

func _process(delta):
if input_cooldown > 0:
input_cooldown -= delta

func _input(event):
if event.is_action_pressed("fish_inventory") and input_cooldown <= 0:
input_cooldown = 0.2
toggle_inventory()
get_viewport().set_input_as_handled()

func toggle_inventory():
var player = get_tree().get_first_node_in_group("player")
if player and player.get("is_showing_fish") and player.is_showing_fish:
return

if player and player.get("controls_locked") and player.controls_locked and not is_open:
return

if is_open:
close_inventory()
else:
open_inventory()

func open_inventory():
if is_open:
return

is_open = true
visible = true

var player = get_tree().get_first_node_in_group("player")
if player:
player.controls_locked = true

if player.has_method("stop_movement"):
player.stop_movement()

if player.get("velocity") != null:
player.velocity = Vector2.ZERO

var idle_animation = "idle_down"
if player.get("last_direction") != null:
var last_dir = player.last_direction
if last_dir.y < 0:
idle_animation = "idle_up"
elif last_dir.y > 0:
idle_animation = "idle_down"
elif last_dir.x < 0:
idle_animation = "idle_left"
elif last_dir.x > 0:
idle_animation = "idle_right"

if player.has_node("AnimatedSprite2D"):
var sprite = player.get_node("AnimatedSprite2D")
if sprite.has_method("play"):
sprite.play(idle_animation)
elif player.get("animated_sprite"):
if player.animated_sprite.has_method("play"):
player.animated_sprite.play(idle_animation)
elif player.has_method("set_idle_animation"):
player.set_idle_animation()

for child in player.get_children():
if child is AnimatedSprite2D:
child.play(idle_animation)
break

if player.get("is_moving") != null:
player.is_moving = false

if chat_ui:
chat_ui.visible = false

if tween:
tween.kill()

tween = create_tween()
tween.tween_property(self, "modulate:a", 1.0, 0.3)

func close_inventory():
if not is_open:
return

is_open = false

var player = get_tree().get_first_node_in_group("player")
if player:
player.controls_locked = false

if chat_ui:
chat_ui.visible = true

if tween:
tween.kill()

tween = create_tween()
tween.tween_property(self, "modulate:a", 0.0, 0.2)
await tween.finished

visible = false

func _on_fish_item_selected(fish_data):
close_inventory()

var player = get_tree().get_first_node_in_group("player")
if player and player.has_method("show_fish_in_hands"):
player.show_fish_in_hands(fish_data)

func add_fish_item(fish_texture: Texture2D, fish_name: String, fish_size: float):
var fish_item_scene = preload("res://scenes/fish_inventory_item.tscn")
var fish_item = fish_item_scene.instantiate()

var container = $ScrollContainer/VBoxContainer
container.add_child(fish_item)

await get_tree().process_frame

fish_item.custom_minimum_size = Vector2(64, 64)
fish_item.size_flags_horizontal = Control.SIZE_EXPAND_FILL
fish_item.size_flags_vertical = Control.SIZE_EXPAND_FILL

fish_item.setup(fish_texture, fish_name, fish_size)

if not fish_item.fish_selected.is_connected(_on_fish_item_selected):
fish_item.fish_selected.connect(_on_fish_item_selected)

fish_item.mouse_filter = Control.MOUSE_FILTER_STOP

if fish_item.has_node("Background"):
var bg = fish_item.get_node("Background")
bg.mouse_filter = Control.MOUSE_FILTER_STOP
  • fish_inventory_item.tscn — this is a single inventory item: just a background, an icon, and some text. It has hover animation using mouse_entered/mouse_exited signals, and it works perfectly if I run this scene alone and the animation are there.
extends Control

@onready var background = $Background
@onready var fish_icon = $FishIcon
@onready var fish_label = $FishLabel
@onready var size_label = $SizeLabel

const FONT_PATH = "res://assets/sprites/GUI/Peaberry-Bold.ttf"
const LABEL_COLOR = Color(0.91, 0.75, 0.48)
const SIZE_LABEL_COLOR = Color(0.8, 0.8, 0.8)

var fish_data: Dictionary = {}
var original_scale: Vector2
var is_hovered: bool = false
var tween: Tween

signal fish_selected(fish_data)

func _ready():
var font = preload(FONT_PATH)
fish_label.add_theme_font_override("font", font)
fish_label.add_theme_color_override("font_color", LABEL_COLOR)
fish_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
if size_label:
size_label.add_theme_font_override("font", font)
size_label.add_theme_color_override("font_color", SIZE_LABEL_COLOR)
size_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
original_scale = scale
mouse_filter = Control.MOUSE_FILTER_STOP
if not mouse_entered.is_connected(_on_mouse_entered):
mouse_entered.connect(_on_mouse_entered)
if not mouse_exited.is_connected(_on_mouse_exited):
mouse_exited.connect(_on_mouse_exited)
if not gui_input.is_connected(_on_gui_input):
gui_input.connect(_on_gui_input)
size = Vector2(64, 64)
if background:
background.custom_minimum_size = Vector2(64, 64)

func setup(fish_texture: Texture2D, fish_name: String, fish_size: float):
fish_data = {
"texture": fish_texture,
"name": fish_name,
"size": fish_size
}
fish_icon.texture = fish_texture
var size_category = get_size_category(fish_name, fish_size)
var fish_text = "%s (%.1f cm)" % [fish_name.capitalize(), fish_size]
fish_label.text = fish_text
var size_category_text = size_category.capitalize()
if size_label:
size_label.text = size_category_text
else:
fish_label.text = "%s\n%s" % [fish_text, size_category_text]
fish_data["size_category"] = size_category

func get_size_category(fish_name: String, size: float) -> String:
return FishDatabase.get_size_category(fish_name, size)

func _on_mouse_entered():
is_hovered = true
if tween:
tween.kill()
tween = create_tween()
tween.parallel().tween_property(background, "modulate", Color(1.15, 1.15, 1.15), 0.15)
tween.parallel().tween_property(background, "scale", Vector2(1.15, 1.15), 0.15)

func _on_mouse_exited():
is_hovered = false
if tween:
tween.kill()
tween = create_tween()
tween.parallel().tween_property(background, "modulate", Color.WHITE, 0.15)
tween.parallel().tween_property(background, "scale", Vector2(1, 1), 0.15)

func _on_gui_input(event):
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
var player = get_tree().get_first_node_in_group("player")
if player and player.get("is_showing_fish") and player.is_showing_fish:
return
fish_selected.emit(fish_data)
if tween:
tween.kill()
tween = create_tween()
tween.tween_property(background, "scale", Vector2(1.25, 1.25), 0.09).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
tween.tween_property(background, "scale", Vector2(1.15, 1.15), 0.09).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_IN)
await tween.finished
if is_hovered:
if tween:
tween.kill()
tween = create_tween()
tween.parallel().tween_property(background, "scale", Vector2(1.15, 1.15), 0.1)
else:
if tween:
tween.kill()
tween = create_tween()
tween.parallel().tween_property(background, "scale", Vector2(1, 1), 0.1)
  • main.tscn — this is my main scene. Here, I simply add the fish_inventory.tscn scene as a child.

And this is the code that adds fish in the items:

func _on_fish_caught(fish_data):
fish_sprite.texture = fish_data.texture
fish_sprite.visible = true
fish_sprite.rotation_degrees = -45
var scale_factor = fish_data.size / FISH_DISPLAY_BASE_CM
fish_sprite.scale = Vector2.ONE * scale_factor
is_showing_fish = true
controls_locked = true
show_fish_catch_view()
play_idle_animation()
fish_sprite.start_jumping()

var fish_inventory = null
for node in get_tree().get_root().find_children("*", "", true, false):
if node.get_script() and node.get_script().get_path().ends_with("fish_inventory.gd"):
fish_inventory = node
break

if fish_inventory:
fish_inventory.add_fish_item(fish_data.texture, fish_data.name, fish_data.size)
print("The fish was added into the inventory through the code")
else:
print("FishInventory not found")
  • In fish_inventory_item.tscn, mouse hover/tween animation works fine: mouse_entered, mouse_exited, and gui_input signals are triggered, and the background animates on hover.
  • When I instance this item into my main scene (via code), the item is visible, but the animation on hover does NOT work. The mouse signals do not fire at all.
  • Mouse filter is set to STOP for Control, which is root for fish_inventory_item.tscn. ScrollContainer and its parents are set to IGNORE in the fish_inventory.tscn.
  • I need the animation and the button to work in the main scene.

Thanks for your help in advance!

Something in your scene is getting the hover instead of your item.

  1. Run your game.
  2. Go to the Misc tab in the Debugger.
  3. Click the button you think should be getting the hover.
  4. The debugger will tell you what object is actually getting the click. This is the item that’s getting the hover signal instead of your item.

If you have more than one CanvasLayer in your fully formed UI, that is likely the culprit. Hiding it or changing the z-order will make the problem go away.

For example, I had a pause menu on a CanvasLayer, and the game’s UI on a CanvasLayer. Whenever I paused the game, all the keyboard controls on the menu worked, but the mouse wouldn’t click anything. Turned out the Game’s UI was on top. So, I moved the pause menu’s UI to the bottom of the tree, and it started working again. Whenever it was hidden, it didn’t interfere with the game.

2 Likes