Bite-Sized Godot: Setters and getters in Godot 4 / GDScript 2


Sample project

As my first content update for Godot 4, I’d like to demonstrate the new syntax for setter and getter functions, as it’s pretty different from the setget syntax of Godot 3. If you’re new to setter and getter functions, I’ll also demonstrate why this is something you should care about.

As you may recall, setter and getter functions are used to run custom logic when data is written to or read from in our code. In Godot 3, we used the setget keyword, followed by the name of our setter and getter functions:

var health: float = 100.0 setget _set_health, _get_health

func _set_health(new_value: float) -> void:
    print('setting')
	health = new_value

func _get_health() -> float:
    print('reading')
	return health

In Godot 4, the setget keyword is gone and we instead have two options for defining our setter and getter functions. The first option is quite similar to Godot 3, but instead of the syntax setget setter, getter we just place a colon and assign our functions to the set and get properties:

var health: float = 100: set = _set_health, get = _get_health

func _set_health(new_value: float) -> void:
	print('setting')
	health = new_value

func _get_health() -> float:
	print('reading')
	return health

The other option, and the one I’ll use for the rest of this demonstration, is to define the set and get functions right after declaring the variable:

var health: float = 100:
	set(new_value):
		print('setting')
		health = new_value
	get:
		print('reading')
		return health

These two options, on top of being a bit more readable, also make it a lot clearer when a setter or getter function is excluded, since you can just choose not define the relevant function:

var health: float = 100:
	get:
		print('getter only')
		return health

var mana: float = 50: set = _set_mana

func _set_mana(value: float) -> void:
    print('setter only')
    mana = value

So why care about setter and getter functions? As an example, let’s say we have a game where the player’s health should always be a float between 0 and 100. Using a setter, we can make sure the value is clamped to this range before being applied to the variable.

const MIN_HEALTH := 0.0
const MAX_HEALTH := 100.0

var health: float = 100:
	set(new_value):
		health = clamp(new_value, MIN_HEALTH, MAX_HEALTH)

Another common use includes emitting a signal every time a value is updated so that other objects that depend on that data can always be up to date, such as our updating our UI:

signal mana_updated(new_value)

var mana: float = 100:
	set(new_value):
		mana = value
        emit_signal('mana_updated', mana)

For getter functions, I personally don’t use them that much, but a common use case is to conditionally calculate an expensive value if it’s not already available from a cache (which can also be combined with a setter function so you know when the cache needs to be invalidated):

var expensive_variable:
	get:
		if !cache.has('expensive_variable'):
			cache['expensive_variable'] = calculate_expensive_variable()
		return cache['expensive_variable']

You can also use getter functions to do things like transforming internal data for an external resource or to hide a long or complex path to a resource.

var formValue:
	get:
		return form.inputs.name.value

And that’s setter and getter functions in Godot 4. Not much too them but they can come in handy. As one last note, you may recall that in Godot 3, setter and getter functions were not internally called by default, but that is no longer the case in Godot 4. Your setter and getter functions will now always get called regardless of who’s operating on the data.