Godot Version
4.3
Question
I’m new to observable patters and I’m looking for an optimal way of implementing this inventory system. What I have works, but I am wondering if there are improvements that could be made.
There are 5 components that make up my existing system. Interactor.gd
, Interactable
, Pickup
, Item
, and Inventory
.
Instead of having the Interactable
driving the interaction based on input, my system collects all nearby interactables in the Interactor
and returns the one closest to the center of the screen so you dont need to be looking directly at it to pick it up. The Interator
exists on the player and controls gathering all of the interactables within its vicinity and sorts them and assigns the interaction that has focus.
When the player presses the interact key, if there is a focused interactable it will call its interact function and call in self as the caller.
Because interactables are meant to be generic and the Interactor shouldn’t care about what the interaction will do, I don’t pass down any additional information into the interaction. Instead, the interactable node emits a signal that it’s child components like my Pickup
script are monitoring to handle a pickup interaction.
This all felt good until I needed to get the item from the Pickup
and put it into the Inventory
that exists on the player. I don’t want to pass down the inventory through the signal chain because then it would know about things it shouldn’t, so instead I get the inventory of the player by first getting the parent from the interactor node and then getting the inventory node. This works because I know the structure of the player, but it doesn’t feel flexible.
So what I’m wondering is if there is a better way to get a pickup interaction to communicate with an inventory from the thing that interacted with it.
I removed code that isn’t relevant to the issue
Interator.gd
extends Area3D
#region Locals
var _focused_interactable: Interactable
var _interactables: Array[Interactable] = []
#endregion
#region System funcs
func _input(event):
if Engine.is_editor_hint(): return
if not event is InputEventKey or not event.is_action_pressed("interact"): return
if _focused_interactable:
_focused_interactable.interact(self)
func _process(_delta):
if Engine.is_editor_hint(): return
_get_nearest_interactable()
#endregion
#region Signal funcs
func _on_area_entered(area):
if area is Interactable:
print("{0} entered interaction area".format([area.name]))
_interactables.append(area as Interactable)
func _on_area_exited(area):
if area is Interactable:
print("{0} exited interaction area".format([area.name]))
var index = _interactables.find(area)
if index > -1:
_interactables.remove_at(index)
print("You have {0} items.".format([%Inventory.get_items().size()]))
#endregion
#region Local funcs
func _get_nearest_interactable():
if _interactables.size() == 0:
_focused_interactable = null
return
var nearest_interactable = _get_nearest_node(_interactables)
# Stuff happens to determine the focused interactable
_focused_interactable = nearest_interactable
func _get_nearest_node(objects):
# Implementation
#endregion
Interatable.gd
@tool
class_name Interactable
extends Area3D
#region Signal Defs
signal interacted(root: Node, interactor: Node)
#endregion
#region Public funcs
func interact(interactor: Node):
print_rich("[color=Cornflowerblue]Interacted with:[/color] {0}".format([name]))
interacted.emit(self, interactor)
#endregion
Pickup.gd
@tool
class_name Pickup
extends Node3D
@export var item: Item :
set(resource):
item = resource
update_configuration_warnings()
func _get_configuration_warnings():
if not Engine.is_editor_hint(): return
var warnings = []
if not item:
warnings.append("Must attach an item to the pickup")
return warnings
func _on_item_pickup_interacted(root: Node, interactor: Node):
print("You picked up a: ", item.name)
var inventory = interactor.get_parent().find_child("Inventory") as Inventory
inventory.add_item(item)
root.queue_free()
item.gd
class_name Item
extends Resource
@export var name: String
@export var icon: Texture2D
Inventory
class_name Inventory
extends Node
var _content: Array[Item] = []
func add_item(item: Item):
_content.append(item)
func remove_item(item: Item):
_content.erase(item)
func get_items() -> Array[Item]:
return _content