I’m working on a simple game where the user gets to click on falling things to deal damage to them.
While some values are specific to the instance of each such thing - for example, how quickly it falls, how much health it has, how many points its worth, etc - others are essentially specific to the player (of which there can only be one) - for example, how much damage to deal per click, what the chance of each click dealing increased damage is, etc.
Is it OK to use static variables for the latter set of player-specific data while continuing to use local variables to store instance-specific data? The alternative is to basically pass this player-specific data to each falling object as it is created in anticipation of the player interacting with them, but that seems like a very roundabout and counter-intuitive way of doing it (essentially, I’d be saying that instead of dealing X damage with each click, the item should deal itself X damage when clicked). There’s no intention of ever adding multiplayer functionality to the game.
I’ve been taught to avoid static variables like plague, and I’ve been successful in doing so - but it does seem to me that the above is a legitimate case for their use. Opinions?
In this case you will set the player as a singleton, which will be static, and access it as an instance (something like Player.Instance) while variables will look something like Player.Instance.damagePerClick, and will not be static.
@GeorgeCH_1 , if it’s a global store of data that should be globally accessible, it’s generally fine. Be aware, though, that if the global variable are modified from external sources, that can be really, really hard to follow.
I don’t get why people insist on making the API for accessing singletons horrible. The user code doesn’t need to know that it’s interacting with a singleton:
//in singleton:
[SerializeField]private int damagePerClick;
public static int DamagePerClick { get { return Instance.damagePerClick; } }
//in calling code:
Player.DamagePerClick
I find it clearer to specify the pattern I am using. Personal preferences I suppose.
Well with the player as a singleton, there’s no point in setting those variables static.
Possibly you want to use Events as well? So you’d have an OnClick event in the item, and a listener that goes “deal X damage” in the player class that is called with the event. This way the player completely manages the damage it deals.
In the code snippet it looks like he meant the DamagePerClick property to be static because internally it uses Instance. If it’s not static then there’s no point in using Instance and in the calling code Player needs to be a reference (and it looks like it’s not).
What that means is to do what you’re doing - don’t use globals (which is what statics and singletons really are) as your first choice. And the reasons are exactly what you note: most things will eventually have more than one version - like you knew it was going multiplayer (or the character can split in two … .)
But, you know the awards they give out for games following coding standards? Those are great, but most people just vote for their friends. It’s hard for a game by an unknown to break in.
That advice is really meant to help you avoid creating bad habits for yourself. It’s easy to use static/global variables for everything, but that leads to code that looks like a mess of spaghetti that’s hard to debug or (worse yet) hard to fix.
If you’ve fully analyzed your problem and have concluded that singletons are the way to go, then by all means, use it. It’s also just as easy to fall into the trap on the other side: Making your job harder for ideological reasons.
You could just add static reference to the current player in your game manager or session object or whatever.
I usually avoid singletons using the toolbox pattern.
Not much of a pattern, but very good practice. Your root manager is just a reference list of managers, in one form or another. It’s worth noting these could all be singletons and the basic idea would remain sound.
his is what I’m struggling with now… In the past I had a lot of bones but no meat and now I’ve flipped that situation and have a lot of stuff to tie together.
also in your example the properties make singletons. It’s really weird to let a property call instantiate a core game module to me though. Is it intended as a fallback, or do you really just start dropping calls to this ToolBox.SomeThing? It seems like that’s create order of execution problems.
Typically I end up with either:
A: A bunch of fabs in the scene doing root-level stuff with attached MonoB’s
B: A single fab in the scene and its root-level stuff is just running Instantiate() on a bunch of GO’s
I like B because when there are order of execution issues it’s easy to fix them. That said, since i started thinking of the Awake(your stuff) → Start(their stuff) paradigm “properly” I have run into these issues much much more rarely as I have become more skilled.
Yeah the example simplifies the idea of a toolbox to it’s core which is exactly that a reference list to all sorts of tools you could need in your game. It’s not limited to managers either it for example contain data-sets and other resources shared during scenes.
The one on Unity wiki goes deeper and actually makes it a list where you can add arbitrary tools on runtime instead of using lazy-init properties to initialize pre-defined tools.
The Lazy-init is just there so that you won’t run in to any unnecessary null-reference exceptions. Usually I have behavior that initializes the scene, spawns the player and user interface to the scene so I’ll just initialize the crucial tools before instantiating those. This way you’ll also avoid any spikes that might be caused by loading and instantiating resources while playing.
Mainly because you often need to load the players save file so you’ll know which character or load-out to instantiate and what data to display in the user interface.
Be careful with this. Making the consumers of a class aware of the internals tends to make them more tightly coupled. Which is why I tend to prefer Class.property instead of Class.instance.property.