Recursion when creating Item system

Godot Version

Godot 4.3

Question

Hello! So, I am making the Items system for my game, and I watched a tutorial on how to make it using resources. So then I tried to implement this to my project, I made an Item class and a Weapon class that extends from the Item class:

item.gd:

extends Resource
class_name Item

@export var item_name: String
@export var item_description: String
@export var item_icon: Texture
@export var item_scene: PackedScene

func _ready() -> void:
	pass

weapon.gd

extends Item
class_name Weapon

@export var weaponlevel: int
@export var weapontype: String
@export var fire_rate: float
@export var range_size: int
@export var damage: int
@export var knockback: int

const DAMAGE_VARIATION: float = 0.1  # 10% de variação fixa

func _ready() -> void:
	pass

func dmg_var():
	var variation = randf_range(-DAMAGE_VARIATION, DAMAGE_VARIATION)  # Varia entre -10% e +10%
	var final_damage = damage * (1.0 + variation)
	return final_damage

Then I have the scene of one of my weapons, which is an Area2D node:

extends Area2D

@export var sprite: Node2D
@export var timer: Timer
@export var audio_stream: AudioStreamPlayer2D
@export var gunrange: CollisionShape2D
@export var animation_player: AnimationPlayer

@export var item_data: Weapon

func _ready() -> void:
	print(item_data.item_name)
func _physics_process(delta: float) -> void:
	gunrange.scale = Vector2(item_data.range_size, item_data.range_size)
	var enemies_in_range = get_overlapping_bodies()
	timer.wait_time = item_data.fire_rate
	if enemies_in_range.size() > 0:
		var target_enemy = enemies_in_range.front()
		look_at(target_enemy.global_position)

		# Verifica se a rotação exige espelhamento
		update_weapon_flip()
		
		# LĂłgica de disparo
		if timer.is_stopped():
			timer.start()
	else:
		# Se nĂŁo houver inimigos no range, desativa o timer
		if not timer.is_stopped():
			timer.stop()

func update_weapon_flip() -> void:
	# Posição global do pivot (arma) e do inimigo
	var enemy_pos = get_overlapping_bodies().front().global_position
	var player_pos = global_position

	# Verifica se o inimigo está à esquerda ou à direita do jogador
	if enemy_pos.x < player_pos.x:
		sprite.scale.y = -4  # Espelha se o inimigo está à esquerda
	else:
		sprite.scale.y = 4  # Mantém posição padrão se o inimigo está à direita

func shoot():
	const BULLET = preload("res://scenes/bullet.tscn")
	var new_bullet = BULLET.instantiate()
	var final_damage = item_data.dmg_var()
	new_bullet.dmg = final_damage
	new_bullet.knockback = item_data.knockback
	new_bullet.global_position = %ShootingPoint.global_position
	new_bullet.global_rotation = %ShootingPoint.global_rotation
	%ShootingPoint.add_child(new_bullet)
	audio_stream.play()
	if animation_player.is_playing():
		animation_player.stop()
	animation_player.play("shoot")
	animation_player.queue("idle")

func _on_timer_timeout() -> void:
	shoot()
	

The problem is: @export var item_scene: PackedScene
I can’t get the item_scene in my pistol script because of recursion, I wanted to be able to spawn my weapon scene and add it to my inventory at the same time by having the scene in the Weapon Resource, but I can’t find a way to get the stats for the weapon in other efficient way from it’s own weapon class resource. Is there a better and efficient way to do this than just removing the weapon scene from the class? Or is it better to just handle the weapon scene and the inventory separately? In this case I would add an instance of item_data to the inventory whenever the player obtains the weapon. Sorry if this is confusing, english is not my native language :")

I dont see the recursion. The weapon scene is never instantiated and a packed scene is not a scene yet.

What i would do in the simplest sense is instantiate the weapon scene and give the weapon item reference for stat initialization. This is a shared reference and does not duplicate data.

If the weapon packed scene is already has its own copy of the weapon item resource then you dont need to do anything and there is still no recursion, just duplicate data.

The problem is, when I try to set the “Item Scene” variable on the resource inside one of the weapons, since I load the Item_Data containing the resource, it detects recursion. Here I recorded how it happens: https://youtu.be/_tR0H7kok9Y

For more context, I want to be able to spawn the scene of the weapon later based on which Items I have in the player inventory, so having the scene associated with the weapon characteristics would be good. But I need to get the stats contained in the weapon resource as well.

1 Like

if 2 resources referencing each other - there will be resource importing recursion, its engine limitation. For instance, if you have a “Guild” resource that contains predefined characters as resources

@export var characters:Array[Character]

you cant set back property from “Character” back to “Guild” resource

@export var guild:Guild

it will be a “recursion”. But if you add it by code when project runs, there won’t be any error.

So by default its not possible to make reference resource <=> resource,
only resource => resource

I see, is there a better solution for my case then? For getting the stats for the weapon from the weapon class resource?

singleton with all needed nodes/resources. Then read it when weapon/any item created.
weapon can has a meta or property:StringName that points to correct resource/node from singleton

the when needed read it

damage:int=Data.get_node(str(get_meta("stats_holder")))[&"damage"]

I use similar organization of items data


OR additionally you can set new child node that fills data of weapon and then freed. get_parant().weapon_stats=res.values; queue_free()
cound be done in same scene