How to make instanced scenes unique when using signals?

Godot Version

4.5.1

Question

Hello, I have a scene with some functionality that uses signals.

For instance, the interaction I want is to pick up the red scene and have it interact with the purple scene. However, I only want it to interact with the purple scene that is above the bright blue scene. I was finally able to get the functionality to work with signals, but I am not sure why the purple scene off to the left (which is not on top of the blue scene) is affected.

I did some reading and it feels like there is something to do with resources but I am not exactly sure. How could I make the scenes unique inside of my level?

Thanks!

Hi Welchaj,

I would select the purple scene to the left in the Scene view and then make the sub-resources unique in the Inspector view (as shown below).

After duplication of scenes the sub-resources are usually identical and then start behaving the same.

Tell me if that fixes it for you.

Kind regards :four_leaf_clover:

1 Like

Hello, thanks for helping. I got the following error,

image

I think this is because I gave the scene class_name “MixingBowl”. I removed “class_name MixingBowl” from my purple scene script and I get no errors, but the behavior is still the same.

Just to make sure I did this right:

I go into scene tree in my level, and click on the second instantiated scene:

Then go and click “Make Sub-Resources Unique”?

I don’t know if this matters, but I had several scenes with “class_name” written in them. I just removed all of them and the game still works as intended, besides the problem I have.

Also, I don’t know if this matters, but I noticed in the editor the original scene has the intended script while the second one (which I made sub-resources unique) is the main_level.gd script.

TBH, I’m not really clear on what your original problem was, but using class_name wasn’t it.

You’d be better off showing us your code if you want help, and clearly defining the problem. Based on what I can see, signals are either A) not the solution to your problem, or B) signals are not the solution to your problem the way you are using them.

Please format your code by pressing Ctrl + E on your keyboard and pasting the code inside the ``` on top and bottom of the code.

You did follow those instructions correctly, they just aren’t what you need; it made your Script unique which is now clobbering a global class_name, reset/clear the script and re-attach the original script file to remove the built-in script problem. @kraasch may have recommended doing this because changing 3D materials often results in shared changes between every model using that material, but the material didn’t change, and the real solution to that problem is to set the resource “Local to Scene”, not Unique.

You will have to show some code on how your connected your signals, and try enabling “Visible Collision Shapes” in the debug menu to see if you are overlapping both objects.

2 Likes

class_name became an issue when I followed what kraasch suggested.

I am essentially trying to have some baking game where you put ingredients in a mixing bowl attached to a mixer.

Red scene is an ingredient and purple is the mixing bowl. Blue cylinder is the mixer.

When I pick up and attach the red scene to the blue cylinder it shows up on the purple mixing bowl:

This is the behavior I want, but I noticed that if I have a second purple mixing bowl that is not attached to the blue cylinder, it ALSO receives the action to add the red scene:

(The second one was way far back and I don’t think it is possible to have been touched by my raycast detector.)

I will post my code below but to get this working I essentially was looking to use signals to tell the purple scene to show the red scene when an interaction happened, and delete the red scene in the level.

player.gd:

extends CharacterBody3D

@onready var player: CharacterBody3D = $"."

@onready var level: Node3D = $".."
@onready var mesh_instance_3d: MeshInstance3D = $MeshInstance3D
@onready var item_detector: RayCast3D = $MeshInstance3D/ItemDetector
@onready var socket: Marker3D = $MeshInstance3D/Socket
@onready var equipment_detector: RayCast3D = $MeshInstance3D/EquipmentDetector



const MIXING_BOWL = preload("uid://cdox80irdgpab")



#Rotating Character
var newdir := Transform3D() 
var orientation := Transform3D()
var motion := Vector2()

#Item
var detected_item
var attached_item
var groups_detected_item: Array
var isAttached: bool = false
var current_ingredients_in_mixing_bowl: Array

#Equipment
var detected_equipment




const SPEED = 5.0
const JUMP_VELOCITY = 4.5
const PLAYER_ROTATION_SPEED = 10





func _ready() -> void:
	
	#sets orientation equal to the global transform which contains position rotation scale and origin
	orientation = mesh_instance_3d.global_transform
	#sets orientation origin to a Vector 3
	orientation.origin = Vector3()

func _physics_process(delta: float) -> void:
	# Add the gravity.
	if not is_on_floor():
		velocity += get_gravity() * delta

	# Handle jump.
	if Input.is_action_just_pressed("ui_accept") and is_on_floor():
		velocity.y = JUMP_VELOCITY
	
	#if (attached_item):
		#attached_item.position = socket.position

	var input_dir := Input.get_vector("left", "right", "forward", "backward")
	var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()

	if direction:
		velocity.x = direction.x * SPEED
		velocity.z = direction.z * SPEED
		
		#putting direction in its own variable
		var target := direction
		# using Quaternion, get the rotation quaternion of the origin, which is of type basis 
		var q_from: Quaternion = orientation.basis.get_rotation_quaternion()
		#this establishes a quaternion that gets the target rotation quaternion. This is done by taking the Basis - which is only position, rotation, and scale
		#and using looking_at (target) which creates a vector looking at the target, and then gets the rotation quaternion of that.
		var q_to: Quaternion = Basis.looking_at(target).get_rotation_quaternion()
		#uses slerp (spherical linear interpolation). orientation.basis is becoming the q_to from q_from and slerped from there.
		orientation.basis = Basis(q_from.slerp(q_to, delta * PLAYER_ROTATION_SPEED))
	else:
		velocity.x = move_toward(velocity.x, 0, SPEED)
		velocity.z = move_toward(velocity.z, 0, SPEED)


	move_and_slide()
	
	#Rotate Character. This is the code that actually rotates the character
	orientation.origin = Vector3() # Clear accumulated root motion displacement (was applied to speed).
	orientation = orientation.orthonormalized() # Orthonormalize orientation.
	mesh_instance_3d.global_transform.basis = orientation.basis #this rotates the character.

func _input(event: InputEvent) -> void:
	if event.is_action_pressed("Interact"):
		
		if item_detector.is_colliding() and isAttached == false:
			detect_item_type_and_attach()
		elif equipment_detector.is_colliding() and isAttached == false:
			pickup_item_from_equipment()
			
		elif isAttached == true:
			if equipment_detector.is_colliding() == true:
				if groups_detected_item.has("MixingBowl"):
					attach_mixing_bowl_to_mixer()
				if !groups_detected_item.has("MixingBowl"):
					if ItemManager.is_mixing_bowl_attached == true:
						add_item_to_mixing_bowl()
			else:
				drop_item()


	if event.is_action_pressed("RunEquipment"):
		print("RunEquipment pressed")

		if equipment_detector.is_colliding() == true:
			detected_equipment = equipment_detector.get_collider()
			var groups_detected_equipment = detected_equipment.get_groups()
			if groups_detected_equipment.has("Mixer") and ItemManager.mixing_bowl_full == true:
				print("run: ", detected_equipment)
				SignalManager.on_start_mixer.emit()
			if groups_detected_equipment.has("Mixer") and ItemManager.mixing_bowl_full == false:
				print("Mixing bowl isnt full")


func _process(_delta: float) -> void:
	pass





func attach_item_to_socket(item) -> void:
	
	
	#SignalManager.on_pickup_item.emit(item)
	item.reparent(socket)
	#detected_item.add_child(socket)
	#
	item.freeze = true
	#
	item.global_transform = socket.global_transform
	
	

	isAttached = true
	return

func pickup_item_from_equipment() -> void:
	detected_equipment = equipment_detector.get_collider()
	
	if ItemManager.is_mixing_bowl_attached == true:
		
		
		pick_up_mixing_bowl()

		
func detect_item_type_and_attach() -> void:
	#print("Item detector collided and no item attached")
	detected_item = item_detector.get_collider()
	groups_detected_item = detected_item.get_groups()
	#print(detected_item.get_groups())
	if groups_detected_item.has("Item"):
		attach_item_to_socket(detected_item)
		
func attach_mixing_bowl_to_mixer() -> void:
	detected_equipment = equipment_detector.get_collider()
	detected_item.global_transform = detected_equipment.equip_socket.global_transform
	
	#Signal sent to main_level.gd
	SignalManager.on_add_mix_bowl_to_mixer.emit(detected_equipment)

	if ItemManager.mixing_bowl_information:
		SignalManager.on_set_ingredients.emit(ItemManager.mixing_bowl_information)
	
	
	detected_item.queue_free()


	isAttached = false
func drop_item() -> void: 
#	I had to declare this variable because it wasn't getting the groups FAST enough before signal
	var array_of_groups: Array = detected_item.get_groups()
	
	SignalManager.on_dropped_object.emit(array_of_groups)
	if groups_detected_item.has("MixingBowl"):
		SignalManager.on_mixing_bowl_dropped.emit(current_ingredients_in_mixing_bowl)
		
	detected_item.queue_free()
	isAttached = false
func pick_up_mixing_bowl() -> void:
	#ItemManager.mixing_bowl_information

	var spawn_mixing_bowl = MIXING_BOWL.instantiate()
	socket.add_child(spawn_mixing_bowl)
	spawn_mixing_bowl.freeze = true
	isAttached = true
	detected_item = spawn_mixing_bowl
	
	SignalManager.on_delete_mixing_bowl.emit()
	if ItemManager.is_donut_dough_made == true:
		SignalManager.on_donut_dough_made.emit()
	else:
		current_ingredients_in_mixing_bowl = ItemManager.mixing_bowl_information
		groups_detected_item = spawn_mixing_bowl.get_groups()
		print(current_ingredients_in_mixing_bowl)
		SignalManager.on_set_ingredients.emit(current_ingredients_in_mixing_bowl)
func add_item_to_mixing_bowl() -> void:
	var array_of_groups: Array = detected_item.get_groups()
	SignalManager.on_item_equipment_interaction.emit(array_of_groups)
	print(array_of_groups)
	detected_item.queue_free()
	isAttached = false

ItemManager.gd

extends Node

var mixing_bowl_information: Array
var mixing_bowl_full: bool = false
var is_mixing_bowl_attached: bool = false
var is_donut_dough_made: bool = false

func _ready() -> void:
	SignalManager.transmit_list_of_ingredients.connect(_transmit_list_of_ingredients)


func _transmit_list_of_ingredients(list_of_ingredients) -> void:
	mixing_bowl_information = list_of_ingredients
	print(mixing_bowl_information)
	print("Updated ingredients in mixing bowl")
	
	if is_donut_dough_made == true:
		print("donut dough made")
	
	elif mixing_bowl_information == [true, true, true, true]:
		mixing_bowl_full = true
	
func _process(delta: float) -> void:
	#print(mixing_bowl_information)
	pass

MainLevel.gd

extends Node3D

const ITEM = preload("uid://b4nkr60ehsdpk")
const PLAYER = preload("uid://8bx1an1edxv7")
const MAIN_LEVEL = preload("uid://bgatqathefj53")
const SUGAR = preload("uid://dasong7gubw4g")
const FLOUR = preload("uid://dxsbdv0ej3ybj")
const MIXING_BOWL = preload("uid://cdox80irdgpab")
const EGGS = preload("uid://bixgrycn7ga3t")
const YEAST = preload("uid://be7yidn8307s4")


var mixing_bowl_items: Array

#I don't think this is needed anymore but im going to leave it here in case I need it
var PickUpItems = {
	"Item" : ITEM,
	"Sugar" : SUGAR ,
	"Flour" : FLOUR,
	"MixingBowl" : MIXING_BOWL,
	"Eggs" : EGGS,
	"Yeast" : YEAST
}

@onready var level: Node3D = $"."
@onready var player: CharacterBody3D = $Player

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	
	SignalManager.on_dropped_object.connect(_on_dropped_object)
	SignalManager.on_pickup_item.connect(_on_pickup_item)
	SignalManager.on_mixing_bowl_dropped.connect(_on_mixing_bowl_dropped)


func _on_dropped_object(Items) -> void:
	print(Items[1])
	if Items[1] == "MixingBowl":
		spawn_object(Items[1])
		if ItemManager.is_donut_dough_made == false:
			SignalManager.on_set_ingredients.emit(ItemManager.mixing_bowl_information)
		if ItemManager.is_donut_dough_made == true:
			SignalManager.on_donut_dough_made.emit()
	else: 
		spawn_object(Items[1])




func spawn_object(ItemName) -> void:
	
	var item_to_dict = PickUpItems[ItemName]
	var spawnobject = item_to_dict.instantiate()
	spawnobject.global_transform = player.socket.global_transform
	level.add_child(spawnobject)

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(_delta: float) -> void:
	pass

func _on_pickup_item(detected_item) -> void:
	detected_item.global_transform = player.socket.global_transform

func _on_mixing_bowl_dropped(items_in_bowl) -> void:
	if ItemManager.is_donut_dough_made == true:
		SignalManager.on_donut_dough_made.emit()
	if ItemManager.is_donut_dough_made == false:
		ItemManager.mixing_bowl_information = items_in_bowl

mixing_bowl.gd

extends "res://Scripts/item.gd"



@onready var flour: MeshInstance3D = $flour
@onready var sugar: MeshInstance3D = $sugar
@onready var eggs: MeshInstance3D = $eggs
@onready var yeast: MeshInstance3D = $yeast
@onready var donutdough: MeshInstance3D = $donutdough




var has_flour: bool = false
var has_sugar: bool = false
var has_eggs: bool = false
var has_yeast: bool = false

var ingredients: Array = [has_flour,has_sugar, has_yeast, has_eggs]
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	
	flour.hide()
	sugar.hide()
	eggs.hide()
	yeast.hide()
	donutdough.hide()
	
	SignalManager.on_item_equipment_interaction.connect(_on_item_equipment_interaction)
	SignalManager.on_set_ingredients.connect(show_ingredients)
	SignalManager.on_donut_dough_made.connect(_on_donut_dough_made)
	

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(_delta: float) -> void:
	pass

func _on_item_equipment_interaction(item_to_attach) -> void:
	print("Item to attach: ", item_to_attach)
	for group in item_to_attach:
		match group:
			"Flour":
				flour.show()
				has_flour = true
			"Sugar":
				sugar.show()
				has_sugar = true
			"Yeast":
				yeast.show()
				has_yeast = true
			"Eggs":
				eggs.show()
				has_eggs = true
			
	ingredients = [has_flour, has_sugar, has_yeast, has_eggs]
	SignalManager.transmit_list_of_ingredients.emit(ingredients)
	print("ingredients updated: ", ingredients)

func show_ingredients(items) -> void:
	print("items: ", items)
	
#	there is probably a better way to do this but i dont know how else to do this right now
	if (items):
		if items[0] == true:
			flour.show()
			has_flour = true
		if items[1] == true:
			sugar.show()
			has_sugar = true
		if items[2] == true:
			yeast.show()
			has_yeast = true
		if items[3] == true:
			eggs.show()
			has_eggs = true
		
		if items == [false, false, false, false]:
			flour.hide()
			has_flour = false
			sugar.hide()
			has_sugar = false
			yeast.hide()
			has_yeast = false
			eggs.hide()
			has_eggs = false
		
		print("mixingbowl.gd: Items: ", items)
		ItemManager.mixing_bowl_information = items


func _on_donut_dough_made() -> void:
	print("donut dough made")
	donutdough.show()
	ItemManager.is_donut_dough_made = true

mixer.gd

extends "res://Scripts/equipment.gd"

@onready var mixer_run_time: Timer = $MixerRunTime

@onready var equip_socket: Marker3D = $Equip_Socket
@onready var spindle: MeshInstance3D = $Spindle
const MIXING_BOWL = preload("uid://cdox80irdgpab")



var mixing_bowl
var detected_item
var mixbowl
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	SignalManager.on_add_mix_bowl_to_mixer.connect(_on_add_mix_bowl_to_mixer)
	SignalManager.on_delete_mixing_bowl.connect(_on_delete_mixing_bowl)
	SignalManager.on_start_mixer.connect(_on_start_mixer)

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(_delta: float) -> void:
	pass
	
func _on_delete_mixing_bowl() -> void:
	if ItemManager.is_mixing_bowl_attached == true:
		if ItemManager.is_donut_dough_made == false:
			SignalManager.transmit_list_of_ingredients.emit(mixbowl.ingredients)
			ItemManager.is_mixing_bowl_attached = false
		if ItemManager.is_donut_dough_made == true:
			SignalManager.on_donut_dough_made.emit()
		mixbowl.queue_free()
	else:
		pass


func _on_add_mix_bowl_to_mixer(equipment) -> void:
	var spawn_mixing_bowl =	MIXING_BOWL
	#level.add_child(spawn_mixing_bowl)
	print(spawn_mixing_bowl)
	mixbowl = spawn_mixing_bowl.instantiate()
	self.add_child(mixbowl)
	mixbowl.global_transform = equipment.equip_socket.global_transform
	mixbowl.freeze = true
	ItemManager.is_mixing_bowl_attached = true
	
	if ItemManager.is_donut_dough_made == false:
		mixbowl.ingredients = ItemManager.mixing_bowl_information
	if ItemManager.is_donut_dough_made == true:
		SignalManager.on_donut_dough_made.emit()

func _on_start_mixer() -> void:
	mixer_run_time.start()
	print("Mixer started")
	spindle.rotate(Vector3(100,100,10).normalized(), 90)


func _on_mixer_run_time_timeout() -> void:
	print("Mixer FINISHED") # Replace with function body.
	spindle.rotate(Vector3(1000,100,100).normalized(), 90)
	ItemManager.mixing_bowl_information = [false, false, false, false]
	ItemManager.mixing_bowl_full = false
	SignalManager.on_set_ingredients.emit(ItemManager.mixing_bowl_information)
	SignalManager.on_donut_dough_made.emit()

Using a global SignalManager means everything is responding to each signal, it’s best to avoid global signals unless you specifically need them. Seems like you do not need to signal when you can use your equipment_detector raycast to get the objects and run functions on only that object.

3 Likes

When you say “everything is responding to each signal” is that because it is a global? So if I had used regular signals in my scene scripts it would only apply to that instance of a scene inside of my level?

When you have one signal that everything is connected to, then a single emit will run each and every connected function. You could create signals on the objects, but the method you retrieve these objects is better suited to using functions directly. Signals are best used to describe the “when something happens”, but your “when” is already defined as a raycast collision

For example in this code it seems better to call a function on detected_equipment instead of signaling up to a global, which then connects back down the tree to approximately detected_equipment

func attach_mixing_bowl_to_mixer() -> void:
	detected_equipment = equipment_detector.get_collider()
	detected_item.global_transform = detected_equipment.equip_socket.global_transform
	
	# call function directly
	detected_equipment.add_mix_bowl_to_mixer()

But I don’t have the full picture unwound so this may not be 100% accurate for your project.

2 Likes

Ok I think this makes sense. I ask because I tried to rewrite some of my code to use signals not part of SignalManager.gd and the behavior was still the same. I am going to try what you suggested and see how it works.

So it looks like my issue was using signals. I basically rewrote my entire code to not use signals and its working fine. I thought using “reparent()” was not always good to use but it has made it much easier to get the behavior I want.

Thanks!

1 Like