How do I get inventory to work?

Godot Version

4.6.2

Question

I’m trying to figure out how to have my main character just pick up an item when they collide with the item’s hit box. I have the player and item in separate scenes, then instantiated in my main scene. All scripts will be posted below. I have the array “inventory” declared in the player script, I have a “collect” signal declared in the item’s script and a function where it disappears. The main.tcsn script is mostly empty because I want to focus on the interaction before I go off on other parts of the project.

Player script:

extends CharacterBody2D

@export var speed = 100
var target = position
var inventory = []


func _ready():
	$Playersprite.show()
func movedown():
	$MovementFrames.show()
	$MovementFrames.play("Walk_down", 1, false)
func moveup():
	$MovementFrames.show()
	$MovementFrames.play("Walk_up", 1, false)
func moveright():
	$MovementFrames.show()
	$MovementFrames.play("Walk_right", 1, false)
func moveleft():
	$MovementFrames.show()
	$MovementFrames.play("Walk_left", 1, false)

func _input(event):
	if event is InputEventMouseButton:
		if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
			target = get_global_mouse_position()


func _physics_process(_delta: float):
	var direction: Vector2 = target - position
	velocity = position.direction_to(target) * speed
	
	if position.distance_to(target) > 1:
		move_and_slide()
		$Playersprite.hide()

		if absf(direction.y) > absf(direction.x): # moving up or down
			if direction.y > 0: # moving down (positive y)
				movedown()
			else: #moving up (negative y)
				moveup()
		else: # moving left or right
			if direction.x > 0: # moving right
				moveright()
			else:
				moveleft()
	else:
		$MovementFrames.hide()
		$Playersprite.show()

Item script:

extends RigidBody2D

signal collect


func _on_area_entered(area: Area2D) -> void:
	collect.emit()
	queue_free()

Main script:

extends Node2D

@export var Player = PackedScene
@export var Item = PackedScene
func _ready():
	pass

I know it doesn’t seem like much but I’ve been banging my head against this problem for way to long without asking for help.

I guess if someone can explain if I should have the inventory variable in the main script or if it’s okay how it is, and how to get the player script to interact with the item script. I’ve tried making the item a packed scene, a child scene, I’ve tried consolidating both into the main scene (the main scene script still has the remnants of this).

I try to stay pretty active on my threads so if I forgot anything useful just ask and I’ll do my best to provide any information.

Nothing is listening to your collect signal.

I couldn’t get it to signal anywhere outside the item script. I would ideally want to have a function to append the inventory array on the collect signal, but I don’t know how to connect it to, for example the player script where the inventory is.

Well I would make some different decisions that you have. Instead of having the item’s Hitbox detect another Area2D (presumably on the Player), I would just have it detect the Player. Then instead of having a signal, I’d just call a function on the Player.

  1. Put the Player on its own physics layer. I recommend 2 if nothing is there yet. Only the Player should ever be on this layer.
  2. Add an Area2D to your Item. Set it to not be on any physics layer, and its only physics mask should be the Player’s layer so it can detect it.
  3. Add a CollisionShape2D onto your Area2D and make it larger than the RigidBody2D’s collision shape so you can pick it up.
  4. Delete the code on your RigidBody2D.
  5. Add this code to your Area2D:
extends Area2D


func _ready() -> void:
	body_entered.connect(_on_body_entered)

func _on_body_entered(body: Node2D) -> void:
	body.collect(self)
  1. Add this code to your Player
func collect(item: Node2D) -> void:
	print("Collected %s" % item.name)
	item.queue_free()

Note your object still has the properties of a RigidBody2D, and it can be collected by the Player. You let the Player destroy the object itself once it’s done with it. Alternately, you can add it as an item in the inventory, and not destroy it, or store it so if the user drops it, it reappears. (Up to you!)

I did what you said, and I’m getting an error because the “player” isn’t declared in the item script. Short of declaring a packedscene for the player I’m not sure how to make it see the player node.

Sorry typo.

extends Area2D


func _ready() -> void:
	body_entered.connect(_on_body_entered)

func _on_body_entered(body: Node2D) -> void:
	body.collect(self)

That makes sense. Now to get it into the inventory array, do I just have to declare it as a variable then append that into the array as part of the collect function?

Yup. That’ll work!

Okay so I tried what I said and it keeps throwing an error

Here’s the function script:

func collect(item: Node2D) -> void:
	print("Collected %s" % item.name)
	var inv_item = item
	inventory.append[inv_item]
	print(inventory)

Here’s the error, I don’t know what it means:

E 0:00:04:239   collect: Invalid access to property or key 'Logo:<Area2D#40399537655>' on a base object of type 'Callable'.
  <GDScript Source>player.gd:54 @ collect()
  <Stack Trace> player.gd:54 @ collect()
                area_2d.gd:7 @ _on_body_entered()

What’s the Logo object it’s talking about?

It’s what I ended up making the Item so I knew exactly what I was seeing when it ouput the name

1 Like

And what is the script for that?

Nothing new, it’s just what I named the node in the scene tree

What kind of node is it?

It’s an area2d

And that’s line 54 in player.gd?

append() is a function, so you need to use round brackets for calling it.

	inventory.append(inv_item)
2 Likes
func collect(item: Node2D) -> void:
	print("Collected %s" % item.name)
	var inv_item = item
	inventory.append[inv_item] #Line 54
	print(inventory)

Good call, I never would’ve noticed that.

Yup, that’s the problem.