How to best structure the connection between the UI and the inventory components

Godot Version

4.4

Question

I’ve been working on a VaultSystem for some time now (inventory, storage, loot chests, etc.).
I’ve already built quite a bit for testing, and I’m currently at the point where I’m unsure how to best structure the connection between the UI and the component.
I work with .net C#

UI
Inventory with visible slots

Player
VaultComponent (Add, Remove, Drop, etc.)

Chest
VaultComponent (Add, Remove, Drop, etc.)

I want to be able to move items between slots via drag & drop in the UI.
However, items should also be added to the VaultComponent through initialization, and the UI needs to reflect those changes as well.

Now the question:
What would be the optimal solution here, does anyone have an idea?
For clarification: I know how Drag & Drop works, and I know how to implement my internal VaultComponent.
This is purely about the architectural structure.

Option A
UI updates VaultComponent via signals
VaultComponent updates UI via signals

Option B
UI has a binding method to the VaultComponent and directly executes methods
UI gets updated by the VaultComponent via signals

Option C
???

This is one of those questions I have all the time. :sweat_smile:

Option C might be MVC or MVP. Basically you need another object that is the connection between your UI and the vault.

I just wanted to mention this option, BUT I’m really not sure if this is suitable for your case. I used this only once for web dev a long time ago.

Here is an explanation/example:

1 Like

The pattern sounds interesting, thanks for that, i’ll take a closer look at this. :slight_smile:

Hey Triky, I’ve dealt with something like this before. What worked for me was using a middle controller that handles the communication between the UI and the VaultComponent. So instead of the UI and Vault talking directly to each other, they send signals to the controller, and it updates things as needed.

It helped keep things organized and easier to manage, especially when more systems started interacting with the inventory. Just an idea if you’re looking for a clean way to handle the connection. Curious to see what you end up going with!

1 Like

That’s the MVC/MVP approach @trizZzle suggested above. It’s common in web development. There’s a discussion about MVC here in this thread from last month: Awaiting Animations Triggered by Signals It is a good approach, and it will work, but I personally think it’s overkill for this application.

@Triky Based on your requirements, I think you would benefit from defining a relationship between VaultComponent and UI. In Object-Oriented Programming, we can break down everything into is-a and has-a. In other words, an object that needs functionality either is an object with that functionality, or has an object with that functionality. Godot does not support multiple inheritance, and encourages Composition over Inheritance with its Node system.

So let’s start with a Chest.


Chest
So Chest either has a VaultComponent, or it is a VaultComponent. Likewise, it either has a UI, or it is a UI Let’s assume that a Chest is an object that appears in-game either as a 2D or 3D object. I’m going to go a step further, and assume it can be either or both, because you might want to use this code again on another game. By assuming that, it will inform our decision on is-a vs has-a.

If we know that Chest is an object that appears in the game world, it makes sense that its appearance and interaction with the player (like an Area2D/3D to trigger interaction) is all a part of the object. Now, we could inherit from VaultComponent or UI. It seems to make less sense that it would inherit from UI, because it’s not UI - it’s an object on the screen. So let’s say it has UI. But now what about VaultComponent?

Chest could inherit from VaultComponent, but we will run into two issue there pretty quickly. First, Chest is in the world. It is either a Node2D or a Node3D because it needs positional data in the world. So to inherit from VaultComponent, VaultComponent would need to inherit from Node2D or Node3D. Not necessarily an issue, because you’re only doing one game and you don’t need to future-proof.

The second issue though, is that when we go to design how this looks with Player, if we decide Chest is-a VaultComponent, then does Player also become a VaultComponent or do we create an Inventory object that is-a VaultComponent, even though it doesn’t have positional data because it does not exist physically in the world.

Based on this, I would recommend that Chest is a Node3D, and has both a VaultComponent and a UI. (You may have an objection here that your UI system is its own thing and monolithic - and I would ask you to hold onto that thought.) We are not done though. The original question, was how do these two things interact?

  1. Signals?
  2. Direct connection?
  3. Profit???

Let’s say that signals is our default fall-back position for a second. If we are going to use a direct connection, what is the relationship between these two objects? We’ve got two possibilities we have discussed: is-a and has-a. There’s actually a third: None. And a fourth: Sibling.

Does it make sense for VaultComponent and UI to have no relationship? Not really. You can’t have one without the other. In fact, they are suspiciously like the Model and View parts of an MVC architecture.

Does it make sense for them to has a sibling relationship? Well, if we go the MVC route - sure. They don’t technically need to know about one another. But, what exactly does that get you? It gets you another object. VaultComponentWithUIContainer to wrap up everything, including the business logic. Or… something very decentralized (which then we would fall back on our signals.)

But really, if we look at the two pieces, they cannot function independently. One without the other is useless. So it makes sense that they know about one-another. So how can we combine them? Is it an is-a or a has-a relationship? Well, we’ve already determined that one cannot exist without the other, so an is-a relationship doesn’t make a lot of sense. That leaves us with has-a. So which has which? It probably makes more sense that the VaultComponent needs a UI, than the other way around.

So if UI is a node on the VaultComponent scene, the Chest would have VaultComponent as a node in its scene. And VaultComponent and the UI would just talk directly to one-another. When Chest gets an “interact” action from the Player, it calls the open() function on VaultComponent UI becomes visible, and either asks or is fed a list from VaultComponent of what’s in it. When the Player drags-and-drops, the UI calls the appropriate function on VaultComponent.

If you have an complete, integrated UI, this may feel like an issue. How do you make sure there’s enough room in the UI, etc? There are a couple of ways to handle this. First is floating windows = just make it a feature that the user can move chests, inventory, etc. around on their screen. Second, you can actually pass the VaultComponentUI as an argument signal to the MainUI and have it insert it in the right place.

Now that we’ve looked at a Chest, let’s look at Player.


Player
Really the player has an Inventory. In most single-player games, that’s usually single screen. In an MMORPG, it’s lots of mini-containers in your inventory that open up for more space. However you implement it, I would recommend that you have an Inventory node on your player, and inside that your VaultContainer In that way you can have your inventory open/close code inside your Inventory node and not clutter up your Player node code. But really, it’s up to you. Because at this point, you can drop that VaultContainer on anything you want and just interface with it.


Anyway, these are just my thoughts. The MVC approach will work, but as I said in the other thread, it’s really most useful in a distributed client-server architecture - which you don’t have here. Even if you had a multiplayer game, I wouldn’t recommend MVC for the VaultComponent itself. That said, I love MVC. When done well it’s very clean and easy to read. When done poorly - it becomes a mess of spaghetti code. So if you do use it, I recommend you just make sure you have your model, view and controller clearly defined.

2 Likes

I’m still experimenting a bit.

@dragonforge-dev Your solution does sound a lot simpler, but it brings me to the point where I end up with multiple CanvasLayers, and from what I understand, that means I can’t use simple drag & drop anymore.

There’s probably a solution for that too, but yeah, I still need to experiment a bit more.

1 Like

You can have one CanvasLayer in the MainUI and pass the UI portions of your VaultContainers to it through a signal when they are open. Let MainUI determine where on the CanvasLayer they go. In Godot, everything is passed by reference, so you’re not losing performance or using extra memory because all you’re really passing is a memory address.

1 Like