Search Unity

How to have an organized, extendable interaction system?

Discussion in 'Scripting' started by Kombiice, Jan 23, 2018.

  1. Kombiice

    Kombiice

    Joined:
    Jan 20, 2015
    Posts:
    64
    Hey there!
    I'm actually working on an interactions system but can't really figure out the most efficient and organized way to do it.

    It's like follows:
    There is a 3d top down scene with a raycast manager.
    And there are certain objects, like safes, light switches, doors, drawers and more, you can / will be able to interact with.
    Each object has a certain behaviour script that determines basic door, safe. aso. functionality.
    But there will also be common actions, the player will be able to do, like breaking a shut. The shut position will be set up for each object individually.

    My goal is to have a switch function that will be called by (for example) right clicking for a short time on an object which allows the player to either open / close something or turn something on / off (Functionality will be determined by the object script itself)

    And a more advanced options menu, which will be opened when right clicking for longer on an object. There, you can (for example) tell the player to break a shut, or do object specific things (like looking through a shut, which is only possible with doors).
    There should also be checks if the player is close enough by shooting rays.

    As these functions are partly common for other objects, partly not, I'm wondering how I'd be able to set up a system like that?
    How do I build a extendable foundation for that?
    Where do I set up actions like breaking a shut?


    I hope you can help me with this problem (Provided you barely understand what I'm talking about ;)).

    Cheers!
     
  2. WarmedxMints

    WarmedxMints

    Joined:
    Feb 6, 2017
    Posts:
    1,035
    You could just create an abstract base interactable class which they could derive from.
     
  3. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    Sure, just make an "IInteractable" interface with PrimaryUse, SecondaryUse, Focus, and Unfocus methods defined, then implement that interface in whatever MonoBehaviours you like, along with a collider or trigger and on the "Interactable" layer.

    The Focus and Unfocus methods can be used for a few different things- I personally like using them to make a World-Space UI child canvas show an icon when the object is near the player, or make it turn off when the player wanders away. The PrimaryUse would be whatever you want the "use" button to do, whatever that is. SecondaryUse likewise might be for right clicking or otherwise hitting some "alternative use" button.

    All of this is reactionary though- your input controller would be what determines what's used and where. In there, you just have a raycast to X distance, masked to only the Interactable layer. When an object is hit, hitObject.GetComponent<IInteractable>().Focus(), then set it to the "currentlyHovered" field. Each frame, keep checking what's hit- if the "currentlyHovered" field is occupied but a new object is hovered, or none, then call Unfocus() on the existing object before setting the field to null. And, of course, if the use button is pressed while an object is being hovered, then call .PrimaryUse(), etc...

    In my own implementation, I actually added "InRange" and "OutOfRange" methods to the interface- these are activated when the player gets within a given radius regardless of the raycast. It allows me to display gray icons for usable objects that are within range but not being looked at, and a bright icon for whatever's being hovered, if anything.

    Hope that gives you some ideas. If you're using Unity 2017.2+, I actually have a little demo I made a month or so ago that's specifically for showing this functionality. Let me know if you want it.
     
  4. Kombiice

    Kombiice

    Joined:
    Jan 20, 2015
    Posts:
    64
    Thanks for the answer:)
    Already thought about that!
    Does it mean that I've got to create an interactable script with all the functionality and just derive from this in the (for example) door script?

    What about the "special use" menu? Should I derive common usage from the interactable script and mix it with special functionality from (for example) the door script and show all of these in a UI menu?
     
  5. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
  6. WarmedxMints

    WarmedxMints

    Joined:
    Feb 6, 2017
    Posts:
    1,035
    It all depends on what the common factors are. If the only similarity between the interactable objects is how long the mouse button is held down for, then you could do that in raycast and simple call methods from an interface.

    Something like this;
    Code (CSharp):
    1. public interface InteractableObject
    2. {
    3.     void LeftMousePress();
    4.     void RightMousePress();
    5.     void LeftMouseHeldOneSecond();
    6.     //..ect
    7. }
    8.  
    9. public class Door : MonoBehaviour, InteractableObject
    10. {
    11.     public void LeftMouseHeldOneSecond()
    12.     {
    13.         //Do something
    14.     }
    15.  
    16.     public void LeftMousePress()
    17.     {
    18.         //Do something
    19.     }
    20.  
    21.     public void RightMousePress()
    22.     {
    23.         //Do something
    24.     }
    25. }
    If your classes are mainly going to be running the same code when the methods are called, you may want to use an abstract class, like this
    Code (CSharp):
    1. public abstract class InteractableObject : MonoBehaviour
    2. {
    3.     public virtual void LeftMousePress(Vector3 newPos)
    4.     {
    5.         transform.position = newPos;
    6.     }
    7.  
    8.     public virtual void RightMousePress(Quaternion newRot)
    9.     {
    10.         transform.rotation = newRot;
    11.     }
    12.  
    13.     public virtual void LeftMouseHeldOneSecond()
    14.     {
    15.         Destroy(gameObject);
    16.     }
    17.     //..ect
    18. }
    19.  
    20. public class Door : InteractableObject
    21. {
    22.     public override void LeftMouseHeldOneSecond()
    23.     {
    24.         //Ignores the base class method
    25.         print("Can't Kill me!");
    26.     }
    27.  
    28.     public override void LeftMousePress(Vector3 newPos)
    29.     {
    30.         base.LeftMousePress(newPos);
    31.     }
    32.  
    33.     public override void RightMousePress(Quaternion newRot)
    34.     {
    35.         base.RightMousePress(newRot);
    36.     }
    37. }
    Now they are both very simple and almost pointless examples. You can decided which route is best for you. Personally, I tend to use a mix of both myself.
     
  7. Kombiice

    Kombiice

    Joined:
    Jan 20, 2015
    Posts:
    64

    Thank you so much for the answer!
    But how would I declare the usages? Breaking a shut should be something that can be commonly used. But a door open / close function will just be used for doors.

    Kind regards!
     
  8. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    I linked to my example which is very similar to Lysander's post.
     
  9. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    There's a different design, which is to have Interactable be a component that links to other components. This means that setting up interactions happens in the inspector, not in code.

    The idea here is that you have an Interactable script that handles getting interacted with. Then that script passes messages to other scripts to have them do things, without those scripts needing to know about interactions.

    The easiest implementation is to use UnityEvents for this:

    Code (csharp):
    1. public class Interactable : MonoBehaviour {
    2.  
    3.     public UnityEvent ToggleOn;
    4.     public UnityEvent ToggleOff;
    5.     private bool isOn;
    6.  
    7.     public void OnInteract() {
    8.         isOn = !isOn;
    9.         if (isOn)
    10.             ToggleOn.Invoke();
    11.         else
    12.             ToggleOff.Invoke();
    13.     }
    14. }
    And then you assign methods to ToggleOn and ToggleOff in the inspector. So if you have a switch that controls a door, you assign the door's Open to the swtiches ToggleOn and the door's Close to the switches ToggleOff.

    What's really nice about this is that your Door doesn't need to know that it's Interactable. It just needs to be able to open. It's also a lot easier to set up one-off interactable things. Need a button that makes an enemy attack? No coding needed.

    Breaking can be done pretty easily. Assuming you always want things to end up open after being broken::
    Code (csharp):
    1. public UnityEvent OnBreak;
    2.  
    3. public void BreakOpen() {
    4.     if(!isOn)
    5.         OnInteract();
    6.     OnBreak.Invoke();
    7.     Destroy(this); //or set a deactivated bool
    8. }
    The large drawback here is that UnityEvent is a tad restrictive - you can only assign methods that have 0 or 1 arguments, and only arguments that Unity's able to draw. If you want something similar, but with more options, there's a ton of different implementations of the same system, just better, available on the asset store and online. I even made one (free, open source).
     
    lordconstant likes this.
  10. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    Interfaces don't determine "what happens"- implementations are varied. Since you're accessing components through the interface, it makes it possible to have the exact same function call do completely different things. "PrimaryUse" in my example could open doors for door objects, pick up items, knock things over, start up dialogues with characters, etc... The caller doesn't need to know the specifics, that's the point of a common interface.

    Each usage you have would be a different MonoBehaviour script you've made- doors would have their own DoorInteraction script, and a person might have a CharacterInteraction script. All would implement "IInteractable", would be included in results found from the GetComponent<IInteractable>() call on raycasthit objects, and can be invoked without knowing their specific types.

    Please Google more information about interfaces for more info, or pick up an actual book on C# programming, and read through all of the sections (up through intermediate, at least) in Scripting on the Unity Learn site. This isn't really directed at you but just in general- it really can't be overstated how much new developers are hobbling themselves by not studying the possibilities before jumping in. "I learn better from looking through existing code" is one of the most common statements I see around here... on the support forum. I'll give you a second to think about that, lol.
     
    Last edited: Jan 24, 2018
  11. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    So true. That's how I first sort of began learning to program. There were many issues with my learning 'curve' because of that :)