How to completely disable MonoBehavior and stop receiving some or all Unity messages?

Consider this code:

public class MyComponent : MonoBehaviour {
    void Awake() {
        enabled = false;
    }
    void OnTriggerEnter2D(Collider2D Other) {
          Debug.Log("OnTriggerEnter");
    }
}

The OnTriggerEnter2D message is still sent.
There’s this documentation: Unity - Scripting API: MonoBehaviour.OnTriggerEnter2D(Collider2D)

In it it says:

Note: Trigger events are only sent if one of the Colliders also has a Rigidbody2D attached. Trigger events are sent to disabled MonoBehaviours, to allow enabling Behaviours in response to collisions.

OK. But what if I really want to “fully disable” the MonoBehavior on a GameObject and stop receiving messages? Or I just want to unsubscribe one of Unity messages, like this OnTriggerEnter2D. Sure, I can do:

    void OnTriggerEnter2D(Collider2D Other) {
          if(enabled)
              Debug.Log("OnTriggerEnter");
    }

But this looks more like a workaround than a proper solution.

So, I see a few kinda-solutions to this problem:

  1. Disable and remove the component from the object. Which kinda fixes this problem with OnTrigger2D, but what if I want to also disable Awake and Start messages and other messages? Still not really fully disabled.
  2. Disable the collider, which again fixes it only for the OnTrigger2D event.

There’s this thread: C# - Disabled Script Still Runs!

The upvoted answer there is:

you could add an ‘isEnabled’ boolean which cancels the execution in TryInteract. Unity’s script enabled feature only stops Unity messages like calling Update() and so on, it doesn’t prevent you from still calling your methods.

But when I do it:

public class MyComponent : MonoBehaviour {
    public bool isEnabled = false;

    void Awake() {
        enabled = false;
    }
    void OnTriggerEnter2D(Collider2D Other) {
          Debug.Log("OnTriggerEnter");
    }
}

The message is still received.

Does it solve the problem? If yes, implement it and carry on.

1 Like

It works, but it doesn’t solve the problem.
Making things work is just half the job. The other half is to make things right:

In my case I decided to go with disabling the collider, but I would really, really like to know how to unsubscribe from receiving messages, similar to the original GoF Observer pattern. Well, I would prefer to have a BoxCollider2D.OnTriggerEnter2D event and just subscribe to it when I need and then unsubscribe when I don’t need. Would be much simpler and cleaner to work with.

I mean yes if you don’t want to recieve collision callbacks, then you disable the collider. Otherwise the behaviour of how, where and when each Unity message is called is well documented, and we just need to work within those parameters.

The reason why we don’t have delegates or virtual methods has long been established: it’s easier for newer users and its more performant in the long run. It’s not going to change.

1 Like

Well, and what if I have multiple colliders? Now I have to disable all of them, keep track of them, etc. Observer solves all of it: it would allow us to subscribe to specific colliders and not just to all of them, then if we want to subscribe to all of them, we could to it in a rigidbody, or if not I’m sure there could be another way to do it: like a separate component AllColliders, idk. But the observer would solve all the issues with the current system: inability to subscribe to a specific collider on a game object, inability to unsubscribe from the events.

It’s not more performant in the long run if you’re cleaning your code: subscribing and unsubscribing to events when it’s needed. In the end you end up with a much, much more performant system than with messages, because you’re not calling a bunch of empty functions when they aren’t needed, you’re only calling functions that are absolutely should be running. Imagine you have 1000 objects, they’ll have many, many different events. With the messages you’re calling all 1000 of them no matter what. With the observer you’ll probably be calling around 200 out of them at any point in time.
I ended up implementing the Observer on my own, anyway, because I need multiple colliders doing multiple things and then reporting back to the main object. So, I have this hierarchy:

MainCharacterGameObject
  AttackColliderGameObject
  ReceiveHitColliderGameObject

Then, in the each of collider’s game objects I have BoxCollider2D and then my own component that looks like this:

public class ColliderReporter : MonoBehaviour {
    public delegate void CollisionEventDelegate(Collider2D Other, GameObject OwningGameObject);
    public event CollisionEventDelegate OnOverlappingStarted;
    public event CollisionEventDelegate OnOverlappingEnded;
    [HideInInspector] public Collider2D Collider;

    void Awake() {
        Collider = GetComponent<Collider2D>();
    }
    
    [UsedImplicitly]
    void OnTriggerEnter2D(Collider2D Other) {
        OnOverlappingStarted?.Invoke(Other, GetOtherOwningGameObject(Other));
    }

    [UsedImplicitly]
    void OnTriggerExit2D(Collider2D Other) {
        OnOverlappingEnded?.Invoke(Other, GetOtherOwningGameObject(Other));
    }

    GameObject GetOtherOwningGameObject(Collider2D Other) {
        var ColliderReporter = Other.GetComponent<ColliderReporter>();
        GameObject OwningGameObject = ColliderReporter ? ColliderReporter.gameObject.transform.parent.gameObject : Other.gameObject;

        return OwningGameObject;
    }

    public void EnableCollider() {
        Collider.Enable();
    }

    public void DisableCollider() {
        Collider.Disable();
    }
}

This way I can subscribe and unsubscribe to collisions in the main game object component and immediately get access to the actual owning game object of the other colliders, not the empty game object the collider lives on.

Well, this system was made using the recommendations on this forum to make empty child game objects with colliders to be able to have different colliders on different channels and detect which one was hit in the main game object. We obviously don’t want those child game objects know about the parent, so I made this component and prefabs, so I can quickly put those hurt/hit boxes on different game objects and report to the main game object. Works well-enough for now.
But, of course, all of those issues wouldn’t exist if Unity allowed us to have SceneComponents like Unreal with relative transforms and allowed to detect collisions on different colliders separately, and implemented Observer pattern on the colliders. This way we’d be able to have a bunch of colliders on the same game object with relative transforms, and we’d be able to just subscribe to the events on those colliders. The system would be many times simpler than what we have to work with right now: a whole hierarchy of whole game objects with a separate collider component on each, and a separate collision detection component on each, because how else are you going to get the events from the colliders?

That’s not new programmer-friendly at all. This is the exact opposite. Like yeah if you need just one collider it’s simpler, but once you finish tutorials, in around 2 hours you’ll want multiple colliders on your character and now you have to build the system above, there is no way around it. Meanwhile, with the observer, it’s just as easy, just subscribe on Awake: BoxCollider2D.OnColliderTrigger2D += BoxCollider2D_OnColliderTrigger2D;. This is very easy, and it scales so well, unlike this way with messages.

They can keep the current system working and add a new one with proper C# events. Everyone who wants to work with the current system can continue working with it. But then people who want can work with the better system, as I described above.

Well, for Unity messages, there are multiple fixes for this problem that I found so far:
We can override SystemBase: SystemBase overview | Entities | 1.3.8

And then we can also override Player Loop: How to create an early and a super late update in Unity - C# and Unity development

That will work for Awake, Start, Update and all the other events like that, from what I understand. But I’m not sure how we could only call collisions only on enabled objects.

Unity doesn’t call messages if components don’t implement them. It scans the type and keeps tabs of messages they implement, and only calling them if they implement said messages. At scale this is more performant that virtual methods or null-checking a delegate for literally every component on every game object.

Delegates are not newbie friendly. I’ve suggested it as solutions to newbies ad nauseum here and about 80% of them just don’t get them. They’re an intermediate concept which requires having grasped plenty of other coding concepts first.

GetComponentsInChildren solves this.

1 Like

Yes, of course. But it still calls all of the messages on all the objects that implement them. That’s an equivalent of permanently subscribing to all events. But if we have delegates and C# events, or an interface implementation of he Observer(that’s what they did in the second link I posted), then you can make only the objects that need it to subscribe and be called when even happens.

It’s not more performant, because you’ll have many less objects getting called. I don’t see how calling 200 virtual functions without branching would be less performant than calling the same 200 functions(but not virtual) with if checks(if enabled) and then 800 functions with if checks in them.

I haven’t tested calling 1000 of those 2 arrays of functions, but I very confidently bet that the 200 function calls without any if checks is much quicker than 1000 function calls with if checks in them.

Nobody is not null-checking anything. For example, Unity Update event would be called in a single place as UpdateEvent.Invoke(). That’s it. There will always be objects in Unity that are subscribed to Update, so there is no need to null-check anything even inside the method that invokes this event.
Or as an array of delegates: foreach(var UpdateDelegate in UpdateDelegates) UpdateDelegate();.
Nobody is null-checking anything anywhere.
If you mean OnTriggerEnter2D, again, as long as I can subscribe and unsubscribe, if it’s a foreach loop with delegates with an empty event property, there are no null checks.
Then each object that wants to be notified of Update just does Unity.UpdateEvent += Unity_UpdateEvent;, no matter the inner implementation of the Unity.UpdateEvent property: it can be either an actual event, or just an array of delegates.
Then once I want to unsubscribe: Unity.UpdateEvent -= Unity_UpdateEvent;. Simple, easy to use, better performance, much more powerful control over what you want and can do, follows the original and time-tested GoF pattern, and you don’t need to do if(enabled) in your code to process the messages.
Yes, the enabled by itself isn’t going to be a big problem here. The problem is that in Unity this kind of a thing is scattered all over the place, like in the case with components and colliders where I need to have a whole infrastructure of game objects and components on them, when just 2 collider components could be enough.
And once you put it all together, the system is very clunky to work with, where you need to have more complex logic and more complex network of objects than you want to have. It stacks and as a result the system is more bug-prone and rigid.

Ok, I can agree to this. But again, I’m not saying Unity should force this onto everyone. The default can stay the current solution no problem. Just allow people to unsubscribe. Like we can even leave the current solution with messages in place and Unity could just add methods like public static void DontUpdate(this MonoBehavior MonoBehavior) or something, that would make it so Unity doesn’t call the Update method, and they’d implement this kind of a method for each of the messages.

But then again, I have a good use case to also subscribe and unsubscribe to the Update in non-MonoBehavior classes and I can’t do it right now. The articles provides a solution, but this is basically modifying the engine. It will work, but I don’t think it’s any good that I will have to implement it and not Unity developers. Because I can do it for most Unity events but I still can’t do it for colliders.

Sure, but if I have to only unsubscribe from some, now I have to somehow track which ones I want and don’t want, etc. It’s all “solvable”, it’s just the solutions aren’t elegant at all and require more work, and it’s harder to understand and work with.
And again, it’s not like I’m talking about some weird stuff that nobody knows and/or uses: it’s the default everywhere: C#, .NET, WPF, C++, Unreal Engine, etc.

The third part of the job is to accept it and move on when the framework you’re using (Unity) didn’t make things right at all, and you just kinda have to work around that fact. Unless you have source access and want to reengineer the entire engine.

Physics callbacks being sent to disabled components is a bad design choice, but it was made over a decade ago and Unity doesn’t break backwards compatibility on things like that.

1 Like

Yep, I do just continue working with what we have, I was just thinking there’s a way to somehow unsubscribe from events.
I was shocked when I found out Unity doesn’t allow to unsubscribe.
I bet there’s still a way to somehow hack into the class that calls the events like OnTriggerEnter2D and just simply remove MonoBehavior from the list of objects that has OnTriggerEnter2D method defined. Similar to how Unity itself examines the MonoBehavior classes and calls the private methods when it’s not really supposed to call private methods.

That stuff happens on the c++ side of the engine, so it’s a lot harder to “hack in” than for quite a lot of other stuff. Again, you can do it with source access, but that’s expensive.

Except you can disable the component to stop receiving (most) callbacks.

There is no doubt that disabling a monobehaviour removes it from the list of objects to be updated. There is no if-check being performed on disabled components.

You always have to null check delegates. Not doing so is an incredibly bad coding practice.

Delegates will also add tons of allocations when subscribing, of which is up for GC when unsubscribing. If you have 200 objects registering to an Update callback, that’s 200 heap allocations, and 200 lumps of data to GC when they’re unsubscribed. This isn’t an issue with the way Unity messages work.

In any case, this conversation has been done ad nauseum and Unity is very unlikely to change how this core principle works. As Baste says, you gotta deal with it and come up with solutions. That’s what game dev is about.