0
votes

In Unity, how can I send an event to a single game object? Or, how else can I solve this problem?

Scenario: Let's say there are six players in a scene. And a bunch of coins for them to collect.

Each coin has a CoinCollectable script. When it detects a collision with a player, it invokes its OnCollected event.

public class CoinCollectable : MonoBehaviour
{
    // The action to invoke when this coin is collected by a player
    public static event Action<GameObject, int> OnCollected;

    // When a coin is touched
    private void OnTriggerEnter2D(Collider2D other) {

        // If not a player, abort
        if (!other.gameObject.CompareTag("Player")) return;

        // Invoke the coin collected event
        OnCollected?.Invoke(other.gameObject, coinValue);
    }
}

All players listen to this event in their PlayerCoinCounter script.

public class PlayerCoinCounter : MonoBehaviour
{
    private void OnEnable()
    {
        // Subscribe to events
        CoinCollectable.OnCollected += IncreaseCoins;
    }

    private void OnDisable()
    {
        // Un-subscribe from events
        CoinCollectable.OnCollected -= IncreaseCoins;
    }

    private void IncreaseCoins(GameObject player, int coinsToAdd)
    {
        // If this is not the player who collected the coin, abort
        if(player != this.gameObject) return;

        // (Increase the current coin counter value)       
    }
}

The problem is that with this setup, every single player has to check "Was I the one who collected the coin?" in their event handler method. This feels cumbersome and not very elegant.

How would you approach this problem? I wonder:

  • Is there a way to send the event just to the one player who actually collected the coin? (To avoid each player having to check if they collected it)
  • If not, then in OnTriggerEnter2D, should I instead do other.GameObject.GetComponent<PlayerCoinCounter>().IncreaseCoins(...)? This does not feel ideal, because I would be assuming that the player has such a component. So I would lose the decoupling that the event approach provides.

PS: I am using events to minimise coupling.

2

2 Answers

2
votes

The coin doesn't have to do anything. I would go the other way round:

Have this on your coin

public class Coin : MonoBehaviour
{
    public int value;
}

Then rather check on the player for collision with coins and increase its own counter.

Something like

public class PlayerCoinCounter : MonoBehaviour
{
    public int coins;

    private void OnTriggerEnter2D(Collider2D other) {

        // If not a player, abort
        if (!other.TryGetComponent<Coin>(out var coin)) return;

        coins += coin.value;
    }
}

No events needed at all.

If you have a "dependency" on a tag or a certain component makes little difference in my eyes.

But I would say the coins are not responsible for increasing the player points but rather the player itself ;)

-1
votes

You can use some IoC container (for example Zenject) to deal with coupling.