I am fairly new to C# and Unity so please forgive me if I don’t get the terminology 100% correct.
I have a class of scriptable objects, ItemObjects, that has several classes inheriting from it (FoodObject, ToolObject, etc). In order to get my inventory slots working correctly, any game object carrying components that reference these scriptable objects always hold them in variables of the base class, ItemObject. For instance, an apple has an ItemObject variable that holds ItemObject (FoodObject).
I can pass these scriptable objects around easily but I’ve just begun to implement item-type specific functions. If you activate an apple, you regain some health. If you activate a sword, you equip it and your damage increases. All these items are activated from the inventory the same way and I was expecting to be able to draw from each item’s class instead of only from the base (because they’re all held as the base ItemObject.)
Is there any way a function can get passed an ItemObject variable that actually holds an ItemObject (FoodObject) and access the values of the FoodObject instead of just those of the base class. I hope that made sense.
//1: unsafe
SomeType obj = (ISomeType) abstraction;
//2: safe
if (abstraction is SomeType)
var obj = abstraction as SomeType;
obj.Do()
//3: safe
if (abstraction is SomeType obj)
obj.Do();
If it works for you then it’s great, but usually it’s not a good thing to use it. The whole meaning of inheritance, interfaces and type hierachies that you have some abstract type without caring what exact type is and without accessing underlying implementation.
Otherwise it can turn into a big switch when you check type, cast, get data and do whatever you want. This is the situation people tried to escape with OOP.
Think about your design, maybe all your tools require player to work, then make abstract function Apply in ItemObject and pass player in it.
If you have apple it will apply regeneration buff to your player.
If you have axe and it need to chop down the tree, then player may have public GameObject target which is calculated every update, something like that.
Then when you press use button, you will call Apply function on this smart ItemObject which will do its work.
If your method expects a FoodObject, pass it a FoodObject typed as FoodObject (the parameter is ‘FoodObject’ and not ‘ItemObject’).
If it expects a ToolObject, pass it a ToolObject typed as ToolObject.
If the method expects an ItemObject, but then discriminates on if it’s a FoodObject or ToolObject internally… then why did it accept an ItemObject? What if I pass in a DrinkObject or a VehicleObject? What does it do now?
As Tekrel stated, this sort of implementation generally breaks the entire point of what is called “polymorphism” (the concept that you can treat a set of objects similarly because they implement a similar interface/inherit from a similar type).
…
So… instead of explaining hacks to work around this intentional design limitation.
I rather instead ask what specific problem are you trying to solve? Show some example code of how you’d LIKE something to behave (it doesn’t have to be working code), and maybe we can suggest a way that actually accomplishes the task you desire in a design that meets the limiting design factors of OOP and polymorphism.
NOTE - yes, OOP includes limitations on you when designing your architecture. That is what a paradigm is about doing. You’re limiting the scope of what you can actually do to better design a system that is easier to manage (though the effectiveness of OOP in doing this can be a debate separate from your question). Total freedom could be had in the form of more bare-metal languages that allow direct memory manipulation. But of course this freedom comes at the cost of both complexity and freedom to design something that is fragile and fails easily. These limitations that paradigms like OOP put in place are made to make more easily maintained code. Breaking it is doable and can get you what you want, but if you don’t understand why/how you’re breaking those rules… you can get yourself into a design problem that you can’t get yourself out of easily.
So not to hijack OP’s thread, but this topic is relevant to myself as I’m also designing an inventory system set up in a similar way, and I’m hoping to start up some good conversation. No doubt answers to my questions will be useful to OP as well.
Right now I have a similar structure: a parent item class, with children item classes (all scriptable objects), and I currently use interfaces to add extra information (such as ISellable adding a sell value, ICraftable adding a crafting recipe entry). Thus, so far, all extra functionality for child item classes is done with interfaces.
If I were to have an inventory that holds a list of the base item class, and I want to be able, say, select a food item and be able to activate and eat it, what’s the correct way to structure that code wise?
Because currently I’m type-casting, checking first if an item is the right type THEN doing the appropriate action. Eg: selecting it displays what actions you can do and what information it has (by type casting through possible interfaces), which admittedly feels a bit hacky as the inventory code has to be hard coded to go through all interfaces I presently use.
Is there a better way of handling that in an inventory that lists different types of items you can hold?
If they’re activated in the exact same way, you can have a virtual (or abstract) Activate method in ItemObject that’s overriden in the child classes.
If you have specific actions that only certain types of items can do, then you need to check what kind of item it is, no matter how you implement it (inheritance or a type enum or whatever). You can’t get around that.