GDScript's  _get() and  _set(): How and When to Use Them

27 December 2020

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.


Next

Adapting GNOME Maps to Mobile Devices: Map Details

Last year, I redesigned the info bubbles in GNOME Maps. Now I’ve made that bit of the UI adaptive, so that it fits on the screen of your favorite Linux phone!

Previous

Coding Custom Widgets with GTK 3 and GJS

GTK provides a lot of useful widgets, but it inevitably can’t cover every possible use case. What if, for example, you need an image widget that resizes itself based on the available width, and clips its corners to fit in a popover? Fortunately, GTK makes it easy to create your own widgets that do whatever you want.