Set animation frame in editor via exported variable

Godot Version

4.2

Question

I have a node (CharacterBody2D) that has both Sprite and AnimationPlayer as children. The texture in the sprite is a spritesheet. I am trying to code an enemy, which comes in different flavours: each row of the spritesheet is a recolour of the same enemy, and each colour will behave similarly. That’s why I’m trying to use the same scene for all of them, selecting the enemy’s subtype in the editor with an exported variable.

To make the changes visible, I want to set the correct frame for each type as soon as I click the type in the editor, so I see the enemy’s color for the selected type. I’m trying to do so with the following code:

extends CharacterBody2D

enum TYPES {STONE, FIRE, GREEN, BLUE}
@export var bat_type : TYPES = TYPES.GREEN :
	set(value):
		bat_type = value
		match bat_type:
			TYPES.STONE:
				$sprite.frame = 9
			TYPES.FIRE:
				$sprite.frame = 3
			TYPES.GREEN:
				$sprite.frame = 0
			TYPES.BLUE: 
				$sprite.frame = 6

However, I will not see any change in the editor, not even if I add the @tool keyword. If I try to run the game, it crashes saying that $sprite is null.

I can understand more or less why this fails (order of things entering in the tree) but I have no clue how to accomplish what I want now. Any help? I could not find anything useful on the forums already. Thank you very much in advance!

Firstly, if you want this property to work in the editor, it needs to be a @tool script.

If you add @tool to a script while the scene is open, it won’t work immediately. You’ll need to save and reload the scene first.

Second, $sprite is not accessible sometimes, because the property is set when the scene is instantiated, before anything is added to the tree.

You’ll probably want to add a check to the setter to return early if the node isn’t in the tree yet:

@export var bat_type : TYPES = TYPES.GREEN :
	set(value):
		bat_type = value
		if not is_inside_tree():
			return
		match bat_type:
			TYPES.STONE:
				$sprite.frame = 9

But then, the frame value won’t be set properly.

There’s an easy fix for that:

func _ready():
	bat_type = bat_type

This reads as a bit hacky, but it’s the simplest immediate solution.

A better solution would probably be to extract the match logic into some kind of set_sprite_frame() function, and call that function in both the setter and the ready func.

1 Like

Does this work?

@tool
extends CharacterBody2D

enum TYPES {STONE, FIRE, GREEN, BLUE}
@export var bat_type : TYPES = TYPES.GREEN :
	set(value):
		bat_type = value
		if is_instance_valid(sprite):
			match bat_type:
				TYPES.STONE:
					$sprite.frame = 9
				TYPES.FIRE:
					$sprite.frame = 3
				TYPES.GREEN:
					$sprite.frame = 0
				TYPES.BLUE: 
					$sprite.frame = 6

@onready var sprite : Sprite = $sprite
2 Likes

Thanks for the clear explanation and the code examples. @apples solution works quite nicely and now I understand what was not working. Among other things, I was not reloading the scene, so I did not see the tool working.

I just tried @Exerion code and works well in the editor, but it throws errors at runtime because when $sprite cannot be called at that point, not even for the check.

For the ready method, I ended up doing this

func _ready():
	
	if Engine.is_editor_hint():
                #Trivial thing to trigger the setter
                bat_type = bat_type
		set_process(false)
		set_physics_process(false)
		
	else:
                #in-game logic

to separate my editor tool from the game logic. Thank you so much for the help!

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.