Best way to reference player class instance?

I’m making an FPS which has a “Player” class attached to a game object in a scene. This class keeps track of things like player health, ammo etc. and has functions for various player behavior.

I need to access this data frequently from other instantiated classes - a good example would be medkits spread across the levels. The way things are currently set up, the Player class has a SetPlayerHealth function which modifies the health of the player, and each pickup detects if collided with and calls this function in the instantiated Player class, passing along the amount of health to modify by.

This works (and I like having pickups and such detect the collision in their own class since it’s more modular), but I’m struggling with figuring out the best way to actually refer to the instanced Player class.

I can make a “player” variable in the other classes and do this either when spawned or when needing to call the Player class:

player = GameObject.FindGameObjectWithTag("Player").GetComponent<Player>();

And then refer to this variable. But while this works well for objects there are just a few, set amounts of (say, UI elements) it seems badly optimized for things like pickups/medkits which continuously instantiate and there are many of - to my understanding finding game objects by tag at runtime isn’t great for performance.

What would be the optimal/recommended way to refer to the instantiated Player class efficiently? (or is there a better approach to basic player structure altogether?)

The normal solution to this is to have a singleton “manager” object in your scene that keeps references to all the important things you might need - the player, the audiomanager, the save system, and then have everything access them through that.

Simple example follows:

public class SceneManager : MonoBehaviour {

  // Declare any public variables that you want to be able 
  // to access throughout your scene
  public Player player;

  public static SceneManager Instance { get; private set; } // static singleton
  void Awake() {
        if (Instance == null) { Instance = this;  }
        else { Destroy(gameObject); }

        // Cache references to all desired variables
        player= FindObjectOfType<Player>();
    }

Now, from any other script you access the cached reference in the singleton, avoiding the need to do any expensive FindWIthTag()-esque lookups by simply looking at:

SceneManager.Instance.player