I need some advice on whether my current GameManager set-up is correctly set up.
I’ve created a singleton script with a DontDestroyOnLoad function on an empty GameManager object.
I intend to use the GameManager to store references to other gameobjects such as the player. As the player is already on the scene at start of the level, I have dragged the player object into the GameManager inspector.
On start everything works fine. However, when I reload the player object reference in the GameManager inspector field is missing, presumably because the Player object gets destroyed.
To address this, I am considering using a OnLevelWasLoaded function in the singleton script (GameManager) to find (FindByType) the player object.
As the player gameobject will need to be accessed by other gameobjects, I plan to allow the other gameobjects to access the reference by going through the GameManager, using GameManager.instance.player;.
If the above is correct, will the function GameManager.instance.player enable other scripts to ‘instantly’ gain access to the player game object reference? As certain objects need to access the player object on awake.
Is my perception on the role of a GameManager correct?
Apologies if this sounds like a silly question, I’m still trying to get my head round some of the main functions.
Make the .player reference a property, and it finds the player instance on demand (and cache it):
private PlayerScript playerObject;
public PlayerScript player {
get {
if (playerObject == null) playerObject = FindObjectByType<PlayerScript>();
return playerObject;
}
}
That way you don’t need to worry about when the player gets updated, because it’ll always happen just in time. And this’ll also work if the player object is destroyed by other means.
Make PlayerScript set the reference itself in Awake. This would be most efficient, as it avoids FindObjectOfType entirely (which is pretty slow). You could, by the way, combine 1 and 2 for a method that’s usually fast and always reliable.
Take that a step further, and put the reference in PlayerScript directly - that is, make PlayerScript its own singleton. This may be inadvisable depending on your future plans, but then, a reference in a GameManager has all the exact same drawbacks as a PlayerScript singleton.
For option one, would I attach the PlayerScript on the player object with the view that the reference to the player will be immediately available to other gameobjects?
How can other gameobjects access the playerobject?
You see ideally I would like all gameobject references to be stored on a single script (GameManager), that way I do not need to use ‘FindObjectsofType’ in every gameobject that needs to access the player object.
You don’t need to attach anything anywhere, really. It’ll set itself up the first time you try to access it.
You would access it from other scripts in what I assume is the same way you were planning to:
GameManager.instance.player.somePlayerFunction();
Yep, and this does that. It’ll call FindObjectOfType exactly once total (unless it’s augmented by option 2), and then every object will reference the same cached object.
You’ve got some good advice on the how from @StarManta . I’m going to wander off into design land.
I’m always very cautious with this type of manager. They are often only slightly better managed versions of global state. With all of the attendant pitfalls that come with global state.
Some things to consider
Make the reference to the player read only. You don’t want other objects changing your reference accidentally.
Does everything really need direct access to your player? This open approach can lead to hard to trace bugs.
Is there any chance the game will go multiplayer? If so this approach will fail.
What would be the best way to create a centralised location with important gameobject references, such as a player object.
Wouldn’t this approach be better, as there would be a single location to access references instead of each gameobject using a findbytype command to search for the object. Within this setup the search will be conducted once with the result shared amongst objects that need it.
It’s completely fine to use Singletons. I personally use them in every projects.
Where the problem lies is when you start to have a bunch of singletons… and a bunch of class communicating directly with them without any logic . Eventually it will become hell to manage, like @Kiwasi said bugs will be hard to trace, the flow won’t be logic. In some cases Singletons are a good solution, in other case they are they are the easy/lazy solution and you will pay for it in the end.
Before deciding this should be a singleton or what your GameManager Singleton should include, (and even before jumping on the computer!)… I would advise on taking a pen and some paper:
Gather the requirements. Write time. What do you want to accomplish? What are you trying to solve? Get Specific!
Identify the main objects and what they generally need to do.
Describe the interactions between these objects. (This is where you determine which object needs to communicate with which one. Each time ask yourself does this object really need to communicate with this one?)
Lastly, once the objects and interactions are defined, drawing up a class diagram is a good idea! (with a UML editor or on paper)
Taking time to do this, will probably lead you to notice that some components do not even need to communicate directly with each other and In the end you will save time.
The steps you describe are pretty much exactly how I approach things at the moment.
I’m guessing this is as you seem to suggest, okay for small projects, which is all I have worked on thus far. However my suspicion is that for anything more complex I will have to change my approach, which is why an answer to BoreMormon’s question would be nice, even if it’s only to steer me in the right direction for future research.
The way I think about singletons is this: Is this thing truly global, and and there any circumstances in the future where it might not be? And, even when you decide to use a singleton, it may be in your best interest to not necessarily take advantage of it in every case.
For example, it may seem straightforward to have a singleton for the Player object. And maybe for your project that’s fine. The tricky part of singletons (and their tempting danger) is that, when (for example) something else collides with the player, you may want to use Player.instance instead of otherCollider.GetComponent(); But what if, a month from now, you decide to add multiplayer? Now you’ve got two Player objects, fighting over who gets to be Singleton. But all of the code you wrote that’s dependent on the concept of having one player object is wrecked and has to be rewritten.
The problem isn’t the singletons; it’s the pattern of thinking that leads to laziness. The weird part is, you can change your own pattern of thinking by changing which names you decide to use. Hack your own brain. Instead of Player.instance, use Player.userControlledPlayer. Instead of LevelLandscape.instance, use LevelLandscape.current or .local. MyGUI.instance becomes MyGUI.onScreenGUI. Each time you type out these names, it’ll force you to consider the actual purpose of these singletons, and - even if for just a second - that’s often enough to keep you from making bad decisions about how they’re being used.