How to override _set() or achieve a similar result in Godot 4?

Godot Version

4.2.1

Question

I have an Autoload node that needs to notify others whenever one of its properties changes. It has a lot of internal properties, and I would like to avoid writing an identical setter for each property.

I tried the following code in the Autoload.

extends Node

signal property_changed(property: String)

func _set(property: StringName, value: Variant) -> bool:
	if !(property in self):
		return false
	else:
		property_changed.emit(str(property))
		return true

However, the setter override never seems to actually run when a property changes. The bottom comment on this issue thread says

As of 4.1.2-stable and 4.2beta4, _set can no longer be used to override parent properties. You can use it to add side effects to setting the property, but attempting to change the actual property value in set will not do anything.

They say that adding side effects using _set() is still possible, but I can’t seem to figure it out. Does anyone have any insight?

GDScript has similarities to C# with get/set. So you can use this with your characters, enemies and even UI when using Autoloads.

class_name Database

static var Hero_List = { "Adam": HeroBase.new(), "Eve": HeroBase.new() }
class_name HeroBase

var Name : String
var Description : String

var Maximum_Health : int
var Current_Health : int

var Attack : int
var Defense : int

class_name Character extends Node

@export var Actor_ID : String

var Current_Hero : HeroBase :
	get : return Database.Hero_List [Actor_ID]
	set(value) : Database.Hero_List [Actor_ID] = value

func Increase_Attack():
	Current_Hero.Attack += 10

To clarify, I am talking adding functionality to the built-in _set() for all properties. Not creating a setter for each separate property

1 Like

Not sure what your problem is. The get/set shown above accesses the entire class. The example function is only accessing one variable of that class.

I would like to extend the set (StringName property, Variant value) method in the base Object class, but this doesn’t seem to be possible. My structure is more like this:

extends Node
class_name DataBase

signal property_changed(property_name: String)

var property1
var property2
var property3
...
var property100

and I would like the database to notify some others when one of its properties has changed, also sending the name of the property. The properties track global progression, and actually have names like “second_chest_opened” etc.

Why aren’t you using Arrays or Dictionaries?

class_name Database

static var Hero_List = { "Adam": HeroBase.new(), "Eve": HeroBase.new() }
class_name HeroBase

var Attribute = { "Strength": int, "Dexterity": int }
class_name Character extends Node

@export var Actor_ID : String

var Current_Hero : HeroBase :
	get : return Database.Hero_List [Actor_ID]
	set(value) : Database.Hero_List [Actor_ID] = value

func Modify_Attribute(name, number):
	Current_Hero.Attribute[name] += number


Here’s an example of a chest within a scene.

class_name Database

static var Chest_List = {"Lv1_Chest1": false, "Lv1_Chest2": true}
class_name Chest extends Node

@export var Chest_ID : String

var Is_Chest_Open : bool :
	get : return Database.Chest_List[Chest_ID]
	set(value) : Database.Chest_List[Chest_ID] = value

func _ready():
	if Is_Chest_Open == true:
	# State = Opened Chest
	else:
	# State = Closed Chest

func Pickup():
	if Is_Chest_Open == false:
		Is_Chest_Open = true
		# Perform Animation and Add items
		# State = Closed Chest

When you want that chest to have more variables, turn that bool into a custom class that holds more variables or holds more dictionaries.

This is part of my metaprogression + dialog/story event triggering system. I can use a dictionary, but I was just trying to use object properties instead of string keys to get editors hints and avoid typos. Thank you for your suggestions. I will probably go to using a dictionary or custom class_name inside the autoload.

You could still use a public enum to avoid typos. Just create a new folder and call it enums, then make new scripts and delete the class name or extends node. Make the entire script an enum list and save them in the folder.

EnumList.Reference

enum Chest_Enum
{
	Lv1_Chest1,
	Lv1_Chest2,	
}
enum Quest_Enum
{
	Ch1_Quest1,
	Ch1_Quest2,	
}
class_name Database

static var Chest_List = {Chest_Enum.Lv1_Chest1: false, 
Chest_Enum.Lv1_Chest2: true}
class_name Chest extends Node

@export var Chest_ID : Chest_Enum

Basically, you’re turning any String Key into an Enum. You will get editor hints and help you see where typos exist. The name looks somewhat longer, but this does help prevent you from making any mistakes.

1 Like