Hi! Happy Holidays!)
Thank you for your response, I am really happy that you found Zara helpful for your project! 
I can describe how I did in-world items in the game that Zara was initially written for, maybe you’ll find something useful in the text below 
I had a thing called RaycastController. It was a MonoBehaviour class that was checking if player points to an item that has some tag (or layer I don’t remember exactly) that tells it that it is an interactable thing. As soon as player pointed to an interactable object, it remembered that object.
I had also InteractionController that looked if RaycastController recognized any object as interactable, and if so, it asked object what tools can be used, and displayed an onscreen menu thing with only correct set of tools from the player’s inventory. After that it waited for user to choose the tool. When player picks the tool, InteractionController passed selected tool name to the interactable object on which the action is taken place, and got list of inventory items in return. All those new gathered items are added to the inventory.
I had an abstract MonoBehaviour class ObjectDescriptionScriptBase that had a set of properties like list of names of tools that can be used to interact with the item. For example, “Tree” interactable object can produce leafs if interacted with bare hands, and branches if interacted with the knife. It also contained a flag to describe if this item can be interacted without tools (like moss on a rock, it can be scraped only with the knife).
ObjectDescriptionScriptBase had a method GetItemsFromObject that was triggered when user interacted with an item using some tool (or bare hands). In this method you could describe what player will gain after the interaction. For example: you have a tree, and player chose to use knife to interact with the item. Player clicked (or pressed E or something), and this method is called (by the InteractionController as I described above). This Func receives argument string “Knife”, and checks: okay, player used Knife, we must return five sticks [new List(new[ ] {Stick{Count=5})].
Or you have an item called Medkit. This function on interaction with this item will return something around twenty items – the entire medical center
return new List<IInventoryItem>(new[]
{
(IInventoryItem) new EpinephrineSolution { Count = 5 },
(IInventoryItem) new DisinfectingPellets { Count = 25 },
(IInventoryItem) new Pin(),
(IInventoryItem) new Acetaminophen { Count = 20 },
(IInventoryItem) new Antibiotic { Count = 30 },
(IInventoryItem) new Aspirin { Count = 50 },
(IInventoryItem) new MorphineSolution { Count = 5 },
(IInventoryItem) new Loperamide { Count = 15 },
(IInventoryItem) new Sedative { Count = 35 },
(IInventoryItem) new Oseltamivir { Count = 16 },
(IInventoryItem) new AntiVenomSolution { Count = 5 },
(IInventoryItem) new Bandage { Count = 10 },
(IInventoryItem) new AtropineSolution { Count = 5 },
(IInventoryItem) new NeedleAndThread(),
(IInventoryItem) new DoripenemSolution { Count = 5 },
(IInventoryItem) new AntisepticSponge { Count = 50 },
(IInventoryItem) new Plasma { Count = 2 },
(IInventoryItem) new SuctionPump { Count = 1 },
(IInventoryItem) new EmptySyringe { Count = 15 },
(IInventoryItem) new BioactiveHydrogel { Count = 3 }
})
So for every interactable item type I had separate script based on ObjectDescriptionScriptBase that describes how player can interact with the object, and what will happen after the interaction.
Extinguished campfire can give you ash for example, that can be used to treat wounds.
Simple interactable object example:
public class ElbaTreeDescription : NatureItemDesctiptionBase
{
public ElbaTreeDescription()
{
Name = "ElbaTree";
CanBeUsedWithoutTool = true;
AvailableToolsToPerformAction = new List<string>(new[]
{
InventoryController.CommonTools.Knife,
InventoryController.CommonTools.SharpDebris,
InventoryController.CommonTools.SharpenStone
});
}
public override List<IInventoryItem> GetItemsFromObject(string toolName)
{
if (toolName == InventoryController.CommonTools.Knife || toolName == InventoryController.CommonTools.SharpDebris || toolName == InventoryController.CommonTools.SharpenStone)
return new List<IInventoryItem>(new[] { new ElbaTreeSticks() });
if (toolName == InventoryController.CommonTools.Hand)
return new List<IInventoryItem>(new[] { new ElbaTreeLeaves { Count = 7 } });
return null;
}
}
So all I needed to do to add a new interactable thing – is to create a prefab, create ObjectDescriptionScriptBase-based class, describe in it behaviour I want, and add this class to a prefab, that simple 
This approach lets you describe other interactions as well, like interaction with a campfire. You can light it, extinguish it, collect ashes – all inside GetItemsFromObject, just checking for a campfire state and a tool used. Interaction can return null and just do a thing, for example, light it up. Actually, I needed to name this method simply OnInteraction(string toolName) )
One script for all items – is handy, but trust me, as your game grows, this “unversal” script will grow with it as well, and at some point it will become a mess. The smaller pieces of code you use the better. It is less convenient to create a separate script for every interactable item type, but in a long run it will save you hours of trying to untangle the mess) It’s just my opinion of course
But it is based on 15 years of enterprise coding experience 