Godot Version
4.6
Question
I have an inventory system that I like to think is fairly robust. All data about an item (value, weight, damage, etc.) is stored in a custom Resource called ItemResource. These resources are stored in an array within a custom Inventory class, which also has methods for adding, removing, and sorting the items.
I also have a custom ItemActor class, which contains an exported ItemResource variable. This class is used to represent an item in the game world. If I want to place an object in the world, I just place an ItemActor somewhere in the scene assign the desired ItemResource, and its create_item() method automatically sets the objectâs sprite, collision shape, and other relevant variables.
When the player interacts with the ItemActor, it sends its ItemResource to the playerâs Inventory through a custom Signal, and then deletes itself from the game world. Easy and simple.
However Iâm having trouble figuring out how to create a brand-new ItemActor and call this method from within the game, such as when the player wants to drop something from their inventory.
Iâd like to have a generic method which takes an ItemResource as a parameter, creates a new ItemActor object from it, and then spawns the item in the game world at the playerâs feet.
Iâm stumbling over how best to do this because I still donât have a firm grasp of how and when to use init(), ready(), instantiate(), new(), etc. Theyâre all so similar that itâs hard to understand exactly what needs to run when.
Could I get some advice on the best way to implement this? Iâve tried creating the following methods in the Actor class (the custom class for the player character), but theyâre throwing errors which prevent them from working.
Thanks in advance for any and all advice!
func place_item(item : ItemResource) â void:
print("The player is attempting to place " + item.name)
# Spawn a new ItemActor with the ItemResourceâs data
var item_to_spawn : PackedScene = load("res://models/item_actor.tscn")
item_to_spawn.instantiate() as ItemActor
item_to_spawn.item_resource = item
This throws the error Invalid assignment of property or key 'item_resource' with value of type 'Resource (SeedResource)' on a base object of type 'PackedScene'.
SeedResource extends ItemResource, but I donât think thatâs relevant in this case because the scene is loaded as a PackedScene so I apparently canât access the internal item_resource variable.
func place_item(item : ItemResource) â void:
print("The player is attempting to place " + item.name)
# Spawn a new ItemActor with the ItemResourceâs data
var item_to_spawn = ItemActor.new()
item_to_spawn.item_resource = item
item_to_spawn.create_item()
create_item() is the ItemActor method which processes the ItemResource data to assign the collision shape and sprite (full code below). This approach throws Invalid assignment of property or key âtextureâ with value of type âAtlasTextureâ on a base object of type âNilâ.
Here are the relevant definitions of my ItemActor and ItemResource classes:
item_actor.gd:
@tool
class_name ItemActor extends CharacterBody3D
@export var item_resource : ItemResource # Contains the definition of the item
@onready var collision_shape: CollisionShape3D = $CollisionShape3D
@onready var sprite: Sprite3D = $Sprite3D
@onready var item_name : String
@onready var interactable : Interactable = $Interactable
@onready var highlight_material = preload("res://materials/highlight_interact.tres")
## Called when the node enters the scene tree for the first time.
func _ready() -> void:
create_item()
## Reads the assigned ItemResource and creates a Sprite, CollisionShape,
## and InteractionShape for the item
func create_item() -> void:
if self.item_resource == null:
push_error("This item has no item resource assigned!")
return
self.item_name = item_resource.name
var size = add_sprite(item_resource.sprite)
var box = add_collision_shape(size)
add_interaction_shape(box)
## Sets the sprite of the ItemActor and returns a Vector2 describing its size
func add_sprite(new_sprite) -> Vector2:
self.sprite.texture = new_sprite
var size : Vector2 = self.sprite.texture.get_region().size
return size
## Sets a CollisionShape3D for the object based on the size of the sprite.
## Returns the collision shape so it can be re-used to create the interaction shape
func add_collision_shape(size : Vector2) -> BoxShape3D:
# Figure out the 3rd dimension of the box
var dimensions = Vector3(size.x / 100, size.y / 100, size.x / 100)
var new_box = BoxShape3D.new()
new_box.set_size(dimensions)
collision_shape.shape = new_box
return new_box
## Sets up the Interactable component. For now it will have the same shape
## as the collision box.
func add_interaction_shape(shape : BoxShape3D) -> void:
var child = CollisionShape3D.new()
var new_box = shape.duplicate()
child.shape = new_box
interactable.add_child(child)
interactable.interact = self._on_pickup
item_resource.gd:
## This serves as the base Resource class for all items in the game.
## Items are created by loading an Item Node, then populating its data from an
## ItemResource.
class_name ItemResource extends Resource
enum ITEM_CLASS {WEAPON, ARMOR, TOOL, RESOURCE}
@export var name : String = ""
@export var sprite : Texture2D
@export var price : int = 5
@export var count : int = 1
@export var tags : Array[String]
@export var item_class : ITEM_CLASS