8 minute read time
Fundamentally, the gameplay loop can be reduced to:
Pick Up Can → Examine / Identify → Place in the Correct Fridge
Because almost the entire game revolves around moving physical objects between different types of storage, I wanted the underlying system to be as generic as possible rather than building something specifically tied to cans and fridges.
That led to what I have been calling the Container / Containable ecosystem.

A can is a Containable.
A fridge is a Container.
The player’s hands are Containers.Containables are contained by Containers.
The Container Component
The Container Component can be added to anything that should physically hold Containables.
A single actor could have multiple Container Components, and as long as it implements the Storage interface, it is able to uniquely implement how each Container is used in different contexts. For example, the player character has a Container Component for each hand.
At the moment, this component assumes its contents are arranged in a two-dimensional grid. This maps particularly well to the current use cases: cans arranged into rows and columns inside fridges, as well as the slots representing the player’s left hand.
The number of rows and columns, along with the spacing between them, are exposed as UPROPERTY values.
Whenever one of these properties changes in the editor, the Container automatically regenerates its slots and updates their positions. This makes it very quick to resize a fridge or experiment with different layouts without having to manually repositioning every slot.

A naive implementation would be to instantiate a USceneComponent for every individual slot.
That would work, but consider a single fridge may contain dozens of slots, and there will eventually be many fridges throughout the level. Creating hundreds of Actor Components for the sole purpose of referencing transforms would be terrible.
Ultimately, the runtime logic only needs to know one thing:
Where should the Containable end up? A transform is enough.
Therefore, each slot is deliberately very lightweight.
It is just a struct containing an FTransform with UPROPERTY (meta = (MakeEditWidget)).
Using this super lightweight struct also makes the layout much easier to inspect and iterate over.
When the Container Component is selected, the slot transforms are rendered as editor gizmos.
The slots clutter the viewport when I specifically select the Container Component, so they are hidden by default and come into view only when I need them to. Even though they are automatically generated, you can adjust them individually if you wish to.
The Containable Actor
The other half of the system is the abstract AUltraContainable actor.
It currently implements several interfaces (IUltraInteractableInterface, IUltraHighlightableInterface, IUltraContainableInterface, IUltraPlacementPreviewableInterface)
The idea is that this class owns all of the behaviour that is common to an object which can be picked up, stored and transferred between Containers.
It knows how to interact with the Container system, how to expose placement previews and how to provide visual feedback when the player focuses it (more on this later on).
It also owns its replicated Container state.
When a Containable changes Containers on the server, that change is replicated to the clients. Each client can then attach the object to the new Container component while preserving its world transform, and smoothly transition it towards the transform of its assigned slot rather than snapping instantly into place.
The actor only ticks while this relative transform adjustment (lerp) is in progress, and disables ticking again once it reaches its target. The duration of this animation is configurable, of course.
That means the same mechanism works whether a can is being picked up from the floor, moved into the player’s hand or placed inside a fridge.


The Can
Finally, we get to what the entire game is actually about.
AUltraCan inherits from AUltraContainable.
By this point, all of the complicated behaviour has already been solved by the parent class. The Can itself can focus primarily on what makes a can a can: its identity. Size, visuals, branding and whatever can-specific gameplay behaviour gets added later.
The Interaction Component
The player has an Interaction Component responsible for determining what they are currently looking at.
It continuously traces along the Visibility channel looking for actors implementing the Interactable interface. When the target changes, the interface is used to inform the previous and new targets that focus has been lost or gained.
Interactables by default know to toggle their white outline when focus is gained and interaction is effectively available.
This outline is a Post Process Material I have created that renders based on any visible meshes that have their Render CustomDepth Pass set to true.
This effect is particularly important when there are hundreds of cans piled together. Without some form of highlighting, it can be extremely difficult to tell which can in the pile you are about to pick up.

When the player interacts, the Interaction Component simply invokes the interaction on the currently focused actor.
For a Containable, that interaction ultimately results in the object being transferred into one of the interactor’s Containers through its Storage interface.
This separation means the interaction system itself does not know about Storage or the Container / Containable ecosystem. It only knows that it has found something interactable and that the player has requested an interaction.
A Physical Challenge
One thing I noticed while playing Librarian: Tidy Up the Arcade Library! is that, once the books have settled, they are effectively left dormant. Pick one up from the bottom of the pile, and the remaining books are left suspended mid-air. Looks jarring and very cheap.
I was originally planning to take the same approach with the cans for the sake of simplicity 🫣 but after experimenting with the piles I quickly realised that there is something extremely satisfying about pulling a can from the bottom and watching a bunch of them fly across the room. It feels very cartoonish and extremely on point with the chaotic vibe I want the game to have.
Because of that, I do not want settled cans to simply become permanently inert.
Instead, picking up or disturbing a can should be able to wake the nearby cans around it and allow the pile to react somewhat naturally. With thousands of physics objects in the level, however, I obviously cannot afford to keep everything awake and simulating at all times.
From previous experience with Chaos (UE5’s physics engine), I already know that I will need to disable its contact-island-based wake behaviour and implement my own dormancy logic, giving me much tighter control over defining groups of cans that are allowed to simulate.
The default behaviour would form a huge contact island, and waking one can up would wake up its thousand neighbours, all on the same frame.
Multiplayer makes this even more interesting. I will need to be deliberate about which cans wake up, which ones begin replicating movement, how long they remain active, and when they can safely return to a dormant state.
It absolutely does not need to be perfect, but I yearn for a good physics and optimisation challenge.
It Just Works
As an extra bit of chaos, I wanted players to be able to steal cans directly from each other’s hands.
I did not have to implement anything new for this!
Because the player’s hands are Containers, stealing a can from another player works the exact same way as taking one from a fridge or picking one up from the floor.
So, amusingly, multiplayer theft was already implemented before I even thought of testing it.

Conclusion
None of the behaviour in this dev diary is specific to cans.AUltraContainable is deliberately abstract.
Containers deal with AActor references rather than concrete Can classes, and communication happens through interfaces instead of hard dependencies between systems.
If I eventually decide that the player should be able to pick up crates, bottles, toys, or some completely different type of object, the underlying storage and interaction logic does not require any modification. In fact, I could probably make a pallet of crates, where the pallet is a Container for the crates, and the crates are Containers for the cans.
Crates would be Containables and Containers simultaneously. There is no limit.
The code remains modular, expandable and, as a nice side effect, portable.
If I ever make another game centred around physically sorting or storing objects, the code would remain largely untouched, and asset production would be the core endeavour.
And with all of that infrastructure in place, I can finally start worrying about a much more important problem:
How many more ridiculous energy drink cans can I fit on the floor before the game starts falling apart?