Instantiate a scene and its content and make them unique

Godot Version

4.2.2

Question

Hi,

I have a Scene named “Creature”, this scene contains a Node2D that contains itself a AnimatedSprite2D.

In that Node2D I have the following Script :

class_name Creature
extends Node2D

# code....

func _ready():
	position = position.snapped(Vector2.ONE * Constants.TILE_SIZE) 
	position += Vector2.ONE * Constants.TILE_SIZE / 2 
	position.y -= y_correction

func spawn(p_id: int, p_pos: Vector2, p_special: bool):
	var anims = $Animation
	id = p_id
	position = Vector2(p_pos.x * 16, p_pos.y * 16)
	special = p_special
	
	var new_texture: Texture2D
	if special:
		new_texture = load("res://assets/animated/creature/overwolrd/" + str(id) + "s" + ".webp")
	else:
		new_texture = load("res://assets/animated/creature/overwolrd/" + str(id) + ".webp")
	
# Change AnimatedSprite2D textures
	for anim_name in anims.sprite_frames.get_animation_names():
		for i in anims.sprite_frames.get_frame_count(anim_name):
			var texture : AtlasTexture = anims.sprite_frames.get_frame_texture(anim_name, i)
			texture.atlas = new_texture

# code....

I have many creatures and I don’t want to create a scene for each one, so the idea is to instantiate a scene and change the AnimatedSprite2D textures, it works. But if I change the textures for one scene, it changes it for all others.

I tried local to scene, make unique, fake array, sub resource unique and whatever I found online, nothing works.

In my Main scene, I have a Node2D called “Creature_Group” with this Script attached :

extends Node2D

@onready var fauna = $"../../../Actors Manager/Creature"
@onready var creature_s = load("res://scenes/creature.tscn")

func _ready():
	var creature_i = creature_s.instantiate()
	
	var creature_1 = creature_i.duplicate()
	creature_1.spawn(1, Vector2(8, 8), false)
	fauna.add_child(creature_1)
	
	var creature_2 = creature_i.duplicate()
	creature_2.spawn(3, Vector2(4, 8), false)
	fauna.add_child(creature_2)

So, how do I do so each Scene instance is unique and I can change their AnimatedSprite2D independently ?

Thanks.

1 Like

If Make Unique and Local To Scene don’t seem to be working, identify any resources that don’t seem to be unique between instantiations and set those resources equal to duplicates of themselves.

For example, I often have to use
Material = Material.Duplicate()

This has an optional argument to force that to be recursive in case there are sub-resources. For example, you might have a custom resource that has a shape inside and you want that shape to be changeable within one instance without changing it in others; setting the recursion to true when duplicating that custom resource should prevent all your instantiations from sharing the same shape too.

I’ve often found Resource.Duplicate in code to get the job done when Make Unique or Local to Scene either didn’t seem to work or just got confusing. You should be able to put it in the _ready function so that it’s called when the node enters the tree.

Be aware though, that there might currently be a bug involving sub resources and arrays: Duplicate() not making a unique copy of my custom resource

Edit: If you are doing things with arrays in custom resources, that git thread may be helpful since it gives a workaround.

you could also make a class to use as a defult creature and use it to make others its hard to explain but there is lots of documentations on it

1 Like

Thanks for the answers, I ended up doing a class.

extends Node2D
class_name Creature

var y_correction = Constants.CREATURE_SIZE / 4
var animation_speed = 4
var moving = false 
var current_dir = Vector2.ZERO

var animated_sprite : AnimatedSprite2D
var new_texture : Texture2D
var sprite_frames : SpriteFrames

var id: int
var pos: Vector2
var special: bool

func _init(p_id: int, p_pos: Vector2, p_special: bool):
	id = p_id
	pos = p_pos
	special = p_special
	
	animated_sprite = AnimatedSprite2D.new()
	new_texture = Texture2D.new()
	sprite_frames = SpriteFrames.new()
	new_texture.resource_local_to_scene = true
	sprite_frames.resource_local_to_scene = true
	
	position = Vector2(p_pos.x * Constants.TILE_SIZE, p_pos.y * Constants.TILE_SIZE)
	position = position.snapped(Vector2.ONE * Constants.TILE_SIZE) 
	position += Vector2.ONE * Constants.TILE_SIZE / 2 
	position.y -= y_correction

	update_spritesheet()
	animated_sprite.play("walk_down")
	add_child(animated_sprite)

func update_spritesheet():
	new_texture = load("res://assets/animated/creature/overwolrd/" + str(id) + ".webp")
	animated_sprite.sprite_frames = create_sprite_frames(new_texture)

func create_sprite_frames(p_texture: Texture2D) -> SpriteFrames:
	var anims = ["walk_down", "walk_up", "walk_left", "walk_right"]

	for anim in anims:
		sprite_frames.add_animation(anim)

	for i in range(0, 4):
		for anim in anims:
			var frame = AtlasTexture.new()
			frame.atlas = p_texture
			match anim:
				"walk_down":
					frame.region = Rect2(i * 32, 0, 32, 32)
				"walk_up":
					frame.region = Rect2(i * 32, 96, 32, 32)
				"walk_left":
					frame.region = Rect2(i * 32, 32, 32, 32)
				"walk_right":
					frame.region = Rect2(i * 32, 64, 32, 32)
			sprite_frames.add_frame(anim, frame)
			sprite_frames.set_animation_speed(anim, 4.0)

	return sprite_frames

func move(dir):
	var tween = create_tween()
	tween.tween_property(self, "position", position + dir * Constants.TILE_SIZE, 1.0 / animation_speed).set_trans(Tween.TRANS_SINE) 
	moving = true 
	await tween.finished 
	moving = false

Then, in my main Scene, I can do :

extends Node2D

@onready var fauna = $"../../../Actors Manager/Creature_Group"

func _ready():
	var fff = Creature.new(117, Vector2(0, 0), true)
	fauna.add_child(fff)
	fff.move(Vector2(2, 1))
	
	var fff2 = Creature.new(747, Vector2(0, 0), false)
	fauna.add_child(fff2)
	fff2.move(Vector2(4, 1))

It works, they are different and move independently.

Have a good day.

1 Like

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