Search Unity

Feedback Possibilities for generic interaction between objects (e.g. item and tool)

Discussion in 'Scripting' started by Olipool, Feb 28, 2023.

  1. Olipool

    Olipool

    Joined:
    Feb 8, 2015
    Posts:
    322
    Hi all,

    I am looking for some inspiration for the task of two objects interacting with each other without too much explicit hardcoding of the relationships. Hmm.. what does that mean?

    An example would be in an adventure game where verbs need to be combined with objects. Another one is combining a tool with an item. I am working on a system right now where the player can choose an item and then select an appropriate tool to use on that item. For example, a door and the tool is a key. Or a chest and the tool is a crowbar. Or a safe and the tool is dynamite. Or a lockpick.

    The most "simple" solution would be to hardcode every combination with a lot of if-statements which sounds too terrible to even try out. So my idea so far is as follows:

    I have an abstract base class Item that has a method
    UseTool(Tool tool)
    which just calls
    tool.UseOnItem(this)
    .
    I then have a child class e.g. a WoodenDoor. That child class can implement some interfaces such as IOpenable and IDamagable. Those bring the methods
    Open()
    and
    TakeDamage(...)
    with them.

    A Tool on the other hand is a scriptable object with fields canOpen and canDealDamage. I can then create assets from that so that I have a Crowbar asset of the type Tool that I can set to canOpen=true and canDealDamage=true in the inspector.
    A Tool then has the method mentioned above
    UseOnItem(Item item)
    . And that checks the following:
    if (item is IOpenable && canOpen) item.Open();
    if (item is IDamagable && canDealDamage) item.TakeDamage(...);


    In an Item object I would also have a list of applicable tools, so when I have a WoodenDoor in the scene, I can drag the Crowbar asset into the list. By that, I can define which tools are allowed for interaction.

    So far it seems to be better than the whole if-statements but it feels like I can't cover all cases for that. So I am a bit stuck and could need some input if that sounds reasonable or what other ideas there are for such a system. Thanks! :)
     
  2. SF_FrankvHoof

    SF_FrankvHoof

    Joined:
    Apr 1, 2022
    Posts:
    780
    Use a base
    IInteractable
    -interface. Always throw in the 'interacter' (e.g. Player) as a parameter in your methods (e.g. as an Interface or Base-Class)
    Then your object can figure out for itself what type of Interaction is possible. If it needs something from the 'interacter', it can than grab/request it during its interaction.
     
    Olipool and mopthrow like this.
  3. Olipool

    Olipool

    Joined:
    Feb 8, 2015
    Posts:
    322
    Thank you for the reply. If I understand you correctly you mean for example a Potion is an IInteractable and then you call:
    potion.InteractWith(player)
    and player is an Interactor?

    Then potion can say:
    if (interactor is Player) interactor.Drink(this);
    if (interactor is Stone) this.Break();

    ...

    But let's say there are 50 interactions that can break a Potion (small stone, big stone, hammer, sword...). And also break a Bottle. Then there is a lot of duplication. That is why I have thought of generalizing the objects with interfaces (IBreakable and so on).
    But somehow I have to handle the exceptions, for example, a stone can break things and IBreakables can be broken but one of those can only be broken by a special stone or a hammer or whatever.
     
  4. mopthrow

    mopthrow

    Joined:
    May 8, 2020
    Posts:
    348
    I normally handle interaction exceptions with a list of IInteractionCondition on each interactable object.

    When the interaction happens I iterate the list of conditions and return a bool result for each check. If one fails, so does the interaction.

    Write a custom class for any kind of check you want, configure it and put as many or as few in your interaction prereqs list for each object as you like. The checks can be anything; time of day, floor the player is standing on, tool the player holds, what they had for lunch, whether they have arms etc.

    In your case as an example one of my IInteractionCondition would be RequiresSpecialItem class that contains a hashset of string item ids. When the interaction starts and the list of conditions is being checked, this condition returns false if the interactor's item isn't in the set.

    I'm sure there are other ways too! This one is always easy to extend and modify for me because it means I can add, delete and modify conditions represented by a new class with zero breakages anywhere else.
     
    Olipool likes this.
  5. SF_FrankvHoof

    SF_FrankvHoof

    Joined:
    Apr 1, 2022
    Posts:
    780
    Yes. This would then normally be called by player:
    potion.Interact(this);


    And then Potion can see if the "this" that was passed is 'valid' for its own interactions.
    if ((IInteractor interactor as ICanDrinkPotion drinker) != null) drinker.Drink(this);


    And that 'special case' will handle this itself, whilst normal IBreakables can still operate using base functionalities.
     
    Olipool likes this.
  6. Olipool

    Olipool

    Joined:
    Feb 8, 2015
    Posts:
    322
    Thanks for the clarification, I will map this out on paper to get in into my brain :)
     
  7. Olipool

    Olipool

    Joined:
    Feb 8, 2015
    Posts:
    322
    Thanks, that sounds very flexible. I realize that both of you have the logic defined in the "items" that get interacted with whereas I have it in the "tools" or interactions. I define what the effect of an interaction on the item is and not what the item does to the interactor. I guess that is dependent on the items and interactors and game logic of each game but I see how to incorporate your findings/experiences. Thanks for that! :)
     
    SF_FrankvHoof and mopthrow like this.
  8. SF_FrankvHoof

    SF_FrankvHoof

    Joined:
    Apr 1, 2022
    Posts:
    780
    Putting it in the 'tools' or 'interactions' (or Player) would mean that those become huge quite easily.
    A wrench can work on 100 different kinds of nuts. But a nut can only be turned by a wrench. Thus it's usually easier to put the interaction in the nut, so you don't get monolithic huge classes.
    A wrench would have to check for 100 different kinds of nuts, and do 100 different things. A nut can simply exit out (return) when the thing interacting with it isn't a wrench.
     
    Olipool likes this.
  9. Olipool

    Olipool

    Joined:
    Feb 8, 2015
    Posts:
    322
    You surely have a point there :) I will list all my items and tools and see what fits best. It might be that I can sum up all items into three or four interfaces (like IOpenable) but I have many tools that can open stuff with different speeds (e.g. Crowbar level 1 to 10).
    On the other hand, there may be unique tools like StorageKey#27. So I will try to come up with a combination of both if possible. Thanks again for the new perspective!