0
\$\begingroup\$

I am coding an interaction system in Unity that does things based on whether the player taps interaction, or holds interaction. Each object holds their own scripts of how a player can interact with them and what happens (e.g., they can physically lift an object, add an object to their inventory, open/close a door, etc)

What I want is for the interactable_object to call component scripts that have been added to it and categorized as tapInteraction or holdInteraction, but I also want the component scripts to be separated from how they're called (e.g., they might be called via tap or hold, it shouldn't matter).

If I use interfaces, the tapInteraction could check for GetComponent<ITap>() and the holdInteraction could check for GetComponent<IHold>(), but then wouldn't each action need separate scripts defined as Lift : ITap and Lift : IHold? I feel like there would be a lot of repetition.

I'm wondering if there is some better way to go about this.

EDIT:

Thank you very much for the input DMGregory and Alex F!

I ended up going with an interface approach similar to what Alex F answered, but with two key differences:

  • It was very important to me to have the actions as components on the gameobject since a specific action (e.g. Lift or AddToInventory) would have a large amount of customization from object to object.
  • I also wanted the interaction to return a status as to whether it succeeded or not.

As such I created a base class Interaction that implements the interface IInteraction with a bool TryInteraction method, and registers/deregisters itself to an interactable_object via OnEnable and OnDisable as the tapAction or holdAction depending on if that Interaction is set as a tapAction or holdAction.

InteractionLift and InteractionInventoryPickUp inherit that base Interaction class, they implement the TryInteraction interface, and depending on their parameters will register themselves to a interactable_object script as either the hold or tap action. Then the interactable_object fires the hold or tap TryInteraction accordingly.

\$\endgroup\$
  • 1
    \$\begingroup\$ I'm not super great with interfaces, but can you have Lift implement IInteraction, and then have public IInteraction tapInteraction; and public IInteraction holdInteraction;. Then you can (maybe, again, not great with interfaces) set these in the editor for the main interactable_object script, without assigning these scripts to the object itself. (No idea if this would actually work, which is why it's not an answer - just a thought.) \$\endgroup\$ – Alex F Jun 7 at 23:54
  • \$\begingroup\$ @AlexF I wouldn't sell yourself short there. That's basically what I was thinking of posting as an answer when I get home. ;) Post your own answer and beat me to it! \$\endgroup\$ – DMGregory Jun 8 at 0:04
3
\$\begingroup\$

You can have just one interface, an IAction or similar. Make Lift implement it. Then, inside your main interactable_object script, say

public IAction holdAction;
public IAction tapAction;

Now you can set the actions just by dragging the scripts into the main script, rather than setting them as extra components.

\$\endgroup\$
3
\$\begingroup\$

Since Alex F already covered tackling the two with a single interface, I'll show you one other trick, using the Unity Events system:

using UnityEngine;
using UnityEngine.Events;

public class InteractableObject : MonoBehaviour {

    public UnityEvent tapAction;
    public UnityEvent holdAction;

    // ...

    public void PerformTapAction() {
        if(tapAction != null) tapAction.Invoke();
    }

    // And so on for hold.

This will let you apply any public zero-argument function as the action to get called on tap / on hold, whether on the same object or a different one, via the Inspector. You can also wire up listeners via code, in which case you can access non-public methods too.

This makes things super flexible - you won't need to create or implement an extra interface for the interactions. You can even implement interactions that call into built-in type methods, without adding an extra type of your own to bridge your interaction system with the types from other developers.

But this can also be a double-edged sword - you can't guarantee anything else about the script that's being called (say, if you wanted to ask it for its preferred hold duration - it's not guaranteed to be a type that has such a quality or query). A chaos-minded Level designer could use this to do just about anything even if it goes outside your system design principles.

So, there are legitimate reasons why you might prefer an interface method, to enforce through your types more predictable relationships for how your game actions plug together, or support additional state & metadata in addition to just firing actions.

But if you want something simple and flexible to let your designers get up and running quickly, this is one way to do it. :)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.