Robust script design for communication between GameObjects?

Hi, I’m new to scripting and trying to find out a proper and robust way to communicate between GameObjects.

Simple example, I want the Player GameObject being able to open a door with key-press, when inside the door’s trigger area.

I know the possibilities of direct referencing the gameObjects/classes.

But I’m wondering if there is a more simple, robust and performant way.

Basically, I don’t want the Player to have to know anything about it’s environment. But if the Player is within an interactable object’s trigger area, I want the Player to trigger something on the interactable, when pressing [F] for example.
The Player however should not need to know who this someone and something is what needs to happen.
Player should only know that it pressed the key and broadcast this fact out in some way.
GameObjects, whose trigger area is occupied by the Player should then receive the key-press signal and decide what to do.

I’m really struggling to find an answer to his. Even my paid Udemy courses lack this kind of information.

I tried around with Interfaces, but failed at finding a way to reference the door gameObject, without having the Player to know about its existence again.

I can imagine that there is a really simple way and I’m just totally blind.

Does anyone know a proper solution to this?

Regards
Massimo

For things like opening a door or picking up an item, the detection itself can be used to reference the other object. For example, colliders or raycasts, which offer you the other object to work with.

For some interactions it may be useful to have a manager or controller class type, which keeps track of multiple different gameobjects (by reference, or because it created them) and manages them or controls their behavior. This also has the added benefit of only requiring one monobehavior, since having tons of monobehaviors is expensive. Some examples would be a WorldController für a procedural world, a UnitManager for an RTS where units have to walk in specific formations, or a CombatManager for some games with, for example, instanced combat. Another good example would be an object pooling system, which is a very popular solution to the problems around instantiating and destroying objects at runtime.

In some other cases, a gameobject may be directly linked to another too. A character may, for example, have a direct reference to its current weapon, or something along those lines. This reference can obviously be changed at runtime in this case, but sometimes you dont even want that.

For “global references” of variables or certain gameobjects, you can either create a static look-up class, or a Singleton class pattern, both of you allowing you to access their contents from anywhere in the project, if done right.

Which of the above, or which combination of the above, to pick depends on what you are doing. There is probably some more ways of interacting i forgot to mention or dont know, but this should already give you a nice overview.
For your specific case, you should be able to detect the collision with the trigger. If your door has a trigger, then you can write the actions relevant to something entering the trigger into the OnTriggerEnter, OnTriggerStay and OnTriggerExit methods. In your case, OnTriggerStay should do the trick. If something is in the trigger, which is a player, and the user presses F at the same time, then do something, like opening the door.

Since you mentioned some Udemy courses, i cannot recomment the free Game Dev tutorial series on Youtube by Sebastian Lague enough. It’s currently 26 videos long and covers a lot of ground, from basic programming, over the Unity basics, to more complex topics, all the while featuring nice examples and very good explanations for what, why and how things are done.

Hope this helps :slight_smile:

1 Like

Thank you very much for your detailed input!

I’ve nearly completed the playlist of Sebastian Lague. Learned a lot, it is amazingly clear and rich of important content. Thanks for sharing.

I’m still struggling about my “door test” environment and considered what you are saying.
So I used the OnTriggerStay method in a Monobehaviour on the door itself now, which checks if it is the Player && if KeyCode.F is pressed, then rotates. Works.

I feel that my approach is not well designed though.

  1. it requires a Monobehaviour on each Door (and later on any other object to be interacted with), which you say is expensive
  2. the Door itself now needs to know which Key needs to be pressed - shouldn’t this information come from a more global GameObject, like a Manager, or the Player itself?

On the other way around, I don’t want the Player to know too much about the Door (that it needs to rotate on keypress, for example), only that it can broadcast “I want to interact” to trigger objects around it. The objects should decide if they react and if so, how they react (turning on, rotating, exploding, etc.).

How would you approach something like this?

Glad you liked the tutorial series! And good that you got your door mechanic to work.

About your questions:

  1. Having hundreds or thousands of Monobehaviors gets expensive eventually, but having a couple ones does not matter. So as long as you dont design a huge open world, it should be fine.
  2. Technically the script knows which key to press, not the door itself. And since every door uses the same script, changing the key in the script, changes it “globally”.
    However, you are right in that you could handle it the other way around. You should be able to just give the doors a capsule collider, and then detect if the player collides with those, and then handle the interaction with the door there. This would definitely be more performant, even tho as i said it does not really matter depending on the game size.
    Just to be clear; the door would still have a script with a function to OpenDoor(), the script just wouldnt need to be a Monobehavior anymore, since the player detects the collisions with doors now, instead of the other way around.

One disadvantage of using colliders in general, is that you could technically stand in two of them at the same time. Imagine there is a door you can open, as well as a book you can pick up with in front of it. Now pressing F (assuming it’s the general “interaction” key) calls both functions.
A better way to handle this would be to have a raycast script, which knows which object is “behind the mouse” and then calls its interaction function when F is pressed. This way, you can control more easily what actually gets activated or not. Unless you wont ever come into a situation where multiple items are within one colliders distance of each other, that will be the better approach.

Hope this helps :slight_smile:

1 Like

Events and delegates are also amazingly powerful when it comes to creating decoupled code. I’m fairly new to them myself, but I use them a lot in my current project.

If you working on prototyping, easiest way to communicate other gameObject/script is SendMessage

// this will invoke OnDamage(10); on all components of that game object
gameObject.SendMessage("OnDamage", 10);
// this will invoke OnDamage(10) on whole hierarchy under that object
gameObject.BroadcastMessage("OnDamage", 10);

But this not fits for production because it is slow and unreliable. Not very slow, you wont notice anything on modern PC, but it may become problem on platforms and interferes with assembly stripping feature and have many other problems.

Trying to make the Player not know anything doesn’t make sense. It should know that it needs to find an interactive object when pressing F.

I normally use a basic IInteractable interface/component. The player will search for this when pressing F, and call the Interact function. The player won’t know it’s a door or anything, just that it’s interactable.

Here’s an example:

public interface IInteractable
{
    void Interact();
}
public class Player : MonoBehaviour {

    public float _interactDistance = 10f;
    public LayerMask _interactLayers;

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.F))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); // Ray to the middle of screen

            if (Physics.Raycast(ray, out RaycastHit hit, _interactDistance, _interactLayers))
            {
                // Does the hit collider have a component that implements IInteractable?
                IInteractable interactable = hit.collider.GetComponent<IInteractable>();
             
                if (interactable != null)
                    interactable.Interact(); // Interacts with object, not knowing what it is...
            }
        }
    }

}

Door script:

public class Door : MonoBehaviour, IInteractable {

    public void Interact()
    {
        // Open/close door
    }

}

You can also have a generic Interactable component that implements IInteractable that just sends a UnityEvent so you can hook up in the inspector.

For “real games” you might need a ‘Parent’ system because the IInteractable may not be on the collider itself, but a ‘root’ gameobject. So you just have to check if it has a parent first, before interacting, etc.

If you really don’t want the player to know, then the objects around the player would have to be listening out for the player. It’s more work than this because when the player presses F, they would all have to do distance checks, or store the player via a collision when he gets close. It’s kind of doing things backwards.

2 Likes

Might be worth looking at the section on scriptable object events here. I’ve used them and like them for certain things
https://unity3d.com/how-to/architect-with-scriptable-objects

2 Likes

BTW, there are a few assets in the asset store that help implement these scriptable object patterns. I use and like this one.

-Jeff

1 Like

@Stardog thanks, this is the solution I was looking for. I struggled with referencing the interface properly, but your code helped me understand.

This is also what I had in mind for the Player. It should only know how to broadcast that it is interacting now (solved with your interface solution) and the GameObjects implementing the interface should receive this trigger and decide what to do.
Works (nearly) flawlessly now. Doors open. Lights go on. Oven start firing up.
But Player does not care and does not brake, if I remove/add anything. Very modular - what I was looking for. :slight_smile:

I’m currently checking for Input.GetKeyDown in the OnTriggerStay() method (because this is where I get my reference for the Iinteractable implementing Collider objects), but this is causing the issue that the key press will not be reliably registered in the OnTriggerStay(). Because latter is calculated in FixedUpdate() and not Update(). Guess I have to work with some form of Events here. Anyways… off-topic.

Thanks for your help!

This is funny.
I’ve watched Ryan Hipple’s talk (too) and this is what I wanted to understand/implement next.

Thanks for posting this! Will look into this.