I have problems with making in-game notebook that shows info of other characters

Godot Version

4.3

Question

Hello there!
I am pretty new to GDScript, but I’m not THAT bad. Even tho, I have a problem I can’t resolve.
As in title - I want to do such notebook. It’s important to be done with something that can change midgame (because we will learn about characters). I’m trying to do basic one first.

First - here’s my notebook node + script (in Postacie)

extends Control

#Dane postaci
var characters = [
	{"name": "Test 1", "data": "Data 1", "splash": "res://assets/characters_sprites/test.png", "info": "InfoText 1"},
	{"name": "Test 2", "data": "Data 2", "splash": "res://assets/characters_sprites/test.png", "info": "InfoText 2"},
	{"name": "Test 3", "data": "Data 3", "splash": "res://assets/characters_sprites/test.png", "info": "InfoText 3"},
]

# Załaduj CharacterUI
var CharacterUI = preload("res://scenes/gui/Notebook/CharacterNotebookUI.tscn")

# Liczba postaci naraz na stronie
var characters_per_page = 2
var current_page = 0

# HBoxContainers
@onready var character_box1 = $CharacterBox1
@onready var character_box2 = $CharacterBox2
@onready var page_count_label = $"../../PageCountCharacter"

func _ready():
	update_page()
	
# Aktualizacja stron
func update_page():
	# Wyczyść obecne
	clear_children(character_box1)
	clear_children(character_box2)
	
	# Które postacie pokazać
	var start_index = current_page * characters_per_page
	var end_index = min(start_index + characters_per_page, characters.size())
	
	# Loop
	for i in range(start_index, end_index):
		var character_data = characters[i]
		print("Instantiating CharacterUI with data: %s" % character_data)
		
		var character_ui = CharacterUI.instantiate()
		character_ui.set_data(character_data)
	
		if i == start_index:
			character_box1.add_child(character_ui)
		else:
			character_box2.add_child(character_ui)
	
	if page_count_label:
		var total_pages = ceil(float(characters.size()) / characters_per_page)
		page_count_label.text = "Strona %d z %d" % [current_page + 1, total_pages]
	else:
		print("PageCountLabel not found")
		
func _on_left_pressed():
	if current_page > 0:
		current_page -= 1
		update_page()

func _on_right_pressed():
	if current_page < ceil(float(characters.size()) / characters_per_page) - 1:
		current_page += 1
		update_page() 

func clear_children(container: Container):
	for child in container.get_children():
		child.queue_free()

As you can see, I try do it that way it imports “pages” from different scene - which is CharacterNotebookUI. Here’s script (Node is very simmilar to the one on picture - Control + from character box1)

extends Control


var splash_rect : TextureRect
var name_label : Label
var data_label : Label
var info_label : Label

func _ready():

	var profile1 = get_node("CharacterBox1/Character1/Profile1") as Control
	var bio1 = get_node("CharacterBox1/Character1/Profile1/Bio1") as VBoxContainer
	var info1 = get_node("CharacterBox1/Character1/Info1") as VBoxContainer
	
	splash_rect = get_node("CharacterBox1/Character1/Profile1/TextureRect") as TextureRect
	name_label = get_node("CharacterBox1/Character1/Profile1/Bio1/Name1") as Label
	data_label = get_node("CharacterBox1/Character1/Profile1/Bio1/Data1") as Label
	info_label = get_node("CharacterBox1/Character1/Info1/InfoText1") as Label


	print("splash_rect: %s" % splash_rect)
	print("name_label: %s" % name_label)
	print("data_label: %s" % data_label)
	print("info_label: %s" % info_label)


func set_data(character_data: Dictionary):
	if splash_rect:
		splash_rect.texture = load(character_data.get("splash", "res://icon.svg"))
	if name_label:
		name_label.text = character_data.get("name", "")
	if data_label:
		data_label.text = character_data.get("data", "")
	if info_label:
		info_label.text = character_data.get("info", "")

What is my problem - game detects that after entering notebook there should be 3 characters shown, so there’s 2 pages. Yet those pages are empty.
I’m really struggling at this point and when I do something, some errors pops up. Is it even possible to make it that way?

Also, I wanted to use Dialogic variables at one point - let’s say that we have variable called character-name, which changes as we get to know them, so it kind of “unlocks” in our notebook.

Please, give me at least an idea what can I do, or if there’s a better way.

Yes this should be possible. Can you tell us what kind of erros pop up and what they say?

With this code - 0 errors. It’s just those pages in notebook that are empty. In second file i also did this:

extends Control

var splash_rect : TextureRect
var name_label : Label
var data_label : Label
var info_label : Label

func _ready():
    var profile1 = get_node("CharacterBox1/Character1/Profile1") as Control
    var bio1 = get_node("CharacterBox1/Character1/Profile1/Bio1") as VBoxContainer
    var info1 = get_node("CharacterBox1/Character1/Info1") as VBoxContainer
    
    splash_rect = get_node("CharacterBox1/Character1/Profile1/TextureRect") as TextureRect
    name_label = get_node("CharacterBox1/Character1/Profile1/Bio1/Name1") as Label
    data_label = get_node("CharacterBox1/Character1/Profile1/Bio1/Data1") as Label
    info_label = get_node("CharacterBox1/Character1/Info1/InfoText1") as Label

    print("Available children in CharacterUI:")
    for child in get_children():
        print(child.name) 

  
    print("Profile1: %s" % profile1)
    print("Bio1: %s" % bio1)
    print("Name1: %s" % name_label)
    print("Data1: %s" % data_label)
    print("InfoText1: %s" % info_label)

func set_data(character_data: Dictionary):
    print("Setting data: %s" % character_data)  

    if splash_rect:
        splash_rect.texture = load(character_data.get("splash", "res://default.png"))
    else:
        print("Error: splash_rect is null")

    if name_label:
        name_label.text = character_data.get("name", "")
    else:
        print("Error: name_label is null")

    if data_label:
        data_label.text = character_data.get("data", "")
    else:
        print("Error: data_label is null")

    if info_label:
        info_label.text = character_data.get("info", "")
    else:
        print("Error: info_label is null")

Then, it worked for a second but showed only 1 position instead of 3 and got error at notebook script in line " character_ui.set_data(character_data)"
error is: Attempt to call function set_data in base previously freed on a null instance

How often is the Notebook updated?
Is it some kind of Biography update that adds more backstory to the characters as you progress through the game or is it some kind of status screen that could have updated stat values that could change every battle?
I am thinking that you could create an Autoload for the Notebook that you can globally access throughout the game

What is my problem - game detects that after entering notebook there should be 3 characters shown, so there’s 2 pages. Yet those pages are empty.
I’m really struggling at this point and when I do something, some errors pops up. Is it even possible to make it that way?

It would be best if you could add a breakpoint in your Notebook’s _ready() method. Either the notebook is empty because things are overlayering your entries or the nodes cannot be found.

For the start, I would check the Remote tab during a running game and see the actual SceneTree. does it look as expected or are very important nodes (such as CharacterNotebookUI) missing?

2 Likes

Is it some kind of Biography update that adds more backstory to the characters as you progress through the game

Yes, it is.

For the start, I would check the Remote tab during a running game and see the actual SceneTree.

I think i found a solution. I simply moved part of my CharacterNotebookUI script. You see, i was using variables in _ready() hoping it would update whole time. I simply moved them to set_data() and boom, it’s working! Not in the best way (need to correct positioning), but yeah!! I saw this doing remote tab, i didn’t even knew that was a thing.
Thank you so much!!!

1 Like

I have a next problem - i deleted var character_data and put it in different script. It’s place when I update characters info. Here’s script

extends Control

var character_data = [
	{"id": "character1", "name": "Test1", "data": "Data1", "splash": "res://assets/characters_sprites/test.png", "info": "InfoText 1"},
	{"id": "character2", "name": "Test 2", "data": "Data 2", "splash": "res://assets/characters_sprites/test.png", "info": "InfoText 2"},
	{"id": "character3", "name": "Test 3", "data": "Data 3", "splash": "res://assets/characters_sprites/test.png", "info": "InfoText 3"},
]

func _ready():
	Dialogic.signal_event.connect(DialogicSignal)
		
func DialogicSignal(signal_name:String, new_name: String =""):
	if signal_name == "Character1NameChanged":
		_on_Character1NameChanged(new_name)
		
func _on_Character1NameChanged(new_name: String):
	var found = false
	for character in character_data:
		if character["id"] == "character1":
			character["name"] = "Silly"
			print("Character1 name updated to: ", character["name"])
			found = true
			break
	if not found:
		print("Character1 not found in character_data")

In debug I get message that name was changed, but when I look into menu, it doesn’t. Any ideas please?

Can you show your menu?

menu? you mean, my node? or what

Yes the place where this name doesnt get changed. when its changed you have to assign it in this menu again

I still don’t think I get it, so I’ll just send what I have.
This is main notebook:
image
Script for “Postacie” (the one in “Notebook” is just for opening by pressing J)

extends Control

#Dane postaci
var character_info = preload("res://scripts/Characters/Characters_Info.gd").new()
var characters = character_info.character_data

# Załaduj CharacterUI
var CharacterUI = preload("res://scenes/gui/Notebook/CharacterNotebookUI.tscn")

# Liczba postaci naraz na stronie
var characters_per_page = 2
var current_page = 0

# HBoxContainers
@onready var character_box1 = $CharacterBox1
@onready var character_box2 = $CharacterBox2
@onready var page_count_label = $"../../PageCountCharacter"

func _ready():
	update_page()
	
# Aktualizacja stron
func update_page():
	# Wyczyść obecne
	clear_children(character_box1)
	clear_children(character_box2)
	
	# Które postacie pokazać
	var start_index = current_page * characters_per_page
	var end_index = min(start_index + characters_per_page, characters.size())
	
	# Loop
	for i in range(start_index, end_index):
		var character_data = characters[i]
		print("Instantiating CharacterUI with data: %s" % character_data)
		
		var character_ui = CharacterUI.instantiate()
		character_ui.set_data(character_data)
	
		if i == start_index:
			character_box1.add_child(character_ui)
		else:
			character_box2.add_child(character_ui)
	
	if page_count_label:
		var total_pages = ceil(float(characters.size()) / characters_per_page)
		page_count_label.text = "Strona %d z %d" % [current_page + 1, total_pages]
	else:
		print("PageCountLabel not found")
		
func _on_left_pressed():
	if current_page > 0:
		current_page -= 1
		update_page()

func _on_right_pressed():
	if current_page < ceil(float(characters.size()) / characters_per_page) - 1:
		current_page += 1
		update_page() 

func clear_children(container: Container):
	for child in container.get_children():
		child.queue_free()

This is node and script for CharacterNotebookUI - it is responsible for “generating” pages:
image

extends Control

var character_info = preload("res://scripts/Characters/Characters_Info.gd").new()
var character_data = character_info.character_data

var splash_rect : TextureRect
var name_label : Label
var data_label : Label
var info_label : Label

func _ready():

	var profile1 = get_node("CharacterBox1/Character1/Profile1") as Control
	var bio1 = get_node("CharacterBox1/Character1/Profile1/Bio1") as VBoxContainer
	var info1 = get_node("CharacterBox1/Character1/Info1") as VBoxContainer

func set_data(character_data: Dictionary):

	splash_rect = get_node("CharacterBox1/Character1/Profile1/TextureRect") as TextureRect
	name_label = get_node("CharacterBox1/Character1/Profile1/Bio1/Name1") as Label
	data_label = get_node("CharacterBox1/Character1/Profile1/Bio1/Data1") as Label
	info_label = get_node("CharacterBox1/Character1/Info1/InfoText1") as Label


	print("splash_rect: %s" % splash_rect)
	print("name_label: %s" % name_label)
	print("data_label: %s" % data_label)
	print("info_label: %s" % info_label)

	if splash_rect:
		splash_rect.texture = load(character_data.get("splash", ""))
	if name_label:
		name_label.text = character_data.get("name", "")
	if data_label:
		data_label.text = character_data.get("data", "")
	if info_label:
		info_label.text = character_data.get("info", "")
		
		

This is my CharacterInfo script, it already has an example script for changing value, i get good print in debug so i assume it works:

extends Control

var character_data = [
	{"id": "character1", "name": "Test1", "data": "Data1", "splash": "res://assets/characters_sprites/test.png", "info": "InfoText 1"},
	{"id": "character2", "name": "Test 2", "data": "Data 2", "splash": "res://assets/characters_sprites/test.png", "info": "InfoText 2"},
	{"id": "character3", "name": "Test 3", "data": "Data 3", "splash": "res://assets/characters_sprites/test.png", "info": "InfoText 3"},
]

func _ready():
	Dialogic.signal_event.connect(DialogicSignal)
		
func DialogicSignal(signal_name:String, new_name: String =""):
	if signal_name == "Character1NameChanged":
		_on_Character1NameChanged(new_name)
		
func _on_Character1NameChanged(new_name: String):
	for i in range(character_data.size()):
		if character_data[i]["id"] == "character1":
			character_data[i]["name"] = "Silly"  # Update the name in character_data
			print("Character1 name updated to: ", character_data[i]["name"])
			break

Yes it should change the value inside the characterinfo-script, but you still have to update it in the CharacterNotebookui. You can emit a signal from characterinfo when a name is changed and then connect this signal to characternotebookui and update the name (for example by calling set_data again)

I made this “improvements” - still, it doesn’t work. Please help :sob::sob: Im starting to being depressed because of ingame notebook.
CharacterNotebookUI:

extends Control

var character_info = preload("res://scripts/Characters/Characters_Info.gd").new()
var character_data = character_info.character_data

var splash_rect : TextureRect
var name_label : Label
var data_label : Label
var info_label : Label

func _ready():

	var profile1 = get_node("CharacterBox1/Character1/Profile1") as Control
	var bio1 = get_node("CharacterBox1/Character1/Profile1/Bio1") as VBoxContainer
	var info1 = get_node("CharacterBox1/Character1/Info1") as VBoxContainer

	var script1 = get_node("res://scripts/Characters/Characters_Info.gd") as Control  
	if script1 == null:
		print("Error: Script1 node not found.")
	else:
		print("Script1 node found.")
		script1.connect("character_name_changed", Callable(self, "_on_character_name_changed"))
	
func _on_character_name_changed():
	set_data(character_data)

func set_data(character_data: Dictionary):

	splash_rect = get_node("CharacterBox1/Character1/Profile1/TextureRect") as TextureRect
	name_label = get_node("CharacterBox1/Character1/Profile1/Bio1/Name1") as Label
	data_label = get_node("CharacterBox1/Character1/Profile1/Bio1/Data1") as Label
	info_label = get_node("CharacterBox1/Character1/Info1/InfoText1") as Label


	if splash_rect:
		splash_rect.texture = load(character_data.get("splash", ""))
	if name_label:
		name_label.text = character_data.get("name", "")
	if data_label:
		data_label.text = character_data.get("data", "")
	if info_label:
		info_label.text = character_data.get("info", "")

Characters_Info:

extends Control

#Sygnał update
signal character_name_changed(new_name: String)

var character_data = [
	{"id": "character1", "name": "Test1", "data": "Data1", "splash": "res://assets/characters_sprites/test.png", "info": "InfoText 1"},
	{"id": "character2", "name": "Test 2", "data": "Data 2", "splash": "res://assets/characters_sprites/test.png", "info": "InfoText 2"},
	{"id": "character3", "name": "Test 3", "data": "Data 3", "splash": "res://assets/characters_sprites/test.png", "info": "InfoText 3"},
]

func _ready():
	Dialogic.signal_event.connect(DialogicSignal)
		
func DialogicSignal(signal_name: String, new_name: String = ""):
	if signal_name == "Character1NameChanged":
		_on_Character1NameChanged(new_name)
		
func _on_Character1NameChanged(new_name: String):
	for i in range(character_data.size()):
		if character_data[i]["id"] == "character1":
			character_data[i]["name"] = "Silly" 
			print("Character1 name updated to: ", character_data[i]["name"])
			emit_signal("character_name_changed", character_data[i]["name"])
			break

Change your notebookui-script:

extends Control

@onready var splash_rect : TextureRect = $"CharacterBox1/Character1/Profile1/TextureRect"
@onready var name_label : Label = $"CharacterBox1/Character1/Profile1/Bio1/Name1"
@onready var data_label : Label = $"CharacterBox1/Character1/Profile1/Bio1/Data1"
@onready var info_label : Label = $"CharacterBox1/Character1/Info1/InfoText1"


var character_id

func set_data(character_data: Dictionary):
	splash_rect.texture = load(character_data.get("splash", ""))
	name_label.text = character_data.get("name", "")
	data_label.text = character_data.get("data", "")
	info_label.text = character_data.get("info", "")

CharactersInfo:

extends Control

#Sygnał update
signal data_changed(id: String)

var character_data = [
	{"id": "character1", "name": "Test1", "data": "Data1", "splash": "res://assets/characters_sprites/test.png", "info": "InfoText 1"},
	{"id": "character2", "name": "Test 2", "data": "Data 2", "splash": "res://assets/characters_sprites/test.png", "info": "InfoText 2"},
	{"id": "character3", "name": "Test 3", "data": "Data 3", "splash": "res://assets/characters_sprites/test.png", "info": "InfoText 3"},
]

func _ready():
	Dialogic.signal_event.connect(DialogicSignal)
		
func DialogicSignal(signal_name: String, new_name: String = ""):
	if signal_name == "Character1NameChanged":
		_on_Character1NameChanged(new_name)
		
func _on_Character1NameChanged(new_name: String):
	for i in range(character_data.size()):
		if character_data[i]["id"] == "character1":
			character_data[i]["name"] = "Silly" # change "Silly" to new_name
			print("Character1 name updated to: ", character_data[i]["name"])
			data_changed.emit(character_data[i]["id"])
			break

The main notebook:

extends Control

#Dane postaci
var character_info = preload("res://scripts/Characters/Characters_Info.gd").new()
var characters = character_info.character_data

# Załaduj CharacterUI
var CharacterUI = preload("res://scenes/gui/Notebook/CharacterNotebookUI.tscn")

# Liczba postaci naraz na stronie
var characters_per_page = 2
var current_page = 0

# HBoxContainers
@onready var character_box1 = $CharacterBox1
@onready var character_box2 = $CharacterBox2
@onready var page_count_label = $"../../PageCountCharacter"

func _ready():
	add_child(character_info)
	character_info.data_changed.connect(_on_data_changed)
	update_page()

func _on_data_changed(id: String):
	characters = character_info.character_data
	# if you want you can add an if statement and only update_page when the id is currently displayed
	update_page()
	
# Aktualizacja stron
func update_page():
	# Wyczyść obecne
	clear_children(character_box1)
	clear_children(character_box2)
	
	# Które postacie pokazać
	var start_index = current_page * characters_per_page
	var end_index = min(start_index + characters_per_page, characters.size())
	# Loop
	for i in range(start_index, end_index):
		var character_data = characters[i]
		print("Instantiating CharacterUI with data: %s" % character_data)
		
		var character_ui = CharacterUI.instantiate()
		character_ui.set_data(character_data)
	
		if i == start_index:
			character_box1.add_child(character_ui)
		else:
			character_box2.add_child(character_ui)
	
	if page_count_label:
		var total_pages = ceil(float(characters.size()) / characters_per_page)
		page_count_label.text = "Strona %d z %d" % [current_page + 1, total_pages]
	else:
		print("PageCountLabel not found")
		
func _on_left_pressed():
	if current_page > 0:
		current_page -= 1
		update_page()

func _on_right_pressed():
	if current_page < ceil(float(characters.size()) / characters_per_page) - 1:
		current_page += 1
		update_page() 

func clear_children(container: Container):
	for child in container.get_children():
		child.queue_free()

Try this code out.
A little explanation:
Your code had the character-data all separate for every characterui which is very redundant and problematic. Here only the main-notebook has the characterdata and updates the characterui accordingly when the data is getting updated

1 Like

Thanks for the code! Unfortunately, set_data is now crashing game.
I get error “Invalid assigment of property or key…”.

BUUUUT when i put my code in characternotebookui IT WORKS!!! OMG THANK YOU SO MUCH
youre literal lifesaver

1 Like

if you want it to work as before you have to call set_data after you add it as child. I forgot to change that:

		var character_ui = CharacterUI.instantiate()
	
		if i == start_index:
			character_box1.add_child(character_ui)
		else:
			character_box2.add_child(character_ui)

		character_ui.set_data(character_data)
1 Like

another direction that might help.

2 Likes

Thanks! I’ll check it out, maybe I’ll learn something useful

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.