I recently found myself writing this monstrosity of a line of code in Godot Engine:
var owner := Client.db.get_subobject("players").get_subobject(str(value.owner))
And immediately thought there must be a better way. It turns out, there is:
the _get
, _set
, and _get_property_list
methods. They allow you to define
properties of your GDScript object at runtime, not just at compile time,
which is a very powerful technique when used appropriately.
Now that code looks like this:
var owner := Client.db.players[str(value.owner)]
Getters and setters, but for everything
_get()
is a virtual method that acts sort of like the get
half of setget
,
but for any property that isn’t already defined by your class. You can do
whatever you want in that function, and you return the value of the requested
property (or null
if the property doesn’t exist).
Likewise, _set()
is called whenever a custom property is set. It should
return true
if the property exists and false
if it doesn’t.
Here’s a demo:
class_name Properties
extends Node
var greeting := "Hello!"
var _data = {
"secret_code": "1234"
}
func _ready() -> void:
# Unlike regularly defined properties, you always need to use `self.` to
# refer to custom properties
# Uses _get() to retrieve the value from _data
print("secret_code: ", self.secret_code)
# Uses _set() to set the value
self.secret_code = "password1"
# Dictionary-style access works exactly the same
print("secret_code: ", self["secret_code"])
# Note how normal properties don't invoke _set() or _get()
greeting = "Howdy!"
print("greeting: ", greeting)
# This crashes with "Invalid get index 'something'"
# That's because _get returns null
print("something: ", self.something)
# This similarly crashes, because _set returns null
self.something = "nothing"
# Watch out!
self.secret_code = null
# This will crash!
print("secret_code: ", self.secret_code)
func _set(property: String, value) -> bool:
print("=== SET %s ===" % property)
if property in _data:
_data[property] = value
return true
else:
return false
func _get(property: String):
print("=== GET %s ===" % property)
return _data.get(property)
This code (minus the lines that crash) outputs:
=== GET secret_code ===
secret_code: 1234
=== SET secret_code ===
=== GET secret_code ===
secret_code: password1
greeting: Howdy!
=== SET secret_code ===
Why is this useful?
Let’s say you’re making a database system for your game. You need to sync data between the client and server, and control which data gets synced according to a fog-of-war effect. Every time a property changes, you need to know about it so you can notify the right clients–but you don’t want to copy and paste the same setget pair for every property of every object in the game!
That’s the situation I was in, and custom properties were an excellent solution. That said, they can make your code less readable if they’re not the right solution, so make sure to look at your use case and decide if it’s the right tool for the job.