Are circular dependencies problem in unity and how to avoid them?

In this presentation circular dependencies are described like enemy script references player and player refs enemy. I think example is like enemy script calls players damagePlayer method and vice versa. So is this a problem for things like potential bugs and how do you avoid it in unity way?

I think that in ECS it doesn’t really matter. The components are just data and the systems process the data.

For standard MonoBehaviours it’s normally better to avoid it. In the case of Player and Enemy where each references the other it’s also fine. In this case it’s normally just a game object reference (maybe for an AI to move to and attack target) and not a direct reference to the Player/Enemy script. Or maybe a reference to a common component like Damageable.

What you normally avoid are children referencing the parent. For example if a Player holds a Weapon then the Weapon is a child of the Player. The Player can reference the Weapon e.g. tell the weapon to fire. But the Weapon should never reference the Player. For example if the Player gains XP from a Weapon kill then the Player should just listen for a killed event from the Weapon instead of the Weapon directly telling the Player it killed a target. This lets you reuse the Weapon with different parents e.g. Weapon on a vehicle or attached to a turret.

I mean no? This adds extra complexity just in case.

If there’s just the player and just the weapon, then you just have the direct references. When you decide to reuse the weapon to be used by different things than the player, then you replace the direct reference with an event.
You can also replace the direct references with more abstractions if you notice that the code is getting clunky to work with.

But “future-proofing” code by adding abstractions and complexity just in case a feature you might or might not want in the future would perhaps benefit from it leads to very bloated, over-abstracted code, very fast.

For the vast majority of those cases, things have transient references to other things. So some kind of projectile/weapon script find something it hurts as a side-effect of a raycast or a trigger/collision enter, does damage to the thing it hit, and then lets go of the reference. It generally doesn’t hold on to a thing.

You see a lot of beginners with code like this:

public class EnemyProjectile {
    public Player player;

    void OnTriggerEnter(Collider col) {
        if (col.trigger == "Player")
            player.TakeDamage();
    }
}

Which is definitely wrong, but the problem isn’t circular dependencies, it’s unneeded dependencies - the collider is a part of the player, so the projectile should just fetch the player from the projectile:

public class EnemyProjectile {
    void OnTriggerEnter(Collider col) {
        var player = col.GetComponent<Player>();
        if (player)
            player.TakeDamage();
    }
}

And as the game grows, you’ll probably end up replacing that with Projectile scripts that hit both the player and the enemy, and probably abstract “something can be hit by damage” away from the player/enemies and into it’s own script, but again do that when that’s required, not beforehand just in case.

2 Likes