I want to create a list of objects for my sports management game, List. I need this to be created empty at the start of the game, and then other scripts will populate it by making players and then adding them to the list. I need multiple scripts to be able to access this list, though, and that’s the part I’m struggling with.
Well, the simplest way to make such a globally available list is to make it a static property. Static properties are accessible without an object reference; you just prefix it with the name of the class itself. Something like this:
public static class Globals {
public static List<Player> players = new List<Player>();
}
Then, anywhere you need it, you can just say Globals.players to reference this list.
Of course global variables can lead to strong coupling in your code, making it harder to refactor, etc. etc. There are other design patterns that avoid some of these problems. But if you’re new to programming, your chief concern is getting something to work at all, not making it elegant. And in a case like this, it’s hard to beat a global for Just Working ™.
Out of curiosity (and because it seems like everyone thinks if you use static variables frequently you might as well throw your computer into a hot tub) is there a better solution? I’ve read into using Scriptable Objects, GetComponent, etc. but I couldn’t find anything that worked.
Static has this weird love hate curve with developers. Initially noobs use them all over the place, to the point that the whole project collapses into a heap. Then they don’t touch them for a while. Then intermediate developers discover the singleton, and start using static all over again. Singletons take longer to collapse, but as the project gets bigger eventually they do, just the same as plain static. Experienced developers eventually return to using static, but sparingly.
Many experienced developers recommend static for things that are truly universal. Like pi. No matter what game you are building, no matter how large it gets, no matter what features you add, no matter how many players you add, pi will still be about 3.142. That is where you should use static.
I would advise against using this GameObject.Find approach, because it adds pointless overhead, and it’s more prone to errors if you type the name of the GO incorrectly, or if the GO does’t exist. Plus, with the static reference approach you can easily check to see if you’re accidentally making another instance of the same thing and display an error or something, and even construct an instance if none exists.
For 99.99% of game projects, ‘singletons’ like this are totally fine, and if anything are the cleanest, fastest, and simplest to implement method of sharing data across your scene.
I wouldn’t use a string version, but Find is awesome. It only has a bad rep from people using it in bad places.
This makes what you’re suggesting just a home-rolled version of a static variable, though. You’re just accessing it via the scene tree rather than via a reference.
First, if you’re going to the bother of wrapping it in a manager anyway, I’d give the manager some kind of easy access method. If there’s only ever one of them then Singleton is indeed reasonable. Otherwise there are plenty of options depending on how you might need to access the data. For instance, you could have a method that maps teams to player managers and returns the appropriate manager for a given team.
Second, at the very least I’d give it public Create/Destroy or Add/Remove and Get methods and keep the actual collection encapsulated. Why? For one, if the data is going to be shared then it should also definitely be protected, otherwise a bug in one class can result in errors in other classes by way of corrupted data. For two, if access is done via methods then it can do sanity checking and/or limit access to make those bugs harder to write - eg: you can’t accidentally null the collection if you can’t access its reference. For three, if/when an error occurs related to this it’ll be easier to debug as there’s a limited number of entry points that can touch or access the data.
I agree with most of the rest of what you said, but this kind of thinking is premature optimisation. Unless the list is used at a high frequency this isn’t something you should be factoring in. A dynamic lookup here and there isn’t an issue in the slightest.
Yeah I knew someone was going to call me on that post. @angrypenguin makes some really decent points. Protecting data by controlling how the collection is accessed is important.
@Darkcoder_1 's point about the pitfalls of the Find method is valid. You do have to make provision for dealing with null references. It is too slow to use every frame. But as an occasional lookup where you cache the reference it’s fine.
I disagree on the wisdom of singletons. They are very powerful, but I’ve been burnt severely in the past. I do use them, but only on rare occasions. And almost never in the traditional sense of a singleton.
My example was simply to show an easy way to access variables on another script. In my defense I was responding to a user who couldn’t make GetComponent work. Most of the lessons about good coding practise don’t make much sense until you figure out how to communicate between scripts.
And getting something working is arguably a more important step than getting it perfect.
And, you’ll never get it perfect anyway.
I’ve yet to be burned, which I suspect is because I don’t use them for anything that makes sense to be anything else. And, to be honest, I think a list of players falls into that case - a Singleton could get the job done, but so could an object that represents a team, or any number of other approaches, many of which simultaneously don’t have the same limitations as a Singleton and help you better model the relationships of data and roles of objects.
I don’t use Singletons because “there is only one”, I use them because “there can only be one, ever, by fundamental design of the system”. Or, to put it another way, I make something a Singleton if I want to force a redesign in the case someone ever wants to add a second thing.
And from my experience as a learner, it takes a while to acknowledge that best practices exist for a reason. I made most of the mistakes mentioned in this thread, despite reading similar advice from other veterans. Some of us have to learn things the hard way.
Now I’ve repented somewhat and have to have a really good reason to go against conventional coding wisdom, even if I haven’t quite figured out the why in every case.