Do You Really Need Setters and Getters in GDScript?

Variable getters and setters are methods that allow the value of an object’s property to be read or changed without accessing it directly.

There are several dangers in accessing properties directly, such as accidental property changes, accessing private values not meant to be seen by other objects, and internal property behavior changes, among others. Using getters and setters is not mandatory, but it does have some benefits.

In this article, you will learn about private and public variables and how GDScript treats them. You’ll also explore the advantages of variable getters and setters and see an actual example of how to implement them for a few use cases in GDScript.

Intro image of the 'Getter & Setter in GDScript' article

Private and Public Variables in GDScript

Similar to Python, there is no distinction between private and public properties in GDScript. Every object can access other objects’ properties to read and modify them.

Taking C++, Java, and many other OOP languages as examples, they all differentiate between private and public member variables. This differentiation is done to prevent objects from directly controlling and misusing other objects’ internal member variables. Since GDScript doesn’t have that distinction, we must use some form of convention to know which property is private and which is public, giving access to other objects through the use of getters and setters.

For example, a player character has a ‘Health’ value and an ‘Attack’ value. The attack force can be temporarily enhanced by collecting a unique artifact. The Health and Attack values are public properties that can be accessed directly by referencing the property.

Unfortunately, this can lead to the issues mentioned before. To avoid these situations, getter and setter methods can be implemented to control access to those properties.

Now, what is inside these getter and setter methods, and how do they prevent all the issues mentioned above?

What Are Property Getters and Setters Good For?

  • Setter value manipulation: The new value assigned to the property can be evaluated and modified as needed inside the setter method. For instance, if the player’s Health value must always stay between 0 and 100, you can clamp the new given value to remain inside these limits. If a potion increases the player’s health by X points and the player already has Y health points, then (X + Y) must not exceed 100. Using this method, you won’t have to perform any mathematical calculations before changing the Health property.
  • Getter value manipulation: The property value returned from the getter can be modified based on the current state of the game or player. Let’s assume the player’s Attack can be enhanced by collecting a unique artifact, which temporarily doubles the player’s attack force. To achieve this, you can define a new private property called m_AttackMultiplier and return (Attack * m_AttackMultiplier) from the getter method. By default, the m_AttackMultiplier will be 1 and will change to 2 when the artifact is collected by the player. This ensures that other objects accessing the Attack property will remain unaware of the multiplier, as the attack property name always stays the same for them.
  • Debug & logging: Printing debug statements and logging events in the getter and setter methods is a great idea when faced with elusive bugs. Printing every access to important properties provides you, as the developer, the benefit of observing the value changes in the property over time and identifying who changed it.
  • Notifications: Notify others when a property value changes. The player’s health is usually reflected using some sort of progress bar on the screen. Therefore, every time the Health property changes, the user interface must be updated to reflect the change in the property’s value. This can be easily achieved with signals in Godot. All you have to do is emit a custom signal from inside the property setter method, pass in the new value, and connect it in another node, which will then update the user interface in response.


Example of Getter and Setter Implementation in GDScript

Now that you understand the general idea of getters and setters, let’s implement the example I described in the sections above. Here is an overview of the player’s properties:

  • Health: A public property representing the current health of the player. Bound to a value between 0 and 100.
  • Attack: A public property representing the damage a player can inflict in each strike. Bound to a value between 1 and 20.
  • m_AttackMultiplier: A private property representing the temporary attack force multiplier affecting the player. This property should not be directly accessed by other nodes.

Step 1: Setting up the Project Environment: Player, World and UserInterface Scenes

The example project contains three scenes: a world scene, a player scene, and a user interface scene. The player scene contains a custom signal that is sent when the player’s health changes. It also includes the player properties and getter and setter methods. The world scene connects to the custom signal and handles the user interface updates based on the passed health value.

If you want to know more about how to set up a user interface in Godot, go to User Interface in Godot (Part 1): How to Build a Fine Menu.

extends Node2D
class_name World

@onready var m_NodePlayer : Player = get_node("Player")
@onready var m_NodeUserInterface : UserInterface = get_node("UserInterface")

# Initialize the world scene
func _ready() -> void:
	# Connect the player's HealthChanged' signal to a local method
	m_NodePlayer.SignalHealthChanged.connect(_OnPlayerHealthChanged)
	
# Handler for the player's HealthChanged' signal events
func _OnPlayerHealthChanged(newHealth : int) -> void:
	
	# Update the player health in the user interface (HUD)
	m_NodeUserInterface.UpdatePlayerHealth(newHealth)
	print("Signal received: Player health changed to %d" % newHealth)
extends CharacterBody2D
class_name Player

# Signal emitted when player health is changed
signal SignalHealthChanged(newHealth : int)

# Constants for health boundaries
const HEALTH_MIN : int = 0
const HEALTH_MAX : int = 100

# Constants for attack boundaries
const ATTACK_MIN : int = 1
const ATTACK_MAX : int = 20

Step 2: Implementing Getter and Setter for the Player’s Health Property

In Godot 3, the keyword used for this functionality was the setget keyword. However, this won’t work in Godot 4. Now, you must define the set and get parts separately. To implement getter and setter methods for a specific property in Godot 4, you have to define the ‘set’ and ‘get’ methods next to the declaration of the property, like this:

var { Property Name } = { Initial Value }: get = { Getter Method Name }, set = { Setter Method Name }

Note: Inline Implementation of the Getter and Setter Methods

You could also implement the getter and setter methods right next to the property’s definition. This might seem intuitive at first, but it will probably make the code confusing in the long run. I suggest sticking to the technique I described.

In the code below, you see a simple implementation of the example we talked about throughout this article. The _GetHealth() method returns the player’s health value as is. On the other hand, the health setter method logs the value change, clamps the given value to the boundaries, sets the new health value, and emits a signal to notify others that the player’s health has changed.

var Health : int = 100: get = _GetHealth, set = _SetHealth    # Define the initial value and getter/setter methods

# Getter for the Health property
func _GetHealth() -> int:
	return Health
	
# Setter for the Health property
func _SetHealth(health : int) -> void:
	
	print("Changing player health to: %d" % health)
	
	# Clamp the new value to make sure its inside the boundaries
	Health = clamp(health, HEALTH_MIN, HEALTH_MAX)
	
	# Emit a signal to notify other nodes about the player health change
	SignalHealthChanged.emit(Health)

Step 3: Implementing Getter and Setter for the Player’s Attack Property

The attack consists of two properties: the current attack force and the temporary attack multiplier, which takes effect when a player collects an artifact. As you can see in the code below, both the setter and the getter modify the value.

The getter returns the current attack force multiplied by the attack multiplier. When the multiplier is equal to 1, the attack force is normal, but when it is changed to 2 or any other value, the Attack property changes. This allows other objects to get the player’s attack value without being aware of the multiplier. To change the m_AttackMultiplier property, there is a specific method called EnhanceAttack, which can be called when the player finds the artifact.

The attack setter performs several tasks, such as logging the change in value, checking for developer errors (the passed attack value should be within the constant boundaries), and finally setting the attack force.

var Attack : int = 8: get = _GetAttack, set = _SetAttack    # Define the initial value and getter/setter methods
var m_AttackMultiplier : int = 1

# Getter for the Attack property
func _GetAttack() -> int:
	
	# Multiply the attack force by the temporary multiplier
	return Attack * m_AttackMultiplier
	
# Setter for the Health property
func _SetAttack(attack : int) -> void:
	
	print("Changing player attack to: %d" % attack)
	
	# Check for developer errors (given attack value is out of bounds)
	if (attack < ATTACK_MIN or attack > ATTACK_MAX):
		print("Unable to set the player attack force. Given attack value is out of bounds: %d" % attack)
		return
		
	# Set the given attack force
	Attack = attack

# Enhance the player's attack by setting a new attack multiplier value
func EnhanceAttack(multiplier : int) -> void:
	m_AttackMultiplier = multiplier

My Take on Getters and Setters in Godot

Getters and setters are not necessary for the average developer. They are just a convenient way of ensuring complete control over property access. You could achieve the same effect by defining a strict code convention that differentiates between private and public properties. All other benefits could be achieved by having public Get and Set methods for each property that other objects are allowed to access.

The information in this article wasn’t detailed enough for you? You can check out the official Godot GDScript Basics documentation page at https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_basics.html.

If you want to learn more about game development and the Godot game engine, there are many articles on various subjects in the Night Quest Games Blog. Good luck!

If the information in this article was helpful to you, please consider supporting this blog through a donation. Your contributions are greatly appreciated and allow me to continue maintaining and developing this blog. Thank you!

Leave a Comment

Your email address will not be published. Required fields are marked *