How do I make a SpriteFrames resource unique? (Including the spritesheet texture) [SOLVED]

Godot Version

Godot Engine v3.5.3.stable.official.6c814135b
OpenGL ES 3.0 Renderer: Apple M1 Pro
Async. shader compilation: OFF

Question

The screenshot shows the script on my AnimatedSprite for a character. The variable clothing references a child AnimatedSprite containing the character’s outfit. I want to be able to swap outfits by swapping the spritesheet texture. It works but then when there are multiple characters, all of their outfits use the swapped spritesheet texture.

So I’m trying to make the SpriteFrames resource unique to the instantiated scene, but it seems like even if I duplicate the SpriteFrames resource and assign that duplicate to the AnimatedSprite, the underlying spritesheet texture is still shared between all the instantiated scenes.

The print output when I run the game confirms that resource was duplicated and is the one being used in the texture swap function:

#[SpriteFrames:1578]
#[SpriteFrames:6392]
#[SpriteFrames:6392]

Following the suggestions on this post and this post, I’ve already tried:

  • Duplicating the resource using duplicate(true) and assigning the duplicate to the AnimatedSprite (also tried adding duplicate(true) to the last line, so it’s texture.atlas = p_texture)
  • Clicking on the SpriteFrames menu in the Inspector and clicking Make Unique
  • Clicking on the SpriteFrames in the Inspector and checking Local To Scene

My guess is the bug isn’t where you think it is. Your duplication is working just fine. It would help to know more about what your atlas textures look like. I suspect you’re doing too much in swap_textures().

If I had two atlas textures I was swapping between, I’d make sure they had the same number of frames in the same positions, and then just change where the texture.atlas is pointing. Maybe I’m missing something, but I don’t see why you should need to change that value for every frame.

My recommendation is that you just write code to automatically load your AnimatedSprite2D up from an Atlas texture from the beginning. Below I’ve pasted the code I wrote a few weeks ago to automatically load up CraftPix’s chibi characters. All I have to do is point it to a different folder and all the sprites change. It’s not perfect, and I’m working on an export bug right now. But I think it might point you in the right direction.

If you write a script that just wipes the animations and recreates them from scratch whenever you change where the atlas texture is pointing, you won’t need to duplicate the old one. You just call the same function whether you’re creating a new character or changing their outfit in code.

@tool
class_name ChibiAnimatedSprite2D extends AnimatedSprite2D

const DEATH_ANIMATION = "dying"
const FALL_ANIMATION = "falling_down"
const HURT_ANIMATION = "hurt"
const IDLE_ANIMATION = "idle"
const IDLE_BLINKING_ANIMATION = "idle_blinking"
const JUMP_ANIMATION = "jump_loop"
const JUMP_START_ANIMATION = "jump_start"
const KICK_ANIMATION = "kicking"
const RUN_SLASH_ANIMATION = "run_slashing"
const RUN_THROW_ANIMATION = "run_throwing"
const RUN_ANIMATION = "running"
const ATTACK_ANIMATION = "slashing"
const JUMP_ATTACK_ANIMATION = "slashing_in_the_air"
const SHOOT_ANIMATION = "shooting"
const JUMP_SHOOT_ANIMATION = "shooting_in_the_air"
const SLIDE_ANIMATION = "sliding"
const THROW_ANIMATION = "throwing"
const JUMP_THROW_ANIMATION = "throwing_in_the_air"
const WALK_ANIMATION = "walking"

@export_dir var sprite_root_folder:
	set(value):
		sprite_root_folder = value
		if sprite_frames:
			sprite_frames = null
		if value:
			_load_sprite_frames()


## Returns true or false if this sprite has this animation.
func has_animation(animation_name: StringName) -> bool:
	return sprite_frames.has_animation(animation_name)


func _load_sprite_frames() ->void:
	#if sprite_frames is null, create a new instance
	if not sprite_frames:
		sprite_frames = SpriteFrames.new()
	#delete default if it exists
	sprite_frames.remove_animation(&"default")
	#iterate through folders
	var dir = DirAccess.open(sprite_root_folder)
	if not dir:
		return
	var animation_list = dir.get_directories()
	for animation_dir in animation_list:
		var animation_name = animation_dir.get_file().to_snake_case()
		sprite_frames.add_animation(animation_name)
		sprite_frames.set_animation_speed(animation_name, 24.0)
		#Set looping for looping animations
		if animation_name.contains(IDLE_ANIMATION) or \
				animation_name.contains("falling") or \
				animation_name.contains("loop") or \
				animation_name.contains("running") or \
				animation_name.contains("sliding") or \
				animation_name.contains("walking"):
			sprite_frames.set_animation_loop(animation_name, true)
		else:
			sprite_frames.set_animation_loop(animation_name, false)
		#change to the specific animation directory
		dir.change_dir(animation_dir)
		var file_list = dir.get_files()
		var path = dir.get_current_dir() + "/"

		#skip over .import files and uids and add the animation frames
		for file in file_list:
			if file.ends_with(".png"):
				sprite_frames.add_frame(animation_name, load(path + file))
		#drop back down a directory for the next loop
		dir.change_dir("..")
	#set the default animations
	set_autoplay(IDLE_ANIMATION)
	animation = IDLE_ANIMATION

Thanks so much for the response! Yes, the spritesheet textures have the same dimensions and the same number of frames in the same positions. (I got the texture swap script from here– it says the texture needs to be replaced for every sprite frame.)

Appreciate you sharing your code for this! Creating the SpriteFrames programmatically was the next thing I was going to look up because it takes me so long to input SpriteFrames animations in the editor (since I have so many animations).

Inside your folders, you have individual PNG images (an individual PNG image per frame), right? (Not spritesheets?)

I was able to get a solution from someone else on Discord!

Just had to duplicate the AtlasTexture as well. So there need to be two duplicates-- SpriteFrames and AtlasTexture :slight_smile:

func init_costume():
	if animation.begins_with("lh"):
		swap_textures(clothing,load("res://clothing/shirt_rainbow.png"))
	else:
		swap_textures(clothing,load("res://clothing/shirt_black.png"))
	clothing.visible = true

func swap_textures(animated_sprite,p_texture) -> void:
	var unique_frames = animated_sprite.frames.duplicate(true)
	animated_sprite.frames = unique_frames
	for anim_name in animated_sprite.frames.get_animation_names():
		for i in animated_sprite.frames.get_frame_count(anim_name):
			var texture : AtlasTexture = animated_sprite.frames.get_frame(anim_name, i).duplicate(true)
			animated_sprite.frames.set_frame(anim_name, i, texture)
			texture.atlas = p_texture

They can wear clothes now!

kittens

2 Likes

Correct. I linked to CraftPix because they offer free versions so you can download one and actually try it out and tinker if you like. I figured the file access stuff would be most useful to you.

Currently the bug I’m experiencing with this code is I must mark any character that uses it to have Editable children or the images don’t show up when exported. I posted about it in detail here: CharacterBody2D only shows up in Exports if Editable Children is checked - #8 by dragonforge-dev When I come up with a solution, which I hope will be in the next week, it will go there.

Glad you got it working!

1 Like

I see, thanks for sharing! Good luck with your export issue, I hope you find a solution soon!

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