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:
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
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
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.