Best way to change visuals of Node when an export variable is modified in Inspector (not at runtime)

Godot Version

Godot 4.4

Question

Hey folks. I have a node in my game of class Door, which extends Sprite2D. It has an export variable on it called “starting_door_state” of custom enum type DoorState. There is also logic for changing the texture of this node at runtime depending on the value of starting_door_state.

What I am trying to achieve is so that the code for changing the texture of the door changes in editor when I modify “starting_door_state” in the inspector, just so that it’s easier for me to visualise which doors are which. In Unreal Engine UI, for example, I would use the PreConstruct method to achieve this effect, and I think ConstructionScript for regular actors. What would be the best way for me to achieve this in Godot?

My current solution is declaring the entire Door class a “tool”, which does work but I’m worried that I am misusing this annotation, as it implies that the entirety of the class is capable of running in editor, which is not the case (there is other runtime exclusive logic in there, such as checking for intersection with the Player). I considered making a separate tool that extends Door, but this won’t really work either because I would need to attach both scripts to the same node (the class one and the tool one) and that doesn’t work.

Any advice is much appreciated - if helpful I have set out my script below:

@tool
class_name Door
extends Sprite2D

enum DoorState {
	LOCKED,
	SUPER_LOCKED,
	CLOSED,
	OPEN,
}

signal player_entered_door(door_id: String)

@onready var collider: Area2D = $Area2D

var current_door_state: DoorState: 
	set(new_door_state):
		current_door_state = new_door_state
		_show_door_state(new_door_state)

@export_category("State")
@export var id: String
@export var starting_door_state: DoorState = DoorState.OPEN:
	set(door_state):
		starting_door_state = door_state
		current_door_state = door_state

@export_category("Visuals")
@export var door_state_textures: Dictionary[DoorState, Texture2D]

func _ready():
	collider.body_entered.connect(_on_body_entered)
	current_door_state = starting_door_state
	
func _show_door_state(door_state: DoorState):
	if door_state_textures.has(door_state):
		texture = door_state_textures[door_state]
		
func _on_body_entered(body: Node2D):
	var player: Player = body as Player
	if player and current_door_state == DoorState.OPEN: 
		player_entered_door.emit(id)
		print(id)
	
	
func _try_open_door(player: Player):
	pass

I did something similar in my character customization script, where I have different models and textures. In my solution I don’t have a dictionary but I simply fetch the models and textures directly from my asset folder so I made it quite easily like this (ie. the head model):

@export_range(0, head_count) var head: int = 1: 
	set(new_value):
		head = new_value
		set_mesh(new_value)

The set_mesh method simply checks for the new_value occurrance (ie. the 1st, 2nd, …, mesh in the head folder), loads it, and sets the new mesh.

Similarly you can do the same with a texture.

By exporting the variable you can simply cycle through the values to see it change in real time inside the editor.

Yes, a tool script will run everything in the editor too. You can use Engine.is_editor_hint() to know if the script is running inside the editor. More information about tool scripts here Running code in the editor — Godot Engine (stable) documentation in English

Thanks, and I assume that this is in a tool script then? Because when I have a similar set thing in my texture change script like in the sample code it doesn’t run in the editor unless I mark it as a tool.

I think the is_editor_hint part is the thing that I was missing - I want to signify that only certain aspects of the class are for using in editor, and everything else has to happen at runtime. Thanks!