Singletons Are Good Actually (Unity3D Devlog Tutorial)

preview_player
Показать описание
We take a look at how Sam set up his Master Singleton in his game Zarvot, and many common problems and considerations taken, as well as the advantages and disadvantages of his “Master Singleton” structure.

Singletons are very useful and can be very dangerous. Hopefully this video sheds some light on how Sam did it for his game.

If you’d like to see a breakdown of a specific system in Zarvot, please leave a comment! The goal of this series is to show how developers architect a full, released game.

You can find Zarvot on Nintendo Switch:

---

Learn Unity and game development with experienced indie game developers. Subscribe for tutorials, devlogs, and gamedev advice uploaded weekly.

Timestamps:
0:46 What is a Singleton?
2:59 How to create a Singleton
4:13 Problems with using Singletons
5:22 The Master Singleton structure
10:46 Warnings in Using Singletons
13:25 Is Using Singletons Bad Practice?
Рекомендации по теме
Комментарии
Автор

So, great video. Well produced clear and well explained.
One thing though, the video is titled as being about singletons, and while technically it is a factor in this discussion the pattern discussed... is not actually a singleton. It is actually an already well known and used pattern. What you are calling a "MasterSingleton" is actually a "ServiceLocator" pattern, and it does come with it's own pros and cons, is often considered an antipattern but does have its uses and the main argument of "Keep it Simple Stupid" (KISS) is certain something I talk about a lot, I will often use singletons/service locators as an intermediate step until things get more complicated. (though by the time I got to the size of game as indicated here, I may have moved to Dependency Injection)
There are things you can do though to minimize the "dangers" of this pattern, for example usually you want to allow the "mastersingleton" to make the decisions for you. for example `Services.Get<Highscore>().AddPoints(Player1, 10)` with an implementation in the background that (for example) tries to get the service from a dictionary, if it fails returns a null object pattern instead (so the consumer of the code doesn't have to check for null).
The benefits include, not having to hardcode every single type, not having to deal with the get/set problem you mentioned (you lazy load instead, on first get request, try to get it, then store it in a dictionary and return the dictionary result from then on) and also allows for different configurations of services that can be saved as different prefabs so you can have different "MasterSingletons" for debugging, test builds, demo builds, per platform etc some that simple don't even provide implementations (e.g no highscoreboard in test mode) when not relevant.
That being said, it is great to see people come up with solutions to problems that skirt so closely to industry standards, it really is a good reminder that we are all just doing our best trying to solve similar problems.

JasonStorey
Автор

Great video, I really appreciated the in-depth explanations provided! Wanted to mention that you can make the Properties visible in unity inspector by adding the following:
[field: SerializeField]
to the property
EX:
[field: SerializeField] // this will make the prop visible in the inspector
public GameTimerCountdown gameTimerCountdown { get; private set; }

IndritVaka
Автор

Your "Master Singleton" is also known as the "Service Locator Pattern". It's generally useful for many game dev situations, but it also is a code smell called "data clump", because you put all these weird things together in one big singleton.

thygrrr
Автор

That was super cool Sam! We really love to see the "behind the scene" of Zarvot. Give us more!
Talking about Singleton, are you using the same approach for Skate Story or you change something?

TNTCProject
Автор

This video was fantastic, I really appreciated the in depth explanations provided! It would be great to see some more deep dives into the game systems you created for zarvot as well as more design patterns (i'm looking at you AI statepattersn/behavior trees) Thanks so much!

CosmicComputer
Автор

This and your scenes video have convinced I can leave the default Unity scene without crying! lol I'm trying out this master singleton method, and one change I've made is replacing the class with a Scriptable Object called LevelInfo that I add to the Singleton Strapper. Every time a new scene loads it passes the SO onto the master singleton who then uses that to determine what things to load or play by default.

hydrocosmo
Автор

Great video. I'm definitely going to consider the use of a service locator pattern in the future after seeing this. Also, I would definitely be interested in seeing a video about what assets were used in the game!

AlecAndersonZx
Автор

Singletons are one of the "handle with care" patterns. Using them smartly is beneficial. Using them as a kitchen-sink solution because they're so convenient will cause you problems. Your pattern is one with thoughts and I'll probably copy it. Not to your extent, I think an observer pattern is better for most of these things, but for a dozen or so truly global things, this is a great approach.

LemuriaGames
Автор

Fantastic video, I’m gonna give it try

damnverificationcode
Автор

Good video !
For me, I've used singletons for years and still use them for game jams or when I need to code really fast, but for well-organized projects I switched to the Service Locator pattern.
In order to respect the principles of S.O.L.I.D, and therefore make my code reusable by removing as much direct references as possible and being able to recover unique script by their interfaces.
I created a static script named SL which has a single static variable:
private static readonly Dictionary <Type, object> _singletons = new Dictionary <Type, object> ();
And static methods to add and remove instances of my scripts from this dictionary:
SL.Add <MyClassName> (this);
SL.Remove <MyClassName> (this);
I then managed to be able to retrieve from anywhere these "singleton" either by the name of their class or by an interface:
var mySingleton = SL.Get <MyClassName> ();
or
var mySingleton = SL.Get <IMyInterfaceName> ();
And in cases where the class I'm looking for may not have been added to the dictionary itself, which often happens in Awake() or OnEnable(), I have methods that will automatically find me the first instance:
var mySingleton = SL.GetOrFindC <MyClassName> ();
or
var mySingleton = SL.GetOrFindI <IMyInterfaceName> ();

All this makes my life easier to have different or specific singletons between scenes but for projects where you can create all your singleton from the start your version work just as fine.

Tchomaster
Автор

I love the quality and it was really helpful as well, thank you

olillin
Автор

It may have gotten a bit to far and maybe there are better solutions to the problem. But the solution just works and is convenient in this case. Also the beauty of all of this is that, just like Sam says, this probably allowed to ship the game in the first place, so congrats!
I'd love to see a video about graphics stuff and how Sam managed to run the game at a steady 60fps on Switch
Keep the great videos coming <3

geri
Автор

Nice video Sam, it's always cool to get a look under the hood of a game. Did you consider using scriptable objects previously before using the master singleton? Also just wanted to let you know that skate story looks amazing, would love to learn more about your process of making these games look so good. Kind regards Arjen

bulletwhalegames
Автор

Interesting approach. I'm currently just creating singletons out of my "manager" scripts. Stuff like the script that handles random events, my inventory, world generation, etc. These scripts I already have loaded into my scene and I know I'm never going to need more than one of each, so it makes sense to just... force them to be unique objects that you can reference directly. I currently have 23 objects in my scene when not at runtime, and the majority of those are singletons. It's not hard to keep track of them... as they are all in the scene.

Of course, I planned my game to only have one main game scene, where levels are loaded and unloaded... so I don't have to worry about jumping to different scenes and maintaining anything. I have a "title screen" scene, and a "game" scene and that's all I can see myself needed... other than maybe a credits screen or something.

Konitama
Автор

I like it! I made it a little different to split into multiple master singleton. (One for battle, one for weather, etc) To make it a bit more clean, but work the same way as yours.

FyresGames
Автор

I'm really glad to see that this didn't get as much flak as I was expecting. I was definitely ready to see like 40% of the views being translated to thumbs down or negative/harsh comments, but everyone here seems really supportive! I definitely feel like singletons have a really bad reputation, but seeing the interactions on this video gives me hope that there really aren't that many "it's bad because it's bad" people in the world 😁

natetaylor
Автор

What are your thoughts on singletons? Good? Bad? Ugly? I know folks have strong opinions :)

ThousandAnt
Автор

Great Video! Really helps out a lot because I hear so much about how Singletons are literally Satan incarnate and you should never use it but I always end up using something akin to a singleton in my game projects because it's just so much more convenient and speeds up development time. I feel like sometimes people can get stuck in a loop of trying to make everything modular and reusable to the point where they severely hamper their game development process. But with Unity DOTS on the horizon maybe those kind of people will get a head start we'll see!

Also I noticed at 8:30 how you used the { get; private set; } for the properties and how you have to use GetComponentInChildren. One thing I do in my projects is I use C# 6.0 expression bodied members to make a pseudo { get; private set; } that Unity can actually work with. ( Although I wish Unity just had { get; private set; } working out of the box but hey, what can you do ¯\_(ツ)_/¯ )

Example:
[SerializeField]

private GameObject cylinder;
public GameObject Cylinder => cylinder;

Also also I then use Monobehaviour's OnValidate function to put the GetComponentInChildren<T>() so that I don't have to set up all the references by hand but it will be populated automatically, just something extra that helps in case I accidentally reset the references somehow.

Thank you for listening to my Ted Talk. Have a good day...

Rennan
Автор

I had this issue where the Singleton pattern became a headache but ended up taking a different approach. It's more like pseudo dependency injection than a singleton pattern, but it functions similarly to how you've coded your 'Reference Hub' singleton.

Instead of using a public static instance variable to give access to globally responsible game systems I use a ScriptableObject asset (Called GameServiceProvider) to 'inject' access to those references into my scripts via the inspector.

I have a GameObject prefab in my Scene with DontDestroyOnLoad, and it contains all of the "Singleton" GameObjects as children. This 'singleton' GameObject has a script called 'GameManager'. It manages a PRIVATE static instance variable of itself, but only to verify it's singleton-ness and destroy any duplicate versions of itself. I guess you could remove the ScriptableObject portion of my system and just expose the GameManager instance and add the GameServiceProvider's functionality directly to it, BUT I like the agency require to give access to these systems through the inspector. It's global access but not really and I don't need to worry about static variables.

Every Child of this GameManager must be derived from a base class that is called GameSystemService. The GameSystemService class contains a protected reference of type GameServiceProvider and a method to set it's value. In my GameManager script I call to get a full list of every 'child singleton'. I pass these references into the GameServiceProvider class which stores them in a Dicitonary<Type, GameSystemService>().

The GameServiceProvider ScriptableObject has the [CreateAssetMenu()] attribute which lets me create an asset of GameServiceProvider. You can think of this asset as the Singleton instance of the 'ZartovSingleon' in the video. It's just targeted access to global systems.

The GameManager script has a field that holds a reference to that asset and initializes its values at runtime. Then in any class that needs access to one of the GameSystemServices I just need to add a [SerializeField] private GameServiceProvider serviceProvider variable and drag-drop the Asset from the project. Then they can call to get a reference to one of the 'singletons' within GameManager.

The naming convention is kind of crap so it's a little confusing sorry.

[CreateAssetMenu(fileName = "GameServiceProvider", menuName = "Game Service Provider")]
public class GameServiceProvider : ScriptableObject
{
private Dictionary<Type, GameSystemService> masterServicesCollection = new Dictionary<Type, GameSystemService>();
public void gameService)
{
if
{
masterServicesCollection.Add(gameService.GetType(), gameService);
}
else
{
Debug.LogError("GameService of type" + +
" is already contained within Master Services Collection");
}
}
public T GetGameService<T>() where T : GameSystemService
{
if (masterServicesCollection.TryGetValue(typeof(T), out GameSystemService service))
{
return (T)service;
}
else return null;
}
public void ClearServices()
{

}
public void Initialize()
{
foreach (var item in masterServicesCollection)
{
item.Value.Initialize(this);
}
}
}

public class GameSystemService : MonoBehaviour
{
protected GameServiceProvider ServiceProvider;
public virtual void gameService)
{
ServiceProvider = gameService;
}
}

public class GameManager : MonoBehaviour
{
[SerializeField] private GameServiceProvider GameServiceProvider;
private static GameManager singletonInstance;
private void Awake()
{

if (singletonInstance == null)
{
singletonInstance = this;

GameSystemService[] gameSystems =
for (int i = 0; i < gameSystems.Length; i++)
{

}

}
else
{
Destroy(gameObject);
}
}
}

lvx
Автор

Thx really helped made me make game 10x faster / easier >:)

healky