Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Thinking with Components--help!

Discussion in 'Editor & General Support' started by Slyder, Oct 26, 2017.

  1. Slyder

    Slyder

    Joined:
    Oct 17, 2013
    Posts:
    270
    So I've been trying to wrap my head around component based design through both Unreal and Unity and would like a little bit of input.

    Think of a Backpack object.... This item can be equipped, placed in inventory, hold items, or searched. When player interacts with Backpack, a sequence of things should happen. I have these components:
    • InventoryItem - inventory related variables, this component is checked for to allow placement in inventory
    • Equippable - inventory can equip this item to certain inventory slot
    • Searchable - the contents of this item can be searched
    • ItemContainer - holds items
    Soft Interaction: Player taps Interact key
    1. Check if player currently has no backpack equipped...if true, try to equip this.
    2. Else...Try adding this item to current inventory
    3. Else... Search this item's ItemContainer contents (open UI)

    Hard Interaction: Player holds Interact key, bringing up a context menu with options:
    1. Take
    2. Equip
    3. Search

    The question here is...what is the best way to handle this sequence of events? Any Interactable may have an entirely different sequence. Also, think of something like a dead player, who could have an "Searchable Component" attached on death allowing players to search the dead players inventory contents.

    Could also fold some of these components into each other. InventoryItem implies that it can be interacted with and could just try to Equip everything as part of the interaction. ItemContainer implies that something can be searched...

    Also, what if there is timing involved in each interaction...like play Search animation and open UI after X seconds.

    Any input?
     
  2. LMan

    LMan

    Joined:
    Jun 1, 2013
    Posts:
    493
    Don't forget about Interfaces!

    Whenever you have a shared functionality, you can make it an interface so that you can apply it to one class and not another.

    For instance:

    You have an abstract base class Item, which holds whatever variables and functions common to all Items.

    Some Items are going to be Usable: ie. a flute.
    Some Items are going to be Consumable: ie. a Potion
    Some Items are going to be Equippable: ie. a Sword

    By making each "X-able" action into an interface, you can simply write a class Sword, that inherits from base class item, and implements the interface Equippable.

    In the sequence you described, All your Interactable objects could have an interface- you can check for an interface by attempting to cast an object as that interface, and then use it to bring up a dialogue with the interaction options.

    Now obviously "Taking" a backpack Item would do something different than "Taking" a Potion. One results in giving the Player an inventory, the other is placed in an inventory if one is available.

    By making the "Take" method virtual or abstract, You could specify the appropriate execution for each without having to write code to handle every type of item. You'd just call "Take" and let the magic happen.
     
    theANMATOR2b likes this.
  3. TimmyTheTerrible

    TimmyTheTerrible

    Joined:
    Feb 18, 2017
    Posts:
    186
    So what i do right now is i have an interactor component on the player. when the player presses a button, a ray is cast into the scene and checks to see if the object hit has an interactable component. my interactable component has a list of all scripts on it and its children that implement the IOnInteraction interface(it gets these components in the start method on scene startup). It then calls the OnInteraction(Transform actor) of all of these interfaces, passing the player in as a reference. What you do with that reference is up to you.

    One component using the interface might play a sound. Another open a door. or add both to an object and both will happen.

    To use your example of an item being looted after a timed animation:
    1 - have the OnInteraction function on the item get the inventory component of the player.
    2 - set a lootAfterAnimation variable(just for example) in the inventory.
    3 - trigger a "loot animation" on the players animator(which you also get in the interaction function using get component).
    4 - what i would do is have the function that actually adds the item be a event that you wire into your animations in the animation inspector on the model. The animation event then calls some such function with no parameters, something like OnAddLoot() that internally adds the item assigned to the lootOnAnimation variable that you set earlier inside of the interaction component... Or something like that anyway :p
     
    theANMATOR2b likes this.
  4. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    @LMan Interfaces is all good, but classic inheritance has limits. Its better to break up functionally into components and apply the strategy pattern. I have a realworld example of this from our game.

    From the beginning we had a HandGrenade class and a fraggrenade and a smokegrenade subclass. But then we needed a Bomb that also used the explosive capabilites from a handgrenade but no grenade functionally what so ever.

    So we moved to the strategy pattern instead now we have a Grenade component (Stretegy) and a IExplosiveEmmiter compontent. A handgrenade slaps both the Grenade and the FragExplosive compnent on the game object. The Grenade will resolve the IExplosiveEmmiter interface and use it on its own
     
  5. FMark92

    FMark92

    Joined:
    May 18, 2017
    Posts:
    1,243
    ...That is nothing that couldn't be achieved with inheritance.
    - Grenade
    -List<IGrenadeEffect>

    - IGrenadeEffect
    ->GrenadeEffectExplosive
    ->GrenadeEffectSmoke
    ->GrenadeEffectFlash
     
  6. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    Thats a strategy pattern
     
    FMark92 likes this.
  7. FMark92

    FMark92

    Joined:
    May 18, 2017
    Posts:
    1,243
    I though it was just classic inheritance. I learn something new every day.
     
  8. tinyant

    tinyant

    Joined:
    Aug 28, 2015
    Posts:
    127
    theANMATOR2b and Slyder like this.
  9. Slyder

    Slyder

    Joined:
    Oct 17, 2013
    Posts:
    270
    https://en.wikipedia.org/wiki/Strategy_pattern

    Strategy pattern is composition!

    With Composition, you don't really need a "BaseItem" class anymore. Think about what a GameObject actually is... It's just some thing with a Transform component. A GameObject is just a container class for all of these components and it holds the code to process through them.

    The code probably looks something like:
    Code (csharp):
    1.  
    2. class GameObject {
    3.     List of MonoBehaviors
    4.     void Update() {
    5.           loop through List and process MonoBehavior Updates
    6.     }
    7. }
    8.  
    The design I decided to run with unless something better comes up...
    Code (csharp):
    1.  
    2. class InteractAction {
    3.      virtual bool TryInteractAction(GameObject Instigator, GameObject Target);
    4. }
    5.  
    6. //Get InventoryItemComponent on Target...return false if not exists
    7. //Get CharacterInventoryComponent on Instigator...return false if not exists
    8. //Call TryAddItem on CharacterInventoryComponent...return false if fails
    9. class PickupInteractAction : InteractAction {
    10.      virtual bool TryInteractAction override;
    11. }
    12.  
    13. class InteractableComponent {
    14.      List of InteractActions;
    15.  
    16.      void OnInteract(GameObject Instigator)
    17.      {
    18.            for each InteractAction A in InteractActions
    19.                 A.TryInteractAction(Instigator, Owner)
    20.      }
    21. }
    22.  
    In this way, the communication between Components can be centralized and called Polymorphically through a sequence of InteractActions. These can even be changed at runtime.

    Communication between components is always the most difficult thing to engineer in my opinion. While you would like to decouple everything, it is not always possible. I think centralizing the communication (where event systems would not be appropriate) is the next best thing. The setup I have chosen also more accurately reflects the actual nature of each Component.

    For a Backpack...
    Player does not Interact directly with InventoryItemComp, EquippableComp, or SearchableComp

    Another design consideration would be....should I combine "ItemContainerComp" and "SearchableComp". By combining these, I could do cool things like mask the inventory item positions for players who have not searched the ItemContainer contents.

    So many possibilities.
     
    Last edited: Oct 27, 2017