Is there a better way to do this?

Godot Version

4.5

Question

Is there a better way to do this?


I’m going to be having alot of variables. The signal is for UI. I’m considering just using _process() and updating the text labels every frame, instead of defining all these setters. Is there a better way to define them? Should I even define them? The coins variable will be changing very quickly. Would it be more performant to use _process() instead of a shit ton of signals? Does the performance of a 2D game even matter? What am I even doing here?

Thanks,
Quandarious GoochHole
Chief Executive Officer of the Godot Foundation

1 Like

(Google translate)
I am a newbie, I give my opinion but I don’t know if it is correct or not.
I don’t like to abuse the signals too much because I can end up a little lost.
If a scene in my game is going to be unique, as may be the case with the UI, I prefer to try to access it by converting it into a singleton or making it an autoload.

In this particular case I would try to convert it to a singleton (I see autoloads as something slightly different):

class_name UI

static var _ref: UI

func _ready() -> void:
	_ref = self

func get_ref() -> UI:
	return _ref;

That way you can do:

var coins: float = 0:
	set(new):
		coins = new 
		UI.get_ref().coins = coins

Here the only thing I think you have to be a little careful with is not calling the UI until its _ready has been executed or putting it, in your scene, as the first child of it, so that it is initialized before the rest (I think it should work like this).

If what you are concerned about is performance, you can also create a variable:

	ui = UI.get_ref()

and this is how you can do:

var coins: float = 0:
	set(new):
		coins = new 
		ui.coins = coins

avoiding always calling UI.get_ref().

I haven’t seen this discussed anywhere but it’s what I prefer to do.

The good thing about signals is that there can be several listeners. If so it is better to use a signal for this. There will also be less coupling with them between your scenes.

Hi,
do not get_ref() in the setter. Option 2 is better performance. For sure, the UI class needs the variable coins.

I’d recommend a different signal for each value, and actually pass what the new value is so that your labels can just use the value when they get the signal and they don’t need to query the coins object to get it.

#coin_node.gd
signal number_of_coins_changed(value: float)

var coins: float = 0.0:
	set(value):
		coins = value
		number_of_coins_changed(value)
#Label.gd
@export coin_node: Node

func _ready() -> void:
	coin_node.number_of_coins_changed.connect(_on_value_changed)

func _on_value_changed(value: float) -> void:
	text = "Coins: %f.2" % value

Also, you know that Emi is the CEO of the Godot Foundation?

I cannot see where EMI is referenced in this conversation? Perhaps someone edited a previous post and took it out, but for now this is a rather cryptic reference.

Anyway, for the OP, personally I don’t like set in variable declarations anyway. It can get very messy and for me the UI is a purly reactive thing. So I would have it only react to signals anyway.

Here I would use an autoloaded signal_manager (or most people seem to call it a signal bus).

signal coins_collected(number_of_coins: int)
signal coins_spent(number_of_coins: int)

So anywhere a coin is collected or spent the relevant signal is emitted (no need for any additional signal declarations). You could combine these into one (coin_amount_changed signal) but I like to keep my signals purpose as clear as possible.

Lets say 5 coins are collected in your player scene. Then to alert the UI or any other scene that needs to know:

SignalManager.coins_collected.emit(5)

The UI then just deals with the signal by listening for it.

However, I too don’t like to have too rapid a build up of signals. So the other thing I might do (which you mentioned) is if coins will be changing rapildy, is to have a ‘data_store’ autoload, that stores the current number of coins. Now anywhere this globally accessible number can be changed by any scene for any reason. Then in the UI update the coins value in the _process function by just checking the current data_store value.

Which is more performant? I don’t know. Is a simple _process function going to cause too much overhead, probably not. Is sending possibly four or five coin update signals going to be too bad, probably not either. If you are going to have tons of variables then sending a tons of signals for maintainability is not really great. But as you probably know, you would have to send literally a ‘shit ton’ of cascading signals across vastly distant scenes to have any noticeable impact on performance. For me it is more about maintainability and in a big var list, setters can really confuse any script IMHO.

So I think the answer is that either way is fine, there is no ‘correct’ way but it comes down to your coding style preferences. Mine is to have a list of vars be just that, without setters and getters all mixed in. And to have a UI that is purely reactive, not have it double as a data store.

2 Likes

In the OP’s signature it says he is the CEO of the Godot Foundation.

1 Like

Oh I see. Were they joking?

Also thier name is QuandaviousGoochHole yet signs off as Quandarious GoochHole, perhaps QuandariousGoochHole was already taken? :slight_smile:

It depends on how you are going to use the variable but I don’t see any problem with your implementation.

I’d add a check if new value is different than the current value, and then emit the signal. Depending on how many functions signall will call, it may be more performent. It’s also better if one of the functions really needed to be called only when there is a change (like a UI animation that is triggered when coin is changed)

1 Like

If you really wanted a variable that emitted a signal you could DRY your code with a new class. Something like this:

class_name TalkyFloat

@export var value: float : set = _set_value, get = _get_value 

var internal_val: float 

signal variable_changed 

func _init(init_val: float) -> void: 
  internal_val = init_val 

func _set_value(new_val: float) -> void: 
  internal_val = new_val 
  variable_changed.emit()  

func _get_value() -> float:
  return internal_val 

Using the class

var coins := TalkyFloat.new(0)

coins.value = 5

coins.variable_changed.connect(signal_handler)

1 Like

Just bruteforce it in _process(). If it shows as a bottleneck when profiling (unlikely), then think about using one of approaches involving signals.

1 Like

If you’re going to use a class as a Singelton, then everything should be eclared static or it should be an Autoload. Since static signals are tricky in GDScript (you have to create a non-static signal wrapper), I’d recommend an Autoload.

The class was not intended to demonstrate a singleton. It is intended to show how all the variables in the original post could be replaced with their own instances of the class.

1 Like