UI not showing in remote tree or in game, but showing in console with print_tree()

Godot Version

4.4

Question

UI Items not showing on screen or in Remote tree, even though they appear in console using print_tree_pretty()

Context

I am creating a general menu made of two pages (left and right) that are emptied and filled when navigating through them. There are four sub-menus: Contacts (list of NPC you’ve met), Journal (list of current active quests), Observation (list of things you…well, observed) and Inventory (list of items you’ve picked up).

In terms of code, I have a simple Class PageComponent, that inherits Control, and that has a fill_page() and empty_page() method.

class_name PageComponent
extends Control

@export var left_page : VBoxContainer
@export var right_page : VBoxContainer

func fill_page():
	pass
	
func empty_page():
	for page in [left_page, right_page]:
		for child in page.get_children():
			child.queue_free()

Each page (Journal, Inventory etc) inherits that class and overwrites fill_page() with whatever they do for filling the pages.

Here is the code for my Observation page

class_name ObservationPageComponent
extends PageComponent

const ITEMS_PER_PAGE : int = 6
const ITEMS_PER_COLUMN : int = 3
var current_page : int = 0

func fill_page():
	#Before filling the page we empty it
	empty_page()
	
	var start_index = current_page * ITEMS_PER_PAGE
	var end_index = min(start_index + ITEMS_PER_PAGE, InteractableSingleton.observables.size())

	for i in range (start_index, end_index):
		var item = InteractableSingleton.observables[i]

		#1-Generate an InventoryItem
		var item_scene = preload("res://Scenes/UI/InventoryItem.tscn")
		var new_inventory_item = item_scene.instantiate()
		for child in new_inventory_item.get_children():
			if child is TextureRect:
				var texture_path = AppSettingsSingleton.images_folder_path + "UI/Observables/" + item.interactable_id + ".png"
				child.texture = load(texture_path)
			elif child is RichTextLabel:
				child.text = item.interactable_data.description

		#Select page to be displayed on
		var local_index = i - start_index
		if local_index < ITEMS_PER_COLUMN:
			left_page.add_child(new_inventory_item)
		else:
			right_page.add_child(new_inventory_item)

And visually it looks like this:

Here you can see, three items that correspond to the three observable things I’ve interacted with in game.

Issue T_T

When switching to the Inventory or Journal submenu, the items that they should contain do not appear in game.

What works

A - I have a radial inventory system that displays the SAME items from the SAME array (stored in a singleton for ease of access) and when I pick up one or more item, I see these items, and when I click on them to select them, I print their properties properly in the console. Same for the quests, I can see them appear in the console thanks to the quest manager, so I know the issue is not in the objects I’m trying to display. The data is okay

B - More importantly, the Observation menu WORKS 100%, and Inventory is literally the same code using the same InventoryItem scene, but just with a different pool of items, which baffles me to no end.

Some more code samples

This is what the InventoryPageComponent looks like (identical to the Observation page)

class_name InventoryPageComponent
extends PageComponent

const ITEMS_PER_PAGE : int = 6
const ITEMS_PER_COLUMN : int = 3
var current_page : int = 0

func fill_page():
	#Before filling the page we empty it
	empty_page()
	
	var start_index = current_page * ITEMS_PER_PAGE
	var end_index = min(start_index + ITEMS_PER_PAGE, InteractableSingleton.pickables.size())
	
	for i in range (start_index, end_index):
		var item = InteractableSingleton.pickables[i]

		#1-Generate an InventoryItem
		var item_scene = preload("res://Scenes/UI/InventoryItem.tscn")
		var new_inventory_item = item_scene.instantiate()
		for child in new_inventory_item.get_children():
			if child is TextureRect:
				var texture_path = AppSettingsSingleton.images_folder_path + "UI/Pickables/" + item.interactable_id + ".png"
				child.texture = load(texture_path)
			elif child is RichTextLabel:
				child.text = item.interactable_data.description
		#Select page to be displayed on
		var local_index = i - start_index
		if local_index < ITEMS_PER_COLUMN:
			left_page.add_child(new_inventory_item)
		else:
			right_page.add_child(new_inventory_item)

And this is what the print_tree_pretty() shows me in the console:

       ┠╴LeftPage
       ┃  ┠╴InventoryItem
       ┃  ┃  ┠╴ItemPhoto
       ┃  ┃  ┖╴ItemText
       ┃  ┃     ┖╴@VScrollBar@7
       ┃  ┖╴@Control@9
       ┃     ┠╴ItemPhoto
       ┃     ┖╴ItemText
       ┃        ┖╴@VScrollBar@8

And same for the Journal btw:

class_name JournalPageComponent
extends PageComponent

...

#region Methods
func fill_page():
	#Before filling the page we empty it
	empty_page()
	
	if !journal_active_quests.is_empty():
		for quest in journal_active_quests:
			fill_quest_header(quest)
			#generate_active_steps(quest)

...

func fill_quest_header(quest : QuestData):
	#1-Generate an InventoryItem
	var header_scene = preload("res://Scenes/UI/JournalHeader.tscn")
	var header_item : Control = header_scene.instantiate()
	for child in header_item.get_children():
			if child is TextureRect:
				var texture_path = AppSettingsSingleton.images_folder_path + "UI/Quests/" + quest.quest_id + ".png"
				child.texture = load(texture_path)
			elif child is VBoxContainer:
				for item in child.get_children():
					if item is Label:
						if item.name.to_lower() == 'questtitlelabel': 
							item.text = quest.quest_title
						if item.name.to_lower() == 'involvedlabel':
							item.text = quest.quest_characters
					if item is RichTextLabel:
						item.text = quest.quest_description
	left_page.add_child(header_item)
...

And this is what print_tree_pretty() shows…

┠╴LeftPage
       ┃  ┖╴JournalHeader
       ┃     ┠╴QuestPhoto
       ┃     ┖╴TitleBox
       ┃        ┠╴QuestTitleLabel
       ┃        ┠╴InvolvedLabel
       ┃        ┖╴InvolvedListLabel
       ┃           ┖╴@VScrollBar@6

What I tried:

-I tried setting up a custom_minimum_size for each main Controls
-I tried to force visible to true, even though they are all in visible = true in the editor

Conclusion

This is drinving me nuts. I can see that the different menus instantiate things right, and that they add the items to the proper page. But I cannot, for the love of me, understand what is happening.
Why is this not showing on screen, better yet in the Remote tab?? Like it’s in the tree, why isn’t it shown on the Remote tree???
And why does my code work for Observation but not Inventory, when the two are literally the same thing with the data changed (and the data exists é_è)

If someone could helping me out on this. UI in Godot is very new to me, so maybe there is something I don’t get about it.

I don’t see anything wrong with the code you posted but Node.print_tree_pretty() does not need to have the node inside the SceneTree to work. If you can’t see them in the remote tab then the nodes weren’t added.

EDIT: Yep @sancho2 sorry :sweat_smile: I’ve updated it.

1 Like

(I am sure mrcdk meant “if you can’t see them in the remote tab”)
Either the nodes go missing after you print the tree or add_child isn’t gotten to or isn’t adding anything.
Where are you putting print_tree?
Double check your start_index/end_index. end_index is the product of a min() command which may be 0 and not run the for loop.

1 Like

Thanks very much @mrcdk and @sancho2 for taking the time to answer and for pointing something I didn’t get in the first place. Which is how print_tree(_pretty) actually works.

I actually found the issue in another node I didn’t bother sharing. I was actually emptying the pages after setting them up. In my script for the NotebookManager, I have that method:

var pages : Array[PageComponent] = []
var current_game_state : String
var current_page_index : int

func _ready() -> void:
	pages.append(contact_page)
	pages.append(inventory_page)
	pages.append(journal_page)
	pages.append(observation_page)
	current_page_index = 2

# a lot more code

func set_page() -> void:
	for i in range(pages.size()):
		if i == current_page_index:
			pages[i].fill_page()
			pages[i].show()
		else:
			pages[i].empty_page()
			pages[i].hide()
	notebook_canvas.visible = true
	print_tree_pretty()

So for each index of the array containing the Journal/Inventory etc, if it’s the index corresponding to what we want to display, we fill it in, but then go to the next index…and you EMPTY_PAGE (idiot). What that means is for Contacts (index 0), Inventory (index 1), Journal (index 2) you set them up, then empty them immediately while passing to the next index. Observation being the last index, it doesn’t get the empty_page() treatment…that’s why it’s the only one working!