Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Architecture: Using an Inventory system to learn how to decouple my code

Discussion in 'Scripting' started by Sun-Dog, Oct 15, 2015.

  1. Sun-Dog

    Sun-Dog

    Joined:
    Mar 23, 2009
    Posts:
    144
    So, I'm still struggling with game architecture; trying to make things as neat and organized as possible, over "just working in a spaghetti manner".

    I've created quite a few small games and systems that work 'just fine', but when I break them down, they are very tightly coupled and brittle, but they are small (if not tiny) so it doesn't really matter. They run.

    My current example project is an inventory system. It works (worked) just fine, but it was really ugly. Now, I'm trying to separate the UI from the Inventory Management, and keep every piece as separate and self sufficient as possible.

    I want the inventory system to only do things related to the inventory - add items, remove items, etc.

    I want the UI to then take the data stored in the inventory system and display it in the appropriate panels.

    But even from the outset of trying to separate the code, I keep stumbling on things that are shared, or need a common entry point, or need a place to live. Part of what I'm finding is - how do all these things find and have references and communicate with each other. In one big class, it's pretty ugly, but it works. Split things up, and then they need to have references - this leads to Public or [SerializeField] variables dragged in the inspector, or Find by Type, or Singleton/Static references... it all starts to get cluttered. (I'm also reading that people frown on public references and say singletons are to be avoided at all costs!)

    Take, for instance, a dragging icon. With Unity's retained UI, I need a GameObject with a sprite on it... but I only need one for the entire system. But several panels needs to have access to it, and it can be passed from one panel to another.

    Do I create one of these on the fly and destroy it afterwards? This fails the pooling test. If I don't "create and destroy", then I need to create this GameObject somewhere and keep a reference to it, and serve that reference to a number of other classes.

    Or even the panels themselves - click on a chest and I need to initiate a loot action - not only does it need to work with the inventory system, but needs to open the loot window and the inventory window... if a reference to the to windows were in the inventory system, then there can be one call to the inventory system and the inventory system keeps track of the windows and what's open and what's not...

    Shouldn't the inventory be encapsulated to the point where it's only managing the items in the inventory itself? Shouldn't the inventory have no knowledge of the UI? And the UI should simply request the data from the Inventory as it needs it?

    I find I keep adding these things to the Inventory system, and the inventory system has a static reference, and the next thing - like a magnet in a junk yard - I have references to the DraggingIcon, MaxLootableDistance, EmptySlotIcon... things that are easy to populate in one place. But it seems - ugly - and makes the Inventory system into a catch all that's doing more than maintaining the inventory.

    Should I be using more events? The act of clicking on the chest fires off an event that then triggers the panels? The panels request data from the inventory? But then who owns the details like a reference to the DraggingIconObject and the MaxLootableDistance and StartingBag?

    I'm just at this point in my coding were I'm starting to look for style, flexibility, maintainability... rather than just brute force coding.

    Trying to focus the discussion, how would you architect an inventory system? Not on the code level, but the broad strokes?
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    Wall o' text ahead. I figure you gave us a nice detailed question. I'll give you a nice detailed response. I'm also drunk, and I get talkative when I'm drunk.



    First and foremost, I approach everything as an 'entity'. It's like GameObjects, but expanded. Most 'things' in the scene are made up of several GameObjects... but there is always that parent GameObject, the 'root' of the 'thing', that parents all its parts (colliders, joints, etc).

    I call this the 'entity', and I have an 'entity' script that I slap on that root gameobject. Here you can see the base version of it:
    https://github.com/lordofduct/spacepuppy-unity-framework/blob/master/SpacepuppyBase/SPEntity.cs

    Of course, each game I make usually needs a little bit more, so I inherit from SPEntity and create something like a "MyGameEntity".

    But SPEntity handles the one critical part... that being making it quick and easy to determine if a GameObject is part of some entity, and to get the entity script off of it. Which is what the 'GetEntityFromSource' method I have on the SPEntity script.

    If I have some OnCollisionEnter code, I immediately pull the entity from the collider so I can work with all of its parts. If that collider does not come back with an entity... then it's arbitrary collision data and I just drop out.


    Furthermore you can search the entity for scripts. GetComponentInChildren can facilitate this. The existence of a some script represents it being that kind of thing.


    You get references to things on events.

    Things like collisions, trigger enters, raycasts, etc, they come with references to the thing that the event correlates too. No need for strongly coupling everything to each other... you receive the individual thing when it happens, and you just pull its entity from it.


    So now with inventory. You'll have some specific things.

    1) InventoryScript - this will just be a container. It'll probably have methods to Add/Remove/Count/etc... things that collections have. That's ALL it does. You can enumerate the collection for the inventory it has. It might sort the inventory. But aside from that it's all it does.

    2) IItem - this would be a contract, an interface, for any kind of script that you could attach to an entity that means its inventoriable. The script can do whatever it wants... just make sure it implements this interface. It could be simple:

    Code (csharp):
    1.  
    2. public interface IItem
    3. {
    4.     string DisplayName { get; } //a pretty name of the item
    5.     float Weight { get; } //maybe your inventory has weight limits
    6.     SPEntity Entity { get; } //a quick reference to the root entity script that represents this item
    7. }
    8.  
    3) Actions - these a structures of 1 or 2 scripts that simulate the action. Maybe it's a TriggerBox and a script that listens for when an entity that is tagged as the 'player' to enter. If then the input of "A" is pressed while in that box, it triggers a sequence of events: play open animation on some treasure box, signal player to animate a pick up animation, etc.

    The treasure box will have a 'Open' method that returns the inventory items it has.

    All it does is it got a reference to the 'Player' that entered its trigger area, and a reference to the Treasure box it controls (yes, this would be wired through the inspector onto a serialized field). The player script has a 'Inventory' property, and the treasure box has its contents returned to the action on open. The action script would just take the items returned by the treasure box and add them to the inventory on the player.

    No singletons, no hard references. It implied them by the context of the situation going on.

    4) UI - ui is a little more specific, it's going to need a reference to the player. I usually always have a singleton in play in the game for the none scene things in play. Singletons aren't bad... they just need to be used correctly. Mine is usually called 'Game' or 'GameScene'... there's only ever ONE game going on... so this being a 'Singleton' isn't a big deal... it IS singular.

    It usually deals with spawning the player on the start of the game... so therefore has a reference to the player.

    The UI therefore can ask the Game for the current player script. The Game singleton can actualy take care of creating the UI and hand it its reference on start... therefore, the UI actually doesn't pay attention to the Game... the Game pays attention to the UI. That's its job... to make sure the scene exists, a player exists, the UI is there, so on so forth. So it creates instances of all those things on start and lets them run.

    So the UI has some player its showing inventory for.

    When the event occurs (player input?) to show its contents. It just enumerates over the items in the inventory of hte player its observing and draws the appropriate things to screen.

    Maybe it's just a text list of the 'DisplayNames' from the IItem interface of the inventory item.

    Or maybe its an icon. Which is designated by the IItem interface of the inventory item (just add the needed properties... all items then just need to have those defined... it's not inventory unless it has its icon defined).

    If there's some shared icon that's always used for dragging or clicking or something. The UI creates that... it maintains that... it's part of the UI structure. Note, UI can be made up of several classes. Again delegating the needed references down ward.

    NOTE, having references isn't bad. It's tightly coupled references that aren't good.

    A UI Icon is needed for UI to exist in and of itself. The UI isn't UI without it. So yeah, it SHOULD have a reference to its icon.

    But the treasure chest can exist without the need for UI. So it doesn't have a reference to it.



    5) Polymorphic contracts -
    Just like the treasure chest should be able to exist and interacted with despite if a player exists. The chest gets its player from the event that triggered it... the collider that entered its trigger box.

    Thing is, it doesn't have to be the player.

    The player entity needs a script for its inventory... that's all. So really... anything with inventory should be able to deal with the treasure chest.

    So lets reverse rolls... I know I said that the treasure chest could have listened for the A button... but lets not do that. That's a player action.

    Instead maybe the treasure chest has an triggerbox on it and a script called 'InteractableDevice' on it. InteractableDevice has a method called 'ActivateDevice(SPEntity entity)', which takes in the parameter of the entity interacting with it.

    Code (csharp):
    1.  
    2. public interface IInteractableDevice
    3. {
    4.     void ActivateDevice(SPEntity entity);
    5. }
    6.  
    When the trigger is entered. The player knows its in a trigger, and it starts waiting for the player input of a button press A. If it is pressed... the player calls ActivateDevice on the device its in proximiity of (it knows, because it got a reference during the OnTriggerEnter event).

    Thing is, now an AI unit can ALSO react to this event.

    An AI unit receives the OnTriggerEnter event, determines its in proximity to an IInteractableDevice, decides to interact with it (maybe add a 'type' property that classifies them as 'itembox', 'button', 'doorway', etc. It decides that yes it likes buttons, calls 'ActivateDevice' on the device, and it does whatever it does with the entity that interacted with it. No tight coupling needed.
     
    GoliathAT, Kiwasi and JoeStrout like this.
  3. Sun-Dog

    Sun-Dog

    Joined:
    Mar 23, 2009
    Posts:
    144
    Thanks for the reply, @lordofduct! Details are what I want to hear and need to know.

    I'm also willing to hear other opinions, if anyone has them, as I know there is never one way to code a cat.

    -

    I had not thought of Interfaces. This is probably because I've not used them that much. I'm aware of interfaces,but have rarely used them. I'll have to give them a spin. When it comes to things like actions and events, I'm just getting my head around them and what they can do.

    Your suggestion of IInteractableDevice is interesting to me. I did have the basic idea working in my original spaghetti code. I had all the "lootable" objects deriving from a base class called Lootable, and derived from it LootableObject and LootableItem. The LootableObject had a list of InventoryItems (and yes, I had a class InventoryItem that defined each item: Name, Icon, Type, etc.) and LootableObject had code for creating random loot lists based on parameters that could be set on the component. This would go on, say, corpses, chests, etc.. The LootableItem simply had a slot for one item, and this could be attached to singular items, like a sword on the ground or a statue with gems for eyes as a quest item. Now, arguably, LootableItem might not need to be it's own sub-class, but the difference was that LootableObject was random and LootableItem was specifically set to one item.

    I'll need to wrap my head around using an interface for this. It's probably a good example for me to practice using them. I'm aware of the IDamagable concept, where all Damagable items implement IDamageable, and are able to choose what to do when damaged, so I suppose ILootable or IInteracable should follow along the same lines. I've just never actually used them in production, so it's a bit intimidating! :D
     
  4. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,534
    @lordofduct always has great advice. Bookmark it; you'll be coming back to it again and again.

    At a higher level, I'll second @JoeStrout's recommendation to read Design Patterns. Read the real one, not Design Patterns for Language X, or Game Programming Patterns, which is completely different. Design patterns and other standardized architectural practices such as MVC will benefit you and anyone you work with.

    The architectural question you're asking has been worked on for a long time, and there are some fairly standard patterns. For example, if you tell someone that you're using an MVC (Model-View-Controller) architecture, they'll immediately know that you've decoupled Inventory Management (Model) from the UI (View), you have a clearly defined module that coordinates between them (Controller), and that most of the architectural pitfalls have already been thought out. Even if you're not working with anyone else, a standard pattern will give you a structure around which to write your code.

    Completely unrelated to the text above, I recommend going all-in with UnityEvents. They're really nice to work with.
     
    JoeStrout likes this.
  5. Sun-Dog

    Sun-Dog

    Joined:
    Mar 23, 2009
    Posts:
    144
    @TonyLi - thanks for the information. I'll get Design Patterns and see if I can wrap my head around it.

    Just an aside: I've heard this several of times "Just make sure you don't try to use a full blown MVC pattern, as it doesn't play well with Unity". Does this make sense to you? From what I've read, Unity doesn't follow pure OOP rules. (I think due to the componentized design?) Is there any rule of thumb to consider when reading up on design patterns when it comes to coding for Unity?
     
  6. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    4,044
    I'm pretty happy with my inventory system. It's a little more ambitious than most I've seen, since it does use full 3d objects for the drag and drop.

    The system that I use is pretty simple.

    90% of the code is really focused on the drag and drop slots and draggables. This is going to require far more code than anything else. The fact is that having strict control of whats draggable, when its draggable and where you can drop stuff needs a lot of attention.

    I ended up using a strict class hierarchy to handle most things.
    GenericDragDropSlot<T>
    - ItemDragDropSlot
    -- InventoryDragDropSlot (specific to inventory)
    -- CharacterEquipDragDropSlot (a slot on the character)
    -- StoreDragDropSlot
    - PortraitDragDropSlot (you can drag your dudes portraits around also)
    -- PartyDragDropSlot
    -- CharacterActivityDragDropSlot

    I do not strictly separate functionality here, each of these ui elements speaks directly with game data when whatever event triggers. Most of these subclasses are fairly small and range from like 20 lines to 50 (the largest handles character equipment slots and comes in at 180 lines). They are all monobehaviours.

    In terms of the data side, the tricky part isn't so much the inventory (these are lists of items) - the more complex part is templating items vs instancing them. The idea is that you want to separate the template for an item from the instances of the item, allowing stuff like item instance specific overrides and stuff. There's a lot of wiggle room for how formal you may make this (the templates could be just a config file, or it could be way more sophisticated).

    I would personally keep instance data in the game's save files, whereas template data is maintained outside of save games.

    You also need to map the data representation to the in engine representation. This is going to be done by string key at all times. If you have a formal prefab for an item in 3d or you are referencing a region of an atlas, there is going to be the potential for key mismatches and S*** here no matter what you do (there will be at minimum two strings that need to be typed individually and match - one string within the unity editor - one string in your item template definition).
     
  7. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,534
    Full-blown MVC can work fine; it just depends on how you design it. You might look at how Code Control implements MVC, or google around for some articles on MVC in Unity.

    I think the issue with MVC and "OOP" has more to do with inheritance. OOP is just packaging data and related methods into a self-contained object.

    Component-based design (which Unity embraces well) has always been preferred to deep inheritance trees where applicable, although both have their purposes. As Design Patterns put it: "Favor 'object composition' over 'class inheritance'." Inheritance and polymorphism were trendy in schools in the 90s, and a generation grew up thinking they need to force it into everything. As long as you think about composing behavior out of small components before creating deep inheritance trees of monolithic classes, programming in general, and Unity in particular, will be easier.
     
  8. Sun-Dog

    Sun-Dog

    Joined:
    Mar 23, 2009
    Posts:
    144
    @frosted - Interesting look on the inside. What's nice about hearing this, is knowing that some of my choices are not too terrible.

    On the data side, I have an InventoryItem class, which contains all of the details for each item as a concept - Name, Icon, Type, Stackable, Model, Encumbrance, etc.. These get stored in a database on a ScriptableObject saved as a data asset in the project. At runtime, the list of Items gets zipped up into dictionary. Then, throughout the use of the inventory system, I'm just passing around references to the InventoryItem. This sounds a lot like your templating vs. instancing! <Phew!>

    Speaking of ScrptableObjects, I did stumble across this recording from the London User's Group, where one of the presenters discusses ScriptableObjects in a way I hadn't thought of:

    This makes me want to consider testing the use of each item, not as an instance of a class, but a reference to a ScriptableObject.

    When it comes to my "slots", I did have a generic SlotBase class and then derived InventorySlot, EquippedSlot, BagSlot, etc., but it sounds like you've thought it through much more thoroughly... So I think I'll re-investigate how I've put that together (thanks!).

    Lastly, I'm also using strings to glue everything together - the name of the item, actually - as I look items up in the ItemList dictionary by name, as the kvp is <name, item>. I was wondering if there was a safer way of handling this - maybe some sort of hash-thing... I think the Unity project "stealth" did something like this (but I haven't finished the project - it was far too long for my time constraints).

    Thanks for the info... I'm still reading and digesting.
     
    Last edited: Oct 15, 2015
  9. Sun-Dog

    Sun-Dog

    Joined:
    Mar 23, 2009
    Posts:
    144
    @TonyLi - I just bought Design Patterns. The f****r is still selling at £29.99! and for and eBook! It's more than 20 years old! :D I'm sure it's worth it... (I did take a pass on downloading the Chinese OCR'd version. Too many formatting issues...)

    I like the componentized architcture in Unity, fwiw. I do use a little inheritance (like Base and Derived Slots and Panels...), but I tend to not use it that much - mostly because I'm unfamiliar with it.

    I'll try to work out a broad concept plan over the weekend.

    If anyone else has any imput, please do let me know!
     
    Last edited: Oct 15, 2015
  10. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,534
    Enjoy the book! It's worth every £. It's the kind of reference book that you keep handy on your shelf and pull down several times a year.

    I don't mean to sound like a composition zealot. I use inheritance, too. For example, the Dialogue System for Unity has an AbstractDialogueUI class that I've subclassed for Unity UI, leacy Unity GUI, NGUI, etc. And developers have made some really beautiful UIs with it. But, truth to tell, when I finally redesign it for Version 2.0, I'm going to abandon it for composition. It seems like every studio wants their UI to work just a little bit differently. Composition will make it easier to put together just the right parts without inheriting unwanted baggage.
     
  11. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    4,044
    Composition via monobehaviour can really kick ass. The biggest problem I have with using composition via monobehaviour is in larger scale stuff.

    My main HUD currently consists of around 440 game objects that are built out of 1201 components in the editor. This is all pure GUI, buttons, labels, sprites, etc. A bunch of additional stuff is generated at runtime. I don't want to dig through editor hierarchy to find that button component six levels deep, just to see what method the on click delegate is set to.

    Drag and Drop stuff tends to really be logic heavy - "I can't equip something in my off hand slot, because I am using a two handed sword" - "I can't wear platemail because I am a thief" - "I can't drag this sword to inventory because I don't have the money". Trying to deal with that stuff using composition via monobehaviour is not at all something I would want to manage, debug, or extend.

    The same kind of goes for doing stuff like data entry heavy scriptable objects. It's really great when you're dealing with small numbers of stuff. Once you reach a certain point though, some kind of text is just easier to manage (unless you invest significant time in building kick ass editors). Without some kind of really good custom editor, once you have like 100 items, you will really wish that everything you could possibly manage to move to text lived in plain text.

    Or at least, that's my experience. Maybe I just haven't seen better approaches, or maybe a lot of this is specific to my project. YMMV. I am not a super expert, and I am sure some people have figured out better ways.

    I will say that composition via monobehaviour is amazing for visual stuff, where you're layering in various effects and the like. I also built a dialog system out of pure monobehaviour composition that I think kicks some ass. In terms of building tools or libraries, I can definitely see the value, especially when those components are targeted in scope and really solidly tested and refined before release.

    I would just caution leaning too much on the editor for stuff that has a lot of logic or requires a lot of data entry.
     
    Last edited: Oct 16, 2015
  12. sdviosx

    sdviosx

    Joined:
    Jan 4, 2015
    Posts:
    22
    It apparently doesn't follow a pure CBD either. Go with what you are most comfortable with as with all design patterns as you scale you will soon find yourself opening a can of worms. UnityEvents are a good alternative for handling basic communication, their biggest benefit being able to interact with them in the editor. You could also go with a more generic event manager or with an ECS framework.
     
  13. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    How is it not pure OOP?

    Unity allows you to script in Mono/.Net, which is fully object oriented.

    It supports all the requirements:
    It supports inheritence, polymorphism, encapsulation, composition (note, components take advantage of the 'composition' aspect of OOP)
    All types are inherently objects, and can be treated as objects, including primitive types which can be boxed as an object.

    There is some syntax sugar that allows you to treat objects in a manner that syntactically doesn't appear object oriented... but actually is an object when compiled. For instance anonymous functions, which actually get turned into a special class type by the compiler with a weird name to avoid collisions.

    Some people are sticklers about the fact that you can define operators for types, causing you to be able to call functions in a manner that is dissimilar to a 'function call'... but then again, those too are just syntax sugar. Because really the compiler just converts the operator into a function call when compiled to IL.




    You mention the 'componentized design'. This is VERY OOP. That book you're purchasing has it in it as a classic design pattern.

    I have my copy right here in front of me, Table of Contents, Section 4 'Structural Patterns', 'Composite Pattern'... page 163 (32nd printing, April 2005)

    Turn to that section and you will see it.

    The ONLY oddity to the pattern they really attach is that the components of the composite object don't explicitly have to define the methods the composite object calls on its components. Usually this is performed with some contract (an abstract class or interface). In Unity they reflect out the contracts that the components obey (using their SendMessage system). This could be a point of contention for a lot of people about the "pureness" of its OOP.



    Even UnityEvents is OOP and falls under a simple classical design pattern. It's just the observer pattern (see page 293). Only that in the inspector during design time it looks like you're wiring it up in reverse, because you drop the object that is observing into the observed object. But if you go and try to do that in code, you actually have to do it in the traditional order of the observer pattern, where the observer adds a callback token to the observable event.





    What Unity does do is strong arm you into using the Composite Pattern.

    And it doesn't give you a traditional hook into the entry point of the program, and therefore makes setting up some of the other patterns a bit cumbersome... Singleton comes to mind... but then this is one of the Design Patterns in the 'Gang of Four: Design Patterns' book that EVERYONE argues about the legitimacy of anyways.




    With all that said, I have some annoyances with the lack of object identity of core tools in Unity.

    Time and Random being big ones that come to mind.

    Which is why I wrote my own Time and Random wrappers to give it some object identity:
    Time Contract:
    https://github.com/lordofduct/spacepuppy-unity-framework/blob/master/SpacepuppyBase/ITimeSupplier.cs
    Time Types:
    https://github.com/lordofduct/spacepuppy-unity-framework/blob/master/SpacepuppyBase/SPTime.cs
    CustomTimeSupplier:
    https://github.com/lordofduct/space...b/master/SpacepuppyBase/CustomTimeSupplier.cs

    Random Contract:
    https://github.com/lordofduct/spacepuppy-unity-framework/blob/master/SpacepuppyBase/IRandom.cs
    Random Types:
    https://github.com/lordofduct/space...lob/master/SpacepuppyBase/Utils/RandomUtil.cs

    Both with factories.
     
    Last edited: Oct 16, 2015
    Nigey likes this.
  14. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    UI is a tricky one. I constantly toss up between where the responsibility lies. Should the UI pull its updates from the game? Or should the game push updates to the UI? Or should they both interface through some other abstraction?

    That's where the RemoteField class in trend.me originally came from. It was an attempt to have UI that didn't require the game code pushing events and had no dependencies on the game code. The original use case was a flexible UI element that the player could attach to any object in the scene at runtime and get meaningful data. Basically an in game inspector.
     
  15. Sun-Dog

    Sun-Dog

    Joined:
    Mar 23, 2009
    Posts:
    144
    @TonyLi Started it last night, but have yet to get into any meat.

    @frosted
    This I def. agree with. I did start with managing all of my inventory items in a file maker pro database, exporting text, and then created an asset processor that imported the data (at least that's my memory - it's been a while). Then I moved to the scriptable object to keep all the data (and my workflow) inside Unity... and that's where I realized I needed to make a custom editor. I'm aware my editor leaves a lot to be desired, and when I get into serious numbers of items, I may go back to external data management, as "Am I writing a game? or a database?" seems very close to "Am I writing a game? or a game engine?" argument when it comes to using Unity. I balked at creating code that could search on anything more than an exact match or return multiple items, etc. - which would be a requirement for anything with a huge amount of items. The code does prevent two objects with the exact same string name, which is vital.

    @sdviosx Holy BambooShoots, more things to learn about! I've never heard of an Entity Component System! (cry) I suppose I opened my own can of worms. Might as well tighten the belt and jump in with more reading.

    @lordofduct Could this be because many people think OOP is "Inheritance" and when they see things heavily componentized they say it's not OOP? I personally don't know enough to make any statements of that sort, but it is something I've commonly read. Paraphrasing "Unity doesn't follow pure OOP rules. You're better off knowing you need to be using components." or some such. (Can you tell I don't have a degree in computer science? I did study a lot of science, but it was mostly Anthropology...)

    The insight into how Unity approaches things is interesting, and opens up the fact that Unity as software needs to make decisions and patters as well, that roll down to the end users. It's not something I've really considered.

    "Object Identity"... Read up on it here: https://msdn.microsoft.com/en-us/library/bb399376(v=vs.110).aspx So, what you're saying is that currently Unity only supplies Time and Random as a value? And if you could grab these as an object, you could pass time or random around as an argument?

    @BoredMormon I agree about the UI. So far, I've chosen the pull approach. When a panel opens up, it pulls the item data from the inventory. But I also have to "tickle" the panels. When an event happens, and a panel is open, I have to instruct the panel to update itself. This was one of the tightly coupled problems I had. Other objects needed to know about the UI Panels, so they could open them if they were closed, close them if they were opened (say walking too far away from a lootable object), update them when something changed. I assume I'm going to need to move to Unity events for this. When a change happens, fire off an event, and then let the items themselves handle their own behaviour. No - how to architect this is one of the main things I'm working on. To register for an event, one object needs to know what other object to register with for an event, no? So, I'm considering some sort of Event Manager that's centralized? (With a static reference? <ducks>)

    Ok. I didn't get RemoteField at all. But, I'll also admit I've not at all tried anything remotely related to reflection. I understand the concept from the highest level, but I've not had the chance to look into it at all. (One step at a time?)
     
    Kiwasi likes this.
  16. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Don't tackle reflection just yet. It's a pretty advanced topic. And there is almost always another way. Just demonstrating that there are completely generic UI techniques.
     
  17. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    4,044
    @BoredMormon RemoteField... really dude? :)

    Just a note overall, in general, as I've progressed in learning Unity, and as my project has grown. I've found myself wanting to move as much as possible out of the editor. This includes not using monobehaviour composition, not using scriptable objects, and slowly moving more and more stuff into text.

    The editor can be pretty kick ass, there's a lot you can do with it. But it can also become very hard to keep managed and organized.

    I have never once, remotely regretted moving something to text or code. I've frequently regretted having stuff tied to the editor. It's more work to set up initially, but it's almost always easier to manage once the scale grows, or once you enter more 'maintenance' mode, where you need to make bulk changes to stuff.

    This is not to say that any of these things are bad - far from it - it's just that often times there are so many tools for working with plain text that can make your life easier, especially when you have "tons of stuff" - it's just faster and easier to navigate to a class in visual studio than to find a specific game object in a large structure. you can diff files and clearly identify differences, you can run a quick regex, you can dump some csv into excel. Composition is a better approach than inheritance in general, but in terms of working with monobehaviours and the like - the maturity of the tools for working with some kind of text (from code to csv to json to some proprietary format) just makes your life easier in the long run.

    There may be exceptions for complex, intrinsically hierarchical data. One of the data heavy monobehaviour based things I'm most pleased with is the dialog system I made. It would be very difficult to translate this to plain text without essentially building a lightweight scripting language.
     
    Last edited: Oct 16, 2015
    Kiwasi likes this.
  18. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    Possibly it is that. If people are saying "Unity doesn't follow pure OOP rules. You're better off knowing you need to be using components.", than yeah, I'd probably assume they think OOP is inheritance.


    You could pass the supplier of random values and time around as an argument.

    Let take for instance my Tweener:
    https://github.com/lordofduct/spacepuppy-unity-framework/blob/master/SpacepuppyBase/Tween/Tweener.cs

    It has a field 'TimeSupplier':
    Code (csharp):
    1.  
    2.         public ITimeSupplier TimeSupplier
    3.         {
    4.             get { return _timeSupplier; }
    5.             set { _timeSupplier = value ?? SPTime.Normal; }
    6.         }
    7.  
    This means that when you create a Tweener you can set the TimeSupplier to whatever you want.

    And as you can see here we use the 'Delta' from this TimeSupplier when we Update the animation:

    Code (csharp):
    1.  
    2.         internal void Update()
    3.         {
    4.             this.Scrub(_timeSupplier.Delta * _speedScale);
    5.         }
    6.  
    7.         public virtual void Scrub(float dt)
    8.         {
    9.             if (this.IsDead) return;
    10.  
    11.             this.MovePlayHeadPosition(dt);
    12.  
    13.             this.DoUpdate(dt, _normalizedPlayHeadTime);
    14.  
    15.             switch (_wrap)
    16.             {
    17.                 case TweenWrapMode.Once:
    18.                     if (this.IsComplete)
    19.                     {
    20.                         _time = this.PlayHeadLength + _delay + 0.0001f;
    21.                         this.Stop();
    22.                         if (this.OnFinish != null) this.OnFinish(this, System.EventArgs.Empty);
    23.                         break;
    24.                     }
    25.                     else
    26.                     {
    27.                         if (this.OnStep != null) this.OnStep(this, System.EventArgs.Empty);
    28.                     }
    29.                     break;
    30.                 case TweenWrapMode.Loop:
    31.                 case TweenWrapMode.PingPong:
    32.                     if (_time > this.PlayHeadLength * (_currentWrapCount + 1))
    33.                     {
    34.                         _currentWrapCount++;
    35.                         if (this.IsComplete)
    36.                         {
    37.                             _time = this.PlayHeadLength * _wrapCount + _delay + 0.0001f;
    38.                             this.Stop();
    39.                             if (this.OnFinish != null) this.OnFinish(this, System.EventArgs.Empty);
    40.                         }
    41.                         else
    42.                         {
    43.                             if (this.OnStep != null) this.OnStep(this, System.EventArgs.Empty);
    44.                             if (this.OnWrap != null) this.OnWrap(this, System.EventArgs.Empty);
    45.                         }
    46.                     }
    47.                     else
    48.                     {
    49.                         if (this.OnStep != null) this.OnStep(this, System.EventArgs.Empty);
    50.                     }
    51.                     break;
    52.             }
    53.         }
    54.  
    I could have just said:

    Code (csharp):
    1.  
    2.         internal void Update()
    3.         {
    4.             this.Scrub(Time.deltaTime * _speedScale);
    5.         }
    6.  
    But what if I ever wanted to use 'Time.unscaledDeltaTime'? There'd have to be a special version of Tweener that hardcoded unscaled. And another for Fixed.

    Of course I could set that '_speedScale' to Time.scale when I create the Tweener, and then use the 'Time.unscaledDeltaTime', and then get the implied result. Give me the feature of both (how unity expects you to adjust Animations). But that requires you to consider that in your code EVERY time. And what if you change the Time scale while the animation/tweener is running? Now I have to go to every Tweener/Animation in existence, test if they're playing, and adjust their speed as I adjust the Time.timeScale... UGH!

    And don't get me started on if you had to tween the timescale! Now it's just getting stupid complicated.

    So... I went with giving it an object identity. Pass that object along as an argument, instead of the delta time itself. And now if we ever modify the TimeSupplier, anyone who has a reference to it, automatically gets the updated version of it.
     
  19. Sun-Dog

    Sun-Dog

    Joined:
    Mar 23, 2009
    Posts:
    144
    So, I'm putting together a diagram of what I think the relationships are with the new system. I'm re-reading some of these posts and have some thoughts and questions.

    Why should the inventory item be an interface? Rather than just a plain class? or, as I'm considering, a scriptable object? What does this give me over a plain class? I assume that this means that I can make more varied items that don't rely on the base class?

    Interesting how this off-loads the references being passed around to the game play itself, rather than trying to control it.

    So, there is one master static point of entry to all of this - the "game" singleton. Even if this entry point isn't on the "inventory system" itself, there is still a singleton holding all the important references.

    I'm not sure I'm going to try and create the UI on the fly, but have it pre-made in the scene. I suppose I could use serialized fields with references to the UI elements populated in the inspector, couldn't I?

    Ok, Interface again. Makes sense. Again, I like the off loading of the references to the game play itself (as above). I understand Interfaces here, but I'm still unclear about why one would need an interface for the Items.

    -

    Thinking about this, I realize that I'm not expecting the lootable object to wait for anything, but was expecting that the lootable object would react to a click or mousedown of some sort. I'll have to roll this one around and thing about how I expect the game to behave. Do I come close to the lootable object and we get a UI Message or UI Button of some sort that says "Loot available!"? (eg: from your trigger analogy) Or, does the item have a passive indicator (the sparkle cloud is common), and the item needs to be clicked on? And then, if that's true, how can I pass the reference?

    Food for thought.
     
  20. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    So that way you can have different kinds of items, that don't necessarily have to inherit from the same class.

    The idea of being an item is contractual, rather than inheritable.

    This is the way I think of it. When I'm creating some abstract type is that if the concrete type needs to inherit implementation, than I'll use a class. But if all it's inheriting is some property/method names, I'll make it an interface.

    Classes include interface AND implementation. Interfaces only interface (hence the name). Use what you need.


    Of course, this isn't the way you have to do it. It's just the way I approach solving problems.


    To get more specific though, well... in your case you talk about:

    Why not both? Why not a ScriptableObject version, and a MonoBehaviour version?

    Really, there's no difference, since the properties that are needed are really basic.

    Say I'm your designer, and I go into the game and I start building a prop for in the scene. It's a ball maybe, and the ball can be kicked around.

    But then we play test the game and someone is like "man, I'd love to be able to pick up that ball and put it in my knapsack for later use".

    Well... crap... now I have to turn it into an item. And to turn it into an item requires a 'ScriptableObject'.

    OR, if it we could just make it an 'IItem', I could just create a MonoBehaviour version of 'IItem' called say 'ItemComponent', implement the couple properties for it (a couple getters and setters). Drop that script onto the 'Ball' in the scene. And it automatically becomes an inventoriable item. No hoops to jump through.
     
    Kiwasi likes this.
  21. Sun-Dog

    Sun-Dog

    Joined:
    Mar 23, 2009
    Posts:
    144
    Still thinking through this Inventory Item as Interface issue. Going back to what @frosted said:
    This is the general design I've been working with. I have a "list" of potential items in the game, being a List<InventoryItem>. This is the data I have been keeping in the ScriptableObject. Now, my design kept these items in a sort platonic way, where these pure items existed in the ScriptableObject. I didn't think about manipulating the instance in any way, simply using it.
    Code (csharp):
    1. // From inside LootableItem pulling from the Dictionary itemList <string, InventoryItem>
    2. List<InventoryItem> lootList = new List<InventoryItem>();
    3. InventoryItem newItem = itemList.item[lootItemName] as InventoryItem;
    4. lootList.Add (newItem);
    This loot list was passed to the Inventory as part of a Loot Request to the Inventory System, and would be added to the Inventory if action was taken by the player.

    (I'm thinking this one through as we speak!)

    I suppose I can turn this master ItemList from
    Code (csharp):
    1. Dictionary itemList <string, InventoryItem>;
    into
    Code (csharp):
    1. Dictionary itemList <string, IInventoryItem>;
    But when accessing the InventoryItem as a class, it was easy to say things like:
    Code (csharp):
    1.  
    2. if (thisInventoryItem != null) {
    3.     // <snip>
    4.     Image itemIcon = currentSlot.GetComponent <Image> ();
    5.     itemIcon.sprite = thisInventoryItem.itemIcon;
    6. }
    I can do this because I know that the class InventoryItem has the member variable: Sprite itemIcon;

    Interfaces cannot contain fields.

    Interfaces can, however, contain properties.

    Code (csharp):
    1. interface IExample
    2. {
    3.    void DoSomething ();
    4.  
    5.    string SomeIdentifier {get;set;}
    6. }
    So, I would assume that I would need to declare everything I absolutely needed in an Inventory Item as Properties in the Interface description.

    Code (csharp):
    1. interface IInventoryItem
    2. {
    3.    string ItemName {get;set;}
    4.    Sprite ItemIcon {get;set;}
    5.    SlotType TargetSlotType {get;set;}
    6.    ItemType ItemType {get;set;}
    7.    float Encumbrance {get; set;}
    8.    etc MoreStuff {get; set;}
    9. }
    Then I would need to implement all of them in whatever script is using the Interface.

    I guess my lack of experience is showing, as I can imagine this versatility conceptually, but I'm struggling to see the implementation. Most likely, I need to get this system up and running in at least a prototype and see where it fails. that's probably when I'll go "Ah, shoot! Now I understand what they were saying!".
     
    Last edited: Oct 18, 2015
  22. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,848
    It looks to me like you've got the idea.

    However, it is entirely possible that, based on advice here, you're committing the sin of premature generalization.

    If you don't feel a current need for different kinds of inventory items, then you probably shouldn't have an interface or a base class. Only have a simple InventoryItem class.

    Then later, when you say "Doh! I need different kinds of inventory items now, and they can't all be the same class because...", then you do the Extract Interface refactoring (you do have Refactoring within arm's reach on your bookshelf now, right?), and presto, you've got both a nice general design, a concrete use case for it, and enlightenment all at once.
     
  23. Sun-Dog

    Sun-Dog

    Joined:
    Mar 23, 2009
    Posts:
    144
    Well, you may be correct @JoeStrout. I'm running a few tests to see how this works for me. The first thing I've encountered is that I can't serialize an Interface, and serializing the implementing classes does nothing, and I can't see the items in the inspector. Not a big deal, as I don't think I need to see these items in the inspector, per se, but was a tad-bit surprised that I couldn't test a few things just by doing something and inspecting the outcome.

    I think I'm more concerned about the communication between the various elements of my system at this point, so I may put Interfaces (at least as they relate to InventoryItems) to one side. But it has made me spelunk down some paths I'd either been afraid to investigate, or had not done very thoroughly, so nothing was wasted.
     
    JoeStrout likes this.
  24. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    4,044
    I don't really have much of an opinion on if Item should be an interface or not. You could make it a concrete class, and just use it as a member (composition the non monobehaviour kind). It kinda depends on what kind of logic you need and where you want to put stuff. I tend prefer that dumb data definitions as concrete class. But I don't think it matters much honestly.

    In terms of the templating and stuff, this kind of depends on how you want to approach your game, what kind of stuff you need in save games, how much random generation there is, and what your personal preferences are.

    Are you going to be hand placing badguys? How do you want to define the loot? How much item customization are you looking to do? What is the overall scale of the game? What do your save files look like?
     
  25. Sun-Dog

    Sun-Dog

    Joined:
    Mar 23, 2009
    Posts:
    144
    Well, tbh, I've been using this system as much as a practice piece as a practical system. Currently, my little test game has enemies as prefabs, and they each have a LootableObject attached with a predefined randomised loot table. Actually, I use the same LootableObject script on anything that can give more than one set piece of loot: chests, enemies, etc. The loot table is a randomised system that pulls from a list of lists of possible items set up in the inspector and takes a number of parameters to give a random but controllable set of items. In some cases I'm placing enemies by hand, but in most cases I have spawners and spawn points for creating and restocking enemies. I'm expecting to replace all enemies with spawners, but I've not actually done it yet. As this is a hobby project, really, and not my day job, I don't have that proverbial 40 hours a week to spend on it.

    Boiling it down to what my core issue is in all of this fun and frivolity, it is the referencing and communication between objects in a system in a contained and elegant fashion; preserving encapsulation and maintaining clarity of purpose.

    What I've learned is that there does need to be a known point of entry, and that a static reference or singleton is a possible solution, but, pushing that reference off onto the environment or game-play itself is a viable solution as well, and may be preferable if it works consistently. My UI will need to have the former solution, and at least one element will need to know about all the panels in the UI. My loot action may not need a static reference and it could be pushed off onto the environment, as this could be handled by whatever activates the loot action - if the player's reference can be passed during the action and the player has a reference to the inventory system.

    Next, I need to work with Unity Events to see how I can simplify the code I have, so I'm not making as many direct calls to functions on referenced objects, as much as announcing an event has happened and letting the relevant items sort themselves out.
     
  26. Sun-Dog

    Sun-Dog

    Joined:
    Mar 23, 2009
    Posts:
    144
  27. panOfSun

    panOfSun

    Joined:
    Jan 8, 2019
    Posts:
    1
    I know this is an old thread but still this is one of the first results in search.
    What I'd like to add is that for decoupling such things ScriptableObjects may be a good option.
    You can find more info in this video, there are also link to samples in description