Best practices for @onready and @export setters

Godot Version

4.2.1 stable

Question

i’m trying to figure out the best practice for setting a value by a script. i’ll use color as an example but there are other values (like size) that i’d like to update. for example, say i have a scene/node with this script and a Polygon2D child (named “Polygon2D”), and i want this color to update in the editor’s scene, so i’m using @tool.

@tool
class_name MyPolygonWrapper
extends Node2D

@export color := Color(0.5, 0.5, 0.5)
@onready polygon2D = $Polygon2D

func _ready():
  polygon2D.color = color

however, unless i’m reloading the scene in the editor all the time, this doesn’t work; since the scene will stay active, i’d need to update in _process(delta):

func _process(delta):
  polygon2D.color = color

that’s ok, but what if this is a complex computation? we can switch to using getters/setters (and remove the _process calculation), and i would love it if this just worked:

@export color: Color:
  get:
    return polygon2D.color
  set(new_color):
    polygon2D.color = new_color

however, this will break because polygon2D is sometimes null when trying to set the color; i.e., @onready isn’t ready yet with the polygon2D variable. that leads me to some IMO ugly boilerplate here (holding onto an extra variable that needs to be kept in sync):

var _color := Color(0.5, 0.5, 0.5)
@export color: Color:
  get:
    return _color
  set(new_color):
    _color = new_color
    if polygon2D != null:
      polygon2D.color = new_color

func _ready():
  polygon2D.color = _color

the _ready() is necessary in this case in case you set the color before the scene is active (which happens when loading the game), you need it to set the initial color.

is there a better way to do this? i’ll probably take the “performance penalty” of always setting it in _process but i wish the setters worked with polygon2D as if it wasn’t null since i have it specifically added to the scene already.

1 Like

I had the same issue in a tool script with a setter and found a proposal with a discussion of this problem. I used this workaround for Godot 4 from the discussion. Your get/set function would become:

@export var color: Color:
  get:
    return polygon2D.color
  set(new_color):
    if not is_node_ready():
        await ready
    polygon2D.color = new_color

Seems to work here, there’s no error anymore when loading the scene. The error before was: Invalid set index ‘color’ (on base: ‘Nil’) with value of type ‘Color’.

1 Like