Obviously we can't do anything to a class instance that hasn't yet been instantiated, because by definition it doesn't exist yet.
The real question here is "how can a class instance created at runtime get access to its dependencies, in Unity specifically?"
There are a couple of general approaches to this:
Make the GameMap
globally accessible so each Entity instance can reach out and find it. (You're already doing a flavour of this)
Provide a GameMap
instance to each Entity when it's spawned. (This is called dependency injection)
For 1, we have options like...
a) Each instance searches for its dependencies, using GameObject.Find()
as you're doing now, or FindAnyObjectByType() to shortcut straight to grabbing the component you want like so:
GameMap map = FindAnyObjectByType<GameMap>();
As you point out, this isn't ideal, but it's also not a grave sin as long as you cache the reference for reuse via a member variable, so you're not searching for it every frame/every event.
b) Implement the Singleton Pattern. There are many ways to skin this cat, but a common theme is a static method to get an instance (and possibly construct it on-demand if it's absent from the project):
public class GameMap : Monobehaviour
{
private static GameMap _instance;
public static GameMap GetInstance()
{
return _instance;
}
void Awake()
{
_instance = this;
}
//...
Now any code elsewhere in your scene can call:
var map = GameMap.GetInstance();
to get an instance to the one GameMap quite cheaply.
Where Singletons tend to start running into trouble is...
- When you change scenes and your singleton is deleted (fix with DontDestroyOnLoad)
- When you change scenes with
DontDestroyOnLoad
and there's already a singleton in the new scene, so now you have two (fix by self-destructing in Awake()
if _instance != null
)
- When you try to use the singleton in a scene without one, or before it's Awake (fix by constructing on demand, or using script execution order to ensure the singleton initializes first)
- When you want to extend your game so there can be multiple
GameMap
s at once (may require substantial refactoring - this applies to any of the "global access" strategies)
None of this is insurmountable, you just need to be on the lookout for it. But if we're using static members anyway, we could sidestep some of the complexity and just...
c) Make the shared dependency itself static. You lose the ability to put it in the hierarchy or manipulate it in the inspector, but if you so choose you can farm these responsibilities out to auxiliary objects that are either constructed by the GameMap
as needed, or already exist in the scene and register themselves with the GameMap
on start.
On to #2, dependency injection...
When you create your Entity
instances, tell them which GameMap
they should use.
If you're not using a Monobehaviour
, this is as straightforward as passing an argument to their constructor, as you describe in the question:
var myEntity = new Entity(myGameMap);
If you are inheriting from Monobehaviour
, then you wouldn't use a constructor, but instead a setter after instantiating the object or attaching the component:
// Make sure your prefab variable is of the type you want to use.
public Entity myEntityPrefab;
// ...then, when you want to spawn it:
var myEntity = Instantiate(myEntityPrefab);
myEntity.SetGameMap(myMap);
Either way, the Entity
caches the map it's passed, and any code that runs after [construction / Awake()] will know which map to use.
This approach scales to uses where you might eventually introduce multiple GameMap
s, so the Entity objects don't need to know how to request the right one - it's pushed in from whatever higher-level system created them (which usually has more knowledge of the context in which it's creating the Entity).